Wie füge ich Einschränkungen für die Löschkaskade hinzu?


163

Ist es in PostgreSQL 8 möglich, ON DELETE CASCADESdie beiden Fremdschlüssel in der folgenden Tabelle zu ergänzen , ohne diese zu löschen?

# \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)

Beide Tabellen, auf die verwiesen wird, sind unten aufgeführt: hier:

# \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)

Und hier:

# \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)

Und ich frage mich auch, ob es sinnvoll ist, der vorherigen Tabelle 2 Indizes hinzuzufügen?

UPDATE: Danke, und ich habe auch den Rat auf der Mailingliste, dass ich es in 1 Anweisung verwalten könnte und somit ohne explizit eine Transaktion zu starten:

ALTER TABLE public.scores
DROP CONSTRAINT scores_gid_fkey,
ADD CONSTRAINT scores_gid_fkey
   FOREIGN KEY (gid)
   REFERENCES games(gid)
   ON DELETE CASCADE;

1
Ein wenig OT, aber ich stelle fest, dass Sie keine Indizes für referenzierende Spalten erstellt haben (zum Beispiel pref_scores.gid). Das Löschen in der Tabelle, auf die verwiesen wird, dauert ohne diese sehr lange, wenn Sie in diesen Tabellen viele Zeilen erhalten. Einige Datenbanken erstellen automatisch einen Index für die Referenzierungsspalte (n). PostgreSQL überlässt das Ihnen, da es einige Fälle gibt, in denen es sich nicht lohnt.
kgrittn

1
Danke dir! Ich habe tatsächlich bemerkt, dass das Löschen lange dauert, wusste aber nicht, dass dies der Grund ist
Alexander Farber,

1
Welche Fälle wären das, wenn sich Indizes für Fremdschlüssel nicht lohnen?
Alexander Farber

2
Ich habe Ihre Erkenntnis in meine Antwort aufgenommen. (Diese einzelne Anweisung ist auch eine einzelne Transaktion.)
Mike Sherrill 'Cat Recall'

2
@AlexanderFarber: Wann möchten Sie möglicherweise einen Index für die Referenzierungsspalte (n) eines FK weglassen? Wenn es einen anderen Index gibt, der nicht genau übereinstimmt, was gut genug funktioniert (z. B. haben Sie möglicherweise einen Trigrammindex für häufige Ähnlichkeitssuchen, der auch für das Löschen von FK in Ordnung ist). Wenn Löschungen selten sind und außerhalb der Geschäftszeiten geplant werden können. Wenn eine Tabelle häufig den Referenzwert aktualisiert. Wenn die Referenzierungstabelle sehr klein ist, aber häufig aktualisiert wird. Ausnahmen treten häufig genug auf, dass die PostgreSQL-Community lieber die Kontrolle darüber hat, als sie automatisch zu machen.
Kgrittn

Antworten:


218

Ich bin mir ziemlich sicher, dass Sie nicht einfach on delete cascadeeine vorhandene Fremdschlüsseleinschränkung hinzufügen können . Sie müssen zuerst die Einschränkung löschen und dann die richtige Version hinzufügen. In Standard-SQL ist dies meiner Meinung nach am einfachsten

  • eine Transaktion starten,
  • den Fremdschlüssel fallen lassen,
  • Fügen Sie einen Fremdschlüssel mit on delete cascadeund schließlich hinzu
  • Übernehmen Sie die Transaktion

Wiederholen Sie diesen Vorgang für jeden Fremdschlüssel, den Sie ändern möchten.

PostgreSQL verfügt jedoch über eine nicht standardmäßige Erweiterung, mit der Sie mehrere Einschränkungsklauseln in einer einzelnen SQL-Anweisung verwenden können. Beispielsweise

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

Wenn Sie den Namen der Fremdschlüsseleinschränkung, die Sie löschen möchten, nicht kennen, können Sie ihn entweder in pgAdminIII nachschlagen (klicken Sie einfach auf den Tabellennamen und sehen Sie sich die DDL an oder erweitern Sie die Hierarchie, bis "Einschränkungen" angezeigt wird). oder Sie können das Informationsschema abfragen .

select *
from information_schema.key_column_usage
where position_in_unique_constraint is not null

Danke, das habe ich mir auch gedacht - aber was soll ich mit AUSLÄNDISCHEN SCHLÜSSELN machen? Sind es nur Einschränkungen (ähnlich wie NOT NULL), die einfach gelöscht und gelesen werden können?
Alexander Farber

2
@AlexanderFarber: Ja, es handelt sich um benannte Einschränkungen, die Sie einfach löschen und hinzufügen können. Aber Sie möchten das wahrscheinlich innerhalb einer Transaktion tun. Meine Antwort wurde detaillierter aktualisiert.
Mike Sherrill 'Cat Recall'

+1 für das Nachschlagen in pgAdminIII. Sie erhalten sogar die Befehle DROP CONSTRAINT und ADD CONSTRAINT, sodass Sie sie einfach kopieren und in ein Abfragefenster einfügen und den Befehl nach Ihren Wünschen bearbeiten können.
Dave Pile

Nachdem ich die Abfrage geschrieben hatte, bemerkte ich, dass meine Postgres-GUI (Navicat) diese Änderung trivial innerhalb der GUI vornehmen lässt
danneu

Ist dies bei großen Tabellen mit NOT VALIDeiner separaten Transaktion möglich und wird diese validiert? Ich habe eine unbeantwortete Frage dazu.
TheCloudlessSky

11

Basierend auf der Antwort von @Mike Sherrill Cat Recall hat dies für mich funktioniert:

ALTER TABLE "Children"
DROP CONSTRAINT "Children_parentId_fkey",
ADD CONSTRAINT "Children_parentId_fkey"
  FOREIGN KEY ("parentId")
  REFERENCES "Parent"(id)
  ON DELETE CASCADE;

5

Verwendung:

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

Funktion:

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;

Beachten Sie: Diese Funktion kopiert keine Attribute des ursprünglichen Fremdschlüssels. Es werden nur fremde Tabellennamen / Spaltennamen verwendet, der aktuelle Schlüssel wird gelöscht und durch einen neuen ersetzt.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.