【问题标题】:Remove unused parent records from a SQL database table从 SQL 数据库表中删除未使用的父记录
【发布时间】:2013-04-30 14:59:48
【问题描述】:

在 SQL Server 2012 中执行的测试表明,如果即使删除表中的一条记录也会导致外键违规,命令 DELETE FROM dbo.TableName 会停止并返回错误。没有记录被删除。

  • 假设未使用的记录是可以在不违反外键关系的情况下删除的记录。

  • 假设所有记录都可以通过不可为空的整数 ID 列唯一标识。

  • 假设忽略可能导致副作用的功能是安全的,例如触发器或级联删除。

  • 关于表的其他内容,应该假设它的记录和关系。 (例如:解决方案在这些参数内应该是通用的。)

是否存在以以下形式删除未使用的父记录的解决方案:

  1. SQL 会忽略导致错误的记录,并成功执行没有错误的记录?
  2. SQL 将只对一组以某种方式检测到的未使用的父记录执行 DELETE? (对于已知 FK 关系的特定表很容易做到。一般不太容易做到。)
  3. 执行与上述解决方案等效的服务器端代码?

注意事项:

  • SQL 解决方案优于需要 C#、Python、Java、Ruby 等的解决方案。
  • 跨 RDBMS 解决方案优于 RDBMS 特定解决方案。

请说明您的解决方案适用于并已经过测试的 RDBMS。

【问题讨论】:

  • 我不关注此评论:SQL which will perform a DELETE only upon a set of somehow-detected orphan records? (Easily done for a specific table where the FK relationships are known. Less easily done generically.) 我认为根据定义,如果一条记录与外部表中的记录具有某种外键关系,则它只能是孤儿已删除(在这种情况下,基于 OUTER JOIN 选择这些记录总是很容易)。一般地做这件事是什么意思?
  • 在 Oracle 中,您可以将选项 log errors into 与 DELETE 语句一起使用。跨 dbms 解决方案类似于 delete from foo where id in (select ...),其中子选择将返回“孤立”行。
  • Mike Brant:我见过的大多数解决方案都是针对记录/表定义的。您必须知道当前记录的每个父表。 “通用解决方案”将能够检测记录或表的外键关系,并且只删除不会导致外键违规的记录。这与手动指定记录可能与之相关的每个表并执行连接相反。
  • 请问为什么?您创建一个 FK 来限制值。为什么需要清除未引用的 FK 值表?如果下一个插入是您清除的值而现在插入失败怎么办?
  • Blam:这个想法实际上是清除不再引用有效外键的记录表。我们正在删除子实体,而不是父实体。这些是孤立的记录,不再属于数据库。如果它们在生产中使用,我们会尝试通过外键找到它们的父实体,但它不会存在。至于为什么会发生这种事情,情况可能会有所不同——从测试数据到不成熟的应用程序,再到 DB 模式更改,再到允许 0..n 关系而不是 1..n 关系的 DB 模式(这可能在架构但不在应用程序中)。

标签: mysql sql sql-server oracle tsql


【解决方案1】:

在这种情况下,我通常会禁用 fk 约束,如果这不起作用,我通常会删除约束,清理数据,然后重新创建 fk。

这里有一个关于如何做的快速链接 http://www.mssqltips.com/sqlservertip/1376/disable-enable-drop-and-recreate-sql-server-foreign-keys/

【讨论】:

    【解决方案2】:

    在 Oracle 中,您可以使用 LOG ERRORS INTO 选项:

    样本数据设置:

    create table parent (pid integer not null primary key);
    create table child 
    (
      cid integer not null primary key,
      pid integer not null references parent(pid)
    );
    
    insert into parent values (1);
    insert into parent values (2);
    insert into parent values (3);
    
    insert into child values (1,1);
    insert into child values (2,1);
    insert into child values (3,2);
    

    创建将记录所有不可删除行的表:

    begin
      DBMS_ERRLOG.CREATE_ERROR_LOG('PARENT', 'ERR_LOG_PARENT');
    end;
    /
    

    现在运行删除:

    delete from parent
    log errors into err_log_parent ('delete test')
    reject limit unlimited;
    

    SQLFiddle 示例:http://sqlfiddle.com/#!4/d379c/1

    DBMS_ERRLOG 手册:http://docs.oracle.com/cd/B28359_01/appdev.111/b28419/d_errlog.htm
    LOG ERRORS 选项手册:http://docs.oracle.com/cd/B28359_01/server.111/b28286/statements_8005.htm#CEGCHDJF

    【讨论】:

      【解决方案3】:

      没有什么灵丹妙药可以在所有平台上满足您的需求,但您可以编写一种适用于几乎任何 DBMS 的灵丹妙药。是的,我们是在 SQL Server 生产数据库上完成的。这不是一个单步过程,但您可以使用动态 SQL 或类似的方式轻松地自动化第二步。

      回复:

      你必须知道当前记录的每个父表。

      是的,但是您可以编写代码来解决这个问题。使用 INFORMATION_SCHEMA 视图(SQL Server、MySQL)或数据库的等效视图为相关表生成删除语句。然后运行您生成的语句。

      INFORMATION_SCHEMA.TABLES、INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS 和 INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE 是一个不错的起点。

      【讨论】:

        【解决方案4】:

        如果该行引用了 PK,则删除将失败,但会继续。

        SET NOCOUNT ON;
        
        DECLARE @PK varchar(10);
        
        PRINT '-------- Delete PK --------';
        
        DECLARE delete_cursor CURSOR FOR 
        SELECT PK 
        FROM PKstring
        ORDER BY PK;
        
        OPEN delete_cursor
        
        FETCH NEXT FROM delete_cursor 
        INTO @PK
        
        WHILE @@FETCH_STATUS = 0
        BEGIN
            DELETE PKstring where PK = @PK 
        
            FETCH NEXT FROM delete_cursor 
            INTO @PK
        END 
        CLOSE delete_cursor;
        DEALLOCATE delete_cursor;
        

        【讨论】:

        • 反响很好。我对游标不熟悉,所以没想过使用它们。我可以看到这个示例比我发布的示例更受欢迎。
        • 那么,怎么不是答案呢?
        • 这是一个答案,就像其他几个发布到这个问题的人一样。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-05-16
        • 2011-07-12
        • 2016-12-10
        • 2018-03-16
        • 1970-01-01
        相关资源
        最近更新 更多