【问题标题】:Get all the rows referencing (via foreign keys) a particular row in a table获取所有引用(通过外键)表中特定行的行
【发布时间】:2020-01-28 06:51:31
【问题描述】:

这似乎很简单,但我一直无法找到这个问题的答案。

我想要什么?一个主表,其中的行在不再被引用(通过外键)时会自行删除。该解决方案可能特定于 PostgreSql,也可能不特定于 PostgreSql。

如何? 我解决此问题的一种方法(实际上是迄今为止唯一的方法)涉及以下内容:对于引用此主表的每个表,在 UPDATEDELETE一行,要检查 master 中的引用行,还有多少其他行仍然引用引用的行。如果它下降到零,那么我也会在 master 中删除该行。

(如果你有更好的想法,我很想知道!)

详细说明: 我有一个被许多其他人引用的主表

CREATE TABLE master (
  id serial primary key,
  name text unique not null
);

所有其他表格通常具有相同的格式:

CREATE TABLE other (
  ...
  master_id integer references master (id)
  ...
);

如果其中一个不是NULL,它们指的是master 中的一行。如果我去这个并尝试删除它,我会收到一条错误消息,因为它已经被引用:

ERROR:  update or delete on table "master" violates foreign key constraint "other_master_id_fkey" on table "other"
DETAIL:  Key (id)=(1) is still referenced from table "other".
Time: 42.972 ms

请注意,即使我有很多表引用master,也不会花费太长时间来解决这个问题。如何在不引发错误的情况下找出这些信息?

【问题讨论】:

    标签: sql postgresql foreign-keys


    【解决方案1】:

    您可以执行以下操作之一:

    1) 将reference_count 字段添加到主表。每当添加带有此master_id 的行时,在明细表上使用触发器会增加reference count。当行被删除时减少计数。当reference_count 达到0 - 删除记录。

    2) 使用pg_constraint 表(详情here)获取引用表列表并创建动态SQL 查询。

    3) 在每个明细表上创建触发器,删除主表中的master_id。使用BEGIN ... EXCEPTION ... END 忽略错误消息。

    【讨论】:

    • 引用计数确实是唯一理智的方法。 (重新阅读问题后)我个人永远不会使用它(因为它会使每个操作变得复杂并减慢速度),而是定期清理主表中未引用的行。
    • 建议 3. 看起来最不干净,但也是最简单的。使用这种行为是否有性能(或任何其他)惩罚。它是否因 hackish 或其他什么而皱眉?
    • PostgreSql 必须在某处获取我正在寻找的信息(否则它无法强制执行外键约束)。那么 PostgreSql 是如何做到的呢?使用内部引用计数?考虑到运行时间短,我几乎看不到它会扫描所有表。
    • @cassava 第三个建议确实是一个 hack。我不确切知道 PostgreSQL 如何获取参考检查信息。我的猜测 - 简单的表或索引扫描。也许有一些内部魔法来加速它。
    【解决方案2】:

    如果有人想要引用给定主行的所有其他表中的真实行数,这里有一些 PL/pgSQL。请注意,这适用于具有单列约束的普通情况。它更多地涉及多列约束。

    CREATE OR REPLACE FUNCTION count_references(master regclass, pkey_value integer,
        OUT "table" regclass, OUT count integer)
        RETURNS SETOF record 
        LANGUAGE 'plpgsql'
        VOLATILE 
    AS $BODY$
    declare
      x record;           -- constraint info for each table in question that references master
      sql text;           -- temporary buffer
    begin
      for x in
        select conrelid, attname
        from pg_constraint
        join pg_attribute on conrelid=attrelid and attnum=conkey[1]
        where contype='f' and confrelid=master
          and confkey=( -- here we assume that FK references master's PK
            select conkey
            from pg_constraint
            where conrelid=master and contype='p'
          )
      loop
        "table" = x.conrelid;
        sql = format('select count(*) from only %s where %I=$1', "table", x.attname);
        execute sql into "count" using pkey_value;
        return next;
      end loop;
    end
    $BODY$;
    

    然后像这样使用它

    select * from count_references('master', 1) where count>0
    

    这将返回一个表的列表,这些表引用了 id=1 的主表。

    【讨论】:

      【解决方案3】:
      SELECT * 
      FROM master ma
      WHERE EXISTS (
          SELECT *
          FROM other ot
          WHERE ot.master_id = ma.id
          );
      

      或者,反过来:

      SELECT * 
      FROM other ot
      WHERE EXISTS (
          SELECT *    
          FROM master ma
          WHERE ot.master_id = ma.id
          );
      

      如果您只想更新(或删除)master 中未被other 引用的行,您可以:

      UPDATE master ma
      SET id = 1000+id
        , name = 'blue'
      WHERE name = 'green'
      AND NOT EXISTS (
          SELECT *
          FROM other ot
          WHERE ot.master_id = ma.id
          );
      

      【讨论】:

      • 我需要不确定数量的引用表的行为。所以other 不是唯一的另一张桌子! ;-)
      • 如果您需要立即删除,引用计数+触发器(Igor 的回答)是唯一的方法。如果您可以将删除推迟到定期清理,您可以查询目录以查找引用表并从中构建(动态)查询。
      猜你喜欢
      • 2012-07-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-08-09
      • 2019-11-01
      • 1970-01-01
      • 2011-04-17
      • 1970-01-01
      相关资源
      最近更新 更多