Как отключить ссылочную целостность в Postgres 8.2?

Результаты Google по этому вопросу немного тонкие, но предполагают, что это непросто.

Моя конкретная проблема заключается в том, что мне нужно перенумеровать идентификаторы в двух таблицах, связанных друг с другом, так что в таблице B есть столбец "table_a_id". Я не могу перенумеровать таблицу A сначала, потому что тогда ее дети в B указывают на старые идентификаторы. Я не могу перенумеровать таблицу B сначала, потому что тогда они будут указывать на новые идентификаторы до их создания. Теперь повторите три или четыре таблицы.

На самом деле я не хочу заниматься индивидуальными отношениями, когда я могу просто "начать транзакцию, отключить целостность ссылок, вывести идентификаторы сортировки, снова включить целостность ref, совершить транзакцию". Mysql и MSSQL обеспечивают эту функциональность IIRC, поэтому я был бы удивлен, если Postgres этого не сделал.

Спасибо!

Ответ 1

Это не представляется возможным. Другие предложения почти всегда относятся к уменьшению ограничений и их повторному созданию после завершения работы.

Однако, похоже, вы можете создавать ограничения DEFERRABLE, чтобы они не проверялись до конца транзакции. См. Документация PostgreSQL для CREATE TABLE (найдите "отложенные", это посередине страницы).

Ответ 2

Есть две вещи, которые вы можете сделать (они дополняют друг друга, а не альтернативы):

  • Создайте ограничения внешнего ключа как DEFERRABLE. Затем вызовите "SET CONSTRAINTS DEFERRED", что приведет к тому, что ограничения внешнего ключа не будут проверяться до конца транзакции. Обратите внимание, что значение по умолчанию, если вы ничего не указываете, НЕ ДЕФРИРУЕТ (досадно).
  • Вызов "ALTER TABLE mytable DISABLE TRIGGER ALL;", который предотвращает выполнение любых триггеров во время загрузки данных, затем "ALTER TABLE mytable ENABLE TRIGGER ALL"; когда вы закончите, чтобы снова включить их.

Ответ 3

Я нашел эти 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, которые вам нужны для удаления и создания ограничений.

Как только вы отмените ограничения, вы можете делать все, что хотите, с помощью таблиц. Когда вы закончите, повторно представите их.

Ответ 4

Я думаю, вам нужно составить список ограничений внешнего ключа, отбросить их, внести свои изменения и снова добавить ограничения. Проверьте документацию на alter table drop constraint и alter table add constraint.

Ответ 5

Здесь Python script, который удалит все ограничения в транзакции, запустит некоторые запросы, а затем воссоздает все эти ограничения. 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:[email protected]/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

Ответ 6

Если ограничения 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.

Ответ 7

Я думаю, что простым решением было бы создать "временные" столбцы, связывающие, где вы хотите.

обновить значения с помощью внешних ключей для новых столбцов

отбрасывать столбцы в столбце

переименуйте в новые "временные" столбцы с теми же именами, а затем на официальные.