【问题标题】:Recursive CTE with both child and parent links具有子链接和父链接的递归 CTE
【发布时间】:2014-07-18 18:00:18
【问题描述】:

您好,我有一个存储数据映射的表,例如

Data                  Map
id | letter | type    l | r
------------------    -----
 1 |   AA   | HEAD    5 | 1
 2 |   BB   | HEAD    2 | 1
 3 |   CC   | HEAD    6 | 2
 4 |   DD   | HEAD    3 | 2
 5 |  END-1 |  END    7 | 3
 6 |  END-2 |  END    8 | 4
 7 |  END-3 |  END
 8 |  END-4 |  END

http://sqlfiddle.com/#!3/4eccfe/5

我想从给定的来源中找出所有 END 类型的链接,例如对于 AA,我得到 END-1、END-2、END-3;对于 BB,我得到 END-1、END-2、END-3;对于 CC,我得到 END-1、END-2、END-3;对于 DD,我得到 END-4

我已经使用递归 CTE 编写了我想要的内容:

;WITH data(id, letter, type) AS (
    SELECT '1', 'AA', 'HEAD' UNION SELECT '2', 'BB', 'HEAD' UNION SELECT '3', 'CC', 'HEAD' UNION
    SELECT '4', 'DD', 'HEAD' UNION SELECT '5', 'END-1', 'END' UNION SELECT '6', 'END-2', 'END' UNION
    SELECT '7', 'END-3', 'END' UNION SELECT '8', 'END-4', 'END'
), map (l, r) AS (
    SELECT '5', '1' UNION SELECT '2', '1' UNION
    SELECT '6', '2' UNION SELECT '3', '2' UNION
    SELECT '7', '3' UNION SELECT '8', '4'
), my_list (origin, source, target, target_type, sid, tid, level) AS (
    SELECT s.letter, s.letter, t.letter, t.type, s.id, t.id, 0
    FROM data s JOIN map ON (s.id = l OR s.id = r)
    JOIN data t ON (t.id = l OR t.id = r)
    WHERE t.id <> s.id AND s.type <> 'END'
    UNION ALL
    SELECT my_list.origin, s.letter, t.letter, t.type, s.id, t.id, level + 1
    FROM data s JOIN map ON (s.id = l OR s.id = r)
    JOIN data t ON (t.id = l OR t.id = r) JOIN my_list ON s.id = my_list.tid
    WHERE t.id <> s.id AND s.type <> 'END' AND t.id <> my_list.sid
)
SELECT * FROM my_list
WHERE origin = 'BB' AND target_type = 'END'
ORDER BY level
GO

但性能不是很好(在我的真实桌子上)。然后我意识到是连接条件中的 OR 导致了问题,然后我尝试使用 UNION

my_list (origin, source, target, target_type, sid, tid, level) AS (
    SELECT s.letter, s.letter, t.letter, t.type, s.id, t.id, 0
    FROM data s JOIN map ON s.id = l JOIN data t ON t.id = r
    WHERE s.type <> 'END'
    UNION ALL
    SELECT s.letter, s.letter, t.letter, t.type, s.id, t.id, 0
    FROM data s JOIN map ON s.id = r JOIN data t ON t.id = l
    WHERE s.type <> 'END'
    UNION ALL
    SELECT my_list.origin, s.letter, t.letter, t.type, s.id, t.id, level + 1
    FROM data s JOIN map ON (s.id = l OR s.id = r)
    JOIN data t ON (t.id = l OR t.id = r) JOIN my_list ON s.id = my_list.tid
    WHERE t.id <> s.id AND s.type <> 'END' AND t.id <> my_list.sid
)

差异很大(在我的真实桌子上,时间减半)。对于上面的例子,我得到了

Table 'Worktable'. Scan count 6, logical reads 100

Table 'Worktable'. Scan count 5, logical reads 75

但是当我尝试对递归部分做同样的事情时,例如

my_list (origin, source, target, target_type, sid, tid, level) AS (
    SELECT s.letter, s.letter, t.letter, t.type, s.id, t.id, 0
    FROM data s JOIN map ON s.id = l JOIN data t ON t.id = r
    WHERE s.type <> 'END'
    UNION ALL
    SELECT s.letter, s.letter, t.letter, t.type, s.id, t.id, 0
    FROM data s JOIN map ON s.id = r JOIN data t ON t.id = l
    WHERE s.type <> 'END'
    UNION ALL
    SELECT my_list.origin, s.letter, t.letter, t.type, s.id, t.id, level + 1
    FROM data s JOIN map ON s.id = l
    JOIN data t ON t.id = r JOIN my_list ON s.id = my_list.tid
    WHERE s.type <> 'END' AND t.id <> my_list.sid
    UNION ALL
    SELECT my_list.origin, s.letter, t.letter, t.type, s.id, t.id, level + 1
    FROM data s JOIN map ON s.id = r
    JOIN data t ON t.id = l JOIN my_list ON s.id = my_list.tid
    WHERE s.type <> 'END' AND t.id <> my_list.sid
)

结果变慢了(在我的真实桌子上,慢了 5 倍)。

我想知道为什么它会变慢,是否还有其他方法可以摆脱 OR 来加快查询速度?数据库是 MS SQL SERVER 2008R2

谢谢

【问题讨论】:

  • 你真的需要 or/union 结构吗?你不能从一端开始并朝一个方向移动可以这么说=即select distinct m1.r from map m1 left join map m2 on m1.r = m2.l where m2.l is null
  • 单向能得到我想要的我不介意
  • 我不认为你可以完全摆脱ORs,但这可能会被简化。一个重要问题 - 您的示例数据集在 map 中不同,其 id 代表 'END' 行 - 您的实际数据是否如此?
  • 另外,他们都在左边……这也成立吗?我很好奇,因为这在某种程度上暗示你们的关系不是双向的。
  • 是的,END节点都在左边,只有HEAD节点可以同时在左右(例如'BB')

标签: sql sql-server recursion


【解决方案1】:

我可能弄错了,但你不能推动谓词:

WHERE origin = 'BB'

在 CTE 内部。即:

;WITH my_list (origin, source, target, target_type, sid, tid, level) AS (
SELECT s.letter, s.letter, t.letter, t.type, s.id, t.id, 0
FROM data s 
    JOIN map 
        ON s.id = l 
    JOIN data t 
        ON t.id = r
WHERE s.letter = 'BB'
UNION ALL
SELECT s.letter, s.letter, t.letter, t.type, s.id, t.id, 0
FROM data s 
    JOIN map 
        ON s.id = r 
    JOIN data t 
        ON t.id = l
WHERE s.letter = 'BB'
UNION ALL
SELECT my_list.origin, s.letter, t.letter, t.type, s.id, t.id, level + 1 
    FROM data s 
    JOIN map 
        ON (s.id = l OR s.id = r)
JOIN data t 
        ON (t.id = l OR t.id = r) 
    JOIN my_list 
        ON s.id = my_list.tid
WHERE t.id <> s.id 
      AND s.type <> 'END' 
      AND t.id <> my_list.sid
)
SELECT * FROM my_list
WHERE origin = 'BB' AND target_type = 'END'
ORDER BY level

这会提高性能吗?

【讨论】:

  • 是的,将谓词推入根选择可能是最好的第一步。
  • 不抱歉,'BB' 只是一个例子,CTE 必须能够返回所有不同的字母。
  • 不确定我是否理解您的评论。 BB 可以替换为 CTE 内部和 CTE 外部的任何文字
猜你喜欢
  • 2017-01-02
  • 2018-08-21
  • 1970-01-01
  • 2023-03-03
  • 1970-01-01
  • 1970-01-01
  • 2014-05-19
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多