有兩張結構相同的表 table_left、table_right,按 groupId 分組,每組包含多行 data。需求:找出 table_left 中哪些 groupId 在 table_right 中存在「完全相同的數據集」(行數、值、順序均一致)。
將每組視為集合,兩組「完全相等」等價於:
A ⊆ B 且 B ⊆ A ⇔ A \ B = ∅ 且 B \ A = ∅
SQL Server 的 EXCEPT 運算符天然實現「差集」,完美契合此邏輯。
dataPath NVARCHAR(200) 存儲拼接串。CROSS APPLY ... FOR XML PATH('') 將同組 data 按排序拼成 -data1-data2-data3。WHERE EXISTS (SELECT 1 FROM table_right WHERE dataPath = table_left.dataPath) 即可匹配。ALTER TABLE table_left ADD dataPath NVARCHAR(200);
ALTER TABLE table_right ADD dataPath NVARCHAR(200);
UPDATE a SET dataPath = b.dataPath
FROM table_left a
CROSS APPLY (
SELECT (SELECT '-' + x.data FROM table_left x WHERE x.groupId = a.groupId ORDER BY x.data FOR XML PATH('')) AS dataPath
) b;
UPDATE a SET dataPath = b.dataPath
FROM table_right a
CROSS APPLY (
SELECT (SELECT '-' + x.data FROM table_right x WHERE x.groupId = a.groupId ORDER BY x.data FOR XML PATH('')) AS dataPath
) b;
CREATE NONCLUSTERED INDEX ix_left ON table_left (dataPath);
CREATE NONCLUSTERED INDEX ix_right ON table_right(dataPath);
SELECT DISTINCT a.groupId
FROM table_left a
WHERE EXISTS (SELECT 1 FROM table_right x WHERE x.dataPath = a.dataPath);
SELECT DISTINCT a.groupId
FROM table_left a
INNER JOIN table_right b ON b.data = a.data -- 先按單行值過濾,大幅縮小候選集
WHERE NOT EXISTS (
SELECT x.data FROM table_left x WHERE x.groupId = a.groupId
EXCEPT
SELECT y.data FROM table_right y WHERE y.groupId = b.groupId
)
AND NOT EXISTS (
SELECT x.data FROM table_right x WHERE x.groupId = b.groupId
EXCEPT
SELECT y.data FROM table_left y WHERE y.groupId = a.groupId
);
雙向 EXCEPT 皆為空 ⇔ 兩組多重集完全相等。
| 指標 | 方案一(拼接+索引) | 方案二(EXCEPT) |
|---|---|---|
| 邏輯讀 | 較高(兩次全表掃描更新 + 索引查找) | 低(僅索引查找 + 流式差集) |
| CPU 時間 | 高(XML 序列化、字符串操作) | 低(純集合運算) |
| 索引限制 | dataPath ≤ 900 字節,否則無法建索引 | 無長度限制,原列直接參與 |
| 擴展性 | 多列需再拼接或加校驗和 | 原生支援多列 EXCEPT (col1,col2...) |
當 data 超過 900 字節或需比對多列(如 dataSub1, dataSub2),引入計算列校驗和 + 索引:
ALTER TABLE table_left ADD dataChecksum AS CHECKSUM(dataSub1, dataSub2);
ALTER TABLE table_right ADD dataChecksum AS CHECKSUM(dataSub1, dataSub2);
CREATE NONCLUSTERED INDEX ix_left_cs ON table_left (dataChecksum);
CREATE NONCLUSTERED INDEX ix_right_cs ON table_right(dataChecksum);
SELECT DISTINCT a.groupId
FROM table_left a
INNER JOIN table_right b
ON b.dataChecksum = a.dataChecksum
AND b.dataSub1 = a.dataSub1
AND b.dataSub2 = a.dataSub2
WHERE NOT EXISTS (
SELECT x.dataSub1, x.dataSub2 FROM table_left x WHERE x.groupId = a.groupId
EXCEPT
SELECT y.dataSub1, y.dataSub2 FROM table_right y WHERE y.groupId = b.groupId
)
AND NOT EXISTS (
SELECT x.dataSub1, x.dataSub2 FROM table_right x WHERE x.groupId = b.groupId
EXCEPT
SELECT y.dataSub1, y.dataSub2 FROM table_left y WHERE y.groupId = a.groupId
);
CHECKSUM 做快速預過濾,EXCEPT 做精確比對,兼顧性能與準確性。
INNER JOIN ... ON b.data = a.data 能大幅縮小候選組數。