增强 MS SQL 服务器在 UNION 操作上的性能
Enhance MS SQL Server performance on UNION operations
我有一个 MS SQL 服务器 table T1
,其中包含三个代码列:CodeA
、CodeB1
和 CodeB2
。我有一个具有相同列的 table 类型变量。
我必须将 table 变量与 T1
table 连接起来,以便在 table 变量中获取与 CodeB1
[=] 匹配的行52=] CodeB2
,但不匹配 CodeA
,或匹配 CodeA
,但既不匹配 CodeB1
,也不匹配 CodeB2
。
我最初做了一个 SELECT
这样的语句:
SELECT *
(SELECT
CASE WHEN t.CodeA = v.CodeA then 1 else 0 end as [EqualCodeA],
CASE WHEN t.CodeB1 = v.CodeB1 then 1 else 0 end as [EqualCodeB1],
CASE WHEN t.CodeB2 = v.CodeB2 then 1 else 0 end as [EqualCodeB2]
FROM @tableVariable v
INNER JOIN [T1] t
ON t.CodeA = v.CodeA or
t.CodeB1 = v.CodeB1 or
t.CodeB2 = v.CodeB2
)
WHERE NOT(EqualCodeA = 1 AND (EqualCodeB1 = 1 OR EqualCodeB2 = 1)
但是那个查询的性能很差。所以我将谓词中的 OR
切换为 UNION
,像这样:
SELECT *
(SELECT
CASE WHEN t.CodeA = v.CodeA then 1 else 0 end as [EqualCodeA],
CASE WHEN t.CodeB1 = v.CodeB1 then 1 else 0 end as [EqualCodeB1],
CASE WHEN t.CodeB2 = v.CodeB2 then 1 else 0 end as [EqualCodeB2]
FROM @tableVariable v
INNER JOIN [T1] t
ON t.CodeA = v.CodeA
UNION
SELECT
CASE WHEN t.CodeA = v.CodeA then 1 else 0 end as [EqualCodeA],
CASE WHEN t.CodeB1 = v.CodeB1 then 1 else 0 end as [EqualCodeB1],
CASE WHEN t.CodeB2 = v.CodeB2 then 1 else 0 end as [EqualCodeB2]
FROM @tableVariable v
INNER JOIN [T1] t
ON t.CodeB1 = v.CodeB1
UNION
SELECT
CASE WHEN t.CodeA = v.CodeA then 1 else 0 end as [EqualCodeA],
CASE WHEN t.CodeB1 = v.CodeB1 then 1 else 0 end as [EqualCodeB1],
CASE WHEN t.CodeB2 = v.CodeB2 then 1 else 0 end as [EqualCodeB2]
FROM @tableVariable v
INNER JOIN [T1] t
ON t.CodeB2 = v.CodeB2)
)
WHERE NOT(EqualCodeA = 1 AND (EqualCodeB1 = 1 OR EqualCodeB2 = 1)
性能现在大约提高了十倍,但仍然无法接受table。例如。对于 table 变量中的 10K 行和 T1
中的 50K 行,查询需要两分钟。
查看实际执行计划,我看到两个Hash Match (Union)
操作,每个占46%的成本。
我怎样才能提高这个性能?
注1:有一个包括所有三列的非聚集索引,以及三个单独的非聚集索引,每个列一个。
注2:我使用OPTION(RECOMPILE);
是为了让优化器至少知道table变量的实际行数。
对于这种情况,您需要在 T1 上使用三个独立的非聚集索引。另外,如果这三个部分相互排斥,则更改为 "union all" 而不是 "union"。
老实说,我不太明白示例代码如何与您的要求描述相匹配,但假设后者是正确的,这就是我想出的:
-- in order to get the lines in table variable that match for CodeB1 and/or CodeB2, but not CodeA,
-- or match CodeA, but neither CodeB1 nor CodeB2.
SELECT v.*
FROM @tableVariable v
JOIN T1 t
ON (v.CodeA <> t.CodeA AND (v.CodeB1 = t.CodeB1 OR v.CodeB2 = t.CodeB2))
OR (v.codeA = t.CodeA AND v.CodeB1 <> t.CodeB1 AND v.codeB2 <> t.codeB2)
GO
-- convert OR into UNION
SELECT v.*
FROM @tableVariable v
JOIN T1 t
ON (v.CodeA <> t.CodeA AND (v.CodeB1 = t.CodeB1 OR v.CodeB2 = t.CodeB2))
UNION
SELECT *
FROM @tableVariable v
JOIN T1 t
ON (v.codeA = t.CodeA AND v.CodeB1 <> t.CodeB1 AND v.codeB2 <> t.codeB2)
GO
-- further convert OR into UNION
SELECT v.*
FROM @tableVariable v
JOIN T1 t
ON v.CodeA <> t.CodeA
AND v.CodeB2 = t.CodeB2
UNION
SELECT v.*
FROM @tableVariable v
JOIN T1 t
ON v.CodeA <> t.CodeA
AND v.CodeB1 = t.CodeB1
UNION
SELECT v.*
FROM @tableVariable v
JOIN T1 t
ON v.codeA = t.CodeA
AND v.CodeB1 <> t.CodeB1
AND v.codeB2 <> t.codeB2
-- potentially helpfull indexes
CREATE INDEX idx1 ON T1 (CodeA) INCLUDE (CodeB1, CodeB2)
CREATE INDEX idx2 ON T1 (CodeB1) INCLUDE (CodeA)
CREATE INDEX idx3 ON T1 (CodeB2) INCLUDE (CodeA)
这很可能与您的解决方案具有完全相同的成本,优化器可能能够(在内部)将它们转换为完全相同的 action。看到查询计划 and/or 更好地了解手头的数据会很有趣。
PS:正如其他地方已经提到的,尽量避免@tableVariables,#tempTables 在处理多条记录时要好得多。 (您可以根据需要放置索引、统计信息等...)
我有一个 MS SQL 服务器 table T1
,其中包含三个代码列:CodeA
、CodeB1
和 CodeB2
。我有一个具有相同列的 table 类型变量。
我必须将 table 变量与 T1
table 连接起来,以便在 table 变量中获取与 CodeB1
[=] 匹配的行52=] CodeB2
,但不匹配 CodeA
,或匹配 CodeA
,但既不匹配 CodeB1
,也不匹配 CodeB2
。
我最初做了一个 SELECT
这样的语句:
SELECT *
(SELECT
CASE WHEN t.CodeA = v.CodeA then 1 else 0 end as [EqualCodeA],
CASE WHEN t.CodeB1 = v.CodeB1 then 1 else 0 end as [EqualCodeB1],
CASE WHEN t.CodeB2 = v.CodeB2 then 1 else 0 end as [EqualCodeB2]
FROM @tableVariable v
INNER JOIN [T1] t
ON t.CodeA = v.CodeA or
t.CodeB1 = v.CodeB1 or
t.CodeB2 = v.CodeB2
)
WHERE NOT(EqualCodeA = 1 AND (EqualCodeB1 = 1 OR EqualCodeB2 = 1)
但是那个查询的性能很差。所以我将谓词中的 OR
切换为 UNION
,像这样:
SELECT *
(SELECT
CASE WHEN t.CodeA = v.CodeA then 1 else 0 end as [EqualCodeA],
CASE WHEN t.CodeB1 = v.CodeB1 then 1 else 0 end as [EqualCodeB1],
CASE WHEN t.CodeB2 = v.CodeB2 then 1 else 0 end as [EqualCodeB2]
FROM @tableVariable v
INNER JOIN [T1] t
ON t.CodeA = v.CodeA
UNION
SELECT
CASE WHEN t.CodeA = v.CodeA then 1 else 0 end as [EqualCodeA],
CASE WHEN t.CodeB1 = v.CodeB1 then 1 else 0 end as [EqualCodeB1],
CASE WHEN t.CodeB2 = v.CodeB2 then 1 else 0 end as [EqualCodeB2]
FROM @tableVariable v
INNER JOIN [T1] t
ON t.CodeB1 = v.CodeB1
UNION
SELECT
CASE WHEN t.CodeA = v.CodeA then 1 else 0 end as [EqualCodeA],
CASE WHEN t.CodeB1 = v.CodeB1 then 1 else 0 end as [EqualCodeB1],
CASE WHEN t.CodeB2 = v.CodeB2 then 1 else 0 end as [EqualCodeB2]
FROM @tableVariable v
INNER JOIN [T1] t
ON t.CodeB2 = v.CodeB2)
)
WHERE NOT(EqualCodeA = 1 AND (EqualCodeB1 = 1 OR EqualCodeB2 = 1)
性能现在大约提高了十倍,但仍然无法接受table。例如。对于 table 变量中的 10K 行和 T1
中的 50K 行,查询需要两分钟。
查看实际执行计划,我看到两个Hash Match (Union)
操作,每个占46%的成本。
我怎样才能提高这个性能?
注1:有一个包括所有三列的非聚集索引,以及三个单独的非聚集索引,每个列一个。
注2:我使用OPTION(RECOMPILE);
是为了让优化器至少知道table变量的实际行数。
对于这种情况,您需要在 T1 上使用三个独立的非聚集索引。另外,如果这三个部分相互排斥,则更改为 "union all" 而不是 "union"。
老实说,我不太明白示例代码如何与您的要求描述相匹配,但假设后者是正确的,这就是我想出的:
-- in order to get the lines in table variable that match for CodeB1 and/or CodeB2, but not CodeA,
-- or match CodeA, but neither CodeB1 nor CodeB2.
SELECT v.*
FROM @tableVariable v
JOIN T1 t
ON (v.CodeA <> t.CodeA AND (v.CodeB1 = t.CodeB1 OR v.CodeB2 = t.CodeB2))
OR (v.codeA = t.CodeA AND v.CodeB1 <> t.CodeB1 AND v.codeB2 <> t.codeB2)
GO
-- convert OR into UNION
SELECT v.*
FROM @tableVariable v
JOIN T1 t
ON (v.CodeA <> t.CodeA AND (v.CodeB1 = t.CodeB1 OR v.CodeB2 = t.CodeB2))
UNION
SELECT *
FROM @tableVariable v
JOIN T1 t
ON (v.codeA = t.CodeA AND v.CodeB1 <> t.CodeB1 AND v.codeB2 <> t.codeB2)
GO
-- further convert OR into UNION
SELECT v.*
FROM @tableVariable v
JOIN T1 t
ON v.CodeA <> t.CodeA
AND v.CodeB2 = t.CodeB2
UNION
SELECT v.*
FROM @tableVariable v
JOIN T1 t
ON v.CodeA <> t.CodeA
AND v.CodeB1 = t.CodeB1
UNION
SELECT v.*
FROM @tableVariable v
JOIN T1 t
ON v.codeA = t.CodeA
AND v.CodeB1 <> t.CodeB1
AND v.codeB2 <> t.codeB2
-- potentially helpfull indexes
CREATE INDEX idx1 ON T1 (CodeA) INCLUDE (CodeB1, CodeB2)
CREATE INDEX idx2 ON T1 (CodeB1) INCLUDE (CodeA)
CREATE INDEX idx3 ON T1 (CodeB2) INCLUDE (CodeA)
这很可能与您的解决方案具有完全相同的成本,优化器可能能够(在内部)将它们转换为完全相同的 action。看到查询计划 and/or 更好地了解手头的数据会很有趣。
PS:正如其他地方已经提到的,尽量避免@tableVariables,#tempTables 在处理多条记录时要好得多。 (您可以根据需要放置索引、统计信息等...)