Domanda

Ho un database Postgresql su cui voglio fare alcune eliminazioni a cascata. Tuttavia, le tabelle non sono impostate con la regola ON DELETE CASCADE. Esiste un modo per eseguire una cancellazione e dire a Postgresql di eseguirla in cascata solo una volta? Qualcosa di equivalente a

DELETE FROM some_table CASCADE;

Le risposte a questa domanda più vecchia sembra che non esista tale soluzione, ma io ho pensato che avrei posto questa domanda esplicitamente solo per essere sicuro.

È stato utile?

Soluzione

No. Per farlo solo una volta dovresti semplicemente scrivere l'istruzione delete per la tabella che vuoi mettere in cascata.

DELETE FROM some_child_table WHERE some_fk_field IN (SELECT some_id FROM some_Table);
DELETE FROM some_table;

Altri suggerimenti

Se vuoi davvero ELIMINA DA some_table CASCADE; che significa " rimuovi tutte le righe dalla tabella some_table " puoi utilizzare TRUNCATE invece di DELETE e CASCADE è sempre supportato. Tuttavia, se si desidera utilizzare l'eliminazione selettiva con una clausola dove , TRUNCATE non è abbastanza buono.

UTILIZZARE CON CURA - Questo eliminerà tutte le righe di tutte le tabelle che hanno un vincolo di chiave esterna su some_table e tutte le tabelle che hanno vincoli su quei tavoli, ecc.

Postgres supporta CASCADE con comando TRUNCATE :

TRUNCATE some_table CASCADE;

Praticamente questo è transazionale (cioè può essere eseguito il rollback), anche se non è completamente isolato da altre transazioni simultanee e ha molti altri avvertimenti. Leggi i documenti per i dettagli.

Ho scritto una funzione (ricorsiva) per eliminare qualsiasi riga in base alla sua chiave primaria. Ho scritto questo perché non volevo creare i miei vincoli come " on delete cascade " ;. Volevo essere in grado di eliminare complessi set di dati (come un DBA) ma non consentire ai miei programmatori di essere in grado di eliminare a cascata senza pensare a tutte le ripercussioni. Sto ancora testando questa funzione, quindi potrebbero esserci dei bug, ma per favore non provarlo se il tuo DB ha chiavi primarie a più colonne (e quindi esterne). Inoltre, tutte le chiavi devono essere in grado di essere rappresentate in forma di stringa, ma potrebbero essere scritte in un modo che non ha questa limitazione. Uso comunque questa funzione MOLTO RISPARMIO, apprezzo troppo i miei dati per abilitare i vincoli a cascata su tutto. Fondamentalmente questa funzione viene passata nello schema, nel nome della tabella e nel valore primario (in forma di stringa) e inizierà trovando eventuali chiavi esterne su quella tabella e assicurandosi che i dati non esistano - in caso affermativo, chiama ricorsivamente stesso sui dati trovati. Utilizza una matrice di dati già contrassegnati per l'eliminazione per evitare loop infiniti. Per favore, provalo e fammi sapere come funziona per te. Nota: è un po 'lento. Lo chiamo così: seleziona 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;

Se ho capito bene, dovresti essere in grado di fare ciò che vuoi eliminando il vincolo di chiave esterna, aggiungendone uno nuovo (che verrà messo in cascata), facendo le tue cose e ricreando il vincolo di restrizione di chiave esterna.

Ad esempio:

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

Ovviamente, dovresti astrarre cose del genere in una procedura, per il bene della tua salute mentale.

Non posso commentare la risposta del cavallo pallido, quindi ho aggiunto la mia risposta. Il log di Palehorse è ok ma l'efficienza può essere negativa con i set di big data.

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;

È più veloce se hai indici su colonne e il set di dati è più grande di pochi record.

Puoi usarlo per automatizzare, puoi definire il vincolo di chiave esterna con ON DELETE CASCADE .
Cito il il manuale dei vincoli di chiave esterna :

  

CASCADE specifica che quando una riga referenziata viene eliminata, le righe   referenziandolo dovrebbe anche essere automaticamente cancellato.

Ho preso la risposta di Joe Love e l'ho riscritta usando l'operatore IN con sub-selezioni anziché = per rendere più veloce la funzione (secondo il suggerimento di 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;

L'eliminazione con l'opzione a cascata si applicava solo alle tabelle con le chiavi esterne definite. Se si esegue un'eliminazione e si dice che non è possibile perché violerebbe il vincolo di chiave esterna, la cascata causerà l'eliminazione delle righe offensive.

Se si desidera eliminare le righe associate in questo modo, sarà necessario prima definire le chiavi esterne. Inoltre, ricorda che, a meno che tu non gli dica esplicitamente di iniziare una transazione o modifichi le impostazioni predefinite, eseguirà un commit automatico, che potrebbe richiedere molto tempo per ripulire.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top