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

标签 sql database join cardinality

我好像在网上找不到和我类似的情况。我有一个名为 Order 的“订单”表,以及一个名为“订单详细信息”的关于这些订单的详细信息表。某类订单的定义是,如果它有两对订单明细(Value-Unit 对)中的 1 个。因此,我的订单明细表可能如下所示:

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_id 的有效方法是什么?例如对于上表,我只需要接收 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 表很大,而且我对查询语言还很缺乏经验。有没有更快的方法以较低的基数来做到这一点?我对表的模式也有零控制,所以我不能在那里改变任何东西。

最佳答案

首先,我想强调的是,找到最有效的查询是一个好的查询一个好的索引的组合。我经常看到这里的问题,人们希望魔术只发生在其中一个或另一个中。

例如在各种解决方案中,当没有索引时,您的解决方案是最慢的(在修复语法错误之后),但是在 (detail, order_id)

上使用索引要好得多

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

[/ranf-off]


查询

事不宜迟,戈登·利诺夫 (Gordon Linoff) 提供了一些不错的 suggestions .还有另一种选择可能会提供类似的性能。你说你无法控制模式;但您可以使用子查询将数据转换为“更友好的结构”。

具体来说,如果您:

  • 对数据进行透视,以便每个 order_id 都有一行
  • 以及您要检查的每个详细信息的列
  • 交集是有多少订单具有该详细信息...

那么您的查询很简单: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 的查询计划略有不同。这表明在大型数据集上可能存在不同的性能特征。


索引

请注意,上面的代码可能会处理整个表格。因此,从索引中几乎没有收获。但是,如果表有“长行”,则仅在您正在使用的 2 列上建立索引意味着需要从磁盘读取的数据较少。

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

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

  • 如果没有上述索引,您的查询在批处理中是最慢的。
  • 使用上述索引,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'
)

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

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
        )

使用相交和除外:

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'
)

关于SQL - 查找一对行是否不存在的最有效方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43268206/

相关文章:

mysql - Google Big Query 中是否有任何方法可以在不重复使用* 右边的任何行的情况下一对一地进行左外连接?

mysql - 选择一个mysql表的所有内容,并回显对应类别的列表

php - 我如何在 mysql php 中加入 3 个表

sql - 如何查询相关记录之间的Linkage?

mysql - SQL 查询/Spark 数据帧外连接并减去两个表的值

mysql - 如何同步不同服务器的两个数据库?

java - 无法打开 JDBC 连接 oracle 和 spring boot

sql - Oracle sql。比较两个表以查找缺失数据

sql - 无效号码错误!似乎无法绕过它

SQL 服务器 : How can I check to see if a field has a "NULL" or "NOT NULL" constraint in place?