¿Cómo agregar restricciones "al borrar cascada"?

163

En PostgreSQL 8, ¿es posible agregar ON DELETE CASCADESa ambas claves foráneas en la siguiente tabla sin soltar la última?

# \d scores
        Table "public.scores"
 Column  |         Type          | Modifiers
---------+-----------------------+-----------
 id      | character varying(32) |
 gid     | integer               |
 money   | integer               | not null
 quit    | boolean               |
 last_ip | inet                  |
Foreign-key constraints:
   "scores_gid_fkey" FOREIGN KEY (gid) REFERENCES games(gid)
   "scores_id_fkey" FOREIGN KEY (id) REFERENCES users(id)

Ambas tablas referenciadas están abajo - aquí:

# \d games
                                     Table "public.games"
  Column  |            Type             |                        Modifiers
----------+-----------------------------+----------------------------------------------------------
 gid      | integer                     | not null default nextval('games_gid_seq'::regclass)
 rounds   | integer                     | not null
 finished | timestamp without time zone | default now()
Indexes:
    "games_pkey" PRIMARY KEY, btree (gid)
Referenced by:
    TABLE "scores" CONSTRAINT "scores_gid_fkey" FOREIGN KEY (gid) REFERENCES games(gid)

Y aquí:

# \d users
                Table "public.users"
   Column   |            Type             |   Modifiers
------------+-----------------------------+---------------
 id         | character varying(32)       | not null
 first_name | character varying(64)       |
 last_name  | character varying(64)       |
 female     | boolean                     |
 avatar     | character varying(128)      |
 city       | character varying(64)       |
 login      | timestamp without time zone | default now()
 last_ip    | inet                        |
 logout     | timestamp without time zone |
 vip        | timestamp without time zone |
 mail       | character varying(254)      |
Indexes:
    "users_pkey" PRIMARY KEY, btree (id)
Referenced by:
    TABLE "cards" CONSTRAINT "cards_id_fkey" FOREIGN KEY (id) REFERENCES users(id)
    TABLE "catch" CONSTRAINT "catch_id_fkey" FOREIGN KEY (id) REFERENCES users(id)
    TABLE "chat" CONSTRAINT "chat_id_fkey" FOREIGN KEY (id) REFERENCES users(id)
    TABLE "game" CONSTRAINT "game_id_fkey" FOREIGN KEY (id) REFERENCES users(id)
    TABLE "hand" CONSTRAINT "hand_id_fkey" FOREIGN KEY (id) REFERENCES users(id)
    TABLE "luck" CONSTRAINT "luck_id_fkey" FOREIGN KEY (id) REFERENCES users(id)
    TABLE "match" CONSTRAINT "match_id_fkey" FOREIGN KEY (id) REFERENCES users(id)
    TABLE "misere" CONSTRAINT "misere_id_fkey" FOREIGN KEY (id) REFERENCES users(id)
    TABLE "money" CONSTRAINT "money_id_fkey" FOREIGN KEY (id) REFERENCES users(id)
    TABLE "pass" CONSTRAINT "pass_id_fkey" FOREIGN KEY (id) REFERENCES users(id)
    TABLE "payment" CONSTRAINT "payment_id_fkey" FOREIGN KEY (id) REFERENCES users(id)
    TABLE "rep" CONSTRAINT "rep_author_fkey" FOREIGN KEY (author) REFERENCES users(id)
    TABLE "rep" CONSTRAINT "rep_id_fkey" FOREIGN KEY (id) REFERENCES users(id)
    TABLE "scores" CONSTRAINT "scores_id_fkey" FOREIGN KEY (id) REFERENCES users(id)
    TABLE "status" CONSTRAINT "status_id_fkey" FOREIGN KEY (id) REFERENCES users(id)

Y también me pregunto si tiene sentido agregar 2 índices a la tabla anterior.

ACTUALIZACIÓN: Gracias, y también recibí el consejo en la lista de correo, de que podría administrarlo en 1 declaración y, por lo tanto, sin iniciar explícitamente una transacción:

ALTER TABLE public.scores
DROP CONSTRAINT scores_gid_fkey,
ADD CONSTRAINT scores_gid_fkey
   FOREIGN KEY (gid)
   REFERENCES games(gid)
   ON DELETE CASCADE;
Alexander Farber
fuente
1
Un poco de OT, pero me doy cuenta de que no ha creado índices en las columnas de referencia (por ejemplo, pref_scores.gid). Las eliminaciones en la tabla referenciada tardarán mucho tiempo sin ellas, si obtiene muchas filas en esas tablas. Algunas bases de datos crean automáticamente un índice en las columnas de referencia; PostgreSQL lo deja a usted, ya que hay algunos casos en los que no vale la pena.
kgrittn
1
¡Gracias! De hecho, noté que la eliminación tarda mucho, pero no sabía que esa era la razón
Alexander Farber
1
¿Qué casos serían esos, cuando los índices en claves externas no valen la pena?
Alexander Farber
2
Incorporé tu hallazgo en mi respuesta. (Esa declaración única también es una transacción única.)
Mike Sherrill 'Cat Recall'
2
@AlexanderFarber: ¿Cuándo podría querer omitir un índice en la (s) columna (s) de referencia de un FK? Cuando hay otro índice que no es una coincidencia exacta que funcionará lo suficientemente bien (por ejemplo, es posible que tenga un índice de trigrama para búsquedas frecuentes de similitud que también estarán bien para la eliminación de FK). Cuando las eliminaciones son poco frecuentes y se pueden programar fuera de horario. Cuando una tabla tiene actualizaciones frecuentes del valor de referencia. Cuando la tabla de referencia es muy pequeña pero se actualiza con frecuencia. Las excepciones ocurren con la frecuencia suficiente para que la comunidad PostgreSQL prefiera tener control sobre él en lugar de hacerlo automático.
kgrittn

Respuestas:

218

Estoy bastante seguro de que no puede simplemente agregar on delete cascadea una restricción de clave externa existente. Primero debe eliminar la restricción y luego agregar la versión correcta. En SQL estándar, creo que la forma más fácil de hacer esto es

  • comenzar una transacción,
  • suelte la clave foránea,
  • agregue una clave foránea con on delete cascade, y finalmente
  • cometer la transacción

Repita para cada clave externa que desee cambiar.

Pero PostgreSQL tiene una extensión no estándar que le permite usar múltiples cláusulas de restricción en una sola instrucción SQL. Por ejemplo

alter table public.scores
drop constraint scores_gid_fkey,
add constraint scores_gid_fkey
   foreign key (gid)
   references games(gid)
   on delete cascade;

Si no conoce el nombre de la restricción de clave externa que desea eliminar, puede buscarla en pgAdminIII (simplemente haga clic en el nombre de la tabla y mire el DDL, o expanda la jerarquía hasta que vea "Restricciones"), o puede consultar el esquema de información .

select *
from information_schema.key_column_usage
where position_in_unique_constraint is not null
Mike Sherrill 'Retiro del gato'
fuente
Gracias, eso es lo que también pensé, pero ¿qué hacer con las LLAVES EXTRANJERAS? ¿Son solo restricciones (similares a NOT NULL) que pueden descartarse y leerse fácilmente?
Alexander Farber
2
@AlexanderFarber: Sí, se llaman restricciones que puede eliminar y agregar fácilmente. Pero probablemente quieras hacer eso dentro de una transacción. Actualicé mi respuesta con más detalle.
Mike Sherrill 'Cat Recall'
+1 para buscar ot en pgAdminIII. Incluso le proporciona los comandos DROP CONSTRAINT y ADD CONSTRAINT, por lo que puede copiar y pegar en una ventana de consulta y editar el comando a lo que desee.
Dave Pile
Después de escribir la consulta, noté que mi GUI de Postgres (Navicat) me permite hacer este cambio trivialmente desde la GUI: dl.dropboxusercontent.com/spa/quq37nq1583x0lf/wwqne-lw.png
danneu
Para tablas grandes, ¿es esto posible NOT VALIDy validar en una transacción separada? Tengo una pregunta sin respuesta sobre esto.
TheCloudlessSky
11

Basado en la respuesta de @Mike Sherrill Cat Recall, esto es lo que funcionó para mí:

ALTER TABLE "Children"
DROP CONSTRAINT "Children_parentId_fkey",
ADD CONSTRAINT "Children_parentId_fkey"
  FOREIGN KEY ("parentId")
  REFERENCES "Parent"(id)
  ON DELETE CASCADE;
Extraño compañero
fuente
5

Uso:

select replace_foreign_key('user_rates_posts', 'post_id', 'ON DELETE CASCADE');

Función:

CREATE OR REPLACE FUNCTION 
    replace_foreign_key(f_table VARCHAR, f_column VARCHAR, new_options VARCHAR) 
RETURNS VARCHAR
AS $$
DECLARE constraint_name varchar;
DECLARE reftable varchar;
DECLARE refcolumn varchar;
BEGIN

SELECT tc.constraint_name, ccu.table_name AS foreign_table_name, ccu.column_name AS foreign_column_name 
FROM 
    information_schema.table_constraints AS tc 
    JOIN information_schema.key_column_usage AS kcu
      ON tc.constraint_name = kcu.constraint_name
    JOIN information_schema.constraint_column_usage AS ccu
      ON ccu.constraint_name = tc.constraint_name
WHERE constraint_type = 'FOREIGN KEY' 
   AND tc.table_name= f_table AND kcu.column_name= f_column
INTO constraint_name, reftable, refcolumn;

EXECUTE 'alter table ' || f_table || ' drop constraint ' || constraint_name || 
', ADD CONSTRAINT ' || constraint_name || ' FOREIGN KEY (' || f_column || ') ' ||
' REFERENCES ' || reftable || '(' || refcolumn || ') ' || new_options || ';';

RETURN 'Constraint replaced: ' || constraint_name || ' (' || f_table || '.' || f_column ||
 ' -> ' || reftable || '.' || refcolumn || '); New options: ' || new_options;

END;
$$ LANGUAGE plpgsql;

Tenga en cuenta: esta función no copiará los atributos de la clave externa inicial. Solo toma el nombre de la tabla / columna extranjera, elimina la clave actual y la reemplaza por una nueva.

Daniel Garmoshka
fuente