SQL - 查找一对行不存在的最有效方法

时间:2017-04-07 01:25:59

标签: sql database join cardinality

我似乎无法在网上找到类似的情况。我有一个名为Order的'orders'表,以及一个关于这些订单的详细信息表,称为'order detail'。某种类型订单的定义是它是否具有两对订单细节中的一对(价值单位对)。所以,我的订单明细表可能如下所示:

order_id | detail
---------|-------
1        | X  
1        | Y
1        | Z
2        | X
2        | Z
2        | B
3        | A
3        | Z
3        | B

两对合在一起的是(X& Y)和(A& B)。检索那些不包含其中任何一对的order_ids的有效方法是什么?例如对于上表,我只需要收到order_id 2。

我能提出的唯一解决方案主要是使用两个查询并执行自联接:

select distinct o.order_id
from orders o
where o.order_id not in (
    select distinct order_id
    from order_detail od1 where od1.detail=X
    join order_detail od2 on od2.order_id = od1.order_id and od2.detail=Y
) 
and o.order_id not in (
    select distinct order_id
    from order_detail od1 where od1.detail=A
    join order_detail od2 on od2.order_id = od1.order_id and od2.detail=B
)

问题是性能是一个问题,我的order_detail表是巨大的,我在查询语言方面缺乏经验。有更低的基数有更快的方法吗?我也无法控制表格的模式,因此我无法改变任何内容。

2 个答案:

答案 0 :(得分:1)

我会使用聚合和having

select order_id
from order_detail od
group by order_id
having sum(case when detail in ('X', 'Y') then 1 else 0 end) < 2 and
       sum(case when detail in ('A', 'B') then 1 else 0 end) < 2;

这假定订单没有具有相同detail的重复行。如果可能的话:

select order_id
from order_detail od
group by order_id
having count(distinct case when detail in ('X', 'Y') then detail end) < 2 and
       count(distinct case when detail in ('A', 'B') then detail end) < 2;

答案 1 :(得分:1)

首先,我要强调的是,找到最有效的查询是一个好的查询 一个好的索引的组合。我常常在这里看到一些问题,人们只会在其中一个地方寻找魔法。

E.g。在各种解决方案中,当没有索引时,你的解决方案是最慢的(在修复语法错误之后),但对于(detail, order_id)

上的索引则相当好一些

另请注意,您拥有实际的数据和表格结构。您需要尝试各种查询和索引组合,以找到最有效的方法;不仅仅是因为您还没有表明您正在使用哪个平台,而且平台之间的结果可能会有所不同。

[/ ranf断]

查询

不用多说,戈登·林诺夫提供了一些好的suggestions。还有另一种可能提供类似性能的选择。你说你无法控制架构;但您可以使用子查询将数据转换为更友好的结构。

具体来说,如果你:

  • 转动数据,以便每个order_id
  • 有一行
  • 以及您要检查的每个detail的列
  • 并且交叉点是具有该详细信息的订单数量......

然后您的查询就是:where (x=0 or y=0) and (a=0 or b=0)。以下使用SQL Server的临时表来演示样本数据。无论重复id, val对,下面的查询都可以正常工作。

/*Set up sample data*/
declare @t table (
    id int,
    val char(1)
)
insert @t(id, val)
values  (1, 'x'), (1, 'y'), (1, 'z'),
        (2, 'x'), (2, 'z'), (2, 'b'),
        (3, 'a'), (3, 'z'), (3, 'b')

/*Option 1 manual pivoting*/
select  t.id
from    (
        select  o.id,
                sum(case when o.val = 'a' then 1 else 0 end) as a,
                sum(case when o.val = 'b' then 1 else 0 end) as b,
                sum(case when o.val = 'x' then 1 else 0 end) as x,
                sum(case when o.val = 'y' then 1 else 0 end) as y
        from    @t o
        group by o.id
        ) t
where   (x = 0 or y = 0) and (a = 0 or b = 0)

/*Option 2 using Sql Server PIVOT feature*/
select  t.id
from    (
        select  id ,[a],[b],[x],[y]
        from    (select id, val from @t) src
                pivot (count(val) for val in ([a],[b],[x],[y])) pvt
        ) t
where   (x = 0 or y = 0) and (a = 0 or b = 0)

值得注意的是,上面选项1和2的查询计划略有不同。这表明大数据集可能具有不同的性能特征。

索引

请注意,以上内容可能会处理整个表格。因此索引几乎无法获得。但是,如果表格有&#34;长行&#34;,那么您只使用2个列的索引意味着需要从磁盘读取更少的数据。

您提供的查询结构可能会受益于(detail, order_id)等索引。这是因为服务器可以更有效地检查NOT IN子查询条件。有益程度取决于表中数据的分布。

作为附注,我测试了各种查询选项,包括你的固定版本和Gordon's。 (虽然只有很小的数据大小。)

  • 如果没有上述索引,您的查询在批处理中最慢。
  • 根据上述指数,Gordon的第二个查询速度最慢。

替代查询

您的查询(已修复):

select distinct o.id
from @t o
where o.id not in (
    select  od1.id
    from    @t od1 
            inner join @t od2 on 
                od2.id = od1.id
            and od2.val='Y'
    where   od1.val= 'X'
) 
and o.id not in (
    select  od1.id
    from    @t od1 
            inner join @t od2 on 
                od2.id = od1.id
            and od2.val='a'
    where   od1.val= 'b'
)

戈登的第一次和第二次查询之间的混合。修复了第一个中的重复问题和第二个中的性能:

select id
from @t od
group by id
having (    sum(case when val in ('X') then 1 else 0 end) = 0
         or sum(case when val in ('Y') then 1 else 0 end) = 0
        )
    and(    sum(case when val in ('A') then 1 else 0 end) = 0
         or sum(case when val in ('B') then 1 else 0 end) = 0
        )

使用INTERSECT和EXCEPT:

select  id
from    @t
except
(
    select  id
    from    @t
    where   val = 'a'
    intersect
    select  id
    from    @t
    where   val = 'b'
)
except
(
    select  id
    from    @t
    where   val = 'x'
    intersect
    select  id
    from    @t
    where   val = 'y'
)