【问题标题】:Deleting old records while keeping a minimum number of records per parent删除旧记录,同时保留每个父级的最少记录数
【发布时间】:2012-02-10 08:38:56
【问题描述】:

我的问题类似于sql statement to delete records older than XXX as long as there are more than YY rows,但这个问题只涉及一个单亲,我想一次性删除所有父母的记录。

考虑这张表:

CREATE TABLE Children
(
    ChildId int NOT NULL,
    ChildCreated datetime NOT NULL,
    ParentId int NOT NULL
) 

这可以是任何父子关系,因此名称是通用的。

我想删除所有超过一个月的孩子,但需要为每个父母保留最少数量的孩子,无论他们的年龄如何。

我尝试了一些带有嵌套 SELECT 和 GROUP BY 的语句,这些语句给了我一些结果,但没有一个给我正确的结果集。

因为我使用的是 SQL Server,所以我想出了以下效果很好的解决方案:

WITH CTE AS
(
  SELECT ROW_NUMBER() OVER (Partition BY ParentId ORDER BY ChildCreated DESC) 
  As RowNo, ChildCreated FROM Children
)

DELETE FROM CTE WHERE RowNo > 10
AND RevisionCreated < DATEADD(MONTH,-1,GetDate())

公用表表达式将每个父级的所有子级组合在一起,并根据创建顺序添加一个连续的行号。每个父母的最新孩子有 行号为 1,最新的第十个有 10。所以我可以删除所有一行的记录 大于 10,只要它们也超过一个月。

我的问题是,如果我必须在不支持 CTE 的系统上做同样的事情怎么办。这个问题的 ANSI SQL-92 解决方案是什么?

【问题讨论】:

  • 据我所知,CTE 在 ANSI SQL 99 标准中有效。也许您想要一个不支持该标准的数据库解决方案? (也许是 MySQL?)
  • @MarkByers 我不知道 CTE 在 SQL-99 中,是的,我会对不支持 CTE 的系统的解决方案感兴趣。我将问题改为询问 SQL-92。

标签: sql-server delete-row sql-delete


【解决方案1】:

基于其他响应,以及我的查询的相对简单性,我认为我可能过度简化了问题,但我假设由于 parentID 不可为空,因此它不引用 childID,在这种情况下,它可以简单地实现如下

DELETE  Children
FROM    Children a
WHERE   ChildCreated < DATEADD(MONTH, -1, GETDATE())
AND     (   SELECT  COUNT(*)    -- NUMBER OF NEWER CHILDREN WITH THE SAME PARENT
            FROM    Children b
            WHERE   a.ParentID = b.ParentID
            AND (   a.ChildCreated < b.ChildCreated
                OR  (a.ChildCreated = b.ChildCreated AND a.ChildID > b.ChildID)
                )
        ) >= 10

虽然这个确切的 SQL 可能需要根据 RDBMS 进行调整,但我不知道有哪些 RDBMS 无法应用此主体。

【讨论】:

  • 这似乎不起作用,因为您排除了在截止日期之后创建的所有孩子,只查看旧的孩子。想想你的子查询返回什么,也许对于最老的孩子,它返回 11,所以它符合条件,但对于第二大的孩子,它返回 10(因为最老的不比第二大的小)所以,第二老的不合格。
  • 我已经稍微编辑了答案,在子查询之后将 ">" 更改为 ">=",并允许同一父母的 2 个具有相同时间戳的孩子。我不完全理解您的评论,除非它指的是我在子查询之后犯的 ">=" 错误,所以我不知道如何改进我的答案。我发布的查询将删除超过 1 个月以下的孩子到至少有 10 个新孩子的父母。据我所知,这是您需要的标准?我会把它归结为我误解了这个问题。
  • 这个有效,所有三个查询现在都删除了同一组记录。您的甚至适用于重复的 ChildCreated 值。谢谢。
【解决方案2】:

很可能会拖累性能,但声明如下

  • 添加rownumber,为每个组重新启动,方法是对同一组的子项计数的每条记录使用子选择。
  • 返回一个内存表,其中包含ChildId 的给定行号。
  • JOIN 回到ChildId 上的原始表
  • 添加一个WHERE 子句,您可以在其中过滤给定的rownumber 和/或来自Children 的其他列。
  • 在DELETE FROM 语句中使用结果。

SQL 语句

DELETE FROM Children
FROM    Children c
        INNER JOIN (
          SELECT  ChildId
                  , ( SELECT COUNT(*) + 1
                      FROM   Children rn
                      WHERE  rn.ChildCreated < Children.ChildCreated
                             AND rn.ChildId = Children.ParentId
                      ) AS rn           
          FROM    Children
        ) rn ON rn.ChildId = c.ChildId          
WHERE   rn.rn > 10
        AND ChildCreated < DATEADD(MONTH,-1,GetDate())

【讨论】:

  • 有趣,有几点: 1. 'FROM children FROM Children c' 语法对我来说不正确,我认为你可以删除第一个'FROM children' 部分。 2. 您将 rn.ChildId 与 Children.ChildId 进行比较,假设 Id 是按创建顺序排列的,将 rn.ChildCreated 与 Children.ChildCreated 进行比较会更安全。 3. 为什么是COALESCE,它不应该总是ParentId 不能为NULL 吗?否则它似乎可以工作,但确实很慢。
  • 我在实践中对此进行了测试,但要求略有不同。 ChildCreated 字段实际上是一个 LastModified 字段,所以我不能依赖 ChildId 的顺序而必须比较 LastModified 字段,问题是父母可能有两个 LastModified 中具有相同值的孩子。如果第 10 和第 11 大的孩子具有相同的 LastModified 值,则它们都不包括在内。但是,如果您有唯一的 ID,它应该可以工作。
  • @PeterHahndorf - 1. DELETE FROM Children FROM 是有效的语法 afaik。 2. 比较 ChildCreated 日期是对的。 3. 就在 .
  • 抱歉,Delete.. 很好,我做的第一件事就是将 DELETE 更改为 SELECT,然后它就无效了,这是我的错。您当前的版本适用于唯一的 'ChildCreated' 值,除了我必须将 'rn.ChildCreated Children.ChildCreated'
猜你喜欢
  • 1970-01-01
  • 2019-12-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-10-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多