Slo*_*gic 0 sql-server cte union sql-server-2017
下面的查询看似简单明了,但却产生了意想不到的结果。
CREATE TABLE #NUMBERS
(
N BIGINT
);
INSERT INTO #NUMBERS VALUES
(1),
(2),
(3),
(4),
(5),
(6),
(7),
(8),
(9)
;
WITH
A AS
(
-- CHOOSE A ROW AT RANDOM
SELECT TOP 1 *
FROM #NUMBERS
ORDER BY NewID()
),
B AS
(
SELECT A.N AS QUANTITY, 'METERS' AS UNIT FROM A
UNION ALL
SELECT A.N*100 AS QUANTITY, 'CENTIMETERS' AS UNIT FROM A
UNION ALL
SELECT A.N*1000 AS QUANTITY, 'MILLIMETERS' AS UNIT FROM A
UNION ALL
SELECT A.N*1000000 AS QUANTITY, 'MICRONS' AS UNIT FROM A
UNION ALL
SELECT A.N*1000000000 AS QUANTITY, 'NANOMETERS' AS UNIT FROM A
)
SELECT *
FROM B
ORDER BY B.QUANTITY
;
Run Code Online (Sandbox Code Playgroud)
我希望它执行 CTE A 一次,然后将这些结果带入 CTE B 以产生如下结果:
| 数量 | 单元 |
|---|---|
| 4 | 米 |
| 400 | 厘米 |
| 4000 | 毫米 |
| 4000000 | 微米 |
| 4000000000 | 纳米 |
但是,它会产生如下结果:
| 数量 | 单元 |
|---|---|
| 8 | 米 |
| 700 | 厘米 |
| 1000 | 毫米 |
| 6000000 | 微米 |
| 3000000000 | 纳米 |
这意味着它会返回并执行 CTE A 五次,每次在 CTE B 中提及 A 一次。这不仅是不需要的且不直观的,而且看起来效率也不必要地低。
这是怎么回事?CTE 天才将如何重写它以产生所需的结果?
顺便说一句,关于 CTE 的 Microsoft 文档页面包含以下可能相关或可能不相关的神秘声明:
如果定义了多个 CTE_query_definition,则查询定义必须由以下集合运算符之一连接:UNION ALL、UNION、EXCEPT 或 INTERSECT。
最后,重写查询以消除 CTE B 没有帮助:
WITH
A AS
(
-- CHOOSE A ROW AT RANDOM
SELECT TOP 1 *
FROM #NUMBERS
ORDER BY NewID()
)
SELECT *
FROM (
SELECT A.N AS QUANTITY, 'METERS' AS UNIT FROM A
UNION ALL
SELECT A.N*100 AS QUANTITY, 'CENTIMETERS' AS UNIT FROM A
UNION ALL
SELECT A.N*1000 AS QUANTITY, 'MILLIMETERS' AS UNIT FROM A
UNION ALL
SELECT A.N*1000000 AS QUANTITY, 'MICRONS' AS UNIT FROM A
UNION ALL
SELECT A.N*1000000000 AS QUANTITY, 'NANOMETERS' AS UNIT FROM A
) AS B
ORDER BY B.QUANTITY
;
Run Code Online (Sandbox Code Playgroud)
将公用表表达式想象成更像表达式而不像(永久)表是有帮助的。每次引用公共表表达式时,它都必须重新表达自身。
这是一个简单的例子:
DECLARE
@t table(id int);
INSERT
@t
(
id
)
SELECT
id = 1
SET STATISTICS XML ON;
WITH
t AS
(
SELECT
t.id
FROM @t AS t
)
SELECT
t.*
FROM t
JOIN t AS t1
ON t1.id = t.id
JOIN t AS t2
ON t2.id = t.id;
Run Code Online (Sandbox Code Playgroud)
查询计划将如下所示,对于公共表表达式之间的每个连接,都连接到基表变量:
同样,UNION (ALL) 每次也会生成一个引用:
WITH
t AS
(
SELECT
t.id
FROM @t AS t
)
SELECT
t.*
FROM t
UNION ALL
SELECT
t.*
FROM @t AS t
UNION ALL
SELECT
t.*
FROM @t AS t;
Run Code Online (Sandbox Code Playgroud)
如果需要稳定结果,则需要使用:
#temp桌子@table多变的其他答案已经解释了问题发生的原因:基本上,CTE 只是一个表达式,其计算次数与引用次数相同,因此导致A每次计算时返回不同的值。
我想在回答中解决的是问题的这一部分:
CTE 天才将如何重写它以产生期望的结果?
在一些 CTE 天才聚集讨论 CTE 相关业务的地方闲逛可能教会了我一些我想分享的技巧。
我认为对于解决当前的问题非常有用的是两件事:
CROSS APPLY;VALUES。使用这两个,我将专门重写BCTE,如下所示:
B AS
(
SELECT X.*
FROM A
CROSS APPLY
(
VALUES
(A.N, 'METERS'),
(A.N*100, 'CENTIMETERS'),
(A.N*1000, 'MILLIMETERS'),
(A.N*1000000, 'MICRONS'),
(A.N*1000000000, 'NANOMETERS')
) AS X (QUANTITY, UNIT)
)
Run Code Online (Sandbox Code Playgroud)
保留查询的其余部分不变。
该方式B在上面定义,A仅被引用(和评估)一次。它仍然生成一个行集而不是单个行,因为它用行集替换(在 的帮助下CROSS APPLY)返回的行,并且行集(由 构造)本质上将其作为参数,生成所需的值集。AVALUESA.N
您可以在dbfiddle.uk测试完整查询。
| 归档时间: |
|
| 查看次数: |
3006 次 |
| 最近记录: |