PostgreSQL supports partitioning via table inheritance. So the partitioning is made in such a way that every child table inherits single parent table. Parent table is empty and it exists just to describe the whole data set.

PostgreSQL partitioning can be implemented in range partitioning or list partitioning.

  • Range partitioning can be done for example by ID ranges (like 0-100 000, 100 001-200 000, 200 001-300 000…) or Date ranges (like 2009-11-01 – 2009-11-30, 2009-12-01 – 2009-12-31…).
  • List partitioning can be done for example by list of cities (like New York, Los Angeles, Chicago, Houston, Philadelphia…) or list of categories (like Programming, Home, Food…).

It’s important to ensure that there is no overlap between the key values permitted in different partitions.

Five easy steps how to make a partitioned table in Postgresql

  1. Create master table
  2. Create child tables without overlapping table constraints
  3. Create indexes
  4. Create trigger function to inserting data to child tables
  5. Enable constraint exclusion

Simple example of PostgreSQL table partitioning

The initial situation is such that we should make the database table, which is able to archive your data to advertisers by daily basis, over many years in advertising impressions. We also know that the information is needed later, the formation of various reports. It is known also that the information is sought on a daily, a weekly, a monthly, two-month periods, a half-year cycles, and so on.

In such a situation the best option is to partition a table. This example uses a two-month sections, because the data must be able to save a reasonable long time (in this example, examines the four-year period).

1. Create simple master table

This table can contain simple data in a ad impressions by advertiser by daily basis. (Very simple table, because this is a partitioning example):

CREATE TABLE impressions_by_day (
	advertiser_id INTEGER NOT NULL,
	day DATE NOT NULL DEFAULT CURRENT_DATE,
	impressions INTEGER NOT NULL,
        PRIMARY KEY (advertiser_id, day)
);

2. Create child tables

These inherits the master table and adds checks for dates, because we want ensure that we have only right data on each partition. Partitions starts from ‘2009-01-01’ and ends to ‘2012-12-31’. And each partitions contains two months data:

CREATE TABLE impressions_by_day_y2009m1ms2 (
	PRIMARY KEY (advertiser_id, day), 
	CHECK ( day >= DATE '2009-01-01' AND day < DATE '2009-03-01' )
) INHERITS (impressions_by_day);

CREATE TABLE impressions_by_day_y2009m3ms2 (
	PRIMARY KEY (advertiser_id, day), 
	CHECK ( day >= DATE '2009-03-01' AND day < DATE '2009-05-01' )
) INHERITS (impressions_by_day);

CREATE TABLE impressions_by_day_y2009m5ms2 (
	PRIMARY KEY (advertiser_id, day), 
	CHECK ( day >= DATE '2009-05-01' AND day < DATE '2009-07-01' )
) INHERITS (impressions_by_day);

CREATE TABLE impressions_by_day_y2009m7ms2 (
	PRIMARY KEY (advertiser_id, day), 
	CHECK ( day >= DATE '2009-07-01' AND day < DATE '2009-09-01' )
) INHERITS (impressions_by_day);

CREATE TABLE impressions_by_day_y2009m9ms2 (
	PRIMARY KEY (advertiser_id, day), 
	CHECK ( day >= DATE '2009-09-01' AND day < DATE '2009-11-01' )
) INHERITS (impressions_by_day);

CREATE TABLE impressions_by_day_y2009m11ms2 (
	PRIMARY KEY (advertiser_id, day), 
	CHECK ( day >= DATE '2009-11-01' AND day < DATE '2010-01-01' )
) INHERITS (impressions_by_day);

CREATE TABLE impressions_by_day_y2010m1ms2 (
	PRIMARY KEY (advertiser_id, day), 
	CHECK ( day >= DATE '2010-01-01' AND day < DATE '2010-03-01' )
) INHERITS (impressions_by_day);

CREATE TABLE impressions_by_day_y2010m3ms2 (
	PRIMARY KEY (advertiser_id, day), 
	CHECK ( day >= DATE '2010-03-01' AND day < DATE '2010-05-01' )
) INHERITS (impressions_by_day);

CREATE TABLE impressions_by_day_y2010m5ms2 (
	PRIMARY KEY (advertiser_id, day), 
	CHECK ( day >= DATE '2010-05-01' AND day < DATE '2010-07-01' )
) INHERITS (impressions_by_day);

CREATE TABLE impressions_by_day_y2010m7ms2 (
	PRIMARY KEY (advertiser_id, day), 
	CHECK ( day >= DATE '2010-07-01' AND day < DATE '2010-09-01' )
) INHERITS (impressions_by_day);

CREATE TABLE impressions_by_day_y2010m9ms2 (
	PRIMARY KEY (advertiser_id, day), 
	CHECK ( day >= DATE '2010-09-01' AND day < DATE '2010-11-01' )
) INHERITS (impressions_by_day);

CREATE TABLE impressions_by_day_y2010m11ms2 (
	PRIMARY KEY (advertiser_id, day), 
	CHECK ( day >= DATE '2010-11-01' AND day < DATE '2011-01-01' )
) INHERITS (impressions_by_day);

CREATE TABLE impressions_by_day_y2011m1ms2 (
	PRIMARY KEY (advertiser_id, day), 
	CHECK ( day >= DATE '2011-01-01' AND day < DATE '2011-03-01' )
) INHERITS (impressions_by_day);

CREATE TABLE impressions_by_day_y2011m3ms2 (
	PRIMARY KEY (advertiser_id, day), 
	CHECK ( day >= DATE '2011-03-01' AND day < DATE '2011-05-01' )
) INHERITS (impressions_by_day);

CREATE TABLE impressions_by_day_y2011m5ms2 (
	PRIMARY KEY (advertiser_id, day), 
	CHECK ( day >= DATE '2011-05-01' AND day < DATE '2011-07-01' )
) INHERITS (impressions_by_day);

CREATE TABLE impressions_by_day_y2011m7ms2 (
	PRIMARY KEY (advertiser_id, day), 
	CHECK ( day >= DATE '2011-07-01' AND day < DATE '2011-09-01' )
) INHERITS (impressions_by_day);

CREATE TABLE impressions_by_day_y2011m9ms2 (
	PRIMARY KEY (advertiser_id, day), 
	CHECK ( day >= DATE '2011-09-01' AND day < DATE '2011-11-01' )
) INHERITS (impressions_by_day);

CREATE TABLE impressions_by_day_y2011m11ms2 (
	PRIMARY KEY (advertiser_id, day), 
	CHECK ( day >= DATE '2011-11-01' AND day < DATE '2012-01-01' )
) INHERITS (impressions_by_day);

CREATE TABLE impressions_by_day_y2012m1ms2 (
	PRIMARY KEY (advertiser_id, day), 
	CHECK ( day >= DATE '2012-01-01' AND day < DATE '2012-03-01' )
) INHERITS (impressions_by_day);

CREATE TABLE impressions_by_day_y2012m3ms2 (
	PRIMARY KEY (advertiser_id, day), 
	CHECK ( day >= DATE '2012-03-01' AND day < DATE '2012-05-01' )
) INHERITS (impressions_by_day);

CREATE TABLE impressions_by_day_y2012m5ms2 (
	PRIMARY KEY (advertiser_id, day), 
	CHECK ( day >= DATE '2012-05-01' AND day < DATE '2012-07-01' )
) INHERITS (impressions_by_day);

CREATE TABLE impressions_by_day_y2012m7ms2 (
	PRIMARY KEY (advertiser_id, day), 
	CHECK ( day >= DATE '2012-07-01' AND day < DATE '2012-09-01' )
) INHERITS (impressions_by_day);

CREATE TABLE impressions_by_day_y2012m9ms2 (
	PRIMARY KEY (advertiser_id, day), 
	CHECK ( day >= DATE '2012-09-01' AND day < DATE '2012-11-01' )
) INHERITS (impressions_by_day);

CREATE TABLE impressions_by_day_y2012m11ms2 (
	PRIMARY KEY (advertiser_id, day), 
	CHECK ( day >= DATE '2012-11-01' AND day < DATE '2013-01-01' )
) INHERITS (impressions_by_day);

3. Create indexes to child tables

This speed up day field usage, because almost all queries (INSERTs, SELECTs and UPDATEs) on the date field.

CREATE INDEX impressions_by_day_y2009m1ms2_day ON impressions_by_day_y2009m1ms2 (day);
CREATE INDEX impressions_by_day_y2009m3ms2_day ON impressions_by_day_y2009m3ms2 (day);
CREATE INDEX impressions_by_day_y2009m5ms2_day ON impressions_by_day_y2009m5ms2 (day);
CREATE INDEX impressions_by_day_y2009m7ms2_day ON impressions_by_day_y2009m7ms2 (day);
CREATE INDEX impressions_by_day_y2009m9ms2_day ON impressions_by_day_y2009m9ms2 (day);
CREATE INDEX impressions_by_day_y2009m11ms2_day ON impressions_by_day_y2009m11ms2 (day);
CREATE INDEX impressions_by_day_y2010m1ms2_day ON impressions_by_day_y2010m1ms2 (day);
CREATE INDEX impressions_by_day_y2010m3ms2_day ON impressions_by_day_y2010m3ms2 (day);
CREATE INDEX impressions_by_day_y2010m5ms2_day ON impressions_by_day_y2010m5ms2 (day);
CREATE INDEX impressions_by_day_y2010m7ms2_day ON impressions_by_day_y2010m7ms2 (day);
CREATE INDEX impressions_by_day_y2010m9ms2_day ON impressions_by_day_y2010m9ms2 (day);
CREATE INDEX impressions_by_day_y2010m11ms2_day ON impressions_by_day_y2010m11ms2 (day);
CREATE INDEX impressions_by_day_y2011m1ms2_day ON impressions_by_day_y2011m1ms2 (day);
CREATE INDEX impressions_by_day_y2011m3ms2_day ON impressions_by_day_y2011m3ms2 (day);
CREATE INDEX impressions_by_day_y2011m5ms2_day ON impressions_by_day_y2011m5ms2 (day);
CREATE INDEX impressions_by_day_y2011m7ms2_day ON impressions_by_day_y2011m7ms2 (day);
CREATE INDEX impressions_by_day_y2011m9ms2_day ON impressions_by_day_y2011m9ms2 (day);
CREATE INDEX impressions_by_day_y2011m11ms2_day ON impressions_by_day_y2011m11ms2 (day);
CREATE INDEX impressions_by_day_y2012m1ms2_day ON impressions_by_day_y2012m1ms2 (day);
CREATE INDEX impressions_by_day_y2012m3ms2_day ON impressions_by_day_y2012m3ms2 (day);
CREATE INDEX impressions_by_day_y2012m5ms2_day ON impressions_by_day_y2012m5ms2 (day);
CREATE INDEX impressions_by_day_y2012m7ms2_day ON impressions_by_day_y2012m7ms2 (day);
CREATE INDEX impressions_by_day_y2012m9ms2_day ON impressions_by_day_y2012m9ms2 (day);
CREATE INDEX impressions_by_day_y2012m11ms2_day ON impressions_by_day_y2012m11ms2 (day);

4. Add trigger function

Then we need insert trigger and of course trigger function to master table. Conditions must be exactly the same as what the child tables checks.

4.1 Trigger function

CREATE OR REPLACE FUNCTION impressions_by_day_insert_trigger()
RETURNS TRIGGER AS $$
BEGIN
	IF ( NEW.day >= DATE '2009-01-01' AND NEW.day < DATE '2009-03-01' ) THEN 
		INSERT INTO impressions_by_day_y2009m1ms2 VALUES (NEW.*); 
	ELSIF ( NEW.day >= DATE '2009-03-01' AND NEW.day < DATE '2009-05-01' ) THEN 
		INSERT INTO impressions_by_day_y2009m3ms2 VALUES (NEW.*);
	ELSIF ( NEW.day >= DATE '2009-05-01' AND NEW.day < DATE '2009-07-01' ) THEN 
		INSERT INTO impressions_by_day_y2009m5ms2 VALUES (NEW.*);
	ELSIF ( NEW.day >= DATE '2009-07-01' AND NEW.day < DATE '2009-09-01' ) THEN 
		INSERT INTO impressions_by_day_y2009m7ms2 VALUES (NEW.*);
	ELSIF ( NEW.day >= DATE '2009-09-01' AND NEW.day < DATE '2009-11-01' ) THEN 
		INSERT INTO impressions_by_day_y2009m9ms2 VALUES (NEW.*);
	ELSIF ( NEW.day >= DATE '2009-11-01' AND NEW.day < DATE '2010-01-01' ) THEN 
		INSERT INTO impressions_by_day_y2009m11ms2 VALUES (NEW.*);
	ELSIF ( NEW.day >= DATE '2010-01-01' AND NEW.day < DATE '2010-03-01' ) THEN 
		INSERT INTO impressions_by_day_y2010m1ms2 VALUES (NEW.*);
	ELSIF ( NEW.day >= DATE '2010-03-01' AND NEW.day < DATE '2010-05-01' ) THEN 
		INSERT INTO impressions_by_day_y2010m3ms2 VALUES (NEW.*);
	ELSIF ( NEW.day >= DATE '2010-05-01' AND NEW.day < DATE '2010-07-01' ) THEN 
		INSERT INTO impressions_by_day_y2010m5ms2 VALUES (NEW.*);
	ELSIF ( NEW.day >= DATE '2010-07-01' AND NEW.day < DATE '2010-09-01' ) THEN 
		INSERT INTO impressions_by_day_y2010m7ms2 VALUES (NEW.*);
	ELSIF ( NEW.day >= DATE '2010-09-01' AND NEW.day < DATE '2010-11-01' ) THEN 
		INSERT INTO impressions_by_day_y2010m9ms2 VALUES (NEW.*);
	ELSIF ( NEW.day >= DATE '2010-11-01' AND NEW.day < DATE '2011-01-01' ) THEN 
		INSERT INTO impressions_by_day_y2010m11ms2 VALUES (NEW.*);
	ELSIF ( NEW.day >= DATE '2011-01-01' AND NEW.day < DATE '2011-03-01' ) THEN 
		INSERT INTO impressions_by_day_y2011m1ms2 VALUES (NEW.*);
	ELSIF ( NEW.day >= DATE '2011-03-01' AND NEW.day < DATE '2011-05-01' ) THEN 
		INSERT INTO impressions_by_day_y2011m3ms2 VALUES (NEW.*);
	ELSIF ( NEW.day >= DATE '2011-05-01' AND NEW.day < DATE '2011-07-01' ) THEN 
		INSERT INTO impressions_by_day_y2011m5ms2 VALUES (NEW.*);
	ELSIF ( NEW.day >= DATE '2011-07-01' AND NEW.day < DATE '2011-09-01' ) THEN 
		INSERT INTO impressions_by_day_y2011m7ms2 VALUES (NEW.*);
	ELSIF ( NEW.day >= DATE '2011-09-01' AND NEW.day < DATE '2011-11-01' ) THEN 
		INSERT INTO impressions_by_day_y2011m9ms2 VALUES (NEW.*);
	ELSIF ( NEW.day >= DATE '2011-11-01' AND NEW.day < DATE '2012-01-01' ) THEN 
		INSERT INTO impressions_by_day_y2011m11ms2 VALUES (NEW.*);
	ELSIF ( NEW.day >= DATE '2012-01-01' AND NEW.day < DATE '2012-03-01' ) THEN 
		INSERT INTO impressions_by_day_y2012m1ms2 VALUES (NEW.*);
	ELSIF ( NEW.day >= DATE '2012-03-01' AND NEW.day < DATE '2012-05-01' ) THEN 
		INSERT INTO impressions_by_day_y2012m3ms2 VALUES (NEW.*);
	ELSIF ( NEW.day >= DATE '2012-05-01' AND NEW.day < DATE '2012-07-01' ) THEN 
		INSERT INTO impressions_by_day_y2012m5ms2 VALUES (NEW.*);
	ELSIF ( NEW.day >= DATE '2012-07-01' AND NEW.day < DATE '2012-09-01' ) THEN 
		INSERT INTO impressions_by_day_y2012m7ms2 VALUES (NEW.*);
	ELSIF ( NEW.day >= DATE '2012-09-01' AND NEW.day < DATE '2012-11-01' ) THEN 
		INSERT INTO impressions_by_day_y2012m9ms2 VALUES (NEW.*);
	ELSIF ( NEW.day >= DATE '2012-11-01' AND NEW.day < DATE '2013-01-01' ) THEN 
		INSERT INTO impressions_by_day_y2012m11ms2 VALUES (NEW.*);
	ELSE
		RAISE EXCEPTION 'Date out of range.  Something wrong with the impressions_by_day_insert_trigger() function!';
	END IF;
	RETURN NULL;
END;
$$
LANGUAGE plpgsql;

4.2 Trigger

CREATE TRIGGER insert_impressions_by_day_trigger
	BEFORE INSERT ON impressions_by_day
	FOR EACH ROW EXECUTE PROCEDURE impressions_by_day_insert_trigger();

5. Enable Constraint Exclusion

Constraint exclusion is driven by CHECK constraints. If constraint exclusion is disabled then query is not using check constraints and every query scans thru whole all child tables. So constraint exclusion is very important when using partitioned tables.

5.1 Set constraint exclusion on

Add following row to postgresql.conf

constraint_exclusion = on

Or alternatively set constraint exclusion on using following command on psql or psqlrc

SET constraint_exclusion = on;

Finally, the master table is normally available and all UPDATEs, INSERTs, SELECTs and DELETEs goes to the right child tables by date.