正文

SELECT 使用经验 2005-11-13 10:07:00

【评论】 【打印】 【字体: 】 本文链接:http://blog.pfan.cn/wondgergis/6793.html

分享到:

 


Select命令参数很多,功能也很强大,适当的使用一些技巧,可以使程序更洁练,运行速度更快。但如果不是很强求速度的话,我认为太过复杂的命令对以后的维护工作带来困难。呵呵,有时我要理解自己写的命令都有点困难,实在是太复杂了。以下内容是工在工作过程中摸索出来的一些小技巧,希望对别人有帮助。


【在select的输出字段使用函数的技巧】

不要以为select的输出字段只能用“表.字段名”这样的格式,其实这里面大有文章可作。它可用使用函数、甚至自定义函数(当然这样会影响速度,搞不好还会影响结果的正确性,所以很复杂的自定义函数就不要用了)。而在这里最经常用的就是sum()、avg()、iif()之类的函数了。举个例子:

人员资料表ryb,内容如下:

姓名 职称 政治面貌 科室
awfw 助工 党员 通讯科
wafawe 高工 党员 机械科
afawe 技术员 团员 财务科
2wraw 无 无 人事科
afwe 工程师 无 办公室
……

现要统计每个科室各有几名技术员、助工、工程师、高工、党员、团员,以下列表的形式打印出来

科室 技术员 助工 工程师 高工 党员 团员
财务科 2 2 3 3 4 3
……

命令如下:

sele 科室,sum(iif(职称='技术员',1,0)), sum(iif(职称='助工',1,0)), ;
sum(iif(职称='工程师',1,0)), sum(iif(职称='高工',1,0)), ;
sum(iif(政治面貌='党员',1,0)),sum(iif(政治面貌='团员',1,0)) ;
from ryb grou by 科室

是不是很方便?

这里还有一个更绝的例子:

  表recdbf
    〖性质〗分一般、重大、特大;
    〖日期〗

要统计出某年的12个月内每个月每种性质的记录各有多少,若该月没有记录就为零。

结果:
月份  一般 重大 特大  
 1   0  1    3
 2   2  12   3
 ......
 12   3  0   5

这跟上面那个例子有点不同,上例只是按表中科室来分类,而这里就不同,因为不是每个月都有案件的,但在报表里没有案件的月也要占一行。所以这里要建立一个辅助表:tempyf( yf N(2)),内有十二个记录,值为1至12,代表1-12月。

我先是老规则:
sele mont(日期),iif(性质='一般',1,0) as 一般,iif(性质='重大',1,0) as 重大,iif(性质='特大',1,0) as 特大 ;
  from recdbf ;
  where year(日期)=?年份 ;
  into curs temp1

再用tempyf左联接临时表temp1,根据tempyf.yf分组统计。

但一看,结果好象不对,没有记录的月份不在结果当中,而且这两条select好象可以合而为一。这时,狐性大发,立决心要搞定它。

在看MSDN Help时,灵光一闪:把过滤条件从where移到联接条件中,直接使用sum命令统计iif的结果!几经波折,终于写出下面的命令

SELECT tempyf.*,;
  SUM(IIF(ISNULL(recdbf.日期).OR.AT("一般",recdbf.性质)=0,0,1)) AS 一般,;
  SUM(IIF(ISNULL(recdbf.日期).OR.AT("重大",recdbf.性质)=0,0,1)) AS 重大,;
  SUM(IIF(ISNULL(recdbf.日期).OR.AT("特大",recdbf.性质)=0,0,1)) AS 特大;
 FROM tempyf LEFT OUTER JOIN recdbf ;
  ON tempyf.yf = MONTH(recdbf.日期).AND.YEAR(日期) = ?yy; &&注意这里,on后面是可以加上其它条件的
 GROUP BY tempyf.yf

结果完成符合要求!

这个yy是指具体那一年,如果不指定的话,那就是把表中所有的记录算在一起。而且如果要指定具体那一年的话,那这个YEAR(日期) = ?yy的条件是不能放在where或者having里的,不信各位试试。

我最后的感想就是:那个sum命令是可以直接累加iif的结果,而且过滤条件从where移到on后会发生这么大的差别!在where时,是先联接统计后再过滤,结果把那些没有记录的月份也过滤掉了;而在on是先过滤再联接、统计,过滤结果中虽然没有记录的月份也不在其中,但因是左联接,也一样会以null代替,但给iif中的isnull给变成了0!啊,我真是太聪明了!哈哈哈。。。。。

注:其中那个 ON tempyf.yf = MONTH(recdbf.日期) AND YEAR(日期) = ?yy 在视图生成器中是不能直接做的,要用create view as生成(我就做不到,不知各位做不做得到)


【join联接中on 条件表达式的技巧】

标准的join字句是这样的

[DatabaseName!]Table [[AS] Local_Alias]
[[INNER | LEFT [OUTER] | RIGHT [OUTER] | FULL [OUTER] JOIN
DatabaseName!]Table [[AS] Local_Alias]
[ON JoinCondition ...]

join这里是没得改变得了,但在“ON”这里就有文章可做了。既然ON后面跟得是一条逻辑表达式,那这条表达式就可以任写而不必一定要table1.字段1=table2.字段3这样了。在多表查询的时候,在这里适当的加些过滤条件又可省不少功夫。

例如做原材料管理时,每次进货、原材料不合格退货都有记录的。现在老板就想知道某段时间向某个客户的进了多少货及退了多少货,从而知道这个客户的原材料质量好不好。

表结构如下:
客户表khb:khbh,khmx
原材料表yclb:yclbh,yckmc,yclgg
进货单jhd:jhdbh,jhrq,khbh(即这次是向那个客户进货的)
进货单明细jhdmx:jhdbh,yclbh,jhsl
退货单thd:thdbh,thrq,khbh(同上)
退货单明细thdmx:thdbh,yclbh,thsl


【where字句的使用技巧】

这里头的技巧可就多了,真是学都学不完。


【union的使用技巧】

这个字句没什么,只是将几个select后产生的在结构完全一样的表加在地块,变成一个表。具体的例子如下(以下例子不计较这种设计方法的好坏,只是说明命令的使用):

在做生产管理时,原材料的每笔进货、领用、报废、退货情况都要详细记录,老板随时都可能了解一个月内每种原材料的各种情况。而编程时对数据的规范化后,必然要用好几个表来记录这四种操作。现在为了更快的求出结果,命令当然是越少越好。各个表的结构如下:

进货单jhd:jhdbh,jhrq
进货单明细jhdmx:jhdbh,yclbh,jhsl

领货单lhd:lhdbh,lhrq
领货单明细lhdmx:lhdbh,yclbh,lhsl

报废单bfd:bfdbh,bfrq
报废单明细bfdmx:bfdbh,yclbh,bfsl

退货单thd:thdbh,thrq
退货单明细thdmx:thdbh,yclbh,thsl

原材料表yclb:yclbh,yclmc,yclgg

查询日期范围放在begindata,enddata里

为简便起见,只计算进货和退货,其它的照样做就行了。

Sele yclbh,sum(jhsl) as jhsl,100000.00-100000.00 as thsl ;
From jhd join jhdmx ;
On jhd.jhdbh=jhdmx.jhdbh ;
Where betw(jhd.jhrq,begindata,enddata) ;
Grou by yclbh ;
Union ;
Sele yclbh,100000.00-100000.00 ,sum(thsl) ;
From thd join thdmx ;
On thd.thdbh=thdmx.thdbh ;
Where betw(thd.thrq,begindata,enddata) ;
Grou by yclbh ;
Into curs temp1

这样就会产生这样的结果

yclbh jhsl thsl
00001 3432 0 ┐
00002 4234 0 │这些是第一节select产生的
…… ┘
00002 0 3423 ┐
00003 0 4234 │这些是第二节select产生的
…… ┘

现在再对临时表temp1进行一次合计

sele yclbh,sum(jhsl) as jhsl,sum(thsl) as thsl ;
from temp1 ;
grou by yclbh ;
into curs temp2

这个temp2就是结果了。

注1:如果要计算四种操作的话,就再加多两节select就行了。
注2:在命令里使用100000.00-100000.00这样奇怪的表达式是因为select对那些要计算的、事先不能确定长度和类型的字段,它是根据运算过程中产生的第一条记录中,该字段的值来确定这个字段的类型和长度。所以以上那条命令,第一个记录中thsl它是不知道长度是多少的,如果直接用0的话,那它就以为这个字段是数值型,长度是1,没有小数位。这当然不行,所以就要用这样一个表达式,来使它知道这个字段有9位长,小数位是2。


【having的使用技巧】

其实这里没技巧可言,select的条件过滤的先后顺序是这样的(我是根据经验而猜得,不知对否):先对join中的on表达式进行过滤,再到where,中间结果出来后再用having进行过滤,最后才把结果显示出来。所以说having是对select结果的最后一次过滤。它与where的分别就是where能够事先把不要的数据过滤掉,这样select里头就不用处理那么多的数据。但有些数据事先不知道要不要过滤,要根据结果才能确定,这时才用having这个事后诸葛亮。

这里兴修例子来比较一下on、where、having的不同之处

表recdbf内容如下:                还有一个tempyf的辅助表,记录12个月
日期 性质         yf
2000年7月3日 特大 1
2000年7月9日 特大 2
2000年9月3日 特大 3
1999年3月2日 一般 4
1999年3月4日 一般 5
2000年1月3日 一般 6
2000年2月1日 一般 7
2000年2月3日 一般 8
2000年3月4日 一般 9
2000年8月7日 一般 10
2000年11月2日 一般 11
1999年2月3日 重大 12
2000年2月3日 重大
2000年5月2日 重大
2000年8月9日 重大

on的命令如下
SELECT tempyf.*,;
  SUM(IIF(ISNULL(recdbf.日期).OR.AT("一般",recdbf.性质)=0,0,1)) AS 一般,;
  SUM(IIF(ISNULL(recdbf.日期).OR.AT("重大",recdbf.性质)=0,0,1)) AS 重大,;
  SUM(IIF(ISNULL(recdbf.日期).OR.AT("特大",recdbf.性质)=0,0,1)) AS 特大;
 FROM tempyf LEFT OUTER JOIN recdbf ;
  ON tempyf.yf = MONTH(recdbf.日期).AND.YEAR(日期) = ?yy;
 GROUP BY tempyf.yf

其中yy=2000,表示统计2000年的数据

用where的命令如下:
SELECT tempyf.*,;
  SUM(IIF(ISNULL(recdbf.日期).OR.AT("一般",recdbf.性质)=0,0,1)) AS 一般,;
  SUM(IIF(ISNULL(recdbf.日期).OR.AT("重大",recdbf.性质)=0,0,1)) AS 重大,;
  SUM(IIF(ISNULL(recdbf.日期).OR.AT("特大",recdbf.性质)=0,0,1)) AS 特大;
 FROM tempyf LEFT OUTER JOIN recdbf ;
  ON tempyf.yf = MONTH(recdbf.日期);
 GROUP BY tempyf.yf ;
where YEAR(日期) = ?yy &&注意,条件从on移到这里来了

用having的命令如下:
SELECT tempyf.*,;
  SUM(IIF(ISNULL(recdbf.日期).OR.AT("一般",recdbf.性质)=0,0,1)) AS 一般,;
  SUM(IIF(ISNULL(recdbf.日期).OR.AT("重大",recdbf.性质)=0,0,1)) AS 重大,;
  SUM(IIF(ISNULL(recdbf.日期).OR.AT("特大",recdbf.性质)=0,0,1)) AS 特大;
 FROM tempyf LEFT OUTER JOIN recdbf ;
  ON tempyf.yf = MONTH(recdbf.日期);
 GROUP BY tempyf.yf ;
having YEAR(日期) = ?yy &&注意,条件从on移到这里来了

on的结果如下,这是正确的
YF 一般 重大 特大
1 1 0 0
2 2 1 0
3 1 0 0
4 0 0 0
5 0 1 0
6 0 0 0
7 0 0 2
8 1 1 0
9 0 0 1
10 0 0 0
11 1 0 0
12 0 0 0

用where的结果如下:
YF 一般 重大 特大
1 1 0 0
2 2 1 0
3 1 0 0
5 0 1 0
7 0 0 2
8 1 1 0

用having的结果如下:
YF 一般 重大 特大
1 1 0 0
2 2 2 0
5 0 1 0
7 0 0 2
8 1 1 0
9 0 0 1
11 1 0 0

各位看到有什么不同吗?

on是把先把recdbf中不是2000年的记录过滤掉,剩下的就是2000年的了,再用tempyf去和它们进行外联接,其结果可用
sele tempyf.*,recdbf.日期 ;
from tempyf left join recdbf ;
ON tempyf.yf = MONTH(recdbf.日期).AND.YEAR(日期) = ?yy;
 GROUP BY tempyf.yf
来查看,这个中间结果出来后,再用isnull把空值的记录变成0或1,然后由sum去统计,结果就出来了

而where呢,
1、它是先把tempyf外联接recdbf,
相当于sele tempyf.*,recdbf.* from tempyf left join recdbf on tempyf.yf=mont(recdbf.日期)
2、然后把不是2000的记录过滤掉,这里要注意的是,如果某个月没有记录的话,那在第一个步骤后日期那里是null值,这当然不是2000的记录,所以就给这个条件给过滤出去了,所以下一步的sum之后就只剩下那有记录的那个月了,象4、6月等几个月。就没有
3、然后进行sum(……)

再看having
1、第一步和where一样,
2、第二步不同,它是先sum(),这里的sum可不管你是1999年还是2000的,先累加起来再说,这时,1999和2000年的2月份都有“重大”这个记录,sum的结果是2,这里用第三个步骤去分辨这个2之中那个是1999年的,那个是2000的,这当然分不清啦,所以也错了。
3、根据步骤2来把2000的过滤出来。

所以on、where、having这三个都可以加条件的子句中,on是最先执行,where次之,having最后。但有时候如果这先后顺序不影响中间结果的话,那最终结果是相同的。但因为on是先把不符合条件的记录过滤后才进行统计,它就可以减少中间运算要处理的数据,按理说应该速度是最快的。根据上面的分析,可以知道where也应该比having快点的,因为它过滤数据后才进行sum,所以having是最慢的。但也不是说having没用,因为有时在步骤3还没出来都不知道那个记录才符合要求时,就要用having了。

【快速排名次】

  考试排名次,一般有个要求,就是如果有两个人并列第一,那跟着的就不是第二名,而是第三名,即第二名跳空了。所以我想出以下这个方法。

  面对大数据量的排名次,若用scan...endscan或do while这种方法,时间会非常长。1048576个记录中花了十多分钟才排了四十多万条记录。而我这种方法只需九十多秒(硬件:PII300(100*3),128M PC100,5.1G,VFP5.0),方法如下:

1。数据库dele:rec_id c(7) 升序;name c(10);fs n(4,1) 降序;rec_orde n(7),文件大小为29M,索引文件16M
2。建一个视图dele1
SELECT Dele.fs, Dele.rec_orde;
FROM dele;
ORDER BY Dele.fs DESC
3。根据视图dele1建立视图dele2
SELECT DISTINCT Dele1.fs, MIN(Dele1.rec_orde) AS rec_orde;
FROM dele1;
GROUP BY Dele1.fs
4。根据视图dele2和数据库dele建立视图dele3
SELECT Dele.rec_id, Dele.rec_orde, Dele2.rec_orde, Dele.fs;
FROM dele INNER JOIN dele2 Dele2 ;
ON Dele.fs = Dele2.fs;
ORDER BY Dele2.rec_orde
准备工作做好,现在可以排序了:
以上视图是事先做好的,不算入运行时间。

use dele1
repl rec_orde with recn() all
sele 0
use dele3
repl rec_orde_a with rec_orde_b all
brow
这就是你想要的!

 


【快速把表中重复的记录删除掉】

假设记录是否重复以用aa这个字段来判断,则对aa建立一个唯一索引。

set dele off
clos dbf all
sele 0
use temp1
set orde to aa &&打开唯一索引,那重复的记录就会看不到,
dele all &&这里也不会把它们删除掉
set orde to &&然后关闭索引,那些重复的记录就会出显,而且不带删除标志。
set dele on &&把带删除标志的记录隐藏起来
repl aa with -1 all &&剩下的就是重复且不带删除标志的记录,把它们统一做个标志,就是aa=-1
reca all &&然后把所有删除的记录都恢复过来
dele for aa=-1 &&所有AA=-1的记录就是重复的记录,把它们删除掉。

本人做了试验,环境:华硕P2B+PII300(100*3)+128M PC100+15G+VFP6.0+98SE,再加一个有163,840个记录的表,记录大小为14个字节,文件大小为2,457,960字节,索引文件大小为458,752字节。
建立这个唯一索引只需5秒,而做完上面那段命令只需4.262秒,不知这样的速度算不算快?

阅读(5527) | 评论(0)


版权声明:编程爱好者网站为此博客服务提供商,如本文牵涉到版权问题,编程爱好者网站不承担相关责任,如有版权问题请直接与本文作者联系解决。谢谢!

评论

暂无评论
您需要登录后才能评论,请 登录 或者 注册