КАСКАД-УДАЛИТЬ только один раз

160

У меня есть база данных Postgresql, на которой я хочу сделать несколько каскадных удалений. Однако таблицы не настроены с правилом ON DELETE CASCADE. Есть ли способ выполнить удаление и сообщить Postgresql, чтобы он каскадировал его только один раз? Что-то эквивалентное

DELETE FROM some_table CASCADE;

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

  • 0
    Пожалуйста, смотрите мои пользовательские функции ниже. Это возможно с определенными ограничениями.
Теги:

8 ответов

132
Лучший ответ

Нет. Чтобы сделать это, просто как только вы просто напишете инструкцию delete для таблицы, которую вы хотите каскадировать.

DELETE FROM some_child_table WHERE some_fk_field IN (SELECT some_id FROM some_Table);
DELETE FROM some_table;
  • 7
    Это не обязательно работает, так как могут быть другие внешние ключи, каскадные из исходного каскадирования (рекурсия). Вы даже можете попасть в цикл, где таблица a ссылается на b, который ссылается на a. Чтобы достичь этого в общем смысле, смотрите мою таблицу ниже, но она имеет некоторые ограничения. Если у вас простая настройка таблицы, попробуйте приведенный выше код, вам будет проще понять, что вы делаете.
  • 1
    Просто, безопасно. Вы должны запустить их в одной транзакции, если у вас есть вставки плотности.
39

Если вы действительно хотите DELETE FROM some_table CASCADE; что означает "удалить все строки из таблицы some_table ", вы можете использовать TRUNCATE вместо DELETE и CASCADE всегда поддерживается. Однако, если вы хотите использовать выборочное удаление с предложением where, TRUNCATE недостаточно хорош.

ИСПОЛЬЗОВАТЬ С УХОДОМ - Это some_table все строки всех таблиц, которые имеют ограничение внешнего ключа для some_table и все таблицы, которые имеют ограничения для этих таблиц и т.д.

Postgres поддерживает CASCADE с помощью команды TRUNCATE:

TRUNCATE some_table CASCADE;

Удобно, что это транзакционный (то есть может быть откат), хотя он не полностью изолирован от других одновременных транзакций и имеет несколько других предостережений. Прочитайте документы для деталей.

  • 172
    явно "несколько каскадных удалений", отбрасывая все данные из таблицы ...
  • 20
    Это приведет к удалению всех строк всех таблиц, которые имеют ограничение внешнего ключа для some_table, и всех таблиц, которые имеют ограничения для этих таблиц и т. Д. ... это потенциально очень опасно.
Показать ещё 7 комментариев
19

Я написал (рекурсивную) функцию для удаления любой строки на основе ее первичного ключа. Я написал это, потому что я не хотел создавать свои ограничения как "при удалении каскада". Я хотел бы иметь возможность удалять сложные наборы данных (как администратор базы данных), но не позволять моим программистам удалять каскад, не задумываясь о всех последствиях. Я все еще тестирую эту функцию, поэтому в ней могут быть ошибки, но, пожалуйста, не пытайтесь ее использовать, если ваша БД имеет несколько основных (и, следовательно, иностранных) ключей столбца. Кроме того, все ключи должны быть представлены в строковой форме, но они могут быть написаны таким образом, чтобы этого не было. В любом случае я использую эту функцию ОЧЕНЬ СПАСИБО, я слишком ценю свои данные, чтобы включить каскадные ограничения на все. В основном эта функция передается в схеме, имени таблицы и первичном значении (в строковой форме), и она начинается с поиска каких-либо внешних ключей в этой таблице и гарантирует, что данные не существуют - если это так, то она рекурсивно вызывает сам по найденным данным. Он использует массив данных, уже отмеченных для удаления, для предотвращения бесконечных циклов. Пожалуйста, проверьте его и дайте мне знать, как это работает для вас. Примечание: Это немного медленно. Я называю это так: select delete_cascade('public','my_table','1');

create or replace function delete_cascade(p_schema varchar, p_table varchar, p_key varchar, p_recursion varchar[] default null)
 returns integer as $$
declare
    rx record;
    rd record;
    v_sql varchar;
    v_recursion_key varchar;
    recnum integer;
    v_primary_key varchar;
    v_rows integer;
begin
    recnum := 0;
    select ccu.column_name into v_primary_key
        from
        information_schema.table_constraints  tc
        join information_schema.constraint_column_usage AS ccu ON ccu.constraint_name = tc.constraint_name and ccu.constraint_schema=tc.constraint_schema
        and tc.constraint_type='PRIMARY KEY'
        and tc.table_name=p_table
        and tc.table_schema=p_schema;

    for rx in (
        select kcu.table_name as foreign_table_name, 
        kcu.column_name as foreign_column_name, 
        kcu.table_schema foreign_table_schema,
        kcu2.column_name as foreign_table_primary_key
        from information_schema.constraint_column_usage ccu
        join information_schema.table_constraints tc on tc.constraint_name=ccu.constraint_name and tc.constraint_catalog=ccu.constraint_catalog and ccu.constraint_schema=ccu.constraint_schema 
        join information_schema.key_column_usage kcu on kcu.constraint_name=ccu.constraint_name and kcu.constraint_catalog=ccu.constraint_catalog and kcu.constraint_schema=ccu.constraint_schema
        join information_schema.table_constraints tc2 on tc2.table_name=kcu.table_name and tc2.table_schema=kcu.table_schema
        join information_schema.key_column_usage kcu2 on kcu2.constraint_name=tc2.constraint_name and kcu2.constraint_catalog=tc2.constraint_catalog and kcu2.constraint_schema=tc2.constraint_schema
        where ccu.table_name=p_table  and ccu.table_schema=p_schema
        and TC.CONSTRAINT_TYPE='FOREIGN KEY'
        and tc2.constraint_type='PRIMARY KEY'
)
    loop
        v_sql := 'select '||rx.foreign_table_primary_key||' as key from '||rx.foreign_table_schema||'.'||rx.foreign_table_name||'
            where '||rx.foreign_column_name||'='||quote_literal(p_key)||' for update';
        --raise notice '%',v_sql;
        --found a foreign key, now find the primary keys for any data that exists in any of those tables.
        for rd in execute v_sql
        loop
            v_recursion_key=rx.foreign_table_schema||'.'||rx.foreign_table_name||'.'||rx.foreign_column_name||'='||rd.key;
            if (v_recursion_key = any (p_recursion)) then
                --raise notice 'Avoiding infinite loop';
            else
                --raise notice 'Recursing to %,%',rx.foreign_table_name, rd.key;
                recnum:= recnum +delete_cascade(rx.foreign_table_schema::varchar, rx.foreign_table_name::varchar, rd.key::varchar, p_recursion||v_recursion_key);
            end if;
        end loop;
    end loop;
    begin
    --actually delete original record.
    v_sql := 'delete from '||p_schema||'.'||p_table||' where '||v_primary_key||'='||quote_literal(p_key);
    execute v_sql;
    get diagnostics v_rows= row_count;
    --raise notice 'Deleting %.% %=%',p_schema,p_table,v_primary_key,p_key;
    recnum:= recnum +v_rows;
    exception when others then recnum=0;
    end;

    return recnum;
end;
$$
language PLPGSQL;
  • 0
    Это происходит все время, особенно с самообращающимися таблицами. Рассмотрим компанию с разными уровнями управления в разных отделах или общую иерархическую таксономию. Да, я согласен с тем, что эта функция не самая лучшая вещь после нарезанного хлеба, но это полезный инструмент в правильной ситуации.
  • 0
    Если вы переписываете его, принимаете массив идентификаторов, а также генерируете запросы, которые будут использовать оператор IN с вложенными выборками вместо = (так что шаг для использования логики множеств) будет намного быстрее.
Показать ещё 4 комментария
17

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

Например:

testing=# create table a (id integer primary key);
NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "a_pkey" for table "a"
CREATE TABLE
testing=# create table b (id integer references a);
CREATE TABLE

-- put some data in the table
testing=# insert into a values(1);
INSERT 0 1
testing=# insert into a values(2);
INSERT 0 1
testing=# insert into b values(2);
INSERT 0 1
testing=# insert into b values(1);
INSERT 0 1

-- restricting works
testing=# delete from a where id=1;
ERROR:  update or delete on table "a" violates foreign key constraint "b_id_fkey" on table "b"
DETAIL:  Key (id)=(1) is still referenced from table "b".

-- find the name of the constraint
testing=# \d b;
       Table "public.b"
 Column |  Type   | Modifiers 
--------+---------+-----------
 id     | integer | 
Foreign-key constraints:
    "b_id_fkey" FOREIGN KEY (id) REFERENCES a(id)

-- drop the constraint
testing=# alter table b drop constraint b_a_id_fkey;
ALTER TABLE

-- create a cascading one
testing=# alter table b add FOREIGN KEY (id) references a(id) on delete cascade; 
ALTER TABLE

testing=# delete from a where id=1;
DELETE 1
testing=# select * from a;
 id 
----
  2
(1 row)

testing=# select * from b;
 id 
----
  2
(1 row)

-- it works, do your stuff.
-- [stuff]

-- recreate the previous state
testing=# \d b;
       Table "public.b"
 Column |  Type   | Modifiers 
--------+---------+-----------
 id     | integer | 
Foreign-key constraints:
    "b_id_fkey" FOREIGN KEY (id) REFERENCES a(id) ON DELETE CASCADE

testing=# alter table b drop constraint b_id_fkey;
ALTER TABLE
testing=# alter table b add FOREIGN KEY (id) references a(id) on delete restrict; 
ALTER TABLE

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

  • 4
    Предполагая, что внешний ключ должен предотвращать действия, которые делают базу данных несовместимой, это не тот способ, с которым нужно иметь дело. Вы можете удалить «неприятную» запись сейчас, но вы оставляете много осколков зомби, которые могут вызвать проблемы в будущем
  • 1
    Какие осколки вы имеете в виду именно? записи будут удалены через каскад, не должно быть противоречий.
Показать ещё 1 комментарий
5

Я не могу комментировать palehorse ответ, поэтому добавил свой собственный ответ. Palehorse logick в порядке, но эффективность может быть плохой с большими наборами данных.

DELETE FROM some_child_table sct WHERE exists  (SELECT FROM some_Table st 
where sct.some_fk_fiel=st.some_id );
DELETE FROM some_table;

Это быстрее, если у вас есть индексы на столбцах, а набор данных больше, чем несколько записей.

2

Я взял ответ Джо Лав и переписал его, используя оператор IN с суб-выбором вместо = чтобы сделать функцию быстрее (согласно предложению Hubbitus):

create or replace function delete_cascade(p_schema varchar, p_table varchar, p_keys varchar, p_subquery varchar default null, p_foreign_keys varchar[] default array[]::varchar[])
 returns integer as $$
declare

    rx record;
    rd record;
    v_sql varchar;
    v_subquery varchar;
    v_primary_key varchar;
    v_foreign_key varchar;
    v_rows integer;
    recnum integer;

begin

    recnum := 0;
    select ccu.column_name into v_primary_key
        from
        information_schema.table_constraints  tc
        join information_schema.constraint_column_usage AS ccu ON ccu.constraint_name = tc.constraint_name and ccu.constraint_schema=tc.constraint_schema
        and tc.constraint_type='PRIMARY KEY'
        and tc.table_name=p_table
        and tc.table_schema=p_schema;

    for rx in (
        select kcu.table_name as foreign_table_name, 
        kcu.column_name as foreign_column_name, 
        kcu.table_schema foreign_table_schema,
        kcu2.column_name as foreign_table_primary_key
        from information_schema.constraint_column_usage ccu
        join information_schema.table_constraints tc on tc.constraint_name=ccu.constraint_name and tc.constraint_catalog=ccu.constraint_catalog and ccu.constraint_schema=ccu.constraint_schema 
        join information_schema.key_column_usage kcu on kcu.constraint_name=ccu.constraint_name and kcu.constraint_catalog=ccu.constraint_catalog and kcu.constraint_schema=ccu.constraint_schema
        join information_schema.table_constraints tc2 on tc2.table_name=kcu.table_name and tc2.table_schema=kcu.table_schema
        join information_schema.key_column_usage kcu2 on kcu2.constraint_name=tc2.constraint_name and kcu2.constraint_catalog=tc2.constraint_catalog and kcu2.constraint_schema=tc2.constraint_schema
        where ccu.table_name=p_table  and ccu.table_schema=p_schema
        and TC.CONSTRAINT_TYPE='FOREIGN KEY'
        and tc2.constraint_type='PRIMARY KEY'
)
    loop
        v_foreign_key := rx.foreign_table_schema||'.'||rx.foreign_table_name||'.'||rx.foreign_column_name;
        v_subquery := 'select "'||rx.foreign_table_primary_key||'" as key from '||rx.foreign_table_schema||'."'||rx.foreign_table_name||'"
             where "'||rx.foreign_column_name||'"in('||coalesce(p_keys, p_subquery)||') for update';
        if p_foreign_keys @> ARRAY[v_foreign_key] then
            --raise notice 'circular recursion detected';
        else
            p_foreign_keys := array_append(p_foreign_keys, v_foreign_key);
            recnum:= recnum + delete_cascade(rx.foreign_table_schema, rx.foreign_table_name, null, v_subquery, p_foreign_keys);
            p_foreign_keys := array_remove(p_foreign_keys, v_foreign_key);
        end if;
    end loop;

    begin
        if (coalesce(p_keys, p_subquery) <> '') then
            v_sql := 'delete from '||p_schema||'."'||p_table||'" where "'||v_primary_key||'"in('||coalesce(p_keys, p_subquery)||')';
            --raise notice '%',v_sql;
            execute v_sql;
            get diagnostics v_rows = row_count;
            recnum := recnum + v_rows;
        end if;
        exception when others then recnum=0;
    end;

    return recnum;

end;
$$
language PLPGSQL;
  • 1
    Я собираюсь посмотреть на это и посмотреть, насколько хорошо он работает с ограничениями на собственные ссылки и тому подобное. Я попытался сделать что-то подобное, но остановился, чтобы заставить его работать полностью. Если ваше решение работает для меня, я собираюсь его реализовать. Это один из многих инструментов dba, которые должны быть упакованы и помещены на github или что-то в этом роде.
2

Вы можете использовать это для автоматизации, вы можете определить ограничение внешнего ключа с помощью ON DELETE CASCADE.
Я цитирую руководство по ограничениям внешнего ключа:

CASCADE указывает, что при удалении ссылочной строки строки (строки) ссылка на него также должна автоматически удаляться.

  • 0
    Несмотря на то, что это не относится к OP, хорошо планировать, когда нужно удалять строки с внешними ключами. Как сказал Бен Франклин, «унция профилактики стоит фунта лечения».
  • 0
    Я обнаружил, что это решение может быть довольно опасным, если ваше приложение удаляет запись с большим количеством братьев и сестер, и вместо небольшой ошибки вы навсегда удалили огромный набор данных.
2

Удаление с помощью опции каскада применяется только к таблицам с заданными внешними ключами. Если вы выполните удаление, и оно говорит, что вы не можете, потому что это нарушит ограничение внешнего ключа, каскад заставит его удалять оскорбительные строки.

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

  • 2
    Ответ Гранта частично неверен - Postgresql не поддерживает CASCADE для запросов DELETE. postgresql.org/docs/8.4/static/dml-delete.html
  • 0
    Есть идеи, почему это не поддерживается в запросе на удаление?
Показать ещё 2 комментария

Ещё вопросы

Сообщество Overcoder
Наверх
Меню