[英]SQL - most efficient way to find if a pair of row does NOT exist
我似乎在網上找不到類似的情況。 我有一個名為“訂單”的表,稱為“訂單”,還有一個有關這些訂單的詳細信息的表,稱為“訂單明細”。 某種類型的訂單的定義是,如果它具有兩對訂單明細(值-單位對)中的一對。 因此,我的訂單明細表可能如下所示:
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表很大,而且我對查詢語言缺乏經驗。 有沒有一種更快的方法來降低基數呢? 我對表的架構也有零控制,因此我無法在其中進行任何更改。
我將使用聚合並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;
首先,我想強調的是,找到最有效的查詢是好的查詢和好的索引的結合。 我經常在這里看到這樣的問題:人們希望魔術只在一個或另一個中發生。
例如,在各種各樣的解決方案中,當沒有索引時,您的解決方案最慢(在修復語法錯誤之后),但在索引為(detail, order_id)
另請注意,您具有實際的數據和表結構。 您需要嘗試各種查詢和索引組合,才能找到最合適的方法。 尤其重要的是,由於您尚未說明正在使用的平台,並且結果可能會因平台而異。
[/ ranf斷]
事不宜遲,戈登·利諾夫(Gordon Linoff)提供了一些好的建議 。 還有另一種可能提供類似性能的選項。 您說無法控制架構; 但是您可以使用子查詢將數據轉換為“更友好的結構”。
具體來說,如果您:
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的查詢計划略有不同。 這表明在大型數據集上具有不同性能特征的可能性。
請注意,以上內容可能會處理整個表格。 因此,索引幾乎無濟於事。 但是,如果表具有“長行”,則僅在您正在使用的2列上建立索引,這意味着需要從磁盤讀取的數據更少。
您提供的查詢結構可能會受益於諸如(detail, order_id)
類的索引。 這是因為服務器可以更有效地檢查NOT IN
子查詢條件。 效益如何取決於表中數據的分布。
附帶說明一下,我測試了各種查詢選項,包括您和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'
)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.