如何选择引用同一列作为外键的所有(表,列)

Posted

技术标签:

【中文标题】如何选择引用同一列作为外键的所有(表,列)【英文标题】:How do I SELECT all (table, column) referencing the same column as foreign key 【发布时间】:2021-08-09 12:44:47 【问题描述】:

(TL;DR 结尾)

我正在合并 2 个大小合适的 postgres 数据库。 由于存在 ID 冲突和许多外键,我会很高兴UPDATE foo SET bar_id = bar_id + 100000 CASCADE 是 SQL 中的一个东西,因此它会神奇地相应地更新所有内容。不幸的是,事实并非如此。

所以我想使用一个 LOOP 结构(见下文),它可以简单地编辑任何地方的引用。 我想要一个选择查询,返回引用我想要的列的所有 table_name、column_name。

DO
$$
DECLARE
    rec record;
BEGIN
    FOR rec IN 
    (SELECT table_name, column_name FROM /*??*/ WHERE /*??*/)   -- <<< This line    
    LOOP  
    EXECUTE format('UPDATE %I SET %s = %s + 100000 ;',  
        rec.table_name,rec.column_name,rec.column_name); 
    END LOOP;
END;
$$
LANGUAGE plpgsql;

我已经知道如何获取具有特定 column_name 的所有表 (+column_name),当外键列与其引用的列共享名称时,我使用该特定列名。或者即使它是我知道的 column_name 列表:

SELECT col.table_name, col.column_name
        FROM information_schema.columns col 
        right join
        information_schema.tables tab 
        ON col.table_name = tab.table_name 
        WHERE column_name = 'foo_id' 
        --  IN ('FOO_ID','BAR_FOO_ID') | or : like '%foo_id' | both works well most of the time
        and tab.table_type = 'BASE TABLE' 

但是…… 我现在有一个表 PLACES,其中至少有 60 个不同的约束(匹配 LIKE '%place_id')引用了 place_id 列。 还有一些列引用了以其他方式命名的地点 ID,例如“foo_currentplace”、“foo_storageroom”、“foo_lastrecomposition_place”、“operating_theatre”等。另一方面,有来自placetype表的列引用'placetype_id'是LIKE '%place%' ,我不想更改placetype_id,所以我们无法猜测哪一列将包括或不仅来自他们的名字。

我知道有information_schema.table_constraints 表,但它不会告诉引用的列。 如果我们可以从约束名称中获得定义,则可以匹配:ILIKE format('%%REFERENCES %s(%s)%%',table_name,column_name) 但该定义也不属于table_constraints 表。

(对于那些想知道的人,我正在研究与绝育服务相关的医院数据库。)

我想要什么/TL;DR

我需要一个 SELECT 查询(或函数定义)返回整个数据库的所有列 (schema_name,table_name,column_name)(table_name,column_name) 具有引用指定列(参数)的外键约束。

【问题讨论】:

【参考方案1】:

评论可能太长了。

Postgres 绝对支持级联更新约束(参见here)。但是,这是约束的定义的一部分,而不是update 语句:

ALTER TABLE foo 
    ADD FOREIGN KEY fk_foo_bar_id FOREIGN KEY (bar_id) REFERENCES bar(bar_id)
    ON UPDATE CASCADE;
----^ this is the important part

注意:您需要重新定义所有外键约束,以使它们成为级联更新约束。

并且,作为编辑评论,我通常避免级联更新外键约束(因为我不喜欢主键更改值的想法)。但是,这绝对是它们有用的地方。

那么如果您更改bar.bar_id,更改将反映在foo 中。 Here 是一个 dbfiddle。

【讨论】:

嗯,是的,重新定义约束可能是一个很好的解决方法,而不是集群更新,但不会改变问题:我没有可靠和快速的方法来 identify循环作为外键引用bar_id 的所有列(或约束名称)。我的意思是,无需手动滚动数百个表和数千个约束定义即可。 @MartialP 。 . .具有讽刺意味的是,您可以尝试更新并查看您遇到的错误。 好吧,你知道我怎样才能创建一个循环,一次又一次地尝试相同的更新,但在每次迭代时向错误中提到的违反约束添加一个ON UPDATE CASCADE 子句? :-) @MartialP。 . .这个想法是一个表会失败,你可以通过级联更新来解决这个问题。然后下一个将失败。 . .等等。 我理解正确,(是的,我笑了)。我只是一个不做电脑的忠实粉丝。如果我可以repeat 编辑违反约束的定义until 更新不再出现错误,我认为必须有一种方法告诉我的计算机为我做这件事,特别是因为我听到一个理论告诉计算机简直就是为了做这样的事情而制造的。我的回答和你的一样讽刺。【参考方案2】:

好的,我已经做到了:-) 以下查询返回数据库中引用 FOO_TABLE.foo_column 作为外键的每一列:

SELECT 
fk.table_schema as schema, --optional in my case, I only use public
fk.table_name as table,
substring(fk.constraint_def, 14, position(')' in constraint_def)-14) as column
FROM
    (SELECT tc.*,
     pg_get_constraintdef(c.oid) as constraint_def
     --,c.oid 
     from pg_constraint c
     left join information_schema.table_constraints tc
     on c.conname = tc.constraint_name
     where tc.constraint_type = 'FOREIGN KEY')
     as fk    
WHERE constraint_def ILIKE format('%%REFERENCES %s(%s)%%',
                                 'FOO_TABLE','foo_column')  
ORDER BY table_schema,table_name,3;

Test it there

我发现information_schema.table_constraintspg_constraint 获取大部分信息,其中包括内部引用OID,并且有一个内置函数pg_get_constraintdef() 从它的OID 返回约束对象的定义。 然后,定义的一些子字符串足以提取column_name AND 使用我在问题中准备的(I)LIKE 过滤器过滤引用的列。

----------------- 其他答案------------------

我通过改进@Abelisto 建议构建的另一个可接受的查询:

SELECT table_name, column_name
FROM (SELECT table_name, SUBSTR(column_name, 2, LENGTH(column_name)-2) as column_name,
             referenced_table,SUBSTR(referenced_column, 2, LENGTH(referenced_column)-2) as referenced_column
      FROM(select
             conrelid::regclass::text as table_name,
            (select array_agg(attname) from pg_attribute where conrelid = attrelid and attnum = any(conkey))::text as column_name,
             confrelid::regclass::text as referenced_table,
            (select array_agg(attname) from pg_attribute where confrelid = attrelid and attnum = any(confkey))::text as referenced_column
             from pg_constraint where contype = 'f' 
             ) b ) a
WHERE (referenced_table, referenced_column) = 'FOO_TABLE','foo_column');

Test it there

我不认为那里的表现真的很重要,所以应该根据侧面的需求来选择。我认为我的第一个解决方案的优势在于如果您想更改约束定义(例如,添加一个ON UPDATE CASCADE 子句),但第二个解决方案似乎更“紧凑”,只是为了返回表.column.

【讨论】:

@Gordon Linoff :由此,我可以使用 pl/pgsql 集群更新或更改约束以在所有这些上设置 ON UPDATE CASCADE。两者都可以,但我会坚持我的集群更新方法,因为我不想做另一个函数来撤消第一个函数,只是为了在之后删除 ON UPDATE CASCADE 子句。 dbfiddle.uk/…

以上是关于如何选择引用同一列作为外键的所有(表,列)的主要内容,如果未能解决你的问题,请参考以下文章

sql怎么设置外键

如何将具有外键的 NOT NULLABLE 列添加到已包含数据的现有表中?

如何添加一列外键的值?

什么是主键和外键?

如何在Django的同一个表中阻止外键的相同对象引用?

由于引用同一个表,外键的插入约束