How do I disable referential integrity in Postgres 8.2?
Asked Answered
M

7

36

Google results on this one are a bit thin, but suggest that it is not easily possible.

My specific problem is that I need to renumber the IDs in two tables that are related to each other such that table B has an "table_a_id" column in it. I can't renumber table A first because then its children in B point to the old IDs. I can't renumber table B first because then they would point to the new IDs before they were created. Now repeat for three or four tables.

I don't really want to have to fiddle around with individual relationships when I could just "start transaction; disable ref integrity; sort IDs out; re-enable ref integrity; commit transaction". Mysql and MSSQL both provide this functionality IIRC so I would be surprised if Postgres didn't.

Thanks!

Marilou answered 26/9, 2008 at 14:29 Comment(1)
Check this out : postgresql.org/docs/8.2/static/sql-set-constraints.htmlGammy
L
18

It does not seem possible. Other suggestions almost always refer to dropping the constraints and recreating them after work is done.

However, it seems you can make constraints DEFERRABLE, such that they are not checked until the end of a transaction. See PostgreSQL documentation for CREATE TABLE (search for 'deferrable', it's in the middle of the page).

Louisalouisburg answered 26/9, 2008 at 14:41 Comment(0)
C
48

There are two things you can do (these are complementary, not alternatives):

  • Create your foreign key constraints as DEFERRABLE. Then, call "SET CONSTRAINTS DEFERRED;", which will cause foreign key constraints not to be checked until the end of the transaction. Note that the default if you don't specify anything is NOT DEFERRABLE (annoyingly).
  • Call "ALTER TABLE mytable DISABLE TRIGGER ALL;", which prevents any triggers executing while you load data, then "ALTER TABLE mytable ENABLE TRIGGER ALL;" when you're done to re-enable them.
Crenate answered 26/9, 2008 at 14:46 Comment(5)
As of PostgreSQL 9 you need to specify what you want to disable: ALTER TABLE mytable DISABLE TRIGGER USER; This will disable only user created constraints, i.e.: your FK & PK constraints.Cohlette
I needed to DISABLE TRIGGER ALL; to disable FK constraints on PG9.Jobye
ALTER TABLE mytable DISABLE TRIGGER ALL requires superuser privilege; SET CONSTRAINTS DEFERRED must be executed inside a transaction.Cora
As per this answer another option is to use SET session_replication_role = replica; to disable all triggers for the remainder of the session. (Also requires superuser privileges.)Septet
It's SET CONSTRAINTS ALL DEFERRED in 9.x.Chopine
D
34

I found these 2 excellent scripts which generate the sql for dropping the constraints and then recreating them. here they are:

For dropping the constraints

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

For recreating them

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;

Run these queries and the output will be the sql scripts that you need for dropping and creating the constraints.

Once you drop the constraints you can do all you like with the tables. When you are done re-introduce them.

Deplore answered 29/5, 2012 at 7:41 Comment(3)
I used a slight modification of those statements to recreate my constraints as DEFERRABLE and be able to issue SET CONSTRAINTS ALL DEFERRED. CheersCora
Thanks a lot. This saved my day.Roybn
This is really interesting. I edited your answer to add quotes surrounding entity names, to make it compatible with non-lowercase entities. Anyway, I can't understand why ordering descending when recreating constraints, that seems not be needed at all.Influx
L
18

It does not seem possible. Other suggestions almost always refer to dropping the constraints and recreating them after work is done.

However, it seems you can make constraints DEFERRABLE, such that they are not checked until the end of a transaction. See PostgreSQL documentation for CREATE TABLE (search for 'deferrable', it's in the middle of the page).

Louisalouisburg answered 26/9, 2008 at 14:41 Comment(0)
P
5

I think you need to make a list of your foreign key constraints, drop them, do your changes, then add the constraints again. Check the documentation for alter table drop constraint and alter table add constraint.

Palsy answered 26/9, 2008 at 14:43 Comment(0)
P
5

Here's a Python script that will delete all constraints in a transaction, run some queries, then recreate all those constraints. pg_get_constraintdef makes this super-easy:

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
Panties answered 29/4, 2010 at 19:2 Comment(2)
What are the performance implications of such an approach? Sounds awefully expensive to me.Topeka
A recipe like this is only appropriate for offline data migration operations so there aren't really any "performance" implications.Panties
W
0

If the constraints are DEFERRABLE, this is really easy. Just use a transaction block and set your FK constraints to be deferred at the beginning of the transaction.

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

SET CONSTRAINTS sets the behavior of constraint checking within the current transaction. IMMEDIATE constraints are checked at the end of each statement. DEFERRED constraints are not checked until transaction commit.

So you could do:

BEGIN;

SET CONSTRAINTS
    table_1_parent_id_foreign, 
    table_2_parent_id_foreign,
    -- etc
DEFERRED;

-- do all your renumbering

COMMIT;

Unfortunately, it seems Postgres defaults all constraints to NOT DEFERRABLE, unless DEFERRABLE is explicitly set. (I'm guessing this is for performance reasons, but I'm not certain.) As of Postgres 9.4, it isn't too hard to alter the constraints to make them deferrable if needed:

ALTER TABLE table_1 ALTER CONSTRAINT table_1_parent_id_foreign DEFERRABLE;

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

I think this approach would be preferable to dropping and recreating your constraints as some have described, or to disabling all (or all user) triggers until the end of the transaction, which requires superuser privileges, as noted in an earlier comment by @clapas.

Whitewall answered 5/5, 2015 at 13:59 Comment(0)
W
-3

I think that an easear solution would be to create "temporary" columns associating where you want them to be.

update the values with the foreign keys to the new columns

drop the inicial columns

rename to the new "temporary" columns to the same names then the inicial ones.

Windward answered 24/1, 2011 at 9:40 Comment(1)
-1 for not being an answer to the original posters question. Also, the proposed solution does not seem like A Good Idea (TM).Estienne

© 2022 - 2024 — McMap. All rights reserved.