2009-11-25 1 views
20

(Note:. Mise à jour avec la réponse adoptée ci-dessous)PostgreSQL: UPDATE implique déplacent à travers les partitions

Pour PostgreSQL 8.1 (ou version ultérieure) table partitionnée, comment définir un déclencheur UPDATE et de la procédure de "déplacer" un enregistrement d'une partition à l'autre, si le UPDATE implique une modification du champ contraint qui définit la séparation de partition?

Par exemple, j'ai enregistrements de table partitionnée dans les dossiers actifs et inactifs comme ceci:

create table RECORDS (RECORD varchar(64) not null, ACTIVE boolean default true); 
create table ACTIVE_RECORDS (check (ACTIVE)) inherits RECORDS; 
create table INACTIVE_RECORDS (check (not ACTIVE)) inherits RECORDS; 

Le puits travail déclenchement et la fonction INSERT: nouveaux records actifs se mettre dans une table, et de nouveaux enregistrements inactifs en autre. Je voudrais UPDATE s dans le champ ACTIVE pour "déplacer" un enregistrement d'une table descendante à l'autre, mais je rencontre une erreur qui suggère que cela peut ne pas être possible.

spécification Trigger et message d'erreur:

pg=> CREATE OR REPLACE FUNCTION record_update() 
    RETURNS TRIGGER AS $$ 
    BEGIN 
     IF (NEW.active = OLD.active) THEN 
     RETURN NEW; 
     ELSIF (NEW.active) THEN 
     INSERT INTO active_records VALUES (NEW.*); 
     DELETE FROM inactive_records WHERE record = NEW.record; 
     ELSE 
     INSERT INTO inactive_records VALUES (NEW.*); 
     DELETE FROM active_records WHERE record = NEW.record; 
     END IF; 
     RETURN NULL; 
    END; 
    $$ 
    LANGUAGE plpgsql; 

pg=> CREATE TRIGGER record_update_trigger 
     BEFORE UPDATE ON records 
     FOR EACH ROW EXECUTE PROCEDURE record_update(); 

pg=> select * from RECORDS; 
record | active 
--------+-------- 
foo | t   -- 'foo' record actually in table ACTIVE_RECORDS 
bar | f   -- 'bar' record actually in table INACTIVE_RECORDS 
(2 rows) 

pg=> update RECORDS set ACTIVE = false where RECORD = 'foo'; 
ERROR: new row for relation "active_records" violates check constraint "active_records_active_check" 

Jouer avec la procédure de déclenchement (retour NULL et ainsi de suite) me donne à penser que la contrainte est vérifiée, et l'erreur soulevée, avant mon déclenchement est invoqué, ce qui signifie que mon approche actuelle ne fonctionnera pas. Cela peut-il fonctionner?

MISE À JOUR/RÉPONSE

Voici la procédure de déclenchement UPDATE je fini par utiliser la même procédure affectée à chacune des partitions. Le crédit est entièrement à Bell, dont la réponse m'a donné l'idée fondamentale de déclencher sur les partitions:

CREATE OR REPLACE FUNCTION record_update() 
RETURNS TRIGGER AS $$ 
BEGIN 
    IF ((TG_TABLE_NAME = 'active_records' AND NOT NEW.active) 
     OR 
     (TG_TABLE_NAME = 'inactive_records' AND NEW.active)) THEN 
    DELETE FROM records WHERE record = NEW.record; 
    INSERT INTO records VALUES (NEW.*); 
    RETURN NULL; 
    END IF; 

    RETURN NEW; 
END; 
$$ 
LANGUAGE plpgsql; 
+0

Votre "exemple" est incomplète: définition manquante pour "partitioned_records"; vous définissez un déclencheur pour "partitioned_records" mais sélectionnez et mettez à jour "RECORDS". –

+0

@Milen, merci - erreurs cut'n'paste. Va remédier. – pilcrow

+0

Quel est le point d'utiliser une partition dans ce contexte lorsque vous pourriez utiliser un index partiel? – kalu

Répondre

17

Il peut être fait pour travailler, le déclencheur qui fait le mouvement a juste besoin de définir pour chaque partition, pas table entière. Donc, commencer comme vous l'avez fait pour les définitions de table et le déclencheur INSERT

CREATE TABLE records (
record varchar(64) NOT NULL, 
active boolean default TRUE 
); 

CREATE TABLE active_records (CHECK (active)) INHERITS (records); 
CREATE TABLE inactive_records (CHECK (NOT active)) INHERITS (records); 

CREATE OR REPLACE FUNCTION record_insert() 
RETURNS TRIGGER AS $$ 
BEGIN 
    IF (TRUE = NEW.active) THEN 
    INSERT INTO active_records VALUES (NEW.*); 
    ELSE 
    INSERT INTO inactive_records VALUES (NEW.*); 
    END IF; 
    RETURN NULL; 
END; 
$$ 
LANGUAGE plpgsql; 

CREATE TRIGGER record_insert_trigger 
BEFORE INSERT ON records 
FOR EACH ROW EXECUTE PROCEDURE record_insert(); 

... nous allons avoir des données de test ...

INSERT INTO records VALUES ('FirstLittlePiggy', TRUE); 
INSERT INTO records VALUES ('SecondLittlePiggy', FALSE); 
INSERT INTO records VALUES ('ThirdLittlePiggy', TRUE); 
INSERT INTO records VALUES ('FourthLittlePiggy', FALSE); 
INSERT INTO records VALUES ('FifthLittlePiggy', TRUE); 

Maintenant, les déclencheurs sur les partitions. La vérification if NEW.active = OLD.active est implicite dans la vérification de la valeur active car nous savons ce qui est autorisé à être dans la table en premier lieu.

CREATE OR REPLACE FUNCTION active_partition_constraint() 
    RETURNS TRIGGER AS $$ 
    BEGIN 
     IF NOT (NEW.active) THEN 
     INSERT INTO inactive_records VALUES (NEW.*); 
     DELETE FROM active_records WHERE record = NEW.record; 
     RETURN NULL; 
     ELSE 
     RETURN NEW; 
     END IF; 
    END; 
    $$ 
    LANGUAGE plpgsql; 

CREATE TRIGGER active_constraint_trigger 
    BEFORE UPDATE ON active_records 
    FOR EACH ROW EXECUTE PROCEDURE active_partition_constraint(); 

CREATE OR REPLACE FUNCTION inactive_partition_constraint() 
    RETURNS TRIGGER AS $$ 
    BEGIN 
     IF (NEW.active) THEN 
     INSERT INTO active_records VALUES (NEW.*); 
     DELETE FROM inactive_records WHERE record = NEW.record; 
     RETURN NULL; 
     ELSE 
     RETURN NEW; 
     END IF; 
    END; 
    $$ 
    LANGUAGE plpgsql; 

CREATE TRIGGER inactive_constraint_trigger 
    BEFORE UPDATE ON inactive_records 
    FOR EACH ROW EXECUTE PROCEDURE inactive_partition_constraint(); 

... et tester les résultats ...

scratch=> SELECT * FROM active_records; 
     record  | active 
------------------+-------- 
FirstLittlePiggy | t 
ThirdLittlePiggy | t 
FifthLittlePiggy | t 
(3 rows) 

scratch=> UPDATE records SET active = FALSE WHERE record = 'ThirdLittlePiggy'; 
UPDATE 0 
scratch=> SELECT * FROM active_records; 
     record  | active 
------------------+-------- 
FirstLittlePiggy | t 
FifthLittlePiggy | t 
(2 rows) 

scratch=> SELECT * FROM inactive_records; 
     record  | active 
-------------------+-------- 
SecondLittlePiggy | f 
FourthLittlePiggy | f 
ThirdLittlePiggy | f 
(3 rows) 
+0

@Bell, belle réponse (et joli chapeau). Je pense que vous l'avez, et, si oui, vous obtiendrez la coche verte une fois que je vérifie! – pilcrow

+0

Donc, en bref, j'ai besoin d'appliquer la logique de partition à nouveau dans un déclencheur pour chaque insertion sur chaque partition? –

+0

@David Nathan - Non. La logique des opérations INSERT (ou COPY) est fournie par Postgres.La question et la solution concernent la gestion de UPDATE lorsque l'enregistrement mis à jour se trouve dans une partition différente de l'original. – Bell

Questions connexes