Как отключить ссылочную целостность в Postgres 8.2?
результаты Google на этом немного тонкие, но предполагают, что это не так легко возможно.
моя конкретная проблема заключается в том, что мне нужно перенумеровать идентификаторы в двух таблицах, связанных друг с другом, так что таблица B имеет столбец "table_a_id". Я не могу сначала перенумеровать таблицу A, потому что ее потомки в B указывают на старые идентификаторы. Я не могу сначала перенумеровать таблицу B, потому что тогда они будут указывать на новые идентификаторы до их создания. Теперь повторите для трех или четырех таблицы.
Я действительно не хочу возиться с отдельными отношениями, когда я мог бы просто "начать транзакцию; отключить целостность ref; сортировать идентификаторы; повторно включить целостность ref; зафиксировать транзакцию". Mysql и MSSQL предоставляют эту функциональность IIRC, поэтому я был бы удивлен, если бы Postgres этого не сделал.
спасибо!
7 ответов
это кажется невозможным. Другие предложения почти всегда относятся к снятию ограничений и их воссозданию после завершения работы.
однако, похоже, вы можете сделать ограничения DEFERRABLE
, такие, что они не проверяются до конца транзакции. См.документация PostgreSQL для CREATE TABLE
(поиск "deferrable", он находится в середине страницы).
есть две вещи, которые вы можете сделать (это взаимодополняющие, а не альтернативы):
- создайте ограничения внешнего ключа как отложенные. Затем вызовите " SET CONSTRAINTS DEFERRED;", что приведет к тому, что ограничения внешнего ключа не будут проверены до конца транзакции. Обратите внимание, что по умолчанию, если вы ничего не указываете, не откладывается (раздражающе).
- вызов " ALTER TABLE mytable DISABLE TRIGGER ALL;", который предотвращает выполнение триггеров во время загрузки данные, затем "ALTER TABLE mytable ENABLE TRIGGER ALL;" когда вы закончите, чтобы повторно включить их.
Я нашел эти 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, необходимые для удаления и создания ограничений.
Как только вы отбросите ограничения, вы можете делать все, что вам нравится, с таблицами. Когда вы закончите, снова представьте их.
Я думаю, вам нужно составить список ограничений внешнего ключа, удалить их, внести изменения, а затем снова добавить ограничения. Проверьте документацию для alter table drop constraint
и alter table add constraint
.
вот скрипт 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
если ограничения DEFERRABLE
, это очень легко. Просто используйте блок транзакций и установите ограничения FK, которые будут отложены в начале транзакции.
от http://www.postgresql.org/docs/9.4/static/sql-set-constraints.html:
SET CONSTRAINTS задает поведение проверки ограничений в текущей транзакции. Непосредственные ограничения проверяются в конце каждого оператора. Отложенные ограничения не проверяются до совершения транзакции.
так что вы могли бы сделать:
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.
Я думаю, что решение easear было бы создать "временные" столбцы, связывающие, где вы хотите, чтобы они были.
обновите значения внешними ключами до новых столбцов
отбросьте официальные столбцы
переименовать в новые "временные" столбцы с теми же именами, что и официальные.