【问题标题】:Hoping to remove an ugly self-join on a CTE希望删除 CTE 上丑陋的自加入
【发布时间】:2018-06-11 15:28:44
【问题描述】:

我有一个创建排序字典的查询(排序是因为有一个增量id 来识别键的相对位置)

然后我想知道,对于每一行,value 是否作为key 存在于稍后在字典中的任何其他行中。我正在使用CROSS APPLY 中的相关查询来执行此操作。在 CTE 上有效地自我加入。

据我了解,这意味着代表 Dictionary 的 CTE 必须计算两次?

除了使用表变量(它在函数内部),有没有人有任何替代建议?

WITH
  dictionary([id], [key], [val]) AS
(
            SELECT 1, 'a', 'b' 
  UNION ALL SELECT 2, 'b', 'c'
  UNION ALL SELECT 3, 'c', 'a'
  UNION ALL SELECT 4, 'x', 'w'
  UNION ALL SELECT 5, 'y', 'x'
  UNION ALL SELECT 6, 'z', 'y'
)
SELECT
  *
FROM
  dictionary   dict
CROSS APPLY
(
  SELECT COUNT(*) FROM dictionary WHERE dictionary.id > dict.id AND dictionary.[key] = dict.[val]
)
  lookup(hits)
CROSS APPLY
(
  SELECT 1, 3 WHERE lookup.hits = 0
  UNION ALL
  SELECT 1, 2 WHERE lookup.hits > 0
  UNION ALL
  SELECT 2, 3 WHERE lookup.hits > 0
)
  map([from], [to])

-- [key]s 'c', 'x', 'y' and 'z' should only have one output rows

-- It's "acceptable" for only 'z' to have just one output row IFF a self join can be avoided

我能想到的其他选项都是自连接上的变体...

  dictionary   dict
LEFT JOIN
(
  SELECT key, MAX(id) AS id FROM dictionary GROUP BY key
)
  lookup
    ON  lookup.key = dict.value
    AND lookup.id  > dict.id

或者……

  dictionary   dict
OUTER APPLY
(
  SELECT 1 WHERE EXISTS (SELECT * FROM dictionary WHERE dictionary.id > dict.id AND dictionary.key = dict.value)
)
  lookup(hits)

然而,我试图避免 CTE 的自连接,可能使用我没有想到的窗口函数?任何只是为了避免 CTE 被计算两次...

(忽略lookup.id > dict.id 方面很好,如果这意味着避免自连接...)

编辑:添加了更完整的示例,以及一个 SQL Fiddle,感谢@MartinSmith 指出了一些不一致之处......

http://sqlfiddle.com/#!6/9eecb7db59d16c80417c72d1e1f4fbf1/17407

【问题讨论】:

  • @MartinSmith - 这将使LEFT JOIN 成为目前最糟糕的选择。
  • 计算或访问 anything 的频率取决于优化器。仅仅因为 CTE 在查询中出现多次并不意味着 CTE 将被“计算两次”。整个查询(包括任何 CTE)作为一个整体进行优化,系统尝试最小化某些成本(例如 I/O)。要知道效率低下的地方,您需要检查执行计划,而不仅仅是假设某个语言特性是导致性能不佳的原因。
  • @Damien_The_Unbeliever - 虽然我同意这种观点,但在这种情况下,我相信这几乎是一个语义论点。事实上,使用上面的代码执行计划可以 (而不是计算CTE多次,在 MartinSmith 的回答中,它不会多次计算 CTE。

标签: sql sql-server-2008 common-table-expression self-join


【解决方案1】:

这是使用窗口函数的一种方法。

首先将行反透视,以便键和值成为通用 terms,然后使用 MAX ... OVER (PARTITION BY term) 查找该术语用作键的最高行的 id。

在此示例中,它会设置一个标志并丢弃由反透视添加的重复行(保留该对中的 context = 'v' 行,因为这是具有标志所需信息的行)。

然后您可以使用它来加入包含您的 map 值的表值构造函数。

WITH dictionary(id, [key], value)
     AS (
            SELECT 1, 'a', 'b' 
  UNION ALL SELECT 2, 'b', 'c'
  UNION ALL SELECT 3, 'c', 'a'
  UNION ALL SELECT 4, 'x', 'w'
  UNION ALL SELECT 5, 'y', 'x'
  UNION ALL SELECT 6, 'z', 'y'   
     ),
     t1
     AS (SELECT dict.*,
                context,
                highest_id_where_term_is_key = MAX(CASE
                                                     WHEN context = 'k'
                                                       THEN v.id
                                                   END) OVER (PARTITION BY term)
         FROM   dictionary dict
                CROSS APPLY (VALUES(id, [key], 'k'),
                                   (id, value, 'v')) v(id, term, context)),
     t2
     AS (SELECT *,
                val_in_later_key = CASE
                                     WHEN id < highest_id_where_term_is_key
                                       THEN 1
                                     ELSE 0
                                   END
         FROM   t1
         WHERE  context = 'v' 
         -- Discard duplicate row from the unpivot - only want the "value" row
        )
SELECT id,
       [key],
       value,
       highest_id_where_term_is_key,
       map.[from],
       map.[to]
FROM   t2
       JOIN (VALUES (1, 3, 0),
                    (1, 2, 1),
                    (2, 3, 1) ) map([from], [to], [flg])
         ON map.flg = t2.val_in_later_key
ORDER  BY id 

返回

+----+-----+-------+------------------------------+------+----+
| id | key | value | highest_id_where_term_is_key | from | to |
+----+-----+-------+------------------------------+------+----+
|  1 | a   | b     | 2                            |    1 |  2 |
|  1 | a   | b     | 2                            |    2 |  3 |
|  2 | b   | c     | 3                            |    1 |  2 |
|  2 | b   | c     | 3                            |    2 |  3 |
|  3 | c   | a     | 1                            |    1 |  3 |
|  4 | x   | w     | NULL                         |    1 |  3 |
|  5 | y   | x     | 4                            |    1 |  3 |
|  6 | z   | y     | 5                            |    1 |  3 |
+----+-----+-------+------------------------------+------+----+

【讨论】:

  • VALUES (),()SELECT UNION ALL SELECT 有什么好处吗?
  • @MatBailie - 除了稍微不那么冗长的语法之外,没有其他任何东西。
  • 漂亮多了。我可以为此第二次投票吗? ;)
猜你喜欢
  • 1970-01-01
  • 2015-06-28
  • 2022-01-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多