Pregunta

Tengo una base de datos Postgresql en la que quiero hacer algunas eliminaciones en cascada. Sin embargo, las tablas no están configuradas con la regla ON DELETE CASCADE. ¿Hay alguna manera de que pueda realizar una eliminación y decirle a Postgresql que lo conecte en cascada solo por esta vez? Algo equivalente a

DELETE FROM some_table CASCADE;

Las respuestas a esta pregunta anterior hacen que parezca que no existe tal solución, pero yo pensé que haría esta pregunta explícitamente solo para estar seguro.

¿Fue útil?

Solución

No. Para hacerlo solo una vez, simplemente escribiría la declaración de eliminación para la tabla que desea en cascada.

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

Otros consejos

Si realmente quiere DELETE FROM some_table CASCADE; que significa " eliminar todas las filas de la tabla some_table " ;, puede usar TRUNCATE en lugar de DELETE y CASCADE siempre es compatible. Sin embargo, si desea utilizar la eliminación selectiva con una cláusula where , TRUNCATE no es lo suficientemente bueno.

USO CON CUIDADO : esto eliminará todas las filas de todas las tablas que tienen una restricción de clave externa en some_table y todas las tablas que tienen restricciones en esas mesas, etc.

Postgres admite CASCADE con comando TRUNCATE :

TRUNCATE some_table CASCADE;

Prácticamente esto es transaccional (es decir, puede revertirse), aunque no está completamente aislado de otras transacciones concurrentes y tiene varias otras advertencias. Lea los documentos para más detalles.

Escribí una función (recursiva) para eliminar cualquier fila en función de su clave primaria. Escribí esto porque no quería crear mis restricciones como " en delete cascade " ;. Quería poder eliminar conjuntos complejos de datos (como un DBA) pero no permitir que mis programadores puedan eliminar en cascada sin pensar en todas las repercusiones. Todavía estoy probando esta función, por lo que puede haber errores, pero no lo intente si su base de datos tiene claves primarias de varias columnas (y, por lo tanto, externas). Además, todas las claves deben poder representarse en forma de cadena, pero podrían escribirse de una manera que no tenga esa restricción. Utilizo esta función MUY ESPACIOSAMENTE de todos modos, valoro mis datos demasiado para permitir las restricciones en cascada en todo. Básicamente, esta función se pasa en el esquema, el nombre de la tabla y el valor primario (en forma de cadena), y comenzará por encontrar cualquier clave externa en esa tabla y se asegurará de que los datos no existan; si es así, llama recursivamente sí mismo en los datos encontrados. Utiliza una matriz de datos ya marcados para su eliminación para evitar bucles infinitos. Por favor, pruébelo y dígame cómo funciona para usted. Nota: es un poco lento. Yo lo llamo así: 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;

Si entiendo correctamente, debería ser capaz de hacer lo que quiera soltando la restricción de clave externa, agregando una nueva (que se aplicará en cascada), haciendo sus cosas y recreando la restricción de clave externa restrictiva.

Por ejemplo:

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

Por supuesto, debe resumir cosas como esas en un procedimiento, en aras de su salud mental.

No puedo comentar la respuesta de palehorse, así que agregué mi propia respuesta. Palehorse logick está bien, pero la eficiencia puede ser mala con grandes conjuntos de datos.

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;

Es más rápido si tiene índices en columnas y el conjunto de datos es mayor que pocos registros.

Puede usarlo para automatizar esto, puede definir la restricción de clave externa con ON DELETE CASCADE .
Cito el el manual de restricciones de clave externa :

  

CASCADE especifica que cuando se elimina una fila referenciada, fila (s)   haciendo referencia a él también debería eliminarse automáticamente.

Tomé la respuesta de Joe Love y la reescribí usando el operador IN con sub-selecciones en lugar de = para hacer la función más rápida (según la sugerencia de 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;

La opción Eliminar con la cascada solo se aplica a tablas con claves foráneas definidas. Si hace una eliminación, y dice que no puede porque violaría la restricción de clave externa, la cascada hará que elimine las filas infractoras.

Si desea eliminar las filas asociadas de esta manera, primero deberá definir las claves externas. Además, recuerde que a menos que le indique explícitamente que comience una transacción, o cambie los valores predeterminados, realizará una confirmación automática, lo que podría llevar mucho tiempo limpiar.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top