【问题标题】:Find missing and out-of-sequence records查找缺失和无序的记录
【发布时间】:2020-03-15 14:36:25
【问题描述】:

架构:

简化 - 一个列出 ID 号、版本和状态的表格:

CREATE TABLE archive
    ([id] int, [version] int, [status] varchar(1));

INSERT INTO archive
    ([id], [version], [status])
VALUES
    (1, 1, 'A'),
    (1, 2, 'S'),
    (1, 3, 'T'),
    (1, 4, 'A'),
    (2, 2, 'T'),
    (2, 4, 'T'),
    (3, 1, 'A'),
    (3, 3, 'A');

问题:

一些记录缺少完整的历史记录(版本)。所有 ID 都应该以版本 1 开头,并且版本号应该是连续的(与上述架构中的 ID 2 和 3 不同)。

想要的输出

所有 ID 的列表,显示其现有版本以及“跳过”的版本。根据下面的示例,输出应如下所示:

id | ver | check
---+-----+------
  1|   1 |   1
  1|   2 |   2
  1|   3 |   3
  1|   4 |   4
  2| NULL|   1
  2|   2 |   2
  2| NULL|   3
  2|   4 |   4
  3|   1 |   1
  3| NULL|   2
  3|   3 |   3

我目前的努力:

问题与this one 类似,但没有固定的“Table2”,如已回答的问题。每条记录的版本数未知。

到目前为止,我想出了以下几点:

SELECT sub.id, sub.ver, sub.seq
FROM (
      SELECT CASE WHEN a.id IS NULL THEN b.id ELSE a.id END as 'id', b.version as 'ver', a.seq as 'seq'
      FROM (select *,
                   row_number() over (partition by id order by version asc) as seq
              from archive) a
      FULL OUTER JOIN archive b ON a.id=b.id AND a.seq=b.version) sub
ORDER BY sub.id, sub.ver, sub.seq

下面的输出让我几乎到了那里:

任何帮助将不胜感激。

【问题讨论】:

  • 看到这样一个写得很好的问题多么令人耳目一新:)

标签: sql sql-server tsql


【解决方案1】:

这可以使用recursive cte.来实现

with cte as (
  select 1 as ctr, id, max(version) version from archive group by id
  union all 
  select ctr + 1, id, version from cte
  where ctr < version
)
select t1.id, t2.version, ctr as [check] from cte t1
left join archive t2 on t2.id = t1.id and t1.ctr = t2.version
order by t1.id, t1.ctr;

dbfiddle

【讨论】:

  • 谢谢。非常漂亮和优雅。工作得很好。仅供参考,我收到“在语句完成之前已用尽最大递归 100”错误,但通过快速谷歌很容易解决。
  • @MichalRosa,是的,sql server 需要设置最大递归,默认为 100
【解决方案2】:

这是另一个版本,它使用数字表并且不受任何 CTE 递归边界的影响。与递归 CTE 相比,数字表可以支持的值范围要大得多。

-- Create a numbers table. This table can be generated each time 
-- or stored in a static table. Numbers tables are wonderful things.
DROP TABLE IF EXISTS #Numbers
SELECT
    ROW_NUMBER() OVER (ORDER BY n1.[object_id]) AS [number]
INTO
    #Numbers
FROM
    [sys].[objects] AS n1
    ,[sys].[objects] AS n2

-- Calculate the "range" of version numbers for each [id]
;WITH [range]
AS
(
    SELECT
        [id]
        ,1 AS [min_version]
        ,MAX([version]) AS [max_version]
    FROM
        [archive] AS a
    GROUP BY
        [id]
), [expected]
AS
(
    SELECT
        DISTINCT
        a.[id]
        ,n.[number]
    FROM
        #Numbers AS n
        INNER JOIN [range] AS a
            ON n.number BETWEEN a.[min_version] AND a.[max_version]
)
SELECT
    e.[id]
    ,a.[version] AS [ver]
    ,e.[number] AS [check]
FROM
    [expected] AS e
    LEFT OUTER JOIN [archive] AS a
        ON e.[id] = a.[id]
        AND e.[number] = a.[version]

【讨论】:

  • 谢谢。这也很好用,避免递归有其优点,但为了简单和优雅,我更喜欢第一种解决方案(在实际数据库中,只有少数记录有超过 100 个版本,因此查询不会受到任何性能问题的影响)。我会将您的解决方案保存在我的“食谱书”中。
猜你喜欢
  • 1970-01-01
  • 2017-05-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多