popovnv
5/2/2017 - 9:44 PM

Postgres Partitioning with RETURNING on insert

Postgres Partitioning with RETURNING on insert

-- A method to have RETURNING work if you are partitioning data using trigger. 
-- The method to this madness is:
--
-- 1) Use the normal trigger mechanism to insert the data into the child tables, but
--    Instead of the trigger function returning NULL so that the row does not get⋅
--    inserted into the master table, it returns the row inserted into the child
--    table
--
-- 2) Postgres will insert the new row from the trigger into the master table
--
-- 3) Have an 'after insert' trigger on the master table that deletes from the⋅
--    master table with RETURNING.
--
-- This allows for the following type of statement to insert into the master table:
--⋅

INSERT INTO TABLE measurement( city_id, logdate, peaktemp, unitsales )⋅
           VALUES ( 42, 'today'::date, 12, 400 ) RETURNING *;

-- And the row will be partitioned into the appropriate child table

--- Master measurement table
CREATE TABLE measurement (
    id              int not null,
    city_id         int not null,
    logdate         date not null,
    peaktemp        int,
    unitsales       int
);

-- Child partition tables
CREATE SEQUENCE measurement_y2007m02_seq start with 1 increment by 1 minvalue 1 cache 1;
CREATE TABLE measurement_y2007m02 (
    id int nextval('measurement_y2007m02_seq'::regclass),
    CHECK ( logdate >= DATE '2007-02-01' AND logdate < DATE '2007-03-01' )
) INHERITS (measurement);
CREATE SEQUENCE measurement_y2007m03_seq start with 1 increment by 1 minvalue 1 cache 1;
CREATE TABLE measurement_y2007m03 (
    id int nextval('measurement_y2007m03_seq'::regclass),
    CHECK ( logdate >= DATE '2007-03-01' AND logdate < DATE '2007-04-01'
    )   
) INHERITS (measurement);
...
CREATE SEQUENCE measurement_y2009m01_seq start with 1 increment by 1 minvalue 1 cache 1;
CREATE TABLE measurement_y2009m01 (
    id int nextval('measurement_y2009m01_seq'::regclass),
    CHECK ( logdate >= DATE '2009-01-01' AND logdate < DATE '2009-02-01' )
) INHERITS (measurement);
-- Trigger function to split out between the various child partition tables
CREATE OR REPLACE FUNCTION measurement_insert_trigger()
RETURNS TRIGGER AS $$
DECLARE
    r measurement%rowtype;
BEGIN
    IF ( NEW.logdate >= DATE '2007-02-01' AND NEW.logdate < DATE '2007-03-01' ) THEN
        INSERT INTO measurement_y2006m02 VALUES (NEW.*) RETURNING * INTO r;
    ELSIF ( NEW.logdate >= DATE '2007-03-01' AND NEW.logdate < DATE '2007-04-01' ) THEN
        INSERT INTO measurement_y2007m03 VALUES (NEW.*) RETURNING * INTO r;
    ... 
    ELSIF ( NEW.logdate >= DATE '2009-01-01' AND NEW.logdate < DATE '2009-02-01' ) THEN
        INSERT INTO measurement_y2009m01 VALUES (NEW.*) RETURNING * INTO r;
    ELSE
        RAISE EXCEPTION 'Date out of range.  Fix the measurement_insert_trigger() function!';
    END IF;
    RETURN r;
END;
$$
LANGUAGE plpgsql;

-- Trigger to invoke the insert trigger
CREATE TRIGGER insert_measurement_trigger
    BEFORE INSERT ON measurement
    FOR EACH ROW EXECUTE PROCEDURE measurement_insert_trigger();

-- Trigger function to delete from the master table after the insert
CREATE OR REPLACE FUNCTION measurement_delete_master() RETURNS trigger
    AS $$
DECLARE
    r measurement%rowtype;
BEGIN
    DELETE FROM ONLY measurement where id = new.id returning * into r;
    RETURN r;
end;
$$
LANGUAGE plpgsql;

-- Create the after insert trigger
create trigger after_insert_measurement_trigger
    after insert on measurement
    for each row
        execute procedure measurement_delete_master();