【问题标题】:How do you identify record pattern sequences in records using TSQL?如何使用 SQL 识别记录中的记录模式序列?
【发布时间】:2015-07-29 14:43:12
【问题描述】:

这对我来说是一个相当新的练习,但我需要找到一种方法来识别表格中的模式序列。 例如,假设我有一个类似于以下的简单表:

现在我想做的是识别并分组所有具有值 5、9 和 6 的序列模式的记录,并在查询中呈现它们。您将如何使用 T-SQL 完成这项任务?

结果应如下所示:

我已经寻找了一些可能的例子来说明如何实现这一点,但找不到任何真正有帮助的东西。

【问题讨论】:

  • 你能添加一点关于图案的文字吗?它可以有多大?
  • 所以你会提供像declare @Pattern as table ( Seq Int, Val Int ); insert into @Pattern ( Seq, Val ) values ( 1, 5 ), ( 2, 9 ), ( 3, 6 );这样的模式?看起来像是一个带有一些花哨的 Row_Number 匹配、分组和计数的连接。那时出现了一个奇怪的变体“间隙和孤岛”问题。
  • @BogdanBogdanov 该模式永远不会超过 3 个连续数字。在本例中为 5,9 和 6。但理想情况下,该解决方案应该能够在必要时通过一些修改来容纳更大的序列。该值是整数类型。希望我已经正确解释了您的问题。如果没有,请告诉我
  • @HABO 是的,该表将与您描述的完全一样,我已经考虑并探索了实现 ROW_NUMBER 函数的可能性,但我无法找到一种方法来识别这种模式。我也想过将 CTE 与 ROW_NUMBER 函数结合使用,但仍然没有成功
  • 模式中的值是否保证是唯一的,即模式不能让42 出现两次?

标签: sql-server sql-server-2008 tsql common-table-expression


【解决方案1】:

您可以使用包裹在CTE 中的以下查询来为您的序列中包含的值分配序列号:

;WITH Seq AS (
    SELECT v, ROW_NUMBER() OVER(ORDER BY k) AS rn
    FROM (VALUES(1, 5), (2, 9), (3, 6)) x(k,v)
)

输出:

v   rn
-------
5   1
9   2
6   3

使用上面的CTE,您可以识别岛屿,即包含整个序列的连续行的切片:

;WITH Seq AS (
    SELECT v, ROW_NUMBER() OVER(ORDER BY k) AS rn
    FROM (VALUES(1, 5), (2, 9), (3, 6)) x(k,v)
), Grp AS (
SELECT [Key], [Value], 
       ROW_NUMBER() OVER (ORDER BY [Key]) - rn AS grp            
FROM mytable AS m
LEFT JOIN Seq AS s ON m.Value = s.v
)
SELECT *
FROM Grp

输出:

    Key Value   grp
   -----------------
    1   5       0
    2   9       0
    3   6       0
    6   5       3
    7   9       3
    8   6       3

grp 字段可帮助您准确识别这些岛屿。

您现在需要做的就是过滤掉部分组:

;WITH Seq AS (
    SELECT v, ROW_NUMBER() OVER(ORDER BY k) AS rn
    FROM (VALUES(1, 5), (2, 9), (3, 6)) x(k,v)
), Grp AS (
SELECT [Key], [Value], 
       ROW_NUMBER() OVER (ORDER BY [Key]) - rn AS grp            
FROM mytable AS m
LEFT JOIN Seq AS s ON m.Value = s.v
)
SELECT g1.[Key], g1.[Value]
FROM Grp AS g1
INNER JOIN (
   SELECT grp
   FROM Grp
   GROUP BY grp
   HAVING COUNT(*) = 3 ) AS g2
ON g1.grp = g2.grp

Demo here

注意:此答案的初始版本使用INNER JOINSeq。如果表包含像5, 42, 9, 6 这样的值,这将不起作用,因为42 将被INNER JOIN 过滤掉,并且这个序列被错误地识别为有效序列。此编辑归功于@HABO。

【讨论】:

  • 您的第一个 inner join 不会丢弃任何与模式完全不匹配的 mytable 值,从而有效地忽略不匹配的值而不是模式匹配失败吗?
  • @HABO 是的,第一个INNER JOIN 就是这样做的,即它会过滤掉任何不匹配的值,例如83
  • 如果在形成Grp 时使用LEFT OUTER JOINSeq,那么如果mytable 包含5、42、9、6,那么您将在孤岛中拥有额外的行。对COUNT 的最终检查会将该组视为不匹配的。
  • @HABO 好的,现在我明白你的意思了。接得好!实际上,我最初使用 LEFT JOINCOALESCE 作为 rn,但后来出于效率原因放弃了它。
  • 只要模式包含相同的数字(例如模式是 7,3,3),就会失败
【解决方案2】:

不是很优化,但我认为正确的答案:

CREATE TABLE pattern (
    rowID INT IDENTITY(1,1) PRIMARY KEY, 
    rowValue INT NOT NULL
);

INSERT INTO pattern (rowValue) VALUES (5);
INSERT INTO pattern (rowValue) VALUES (9);
INSERT INTO pattern (rowValue) VALUES (6);

SELECT * FROM pattern;

SELECT Trg.* FROM Keys Trg 
INNER JOIN pattern Pt ON (Trg.fValue = Pt.rowValue) 
INNER JOIN (
    SELECT K.fKey - P.rowID AS X, COUNT(*) AS Xc FROM Keys K 
        LEFT JOIN pattern P ON (K.fValue = P.rowValue) 
    WHERE 
        (P.rowID IS NOT NULL) 
    GROUP BY K.fKey - P.rowID 
    HAVING COUNT(*) = (SELECT COUNT(*) FROM pattern)
) Z ON (Trg.fKey - Pt.rowID = Z.X);

我使用一个表格将其与主表格连接起来。我计算 Key 和模式 Key 之间的差异,并且我只显示差异匹配的行(并且行数计算模式表中的差异匹配行)。

【讨论】:

  • 我将选择第一个答案,我相信第二个答案也可能是一个非常有效的解决方案,但就非常简单和优雅的方法而言(尤其是对我而言)Giorgos 解决方案似乎是什么我在寻找。波格丹,不过,我会给你投票以评价你的答案是否有用。谢谢!
  • 10x @Mark。我同意你的观点,答案 1 更好(我投票了:))。
猜你喜欢
  • 2014-06-08
  • 1970-01-01
  • 2014-07-17
  • 2021-10-27
  • 2023-04-07
  • 2015-10-30
  • 1970-01-01
  • 1970-01-01
  • 2018-07-13
相关资源
最近更新 更多