vincolo di esclusione
Ti suggerisco invece di utilizzare un vincolo di esclusione, che è molto più semplice, sicuro e veloce:
È necessario installare il modulo aggiuntivo btree_gist
primo. Vedi le istruzioni e la spiegazione in questa risposta correlata:
E devi includere "ParentID"
nella tabella "Bar"
ridondante, che sarà un piccolo prezzo da pagare. Le definizioni delle tabelle potrebbero assomigliare a questa:
CREATE TABLE "Foo" (
"FooID" serial PRIMARY KEY
"ParentID" int4 NOT NULL REFERENCES "Parent"
"Details1" varchar
CONSTRAINT foo_parent_foo_uni UNIQUE ("ParentID", "FooID") -- required for FK
);
CREATE TABLE "Bar" (
"ParentID" int4 NOT NULL,
"FooID" int4 NOT NULL REFERENCES "Foo" ("FooID"),
"Timerange" tstzrange NOT NULL,
"Detail1" varchar,
"Detail2" varchar,
CONSTRAINT "Bar_pkey" PRIMARY KEY ("FooID", "Timerange"),
CONSTRAINT bar_foo_fk
FOREIGN KEY ("ParentID", "FooID") REFERENCES "Foo" ("ParentID", "FooID"),
CONSTRAINT bar_parent_timerange_excl
EXCLUDE USING gist ("ParentID" WITH =, "Timerange" WITH &&)
);
Ho anche cambiato il tipo di dati per "Bar"."FooID"
da a int8
int4
. Fa riferimento a "Foo"."FooID"
, che è un serial
, ovvero int4
. Utilizza il tipo di corrispondenza int4
(o solo integer
) per diversi motivi, uno dei quali è la prestazione.
Non hai più bisogno di un trigger (almeno non per questa attività) e non crei l'indice non più, poiché è creato implicitamente dal vincolo di esclusione."Bar_FooID_Timerange_idx"
Un indice btree su ("ParentID", "FooID")
molto probabilmente sarà utile, però:
CREATE INDEX bar_parentid_fooid_idx ON "Bar" ("ParentID", "FooID");
Correlati:
Ho scelto UNIQUE ("ParentID", "FooID")
e non il contrario per un motivo, poiché esiste un altro indice con "FooID"
all'inizio in una delle due tabelle:
A parte:Non uso mai CaMeL con doppie virgolette -identificatori di casi a Postgres. Lo faccio qui solo per rispettare il tuo layout.
Evita la colonna ridondante
Se non puoi o non vuoi includere "Bar"."ParentID"
ridondante, c'è un altro rogue modo - a condizione che "Foo"."ParentID"
è mai aggiornato . Assicurati di farlo, ad esempio con un trigger.
Puoi falsificare un IMMUTABLE
funzione:
CREATE OR REPLACE FUNCTION f_parent_of_foo(int)
RETURNS int AS
'SELECT "ParentID" FROM public."Foo" WHERE "FooID" = $1'
LANGUAGE sql IMMUTABLE;
Ho qualificato lo schema del nome della tabella per esserne sicuro, assumendo public
. Adattati al tuo schema.
Altro:
- VINCOLI per controllare i valori da una tabella correlata in remoto (tramite join ecc.)
- PostgreSQL supporta "accent insensitive " regole di confronto?
Quindi usalo nel vincolo di esclusione:
CONSTRAINT bar_parent_timerange_excl
EXCLUDE USING gist (f_parent_of_foo("FooID") WITH =, "Timerange" WITH &&)
Durante il salvataggio di un int4
ridondante colonna, il vincolo sarà più costoso da verificare e l'intera soluzione dipende da più precondizioni.
Gestire i conflitti
Potresti avvolgere INSERT
e UPDATE
in una funzione plpgsql e intercetta possibili eccezioni dal vincolo di esclusione (23P01 exclusion_violation
) per gestirlo in qualche modo.
INSERT ...
EXCEPTION
WHEN exclusion_violation
THEN -- handle conflict
Esempio di codice completo:
Gestire i conflitti in Postgres 9.5
In Postgres 9,5 puoi gestire INSERT
direttamente con la nuova implementazione "UPSERT". La documentazione:
Tuttavia:
Ma puoi ancora usare ON CONFLICT DO NOTHING
, evitando così possibili exclusion_violation
eccezioni. Controlla solo se alcune righe sono state effettivamente aggiornate, il che è più economico:
INSERT ...
ON CONFLICT ON CONSTRAINT bar_parent_timerange_excl DO NOTHING;
IF NOT FOUND THEN
-- handle conflict
END IF;
Questo esempio limita il controllo al vincolo di esclusione specificato. (Ho chiamato il vincolo esplicitamente per questo scopo nella definizione della tabella sopra.) Altre possibili eccezioni non vengono rilevate.