如何优化此UNION查询?

时间:2019-04-24 03:01:46

标签: sql sql-server

我有以下测试架构:

CREATE TABLE A
(
  ID INT PRIMARY KEY,
  A VARCHAR(1),
  B VARCHAR(3),
  C VARCHAR(3),
  BID INT,
  DATE DATETIME2(7)
)

INSERT INTO A values
(1, NULL, 'AAA', 'XXX', 1, '2018-04-04'),
(2, NULL, 'BBB', 'YYY', 2, '2018-05-05'),
(3, NULL, 'CCC', 'ZZZ', 2, '2018-06-06')

CREATE TABLE B
(
  ID INT PRIMARY KEY,
  X VARCHAR(1),
  Y VARCHAR(1),
  Z VARCHAR(1)
)

INSERT INTO B values
(1, 'A', 'A', 'A'),
(2, 'B', 'B', 'B'),
(3, 'C', 'C', 'C'),
(4, 'D', 'D', 'D'),
(5, 'E', 'E', 'E')

CREATE TABLE C
(
  ID INT PRIMARY KEY,
  NAME VARCHAR(12)
)

INSERT INTO C values
(1, 'Mary'),
(2, 'John'),
(3, 'Peter')

CREATE TABLE D
(
  ID INT PRIMARY KEY,
  CID INT,
  R VARCHAR(3),
  S VARCHAR(3)
)

INSERT INTO D values
(1, 1, 'AAA', 'ABC'),
(2, 2, 'BBB', 'ZZZ'),
(3, 1, 'FOO', 'ZZZ'),
(4, 3, 'BAR', 'YYY')

我有这个查询:

WITH T1 AS
(
    SELECT A.ID, A.A, A.B, A.C, B.X, B.Y, B.Z
    FROM A
    LEFT JOIN B
    ON A.BID = B.ID
    WHERE A.DATE BETWEEN '2018-01-01' AND '2018-12-31'
)

(
    SELECT T1.ID, T1.A, T1.B AS VALUE, T1.X, T1.Y, T1.Z, T2.NAME
    FROM T1
    LEFT JOIN
    (
        SELECT C.NAME, D.R
        FROM C
        INNER JOIN D
        ON C.ID = D.CID
    ) AS T2
    ON T1.B = T2.R
)
UNION ALL
(
    SELECT T1.ID, T1.A, T1.C AS VALUE, T1.X, T1.Y, T1.Z, T2.NAME
    FROM T1
    LEFT JOIN
    (
        SELECT C.NAME, D.S
        FROM C
        INNER JOIN D
        ON C.ID = D.CID
    ) AS T2
    ON T1.C = T2.S
)

小提琴是here

执行计划可以在小提琴中找到。

看看当前的执行计划,有两棵树从一个连接中分支出来。两棵树几乎是相同的。这意味着由于T1UNION ALL内部的查询已执行了两次。我有什么办法可以重写它,可能将UNION ALL替换为JOIN

P.S。用测试模式和小提琴编辑问题。

2 个答案:

答案 0 :(得分:1)

我使用以下方法得到了更好的计划:

;WITH T1 AS
(
    SELECT A.ID, A.A, A.B, A.C, B.X, B.Y, B.Z
    FROM A
    LEFT JOIN B ON A.BID = B.ID
    WHERE A.DATE BETWEEN '2018-01-01' AND '2018-12-31'
)
SELECT T1.ID, T1.A, IIF(v.N=1,T1.B,T1.C) AS VALUE, T1.X, T1.Y, T1.Z, C.NAME
FROM T1
CROSS JOIN (VALUES (1),(2)) v(N) -- or (SELECT 1 N UNION ALL SELECT 2) v
LEFT JOIN D ON ((T1.B = D.R AND v.N=1) OR (T1.C = D.S AND v.N=2))
LEFT JOIN C ON C.ID = D.CID

您还可以在没有CTE的情况下重写它(相同的计划,但是看起来更短):

SELECT A.ID, A.A, IIF(v.N=1,A.B,A.C) AS VALUE, B.X, B.Y, B.Z, C.NAME
FROM A
LEFT JOIN B ON A.BID = B.ID
CROSS JOIN (VALUES (1),(2)) v(N) -- or (SELECT 1 N UNION ALL SELECT 2) v
LEFT JOIN D ON ((A.B = D.R AND v.N=1) OR (A.C = D.S AND v.N=2))
LEFT JOIN C ON C.ID = D.CID
WHERE A.DATE BETWEEN '2018-01-01' AND '2018-12-31'

答案 1 :(得分:-1)

WITH T1 AS
(
    SELECT A.ID, A.A, A.B, A.C, B.X, B.Y, B.Z
    FROM A
    LEFT JOIN B
    ON A.BID = B.ID
    WHERE A.DATE BETWEEN '2018-01-01' AND '2018-12-31'
)

(
    SELECT T1.ID, T1.A, T1.B AS VALUE, T1.X, T1.Y, T1.Z, C.NAME
    FROM T1
    Left JOIN D ON 
    T1.C = D.R
    LEFT JOIN C ON 
    C.ID = D.CID

)
UNION ALL
(
    SELECT T1.ID, T1.A, T1.C AS VALUE, T1.X, T1.Y, T1.Z, C.NAME  
    FROM T1
    Left JOIN D ON 
    T1.C = D.S
    LEFT JOIN C ON