【问题标题】:Delete rows and other rows with a reference to the deleted row删除行和引用已删除行的其他行
【发布时间】:2020-05-13 15:27:38
【问题描述】:

我正在使用 PostgreSQL,我有一个表 family,如下所示:

+----+-------+-----------+
| id | name  | parent_id |
+----+-------+-----------+
|  1 | adam  |         0 |
|  2 | eva   |         0 |
|  3 | peter |         2 |
|  4 | pan   |         3 |
+----+-------+-----------+

现在,当我删除此人时,我也希望删除孩子。例如,删除WHERE name='peter' 会从列表中删除'peter''pan'。 当我删除 'eva' 时,她、'peter''pan' 会被删除。

现在我考虑删除初始行并从已删除行中获取 id。然后我会继续删除,直到现在 id 被返回。请注意,人只有一个父母,但可以有几个孩子。

SQL 中有没有一种巧妙的方法来解决这个问题?如果没有,如何取回所有已删除行的 id?

【问题讨论】:

    标签: sql postgresql recursion sql-delete


    【解决方案1】:

    最好的解决方案是创建一个用on delete cascade 定义的正确外键。这需要在 parent_id 列中存储 NULL 值而不是魔法“零”:

    create table family 
    (
      id int primary key, 
      name varchar(5), 
      parent_id int, 
      foreign key (parent_id) references family on delete cascade
    );
    

    那么你只需要:

    delete from family
    where name = 'peter';
    

    Online example


    如果你想转换你现有的表和数据,你可以这样做:

    --- change all zeroes to NULL
    update family 
      set parent_id = null
    where parent_id = 0;
    
    -- add a primary key in order to be able to create a foreign key
    alter table family add primary key (id);
    
    -- add the foreign key 
    alter table family 
      add foreign key (parent_id) references family (id)
      on delete cascade;
    

    【讨论】:

    • 我选择了你的,因为它是最干净的解决方案,而且我实际上可以更改数据库!
    【解决方案2】:

    您可以使用递归 CTE 生成与给定人员关联的子 ID 列表,然后从该表中删除所有 ID:

    WITH RECURSIVE CTE AS (
      SELECT id
      FROM family 
      WHERE name = 'eva'
      UNION ALL
      SELECT f.id
      FROM family f
      JOIN CTE ON f.parent_id = CTE.id
    )
    DELETE
    FROM family
    WHERE id IN (SELECT id FROM CTE)
    

    Demo on SQLFiddle

    【讨论】:

      【解决方案3】:

      您必须在如下表上使用触发器:

      仅用于删除操作:

      CREATE OR REPLACE FUNCTION delete_child() RETURNS TRIGGER AS $$
          BEGIN                 
              DELETE FROM family WHERE parent_id = OLD.id;                    
              RETURN OLD;
          END;
      $$ LANGUAGE plpgsql;
      

      在同一张表上重复使用多个操作:

      CREATE OR REPLACE FUNCTION delete_child() RETURNS TRIGGER AS $$
          BEGIN      
              IF (TG_OP = 'DELETE') THEN
                  DELETE FROM family WHERE parent_id = OLD.id;                          
                  RETURN OLD;       
              END IF;
          END;
      $$ LANGUAGE plpgsql;
      

      创建触发器:

      CREATE TRIGGER trigger_delete_child
      AFTER DELETE ON family
          FOR EACH ROW EXECUTE PROCEDURE delete_child();
      

      【讨论】:

      • @a_horse_with_no_name:没错。我只是参考在同一张桌子上的多个操作,对桌子有不同的操作。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-02-25
      • 1970-01-01
      • 2019-05-21
      • 2022-04-12
      • 1970-01-01
      • 2012-07-19
      相关资源
      最近更新 更多