Table inheritance and partitioning with PostgreSQL


2014-07-07 · 5 min read

PostgreSQL supports table inheritance and table partitioning.

Inheritance

Table inheritance allows to extract a common set of columns into a parent, master table with children defining additional fields.

create table articles (id serial, title varchar, content text);
create table articles_w_tags (tags text[]) inherits (articles);
create table articles_wo_tags () inherits (articles);

Let's insert some data

insert into articles_wo_tags (title, content)
    values ('Title 1', 'Content 1'),
           ('Title 2', 'Content 2');
insert into articles_w_tags (title, content, tags)
    values ('Title 3', 'Content 3', '{"tag_1", "tag_2"}'::text[]),
           ('Title 4', 'Content 4', '{"tag_2", "tag_3"}'::text[]);

Let's perform a select query on each of these tables.

select * from articles_wo_tags;
 id |  title  |  content
----+---------+-----------
  1 | Title 1 | Content 1
  2 | Title 2 | Content 2
select * from articles_w_tags;
 id |  title  |  content  |     tags
----+---------+-----------+---------------
  3 | Title 3 | Content 3 | {tag_1,tag_2}
  4 | Title 4 | Content 4 | {tag_2,tag_3}

When querying the master table, the query references all rows of that master table plus all of its children tables; values from the common set of columns are displayed. The only keyword can be used to indicate that the query should apply only to a particular table and not any tables below it in the inheritance hierarchy.

select * from articles;
 id |  title  |  content
----+---------+-----------
  3 | Title 3 | Content 3
  4 | Title 4 | Content 4
  1 | Title 1 | Content 1
  2 | Title 2 | Content 2

Changes performed on the master table are propagated to the children.

update articles set content = content || ' Changed';
select * from articles_w_tags;
 id |  title  |      content      |     tags
----+---------+-------------------+---------------
  3 | Title 3 | Content 3 Changed | {tag_1,tag_2}
  4 | Title 4 | Content 4 Changed | {tag_2,tag_3}

Partitioning

Table partitioning means splitting a table into smaller pieces and provides various performance benefits for tables that hold large amounts of data, i.e. the size of a table is about to exceed the physical memory of the database server.

PostgreSQL allows table partitioning via table inheritance. Each partition must be created as a child table of a single parent table (which remains empty and exists only to represent the whole data set).

PostgreSQL implements range and list partitioning methods. The former is done with a range defined by a column or set of columns with no overlap between the ranges. The latter is done by explicitly listing which key values appear in each partition.

Let's start by creating a parent table called logs.

create table logs (created_at timestamp without time zone default now(),
                   content text);

We will partition table by date into four quarters of the year.

create table logs_q1
    (check (created_at >= date '2014-01-01' and created_at <= date '2014-03-31'))
    inherits (logs);
create table logs_q2
    (check (created_at >= date '2014-04-01' and created_at <= date '2014-06-30'))
    inherits (logs);
create table logs_q3
    (check (created_at >= date '2014-07-01' and created_at <= date '2014-09-30'))
    inherits (logs);
create table logs_q4
    (check (created_at >= date '2014-10-01' and created_at <= date '2014-12-31'))
    inherits (logs);

Next step is to create indices on the key column of each child table.

create index logs_q1_created_at on logs_q1 using btree (created_at);
create index logs_q2_created_at on logs_q2 using btree (created_at);
create index logs_q3_created_at on logs_q3 using btree (created_at);
create index logs_q4_created_at on logs_q4 using btree (created_at);

Next, let's create a trigger function to dispatch the data among child tables.

create or replace function on_logs_insert() returns trigger as $$
begin
    if ( new.created_at >= date '2014-01-01' and new.created_at <= date '2014-03-31') then
        insert into logs_q1 values (new.*);
    elsif ( new.created_at >= date '2014-04-01' and new.created_at <= date '2014-06-30') then
        insert into logs_q2 values (new.*);
    elsif ( new.created_at >= date '2014-07-01' and new.created_at <= date '2014-09-30') then
        insert into logs_q3 values (new.*);
    elsif ( new.created_at >= date '2014-10-01' and new.created_at <= date '2014-12-31') then
        insert into logs_q4 values (new.*);
    else
        raise exception 'created_at date out of range';
    end if;

    return null;
end;
$$ language plpgsql;

Let's attach the trigger function defined above to logs table.

create trigger logs_insert
    before insert on logs
    for each row execute procedure on_logs_insert();

Finally, let's insert some data into logs table to see the partitioning in work.

insert into logs (created_at, content)
    values (date '2014-02-03', 'Content 1'),
           (date '2014-03-11', 'Content 2'),
           (date '2014-04-13', 'Content 3'),
           (date '2014-07-08', 'Content 4'),
           (date '2014-10-23', 'Content 5');
select * from logs_q1;
     created_at      |  content
---------------------+-----------
 2014-02-03 00:00:00 | Content 1
 2014-03-11 00:00:00 | Content 2
select * from logs_q2;
     created_at      |  content
---------------------+-----------
 2014-04-13 00:00:00 | Content 3
select * from logs_q3;
     created_at      |  content
---------------------+-----------
 2014-07-08 00:00:00 | Content 4
select * from logs_q4;
     created_at      |  content
---------------------+-----------
 2014-10-23 00:00:00 | Content 5

A short article that dive a bit more in partitioning in PostgreSQL