zoukankan      html  css  js  c++  java
  • 第六章 集合运算

    集合运算是对输入的两个集合进行的运算,参与运算的集合可以是由两个输入的查询生成的结果。
    t-sql支持3种集合运算:并集(UNION)、交集(INTERSECT)和差集(EXCEPT)。INTERSECT和EXCEPT运算是在SQLServer2005种引入的。

    集合运算的基本格式为:

    -- 输入的查询1 
    -- <集合运算> 
    -- 输入的查询2 
    -- [ORDER BY...]

    集合运算会对两个输入查询生成的结果集逐行比较,根据比较结果和所使用的集合运算来确定某一行是否应该包含在集合运算的结果中。因为按照定义,集合运算是在两个集合(或多集)之间进行的运算,而且集合本身是无序的,所以,集合运算设计的两个查询不能包含order by子句。

    参与集合运算的两个查询生成的结果必须包含相同的列数,而且相应列必须具有兼容的数据类型。这里“兼容的数据类型”是指优先级较低的数据类型必须能够隐式地转换为较高级的数据类型。集合运算结果中的列名由第一个查询决定,因此,如果要为结果列分配列名,应该在第一个查询中分配相应的别名。集合运算有个有趣的特点:对行进行比较时,集合运算认为两个null相等。

    ansi sql对每种结合运算都支持两个选项:distinct(默认值)和all。DISTINCT逻辑上可以从两个输入的多集中消除重复的行(让多集成为真正的集合),然后返回一个集合。ALL对两个多集进行运算时不会删除重复行,而是返回一个可能包含重复行的多集。对于3种集合运算,SQLServer 2008均支持DISTINCT选项,但只能在UNION中支持ALL选项。按照语法要求,不能显式指定DISTINCT子句。但如果不显式指定ALL,则默认使用DISTINCT。


    UNION(并集)集合运算
    在集合论中,两个集合(A和B)的并集是一个包含A和B中所有元素的集合。换句话说,如果一个元素属于任何一个输入集合,那么它也属于结果集。

    在T-SQL中,UNION集合运算可以将两个输入查询的结果集组合成一个结果集。如果一个行在任何一个输入集合中出现,它也会在UNION运算的结果中出现。T-SQL支持在UNION集合运算中使用UNION ALL和UNION(隐含DISTINCT)选项。

    对于作为运算的输入查询而生成的多集,UNION ALL集合运算返回在输入的多集中出现的所有行,它实际上不会对行进行比较,也不会删除重复行。假设查询QUERY1返回m行,查询QUERY2返回n行,则QUERY1 UNION ALL QUERY2 返回(m+n)行。

    use tsqlfundamentals2008;
    select country, region, city from hr.employees
    union all
    select country, region, city from sales.Customers;

    从逻辑处理过程来看,UNION(隐含DISTINCT)集合运算通过删除重复记录,可以把两个输入的多集转变成一个集合,这个返回的集合中包含连个输入集中的所有行。注意,如果两个输入集中包含相同的行,则该行在结果中只出现一次;换句话说,运算结果是一个真正的集合,而不是多集。从物理处理过程来看,SQLServer不一定必须删除输入多集中的重复行,在进行集合运算。相反,它可以先把两个多集组合在一起,然后再删除重复行。

    select country, region, city from hr.Employees
    union
    select country, region, city from Sales.Customers;

    如果在集合运算合并两个输入集以后可能存在重复行,而且也要返回重复行,则使用UNION ALL。如果可能存在重复行,但要返回互不相同的行,则使用UNION。如果在合并两个输入集以后不可能会出现重复行,那么这时UNION和UNION ALL在逻辑上是等价的。不过,对于这种情况,则建议使用UNION ALL,这样可避免SQLServer为检查重复行而带来的额外开销。


    INTERSECT(交集)集合运算
    在集合论中,两个集合(A和B)的交集是由既属于A,也属于B的所有元素组成的集合。在T-SQL中,INTERSECT集合运算对两个输入查询的结果集取其交集,只返回在两个查询结果集中都出现的行。INTERSECT集合运算在逻辑上首先删除两个输入多集中的重复行(把多集变成集合),然后返回只在两个集合中都出现的行。

    select country, region, city from hr.Employees
    intersect
    select country, region, city from Sales.Customers;

    一个雇员地址或客户地址究竟出现了多少次,这并不重要。如果某个地址在Employees表中至少出现一次,而且在Customers表中也至少出现一次,那么就返回该地址。记住前面讲过,集合运算对行进行比较时,认为两个null值相等。客户和雇员地址中均包含(UK,NULL,LONDON)这个地址,它能在输出中出现非比寻常。除了country和city列以外,当对雇员行中取值为null的region列和客户行中取值为null的region列进行比较时,集合运算认为两者相等,所以就返回改行。

    当这种处理方式对NULL值得期望行为进行比较时,与其他方法相比,集合运算具有更大的优势。例如,代替INTERSECT集合运算的一种方法是使用内联接,另一种方法是使用exists谓词。在这两种情况下,当对雇员表region列中的null值和客户表region列中的null值进行比较时,比较结果都是unknown,这样的行将被过滤掉。因此,除非增加额外的逻辑,以特定的方式对null进行处理,否则即使(uk,null,london)这行记录在运算两边的表中都出现,内联接和exists的实现方法都不能返回改行。

    INTERSECT ALL集合运算
    ANSI SQL支持带有ALL选项的INTERSECT集合运算,但SQLServer 2008现在还没有实现这种运算。回忆一下UNION ALL集合运算中ALL关键字的含义:要求返回所有重复行。类似的,INTERSECT ALL集合运算中的ALL关键字也意味着不会删除重复行。但INTERSECT ALL与UNION ALL有所不同:前者不会返回所有重复行,而只返回重复行数目较少的那个多集的所有重复行。换句话说,INTERSECT ALL运算不仅关心一个行是否在两个多集中同时存在,还关心它在每个多集中出现的次数。就好像这个集合运算会查找每行的每次匹配一样。如果行R在第一个输入的多集中出现了x次,在第二个输入的多集中出现了y次,则行R应该在运算的结果中出下了minimum(x,y)次。例如,地址(uk,null,london)在employees表中出现了4次,在customers表中出现了6次:因此,对雇员地址和客户地址进行intersect all运算以后,运算应该返回4行,因为从逻辑角度来说,该行只相交了4次。

    提示:
    虽然SQLServer不支持内建的intersect all运算,但用其他解决方案也能生成相同的结果。可以用row_number函数来计算每个输入查询中每行的出现次数(行号)。为此,在函数的partition by子句中指定所有参与集合运算的列,并在order by子句中用select<常量>来表明行的排列顺序并不重要。在排序函数的over子句中使用order by(select<常量>)用这种方法可以告诉SQLServer不必在意行的顺序。SQLServer足够聪明,它能够意识到将要为所有行分配同一常量,因此,没有必要对数据进行排序,更没有必要为此付出一定代价。接着,再对两个带有row_number函数的查询应用intersect集合运算。因为每一行在集合中出现的次数有了编号,所以除了原来的三个地址列,进行交集运算时还要基于每行的行号。对这两个输入集取其交集时,出现次数编号为1~4的所有行就是他们的交集。

    with intersect_all as
    (
        select
            ROW_NUMBER()
                over(partition by country, region, city
                    order by (select 0)) as rownum,
            country, region, city
        from hr.Employees
        intersect
        select
            row_number()
                over(partition by country, region, city
                    order by (select 0)) as rownum
            , country, region, city
        from sales.Customers
    )
    select country, region, city
    from intersect_all;

    except(差集)集合运算
    在集合论中,集合A与B的差集(A-B)是由属于集合A,但不属于集合B的元素组成的集合。可以认为两个集合的差A-B就是从A中减去B中也属于A的元素。在T-SQL中,集合之差使用EXCEPT集合运算实现的。EXCEPT运算对两个输入查询的结果集进行操作,返回出现在第一个结果集中,但不出现第二个结果集中的所有行。

    EXCEPT集合运算在逻辑上先删除两个输入多集中的重复行(把多集转变成集合),然后返回只在第一个集合中出现,在第二个集合中不出现的所有行。换句话说,一个行能够被返回,仅当这个行在第一个输入的多集中至少出现一次,而且在第二个多集中没有出现过。与其他两个集合运算不同的是,except是不对称的。

    select country, region, city from hr.Employees
    except
    select country, region, city from sales.Customers;

    except运算也可以用其他方法来实现。一种方法是使用外联接,筛选出在联接左边出现而在右边不出现的外部行。另一种方法是使用not exists谓词。except all运算与except运算非常类似,但它还考虑了每一行出现的次数。假设行R在第一个多集中出现了x次,在第二个多集中出现了y次,且x>y,则在query1 except all query2中,r出现了x-y次。换句话说,如果一个行在第一个多集中出现了多次,except all逻辑上只返回它在第二个多集中没有相应出现过的那些行。虽然SQLServer没有提供内建的except all运算,但用与intersect all的解决方案类似的方法,也可以为except all提供替代的解决方案。也就是为每个输入查询增加一个row_number计算,算出每行是第几次出现,再对两个输入集应用except运算,这样就只返回出现次数找不到匹配的行。

    use TSQLFundamentals2008;
    with except_all
    as
    (
        select
            row_number()
                over(partition by country, region, city
                    order by (select 0)) as rownum
            , country, region, city
        from hr.Employees
    
        except
    
        select
            row_number()
                over(partition by country, region, city
                    order by (select 0)) as rownum
            , country, region, city
        from Sales.Customers
    )
    select country, region, city
    from except_all;

    sql定义了集合运算之间的优先级。intersect运算比union和except运算的优先级高,而union和except的优先级相等。在包含多个集合运算的查询中,首先计算intersect,然后按照从左到右的出现顺序依次处理优先级相同的运算。

    select country, region, city from Production.Suppliers
    except
    select country, region, city from hr.Employees
    intersect
    select country, region, city from Sales.Customers;

    因为intersect的优先级比except高,所以首先进行intersect运算。要控制集合运算的计算顺序,可以使用圆括号,它总在具有最高的优先级。

    (select country, region, city from production.suppliers
    except
    select country, region, city from hr.Employees)
    intersect
    select country, region, city from Sales.Customers;

    参与集合运算的单个查询可以支持除order by以外的所有逻辑查询处理。不过,只有order by阶段才允许直接应用于集合运算的结果。如果要对集合运算的结果应用于除order by以外的其他逻辑阶段,应该怎么办呢?集合运算查询本身并不支持这样的操作,但通过使用表表达式可以轻易避开这一限制。首先根据包含集合运算的查询定义一个表表达式,然后在外部查询中对表表达式应用任何需要的逻辑查询处理。

    select country, COUNT(*) as numlocations
    from (select country, region, city from hr.Employees
            union
            select country, region, city from Sales.Customers) as U
    group by country;

    这个查询演示了如何对union集合运算的结果应用group by逻辑查询处理。与此类似,在外部查询中也可以应用其他逻辑查询处理。

    不能在涉及集合运算的单个查询中指定order by子句,这一事实也可能引发逻辑问题。如果须要在查询中使用top选项限制行数,这是怎么办呢?再一次,还使用表表达式来解决这个问题。如果须要在集合运算中使用带order by子句的top查询,只要简单的定义一个基于该top查询的表表达式,然后通过一个使用这个表表达式的外部查询去参与集合运算。

    select empid, orderid, orderdate
    from (select top(2) empid, orderid, orderdate
            from Sales.Orders
            where empid = 3
            order by orderdate desc, orderid desc) as d1
    
            union all
    
            select empid, orderid, orderdate
            from (select top(2) empid, orderid, orderdate
                    from Sales.Orders
                    where empid = 5
                    order by orderdate desc, orderid desc) as d2;
    
    use tsqlfundamentals2008;
  • 相关阅读:
    AS出现Connection timed out
    关于eclipse出现The selection cannot be launched,and there are no recent launches
    第十周周赛题解
    FJUT 2401 尼克的任务
    关于3月份的学习总结
    人生的第一题图论
    第六周周赛题解
    Drupal8 社区文档之积极的Drupal版本
    Drupal8 社区文档之Drupal安全吗
    Drupal8 社区文档之技术堆栈
  • 原文地址:https://www.cnblogs.com/panshu/p/3288303.html
Copyright © 2011-2022 走看看