【问题标题】:Selecting most recent and specific version in each group of records, for multiple groups在每组记录中为多个组选择最新和特定版本
【发布时间】:2020-03-15 12:50:52
【问题描述】:

问题:
我有一个记录foo 中的数据行的表。每次更新行时,都会插入一个新行以及一个修订号。表格如下:

id  rev field
1   1   test1
2   1   fsdfs
3   1   jfds
1   2   test2

请注意,表中的最后一条记录是第一行的较新版本。

有没有人知道查询最新版本的行和特定版本的记录的有效方法?例如,rev=2 的查询将返回第 2、3 和第 4 行(虽然不是替换的第 1 行),而对 rev=1 的查询会产生 rev

我实际上不确定这在 SQL Server 中是否可行...

我不希望以迭代的方式返回结果。

【问题讨论】:

  • 你用的是什么版本的sql server?
  • MS SQL 2008 R2,但理想情况下我想生成一个不依赖于任何 sql server 细节的查询。
  • 为什么这个问题被标记为'recursion'和'rcs'?
  • 我刚刚注意到您的“在重复 ID 的情况下”子句 - 您能对此进行扩展吗?不知道是什么意思。
  • 此处可能存在重复问题。请阅读那里的讨论,因为它很有教育意义:stackoverflow.com/questions/121387/…

标签: sql sql-server tsql


【解决方案1】:

仅获取最新版本:

SELECT * from t t1
WHERE t1.rev = 
  (SELECT max(rev) FROM t t2 WHERE t2.id = t1.id)

要获得特定的修订,在这种情况下为 1(如果项目没有修订但下一个最小的修订):

SELECT * from foo t1
WHERE t1.rev = 
  (SELECT max(rev) 
   FROM foo t2 
   WHERE t2.id = t1.id
   AND t2.rev <= 1)

这可能不是最有效的方法,但现在我想不出更好的方法。

【讨论】:

  • 感谢您的回复。这很好用。为什么你认为它效率低下?
  • 因为嵌套查询。基本上对于 t 中的每一行,您都必须进行第二次查询。
  • @Tim - 不正确。 SQL 是声明式的,而不是命令式的。在这种情况下,SQL Server 知道这种模式并且计划非常简单。 plan image
  • 你会如何处理一个特定的版本(本质上都是 rev
  • 尝试将 t1.revision
【解决方案2】:

这是一种替代解决方案,它会产生 更新 成本,但对于 读取 最新数据行的效率要高得多,因为它避免了计算 MAX(rev)。当您对表的子集进行批量更新时,它也可以工作。我需要这种模式来确保我可以有效地切换到通过长时间运行的批量更新更新的新数据集,而没有任何时间窗口可以看到部分更新的数据。

老化

  • rev 列替换为age
  • 使用过滤器创建当前最新数据的视图:age = 0
  • 要创建数据的新版本...
    • 插入:带有age = -1 的新行 - 这是我运行缓慢且长时间运行的批处理过程。
    • 更新:UPDATE table-name SET age = age + 1 用于子集中的所有行。这会将视图切换到新的最新数据 (age = 0),并在单个事务中对旧数据进行老化。
    • DELETE:子集中包含 age &gt; N 的行 - 可选择清除旧数据

索引

  • ageid 创建一个复合索引,这样视图会很好而且很快,也可以用来按id 查找。尽管此键实际上是唯一的,但在老化行时(在UPDATE SET age=age+1 期间)它暂时不唯一,因此您需要使其不唯一,最好是聚集索引。如果您需要查找由age 排序的给定id 的所有版本,您可能需要在id 然后age 上附加一个非唯一索引。

回滚

最后...假设您今天过得很糟糕,批处理中断了。您可以通过运行以下命令快速恢复到以前的数据集版本:

  • UPDATE table-name SET age = age - 1 -- 回滚一个版本
  • DELETE table-name WHERE age &lt; 0 -- 清理坏东西

现有表

假设您有一个现在需要支持老化的现有表。您可以通过首先重命名现有表来使用此模式,然后添加age 列和索引,然后创建包含age = 0 条件的视图与原始表名称相同

此策略可能有效,也可能无效,具体取决于依赖于原始表格的技术层的性质,但在许多情况下,将视图交换为表格应该会很好。

注意事项

我建议将 age 列命名为 RowAge 以表明正在使用此模式,因为它更清楚地表明它是与数据库相关的值,并且它补充了 SQL Server 的 RowVersion 命名约定。它也不会与需要返回一个人的年龄的列或视图冲突。

与其他解决方案不同,此模式适用于非 SQL Server 数据库。

【讨论】:

  • 一个有趣的选择。我可以澄清这一行“UPDATE table-name SET age = age + 1 for all rows in the subset” - 你指的是什么子集,不应该只是所有行的句号吗?
  • 在我的情况下,我需要将老化应用于表的动态子集,因此我不会老化整个表。如果这不是您的情况,那么您可以从查询中删除子集条件。
  • 干杯托尼,我错过了开头的那一行“当你对表格的子集进行批量更新时它也可以工作”,这也增加了我的一点混乱。现在一切都说得通了。
  • 感谢分享这个聪明的模式,值得更多的投票。
  • @ReiMiyasaka 任何多版本解决方案都不能在同一个表中使用自动增量,所以是的,您要么使用单独的表,要么计算 NEW_ID = MAX(ID) +1 并重试由于并发 INSERT 尝试返回相同的 NEW_ID 而失败时的 INSERT。您可能希望在表上添加一个对行和自动增量唯一的 ID 列。
【解决方案3】:

我会这样做。 ROW_NUMBER() 需要 SQL Server 2005 或更高版本

样本数据:

DECLARE @foo TABLE (
    id int,
    rev int,
    field nvarchar(10)
)

INSERT @foo VALUES
    ( 1, 1, 'test1' ),
    ( 2, 1, 'fdsfs' ),
    ( 3, 1, 'jfds' ),
    ( 1, 2, 'test2' )

查询:

DECLARE @desiredRev int

SET @desiredRev = 2

SELECT * FROM (
SELECT 
    id,
    rev,
    field,
    ROW_NUMBER() OVER (PARTITION BY id ORDER BY rev DESC) rn
FROM @foo WHERE rev <= @desiredRev 
) numbered
WHERE rn = 1

内部SELECT 返回所有相关记录,并在每个id 组内(即PARTITION BY)按rev 降序计算行号。

外部SELECT 只是从每个id 组中选择第一个成员(因此,具有最高rev 的成员)。

@desiredRev = 2 时的输出:

id          rev         field      rn
----------- ----------- ---------- --------------------
1           2           test2      1
2           1           fdsfs      1
3           1           jfds       1

@desiredRev = 1 时的输出:

id          rev         field      rn
----------- ----------- ---------- --------------------
1           1           test1      1
2           1           fdsfs      1
3           1           jfds       1

【讨论】:

  • 似乎工作正常。我喜欢您可以轻松选择修订号。但是,如果可能的话,我更喜欢纯 SQL 查询。
  • row_number 很慢,最好的解决方案是之前的 group by
【解决方案4】:

如果你想要每个字段的所有最新版本,你可以使用

SELECT C.rev, C.fields FROM (
  SELECT MAX(A.rev) AS rev, A.id
  FROM yourtable A
  GROUP BY A.id) 
AS B
INNER JOIN yourtable C
ON B.id = C.id AND B.rev = C.rev

在你的例子中,这将返回

 rev field
 1   fsdfs   
 1   jfds   
 2   test2

【讨论】:

  • 找不到列“A”或用户定义的函数或聚合“A.MAX”,或者名称不明确。出现此错误消息,但对我没有太大帮助...
  • 你不需要 A. 在 MAX 之前,只需像这样使用 max:MAX(A.rev) 或 max(rev)
  • 糟糕,我的错误!更正了查询。
  • @user1230724:啊!你是对的,当然。更正了查询(再次...)
【解决方案5】:
SELECT
  MaxRevs.id,
  revision.field
FROM
  (SELECT
     id,
     MAX(rev) AS MaxRev
   FROM revision
   GROUP BY id
  ) MaxRevs
  INNER JOIN revision 
    ON MaxRevs.id = revision.id AND MaxRevs.MaxRev = revision.rev

【讨论】:

  • 还要选择一个特定的修订:SELECT revision.* FROM (SELECT id, MAX(rev) AS MaxRev FROM revision WHERE rev
【解决方案6】:
SELECT foo.* from foo 
left join foo as later 
on foo.id=later.id and later.rev>foo.rev 
where later.id is null;

【讨论】:

    【解决方案7】:

    这个怎么样?

    select id, max(rev), field from foo group by id
    

    用于查询特定版本,例如修订版 1,

    select id, max(rev), field from foo where rev <= 1 group by id
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-02-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-07-15
      相关资源
      最近更新 更多