VFP 中的 SQL SELECT 语句使用要点

更新时间:2023-03-08 08:14:12 阅读量: 综合文库 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

VFP 中的 SQL SELECT 语句使用要点

作者: 康康(张初康)

第一节 select - SQL 的工作流程

再复杂的 SQL SELECT 命令,也是由一些基本的结构组成的。所以在看、去做一条很复杂的 SQL SELECT 命令时,要会把它一级一级的折分,最后折成最简单的,这样才容易理解。而这个折分过程,如果不熟悉 SQL SELECT 命令的工作流程,那就比较难折分了。

大体来说,它是先根据联接条件(即联接条件 on 中的表达式),把几个的表合成一个临时表,然后根据 where 中的条件进行过滤,过滤出来的结果根据分组条件再把这个临时表分成一组一组,然后对分别对些组进行字段计算,最后又得出一个临时表,然后又根据 having 中的条件对这个临时表进行再次过滤,最后输入到指定的地方,如数组、表等。它中间生成的临时表对用户来说,是完全透明的,用户是不可能使用、也不能创建,它是由系统自己创建、自己使用、自己撤除,完全不受用户控制的。我举个例子:有二个表:提货单 thd、提货单明细 thdmx: thd

提货单号 提货日期 thdbh thrq 01 2000/01/02 02 2000/01/15 03 2000/02/01 thdmx

提货单号 产品编号 提货数量 thdbh cpbh Thsl 01 001 5 01 003 15 01 005 12 02 001 13 02 002 14

02 005 20 03 002 14

现在有个要求:要统计 day1 = 2000/01/01 至 day2 = 2000/01/20 这段时间内,提货数量大于 10 的产品有那些,它们各自的总提货量是多少。命令如下:

sele cpbh,sum(thsl) ; from thd join thdmx ;

on thd.thdbh=thdmx.thdbh.and.thsl>10.and.betw(thrq,day1,day2) ; grou by cpbh ; into curs temp1

为什么把 thsl>0 和 betw(thrq,day1,day2) 这二个条件表达式放在 on 那里,参见 on、where 与 having 的区别。它的工作流程:

首先根据 on 中的过滤条件,对所涉及的表进行预处理。过程如下:

两个表根据 thd.thdbh = thdmx.thdbh 进行合并,变成一个这样的临时表:

thdbh1 thrq thdbh2 Cpbh thsl 01 2000/01/02 01 001 5 01 2000/01/02 01 003 15 01 2000/01/02 01 005 12 01 2000/01/02 02 001 13 01 2000/01/02 02 002 14 01 2000/01/02 02 005 20 01 2000/01/02 03 002 14 02 2000/01/15 01 001 5 02 2000/01/15 01 003 15 02 2000/01/15 01 005 12 02 2000/01/15 02 001 13 02 2000/01/15 02 002 14 02 2000/01/15 02 005 20 02 2000/01/15 03 002 14

03 2000/02/01 01 001 5 03 2000/02/01 01 003 15 03 2000/02/01 01 005 12 03 2000/02/01 02 001 13 03 2000/02/01 02 002 14 03 2000/02/01 02 005 20 03 2000/02/01 03 002 14

合并是按照笛卡尔积进行计算的,即二个表都有 10 个记录,那积就会有 10*10 = 100 个记录,这是很历害的。但因为 on 条件中有过滤条件,所以 VFP 并不会这么笨,它会把符合这个条件的记录才放到临时表中的。这样,结果就少了很多记录了。结果出来后,再根据 on 后来的过滤条件 thsl>10.and.betw(thrq,day1,day2) 进行过滤,这样 thsl 大于 10 而且提货日期是在 day1 至 day2 的记录最后才出现在这一步的临时表中。

01 2000/01/02 01 003 15 01 2000/01/02 01 005 12 02 2000/01/15 02 001 13 02 2000/01/15 02 002 14 02 2000/01/15 02 005 20

现在轮到分组了。根据产品编号进行分组,它具体的分组方法我不知道是怎样,我想可能是这样的:

象在投票选举时点票那样,在上面那个临时表从头到尾扫一次,每经过一记录时,它就看一下,当前的产品编号是不是一个新的组,如果是就新增一个分组记号,相当于新增加一个被选举人,然后在它下面加上 thsl 的值,全部记录数完了,就看看有多少个分组标记,各个分组又有多少 thsl。结果就是以下的样子: 001 13 002 14 003 15 005 32

这就是这条命令的结果了。然后把它生成一个 cursor 表,命令就完成了。如果再深入一点,把要求改成某段时间内全部产品的提货情况,如果没有进货记录,那就是 0,一样要出现在结果表里。

这时,就涉及到三个表了:产品表 cpb、提货表 thd、提货明细表 thdmx。我们先用内联接来把这三个表

联接起来。

sele cpb.cpbh,cpb.cpmc,sum(iif(isnull(thdmx.thsl),0,thdmx.thsl)) as thsl ; from cpbh join thdmx ; join thd ;

on cpbh.cpbh=thdmx.cpbh ;

on thdmx.thdbh=thd.thdbh.and.betw(thd.thrq,day1,day2) ; grou by cpbh ; into curs temp1

根据内联接的定义,即某个产品编号在产品表和提货明细表中都存在的记录,才会出现在结果表中,如果某种产品没有提货,那在提货明细表就没有这个记录,这样,也就不会出现在结果表中。那样就符合要求中的\全部产品\这个条件了。所以我们要把 产品表 左联接 提货明细表,这样不管这种产品有没有提货,它都会出现在结果表中,只是以 null 的值出现。但这个现象可以消除的。

命令过程也和上一个要求那样,先进行联接,只是这条命令的中间临时表比上面更大,因为是三个表的记录数相乘。但经过联接条件过滤后,就会剩下这些内容:

001 02 2000/01/15 02 001 13 002 02 2000/01/15 02 002 14 003 01 2000/01/02 01 003 15 004 null null null null null 005 01 2000/01/02 01 005 12 005 02 2000/01/15 02 005 20

然后也一样进行分组,分组时的判断过程也一样,只是在累加的时候有点不同:

sum(iif(isnull(thdmx.thsl),0,thdmx.thsl)),是先用 isnull(thdmx.thsl) 检查 thsl 是不是 null,如果是,则 iif() 就返回 0,如果不是,则返回 thdmx.thsl。然后外层的 sum() 就根据 iif() 返回的数值进行累加,最后做为这个分组的累加值。

还有一种使用方法,说出来也可以加深命令当中的 sum() 函数的处理过程。

人事表rsb:

姓名xm 年龄age 张三 25 李四 32 王五 28

现在想统计一下各个年龄段(20-30,31-40)的人数是多少。

sele sum(iif(betw(age,20,30),1,0) as _20-30,sum(iif(betw(age,31,40),1,0) as _31-40 ; from rsb ; grou by zc ; into curs temp1

因为这条命令没有过滤条件、联接,所以不需事先预处理,一来就进行分组。和投票中点票一样,在\黑板\上写划出三列:

zc _20-30 _31-40

这样,在 rsb 中从头扫到尾时,每经过一记录时,都用 iif(betw(age,20,30),1,0) 检查这个人的年龄是不是处于 20-30,如果是就在_20-30 这一列下面加一横,否则就不加。第二个 iif() 也这样处理。如果当前的年龄是 53,那二个 iif() 都是返回 0,即二列都不加,相当于废票。全部记录都点完了,然后就用 sum() 进行合计了,结果就出来了。

因为二个 sum() 都是扫描完后再合计的,它不象 sum 命令。sum 命令会移动记录指针,而 sum() 函数不会,所以不必怕使用这个函数会造成不良后果。而且二个 iif() 都有自己的判断条件,两者不互相重合,所以一个记录不会重复计算(除非你的命令设计错了)。

如果使用 union 参数把两条运算结果的格式一模一样的命令合在一起,那也是一样的。它是把其中的每一节 select 命令单独运行(它单独运行时的运行流程跟上面说的一样),最后才把每一节的结果首尾相接后,再根据最后那节的 orde by 进行排序。所以一条带 union 的 SQL SELECT 命令,只能有一个 orde by。而每一节 select 命令,却可以有自己的 grou by,它自己的 grou by 只对这一节有影响,是不会影响到其它节的运算的。更不会对最后结果有影响。

这里举个例子:我想统计一段时间内的提货、进货情况。这里要涉及到 5 个表:产品表、提货表、提货明细表、进货表、进货明细表:

sele cpbh,sum(thdmx.thsl) as thsl,100000-100000 as jhsl ; from thd join thdmx ; on thd.thdbh=thdmx.thdbh ; grou by cpbh ; union ;

sele cpbh,0,sum(jhdmx.jhsl) ; from jhd join jhdmx ; on jhd.jhdbh=jhdmx.jhdbh ; grou by cpbh ; orde by cpbh ; into curs temp1

如果看了上面的解释,应该可以理解每一节 SQL SELECT 命令的意思。这里要说的是:

1、union 要求前后两节 SQL SELECT 命令产生的表,在结构上要完全一样,包括字段的顺序也要一样。所以为了达到这个要求,在第一节,就要人为建立一个字段 jhsl,而在第二节命令,也要建立一个字段 thsl 以对应。

2、用 SQL SELECT 产生的表,它不象 crea table 那样可以直接指定字段的类型、长度,而是在根据生成的临时表中第一个记录的长度来确定的。所在在第一节,如果不使用 100000-100000 而是直接使用 0,这样产生的 jhsl 这个字段,它的长度就只有 2 个字节了。所以只有使用这种方法,才能使得这个字段的长度有 7 字节。在字符串也有这样情况,如果第一个记录的长度是 12 个字节,那以后的记录中,超过 12 个字节的内容就会给它去掉,这就是为什么有时在结果表中会出现字符串不完整的情况。解决方法也差不多,在字段列表那里人空加几个空格去。

--------------------------------------------------------------------------------

第二节 on、where、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日 重大

现在的要求是要统计 yy 年中十二个月的事故记录中,一般、重大、特大各有多少。如果没有事故的,则以 0 表示。

我们首先要把今年的记录过滤出来,过滤条件就是 YEAR(日期)=?yy,然后按月份分组统计。

这样一来,如果某个月没有事故记录,那分组后的结果就没有该月的记录,这样不符合要求。所以做个临时表 yf,该表有十二个记录,分别代表 1 至 12 月,用它来左联接 recdbf,这样,即使某个月没有事故记录,也会出现在最后的结果当中,只是以 null 的形式出现罢了。但我们可以使用 isnull() 函数来判断它是不是 null 值,如果是,则 iif() 会把它变为 0,然后交与 sum() 进行统计。

总体设想搞好后,现在就开始写命令了。开始之前先说明:tempyf.yf = MONTH(recdbf.日期)是 yf 表与 recdbf 表的联接条件,是一定要在on的,这个不在讨论范围。我们要讨论的是 YEAR(日期) = ?yy 这个条件放在什么地方会有什么样的结果。

首先把过滤条件放在 on 这里:

SELECT tempyf.*,;

SUM(IIF(ISNULL(recdbf.日期).OR.AT(\一般\性质)=0,0,1)) AS 一般,; SUM(IIF(ISNULL(recdbf.日期).OR.AT(\重大\性质)=0,0,1)) AS 重大,; SUM(IIF(ISNULL(recdbf.日期).OR.AT(\特大\性质)=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(\一般\性质)=0,0,1)) AS 一般,; SUM(IIF(ISNULL(recdbf.日期).OR.AT(\重大\性质)=0,0,1)) AS 重大,; SUM(IIF(ISNULL(recdbf.日期).OR.AT(\特大\性质)=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(\一般\性质)=0,0,1)) AS 一般,; SUM(IIF(ISNULL(recdbf.日期).OR.AT(\重大\性质)=0,0,1)) AS 重大,; SUM(IIF(ISNULL(recdbf.日期).OR.AT(\特大\性质)=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; orde by 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 了。

在两个表联接时才用 on 的,所以在一个表的时候,就剩下 where 跟 having 比较了。在这单表查询统计的情况下,如果要过滤的条件没有涉及到要计算字段,那它们的结果是一样的,只是 where 可以使用 rushmore 技术,而 having 就不能,在速度上后者要慢。

如果要涉及到计算的字段,就表示在没计算之前,这个字段的值是不确定的,根据上篇写的工作流程, where 的作用时间是在计算之前就完成的,而 having 就是在计算后才起作用的,所以在这种情况下,两者的结果会不同。

在多表联接查询时,on 比 where 更早起作用。系统首先根据各个表之间的联接条件,把多个表合成一个临时表后,再由 where 进行过滤,然后再计算,计算完后再由 having 进行过滤。由此可见,要想过滤条件起到正确的作用,首先要明白这个条件应该在什么时候起作用,然后再决定放在那里。

--------------------------------------------------------------------------------

第三节 四种链接的区别及用法 链接:

作为动词,它表示将两个或多个表的内容结合在一起并产生一个结果集,该结果集对每个表的列和行进行合并。表的联接一般都使用它们共有的数据。例如,您可以对有一个共同 pub_id 列的 titles 表和 publishers 表联接,产生一个包含书名信息和出版商信息的结果集。

作为名词,表示对表进行联接的过程或结果,如在术语\内部联接\中表示对表联接的一种特殊的方法。

联接条件(join condition)

一个比较子句,它指定了表是如何通过它们的联接字段相联系的。最普通的联接条件是相等(一个等联接),在等联接中联接字段的值必须相同。例如,您可以通过在 titles 表和 publishers 表的 pub_id 列中查找相匹配的值联接这两个表。然而,任何比较运算符都可以是比较条件的一部分。

内部联接(inner join)

一个联接,在该联接中只有当联接字段的值满足某些特定的准则时才将两个表的记录进行结合并添加到一个查询结果中。例如,在查询设计器视图中,表之间的缺省联接是一个内部联接,它只有当联接字段的值相等时才从两个表中选择记录。

外部联接(outer join)

一个联接,该联接还包括那些和联接表中记录不相关的记录。您可以创建一个外部联接的三种变形来指定所包括的不匹配行:左外部联接、右外部联接和完全外部联接。

左外部联接(left outer join)

一种外部联接类型,在该联接中包括第一个命名表(左边的表,它出现在 JOIN 子句的最左边)的所有行。右边表中没有匹配的行不出现。例如,您可以在 titles 表和 publishers 表之间创建一个左外部联接,以包括所有的书名,不论书名有无出版商的信息。

右外部联接(right outer join)

一种外部联接,在该联接中包括第二个命名表(右边的表,出现在 JOIN 子句中的最右边)的所有行。不包括左边表中没有匹配的行。例如,titles 表和 publishers 表之间的一个右外部联接将包括所有的出版商,甚至包括那些在 titles 表中没有书名的出版商。

以上是MSDN中对链接的定义。现在我们就从这四种链接所使用的不同方法来看他们的结果有什么不同。 titles 表

sh(书号) ph(出版商编号) 232342 001 0432 003 82478123 005

publishers 表

ph(出版商编号) mc(出版商名称) 001 红虎 002 rmh 003 hazl

现要把这两个表的内容合成如下的表结构:

sh(书号) ph(出版商编号) mc(出版商名称)

现在看看采用四种链接方法的结果会有什么不同。先说说他们的命令: 内联接:

sele titles.sh,publishers.ph,publishers.mc ;

from titles inner join publishers ; &&内联接中的inner是可以省略的 on titles.ph=publishers.ph 外联接:

sele titles.sh,publishers.ph,publishers.mc ; from titles outer join publishers ; on titles.ph=publishers.ph 左联接:

sele titles.sh,publishers.ph,publishers.mc ; from titles left join publishers ; on titles.ph=publishers.ph 右联接:

sele titles.sh,publishers.ph,publishers.mc ; from titles right join publishers ; on titles.ph=publishers.ph

大家可能看到,除了在join之前的那个关键字不同之外,其他地方是一模一样的,链接条件(即 on 那一部

分)也是一样的。结果: 内链接:

232342 001 红虎 0432 003 hazl 全链接:

232342 001 001 红虎 Null Null 002 rmh 0432 003 003 hazl 82478123 005 Null Null 左链接:

232342 001 001 红虎 0432 003 003 hazl 82478123 005 Null Null 右链接:

232342 001 001 红虎 Null Null 002 rmh 0432 003 003 hazl 所以我们很容易记住:

1、左链接:就是以join的左边那个表为\主\,以 titles.ph=publishers.ph 为判断标准,不管右边的表有没有对应的记录,都要把左边表的记录放在结果中去,但右边表没有相应的记录那应该放个什么数值进去?答案是就放个 Null,表示没有。在左链接中,某记录在右边表,却不在左边表,那是不放进去结果去的,原因是左边表才是\主\,要不要放由它决定:它有的,就一定放进去,它没有的,就不要了。

2、右链接:和左链接一样,只不过为\主\的一方调过来了,换成是由右边做\主\。

3、内链接:和左、右链接不同,它一定要左、右两边都有的记录才会放进结果,如果有某个记录不存在于

任何一边,那这个记录是不会出现在结果中去的。

4、外链接:跟内联接相,反,相当于左、右链接的合并:不管什么情况,只要某个记录出现在这两个表,就一定会出现在结果中去,然后象左、右链接的处理方法一样,用Null来填充没有对应值的字段。

注:以上说的\有\、\没有\,意思是以 titles.ph=publishers.ph 为判断标准来下决定的。比如当前 titles 表的ph是 \,而在 publishers 中,没有一个记录的 ph 的值是 \的,所以就说 \这个值在 titles 有,在 publisher 中没有,这样 titles.ph 为 \的记录就会被选中,最后放在结果中去。

大家如果想一下,这个 on 的作用跟 where、having 似乎有点类似,都是起到过滤的作用:根据条件选取所取的记录,而根据命令的工作流程,这个 on 是比 where、having 都要早执行的,而它里面的条件表达式又不一定是 titles.ph=publishers.ph 的形式,还可以继续扩充,变成一个很复杂的条件表达式,从而完成一个很有效的、where 和 having 都不能实现的过滤功能。具体的比较请看 on、where、having的区别 一节。

刚才举的例子,表中的 ph 都是没有重复的。现在以内联接为例,举个判断字段中内容有重复的例子:

Temp1 temp2 Aa aa 1 1 1 2 2 2 3 2

sele temp1.aa,temp2.aa ; from temp1 join temp2 ; on temp1.aa=temp2.aa 运行结果是: 1 1 1 1 2 2 2 2

2 2

很明显,有些记录重复了几遍。temp1.aa 中的虽然只有 1 个 2,但 temp2.aa 有 3 个 2,所以结果就会有 1*3 = 3 个 2 了。如果 temp1.aa 而 2 个 2 的话,那结果就会有 2*3 = 6 个 2 了。

知道了这一点,在做多表链接查询的时候很有用。你要考虑第一、二个链接后的结果跟第三个表链接时,会不会出现这种情况?如果有,那是不是你想要的?如果有,那怎么处理?有些朋友说做这个命令的结果中有些记录会比正确的结果大几倍,就要看看是不是出现了这种重复算的情况。

学会了链接,在开始做之前,先要说一个很重要的问题:在视图设计器来看多个表的联接关系,它们之间的链接是用一条线连接起来的,看起来就象一串糖葫芦。如果一个表同时和三个表联接,那看起来就象一支分叉的树枝了,那这种情况结果就不对了。大家可能不明白我在说什么,我举个例子大家就会明白了。

有一个产品表、一个进货明细表、一个出货明细表,现在的要求是要求产品表中所有的产品的进、出情况,也就是把三个表象 join 命令那样合成一个表,如果没有相应的进、出记录,也照样列出来但不计较null值。刚开始学的朋友很可就会这样做:

1、 在设计器里添加这三个表;

2、 然后用产品表中的产品编号分别与其它二个表左链接,这样产品表中就有二个链接(也就是二条线了);

3、 然后把三个表的字段都做为输出字段。

但结果呢?不对。只有一个表的记录出现在结果中,即使把四种链接类型都试一下,结果都是不对的。

为什么呢?我估计是以下原因:如果产品表只与进货表链接的话,系统根据产品表和进货表的联接关系,以产品表为左表,和进货这个右表组成一个临时结果,然后又以临时表为左表,再去找进货表的右边表。而进货表的右边没有表,这时系统就停止链接,交给where去过滤了。但现在产品表同时跟二个表左联接,系统会自动选其中一个先进行链接,链接结果出来后,这个临时结果的右边就没有表了,系统就停止链接动作了。剩下的出货表、退货表都还没链接,所以那个表等于没用。

解决的方法是:进货表用进货表的产品编号全链接产品表,然后产品表又用产品表的产品编号全链接出货表,进、货表的顺序可以调过来,但产品表一定要在中间,且两个链接类型都是全链接,否则结果都不对。

这样的链接情况,在设计器里按链接中的各个表的左右顺序排起来,很直观的:就是一串!没有分叉。这个方法的实现过程就是:

进货表全链接产品表,即使某种产品没有进货,但得出来的结果也一样有这个记录,只是它的进货内容是null值。然后这个临时结果又跟出货表全链接,这次的结果就前一步差不多,有出货内容的记录就有出货数量,否则就是null值。因为没有分叉,所以全部表都链接进去了,结果也就对了(当然如果链接类型错了,结果也是不对的)。

看了刚才那个问题之后,还有一个问题也要说一下。在刚才那个例子中,如果产品表中某个产品编号出现了重复,有N个记录的编号相同,而在进货表里这个编号的记录也出现M个,这样一来,结果就有点不同了。首先在进货表跟产品表的全链接结果里,这个编号就会出现N*M次,就不是一次了。然后这个临时表再去跟出货表全链接时,即使这个编号在出货表里出现一次,但在最后的链接结果中,这个编号还是会出现N*M次,那它的出货记录也重复了N*M次了。如果现在要 sum() 出货记录的话,那出货数量就会放大了 N*M 倍了,进货记录也不准了。所以如果产品表中的编号有重复的话,那结果就很可能会不对了。

但产品表的编号没有重复,那结果就一定会正确呢?也未必。大家试一下,假设进货表和产品表的编号 \001\都是只出现一次,但出货表中就出现了二次。那最后的结果中\还是出现了二次,二次的产品名称、进货数量都是相同的,只是出货数量不同而已。如果这时 sum(),结果还是不对。

所以如果想在多表链接后进行 sum() 之类的汇总操作,使用以上的方法是不行的。解决方法是使用 union,用它来将进货的汇总情况跟出货的汇总情况合起来,从而避免互相干扰。这方面的做法请看以后的 union的使用 一节吧。

--------------------------------------------------------------------------------

第四节 union 的使用

union 可以将几条 SQL SELECT 命令合成一条,要求是这几条命令生成的表,在字段个数、字段类型、字段长度、字段顺序上都完全一样。以下面这些情况下,一般都要使用它的:

一. 把几个结构完全一样的表的记录都加在一起,最后生成的表,在结构上跟那几个表也完全一样,但记录数就是那几个表的记录数的总和。

举个例子:我想统计一段时间内的提货、进货情况,最后生成的表是这样的:

产品编号(cpbh) 产品名称(cpmc) 提货数量(thsl) 进货数量(jhsl)

先用二条命令分别计算提货数量和进货数量,生成二个临时表,最后用 union 合成一个表。

sele thdmx.cpbh,sum(iif(isnull(thsl),0,thsl)) as thsl; from cpk left join thdmx ; on cpk.cpbh=thdmx.cpbh ; grou by cpbh ; into curs temp1

sele jhdmx.cpbh,sum(iif(isnull(jhsl),0,jhsl)) as jhsl; from cpk left join jhdmx ; on cpk.cpbh=jhdmx.cpbh ; grou by cpbh ; into curs temp2

现在 temp1 和 temp2 的格式跟最后的结果有点不同,都是少了一个提货数量(进货数量),不能直接使用 union 联合。所以我们要人为给每个临时表加个对应的字段,命令如下:

sele cpbh,thsl,10000-10000 as jhsl ; from temp1 ; union ;

sele cpbh,0 ,jhsl ; from temp2 ; into curs temp3

在每节 SQL SELECT 命令,都加了一个字段,它的值都是零(没有嘛,当然是零啦)。这样一样,每节 SQL SELECT 命令生成的表在结构上就完全一样了,就可以使用 union 了。大家试一下,如果都不加个字段的话,那虽然不会出错,但结果的结构就跟要求不一样了。

在上面那条命令,每一节都可以使用 \来给字段重新起名。如果在第一节使用了 \,则以后的则可能不用了。否则的话,就是最后使用 \的那节才起作用,前面的都无效了。

temp3 出来了,就可以使用分级合并了。

sele cpbh,sum(thsl) as thsl,sum(jhsl) as jhsl ; from temp3 ; grou by cpbh ; into curs temp4

现在这个 temp4 就是最后正确的结果了。

做了这么多步,大家应该明白这类联合统计的命令是怎样做的吧。但精益求精的我们是不会满足的,还可以对上面的那么多个步骤进行简化:

sele thdmx.cpbh,sum(iif(isnull(thsl),0,thsl)) as thsl,10000-10000 as jhsl; from cpk left join thdmx ; on cpk.cpbh=thdmx.cpbh ; grou by cpbh ; union ;

sele jhdmx.cpbh,0, sum(iif(isnull(jhsl),0,jhsl)) ; from cpk left join jhdmx ; on cpk.cpbh=jhdmx.cpbh ; grou by cpbh ; into curs temp1

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

大家看一下,使用了union 是不是更简洁了?这里只统计进、出情况,如果再加上退、报废等情况,采用第一种方法就要使用五条命令,产生 6 个临时表。而采用第二种方法,无论再加多少种情况,都只需 2 条命令和 2 个临时表就可以。

二. 从一个表的数据中生成分类汇总的信息

表的数据数下:

入库日期 材料类别 材料名称 规格材质 单位 数量 1998.02.23 钢材 不等边角钢 L125*80*10 Q235 t 0.052 1998.02.23 钢材 不锈管 Φ10*2 1Cr18Ni9T t 0.875 1998.02.23 钢材 不锈管 Φ108*4 1Cr18Ni9T t 0.013 1998.02.23 钢材 不锈管 Φ108*4.5 1Cr18Ni9T Kg 27.6 1998.02.23 钢材 不锈管 Φ14*2 1Cr18Ni9T t 2.761 1998.02.23 钢材 不锈管 Φ14*3 1Cr18Ni9T t 0.104 1998.02.23 钢材 不锈管 Φ159*5 1Cr18Ni9T t 0.11 1998.02.23 钢材 不锈管 Φ159*5.5 1Cr18Ni9T Kg 108 1998.02.23 钢材 不锈管 Φ57*3.5 1Cr18Ni9Ti t 0.126 1998.02.23 钢材 不锈管 Φ60*4 1Cr18Ni9Ti t 0.33 1998.04.29 化工产品 喷漆 瓶 2 1998.05.26 化工产品 喷漆 瓶 10 1998.04.29 化工产品 氧气 瓶 156 1998.05.26 化工产品 氧气 瓶 119 1998.04.28 化工产品 乙炔 瓶 94 1998.03.18 化工产品 氩气 瓶 19 1998.04.23 化工产品 氩气 瓶 20 1998.01.01 有色金属 黄铜板 δ =0.75 Kg 4.5 1998.01.01 有色金属 黄铜板 δ =2 Kg 7.6 1998.01.13 有色金属 黄铜棒 Φ13 Kg 4.5 1998.01.01 有色金属 黄铜棒 Φ35 Kg 5.2 1998.01.13 有色金属 黄铜管 Φ10*2 Kg 2.5 1998.01.13 有色金属 黄铜管 Φ12*3 Kg 2.5 1998.01.13 有色金属 黄铜管 Φ6*1 Kg 5.2

要求生成如下格式的查询:

入库日期 材料类别 材料名称 规格材质 单位 数量 1998.02.23 钢材 不等边角钢 L125*80*10 Q235 t 0.05200 1998.02.23 钢材 不锈管 Φ108*4.5 1Cr18Ni9T t 27.60000 1998.02.23 钢材 不锈管 Φ159*5 1Cr18Ni9T t 0.11000 1998.02.23 钢材 不锈管 Φ159*5.5 1Cr18Ni9T t 108.00000 1998.02.23 钢材 不锈管 Φ57*3.5 1Cr18Ni9Ti t 0.12600 1998.02.23 钢材 不锈管 Φ60*4 1Cr18Ni9Ti t 0.33000 1998.02.23 钢材 不锈管 Φ108*4 1Cr18Ni9T t 0.01300 1998.02.23 钢材 不锈管 Φ10*2 1Cr18Ni9T t 0.87500 1998.02.23 钢材 不锈管 Φ14*2 1Cr18Ni9T t 2.76100 1998.02.23 钢材 不锈管 Φ14*3 1Cr18Ni9T t 0.10400 . . 钢材合计 t 139.97100 1998.03.18 化工产品 氩气 瓶 19.00000 1998.04.28 化工产品 乙炔 瓶 94.00000 1998.04.23 化工产品 氩气 瓶 20.00000 1998.04.29 化工产品 氧气 瓶 156.00000 1998.04.29 化工产品 喷漆 瓶 2.00000 1998.05.26 化工产品 喷漆 瓶 10.00000 1998.05.26 化工产品 氧气 瓶 119.00000 . . 化工产品合计 瓶 420.00000 1998.01.01 有色金属 黄铜棒 Φ35 Kg 5.20000 1998.01.01 有色金属 黄铜板 δ =0.75 Kg 4.50000 1998.01.01 有色金属 黄铜板 δ =2 Kg 7.60000 1998.01.13 有色金属 黄铜管 Φ10*2 Kg 2.50000 1998.01.13 有色金属 黄铜管 Φ12*3 Kg 2.50000 1998.01.13 有色金属 黄铜棒 Φ13 Kg 4.50000 1998.01.13 有色金属 黄铜管 Φ6*1 Kg 5.20000 . . 有色金属合计 Kg 32.00000

代码如下:

SELECT ; 入库日期,;

padr(材料类别,len(材料类别)+4,\\ 材料名称,; 规格材质,; 单位,;

数量*1000/1000 as 数量,; 1 as FLAGS ; from 材料入库 ; union all ; SELECT ;

{..} as 入库日期,;

alltrim(材料类别)+\合计\as 材料类别 ,; \as 材料名称,; \as 规格材质,; 单位 ,;

SUM(数量) AS 数量,; 2 as FLAGS ; from 材料入库 ; group by 材料类别 ; order by 2,7 ; INTO table temp ;

其中添加的字段 FLAGS 用于数据排序.

只要记住 union 中每节 SQL SELECT 命令产生的结果中,字段个数、字段类型、字段长度、字段顺序上都完全一样的,才能进行联合。

而在分析一段很长的、又使用了 union 的 SQL SELECT 命令,可以按 union 分成一条一条 SQL SELECT 命令,然后再去分析每一条 SQL SELECT 命令,看每条命令是什么表采用什么链接类型进行链接,过滤条件是什么,按什么进行分组,进行什么样的字段汇总函数。只要懂得了 SQL SELECT 命令的工作流程顺序,再复杂的 SQL SELECT 命令都可以很快就看明白。

本文来源:https://www.bwwdw.com/article/yho3.html

Top