web-dev-qa-db-fra.com

Comment désactiver l'intégrité référentielle dans Postgres 8.2?

Les résultats de Google sur celui-ci sont un peu minces, mais suggèrent que ce n'est pas facilement possible.

Mon problème spécifique est que je dois renuméroter les identifiants dans deux tables liées l'une à l'autre, de manière à ce que la table B contienne une colonne "table_a_id". Je ne peux pas renuméroter la table A d'abord parce qu'alors ses enfants de B pointent vers les anciens ID. Je ne peux pas renuméroter la table B d'abord car ils indiqueraient alors les nouveaux ID avant leur création. Répétez maintenant pour trois ou quatre tables.

Je ne veux pas vraiment avoir à bricoler avec des relations individuelles alors que je pouvais simplement "démarrer une transaction, désactiver l'intégrité de la référence, trier les ID, réactiver l'intégrité de la référence; engager une transaction". Mysql et MSSQL fournissent tous les deux cette fonctionnalité IIRC, donc je serais surpris que Postgres ne le fasse pas.

Merci!

35
sanbikinoraion

Cela ne semble pas possible. D'autres suggestions font presque toujours référence à la suppression des contraintes et à leur recréation une fois le travail terminé.

Cependant, il semble que vous puissiez créer des contraintes DEFERRABLE, de sorte qu'elles ne soient pas vérifiées jusqu'à la fin d'une transaction. Voir la documentation PostgreSQL pour CREATE TABLE (recherchez 'deferrable', il se trouve au milieu de la page).

17
Joel B Fant

Vous pouvez faire deux choses (complémentaires, pas alternatives):

  • Créez vos contraintes de clé étrangère comme DEFERRABLE. Appelez ensuite "SET CONSTRAINTS DEFERRED;", ce qui fera en sorte que les contraintes de clé étrangère ne seront pas vérifiées jusqu'à la fin de la transaction. Notez que la valeur par défaut si vous ne spécifiez rien est PAS DEFERRABLE (ennuyeusement).
  • Appelez "ALTER TABLE mytable DÉSACTIVER TRIGGER ALL;", qui empêche tout déclencheur de s'exécuter pendant le chargement de données, puis "ALTER TABLE mytable ENABLE TRIGGER ALL;" quand vous avez fini de les réactiver.
48
Nick Johnson

J'ai trouvé ces 2 excellents scripts qui génèrent le SQL pour laisser tomber les contraintes et les recréer Les voici:

Pour lâcher les contraintes

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

Pour les recréer

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;

Exécutez ces requêtes et le résultat sera les scripts SQL dont vous avez besoin pour supprimer et créer les contraintes.

Une fois que vous avez supprimé les contraintes, vous pouvez faire tout ce que vous voulez avec les tables. Lorsque vous avez terminé, réintroduisez-les.

33
Dimitris

Voici un script Python qui supprimera toutes les contraintes d'une transaction, exécutera des requêtes, puis recréera toutes ces contraintes. pg_get_constraintdef rend cela super facile:

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 __== '__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
5
zzzeek

Je pense que vous devez dresser une liste de vos contraintes de clé étrangère, les supprimer, faire vos modifications, puis rajouter les contraintes. Consultez la documentation pour alter table drop constraint et alter table add constraint.

5
Liam

Si les contraintes sont DEFERRABLE, c'est vraiment facile. Utilisez simplement un bloc de transaction et définissez vos contraintes FK de manière à les différer au début de la transaction.

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

SET CONSTRAINTS définit le comportement de la vérification de contrainte dans la transaction en cours. Les contraintes IMMEDIATE sont vérifiées à la fin de chaque instruction. Les contraintes DEFERRED ne sont pas vérifiées jusqu'à la validation de la transaction.

Alors tu pourrais faire:

BEGIN;

SET CONSTRAINTS
    table_1_parent_id_foreign, 
    table_2_parent_id_foreign,
    -- etc
DEFERRED;

-- do all your renumbering

COMMIT;

Malheureusement, il semble que Postgres utilise NOT DEFERRABLE comme valeur par défaut pour toutes les contraintes, à moins que DEFERRABLE soit explicitement défini. (J'imagine que c'est pour des raisons de performances, mais je ne suis pas certain.) À partir de Postgres 9.4, il n'est pas trop difficile de modifier les contraintes pour les rendre reportables si nécessaire:

ALTER TABLE table_1 ALTER CONSTRAINT table_1_parent_id_foreign DEFERRABLE;

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

Je pense que cette approche serait préférable de supprimer et de recréer vos contraintes comme certains l'ont décrit ou de désactiver tous les déclencheurs (ou tous les utilisateurs) jusqu'à la fin de la transaction, qui nécessite des privilèges de superutilisateur, comme indiqué dans un commentaire précédent comment by @ clapas .

0
Sean the Bean