36

谷歌关于这个的结果有点薄,但表明这并不容易。

我的具体问题是我需要重新编号两个相互关联的表中的 ID,以便表 B 中有一个“table_a_id”列。我不能先重新编号表 A,因为 B 中的子代指向旧 ID。我不能先重新编号表 B,因为它们会在创建之前指向新的 ID。现在重复三到四张桌子。

当我可以“开始事务;禁用 ref 完整性;整理 ID;重新启用 ref 完整性;提交事务”时,我真的不想摆弄个人关系。Mysql 和 MSSQL 都提供此功能 IIRC,所以如果 Postgres 没有,我会感到惊讶。

谢谢!

4

7 回答 7

48

您可以做两件事(这些是互补的,而不是替代品):

  • 将您的外键约束创建为 DEFERRABLE。然后,调用“SET CONSTRAINTS DEFERRED;”,这将导致直到事务结束才检查外键约束。请注意,如果您未指定任何内容,则默认值为 NOT DEFERRABLE(令人讨厌)。
  • 调用“ALTER TABLE mytable DISABLE TRIGGER ALL;”,以防止在加载数据时执行任何触发器,然后调用“ALTER TABLE mytable ENABLE TRIGGER ALL;” 当您完成重新启用它们时。
于 2008-09-26T14:46:47.947 回答
34

我发现这 2 个优秀的脚本可以生成用于删除约束然后重新创建它们的 sql。他们来了:

用于删除约束

SELECT 'ALTER TABLE "'||nspname||'"."'||relname||'" DROP CONSTRAINT "'||conname||'";'
FROM pg_constraint 
INNER JOIN pg_class ON conrelid=pg_class.oid 
INNER JOIN pg_namespace ON pg_namespace.oid=pg_class.relnamespace 
ORDER BY CASE WHEN contype='f' THEN 0 ELSE 1 END,contype,nspname,relname,conname

为了重建它们

SELECT 'ALTER TABLE "'||nspname||'"."'||relname||'" ADD CONSTRAINT "'||conname||'" '|| pg_get_constraintdef(pg_constraint.oid)||';'
FROM pg_constraint
INNER JOIN pg_class ON conrelid=pg_class.oid
INNER JOIN pg_namespace ON pg_namespace.oid=pg_class.relnamespace
ORDER BY CASE WHEN contype='f' THEN 0 ELSE 1 END DESC,contype DESC,nspname DESC,relname DESC,conname DESC;

运行这些查询,输出将是删除和创建约束所需的 sql 脚本。

一旦你放弃了约束,你就可以对表格做任何你喜欢的事情。完成后重新介绍它们。

于 2012-05-29T07:41:22.340 回答
18

这似乎不可能。其他建议几乎总是提到删除约束并在工作完成后重新创建它们。

但是,您似乎可以设置约束DEFERRABLE,以便在事务结束之前不检查它们。请参阅PostgreSQL 文档CREATE TABLE(搜索“可延迟”,它位于页面中间)。

于 2008-09-26T14:41:22.950 回答
5

我认为您需要列出外键约束,删除它们,进行更改,然后再次添加约束。检查 和 的alter table drop constraint文档alter table add constraint

于 2008-09-26T14:43:07.407 回答
5

这是一个 Python 脚本,它将删除事务中的所有约束,运行一些查询,然后重新创建所有这些约束。 pg_get_constraintdef让这变得超级简单:

class no_constraints(object):
    def __init__(self, connection):
        self.connection = connection

    def __enter__(self):
        self.transaction = self.connection.begin()
        try:
            self._drop_constraints()
        except:
            self.transaction.rollback()
            raise

    def __exit__(self, exc_type, exc_value, traceback):
        if exc_type is not None:
            self.transaction.rollback()
        else:
            try:
                self._create_constraints()
                self.transaction.commit()
            except:
                self.transaction.rollback()
                raise

    def _drop_constraints(self):
        self._constraints = self._all_constraints()

        for schemaname, tablename, name, def_ in self._constraints:
            self.connection.execute('ALTER TABLE "%s.%s" DROP CONSTRAINT %s' % (schemaname, tablename, name))

    def _create_constraints(self):
        for schemaname, tablename, name, def_ in self._constraints:
            self.connection.execute('ALTER TABLE "%s.%s" ADD CONSTRAINT %s %s' % (schamename, tablename, name, def_))

    def _all_constraints(self):
        return self.connection.execute("""
            SELECT n.nspname AS schemaname, c.relname, conname, pg_get_constraintdef(r.oid, false) as condef
                     FROM  pg_constraint r, pg_class c
                     LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
                     WHERE r.contype = 'f'
                    and r.conrelid=c.oid
            """).fetchall()

if __name__ == '__main__':
    # example usage

    from sqlalchemy import create_engine

    engine = create_engine('postgresql://user:pass@host/dbname', echo=True)

    conn = engine.connect()
    with no_contraints(conn):
        r = conn.execute("delete from table1")
        print "%d rows affected" % r.rowcount
        r = conn.execute("delete from table2")
        print "%d rows affected" % r.rowcount
于 2010-04-29T19:02:30.890 回答
0

如果约束是DEFERRABLE,这真的很容易。只需使用事务块并将 FK 约束设置为在事务开始时延迟。

来自http://www.postgresql.org/docs/9.4/static/sql-set-constraints.html

SET CONSTRAINTS 设置当前事务中约束检查的行为。在每个语句的末尾检查 IMMEDIATE 约束。在事务提交之前不会检查 DEFERRED 约束。

所以你可以这样做:

BEGIN;

SET CONSTRAINTS
    table_1_parent_id_foreign, 
    table_2_parent_id_foreign,
    -- etc
DEFERRED;

-- do all your renumbering

COMMIT;

不幸的是,似乎 Postgres 将所有约束默认为NOT DEFERRABLE,除非DEFERRABLE明确设置。(我猜这是出于性能原因,但我不确定。)从 Postgres 9.4 开始,更改约束以使其在需要时可延迟并不难:

ALTER TABLE table_1 ALTER CONSTRAINT table_1_parent_id_foreign DEFERRABLE;

(见http://www.postgresql.org/docs/9.4/static/sql-altertable.html。)

我认为这种方法比某些人所描述的删除和重新创建您的约束更可取,或者禁用所有(或所有用户)触发器直到事务结束,这需要超级用户权限,如@clapas 之前的评论中所述

于 2015-05-05T13:59:54.443 回答
-3

我认为一个简单的解决方案是创建“临时”列,将它们关联到您希望它们所在的位置。

使用新列的外键更新值

删除初始列

将新的“临时”列重命名为与初始列相同的名称。

于 2011-01-24T09:40:34.790 回答