Dynamic table partitioning by string identifier in Postgres

database-partitioning, partitioning, postgresql

Solution

I like the idea of dynamic partitioning. I do not know though how it will affect the performance as I have never used it.

Change the type of `id` to `int default 0` and manually create the sequence to avoid multiple `nextval()` calls on a single insert:

create table device_data (
    id int primary key default 0,
    device_id text not null,
    device_data text not null, -- changed for tests
    received_at timestamp without time zone default now()
);
create sequence device_data_seq owned by device_data.id;

Use dynamic sql in the trigger function:

create or replace function before_insert_on_device_data()
returns trigger language plpgsql as $$
begin
    execute format(
        $f$
            create table if not exists %I (
            check (device_id = %L)
            ) inherits (device_data)
        $f$, 
        concat('device_data_', new.device_id), 
        new.device_id);
    execute format(
        $f$
            insert into %I
            values (nextval('device_data_seq'), %L, %L, default)
        $f$, 
        concat('device_data_', new.device_id), 
        new.device_id, 
        new.device_data);
    return null;
end $$;

create trigger before_insert_on_device_data
    before insert on device_data
    for each row execute procedure before_insert_on_device_data();

Test:

insert into device_data (device_id, device_data) values
    ('first', 'data 1'),
    ('second', 'data 1'),
    ('first', 'data 2'),
    ('second', 'data 2');

select * from device_data_first;

 id | device_id | device_data |        received_at         
----+-----------+-------------+----------------------------
  1 | first     | data 1      | 2016-10-18 19:50:40.179955
  3 | first     | data 2      | 2016-10-18 19:50:40.179955
(2 rows)

select * from device_data_second;

 id | device_id | device_data |        received_at         
----+-----------+-------------+----------------------------
  2 | second    | data 1      | 2016-10-18 19:50:40.179955
  4 | second    | data 2      | 2016-10-18 19:50:40.179955
(2 rows)

Problem

I would like to partition a table in Postgres by previously unknown value. In my scenario that value would be device_id which is a string. This is current situation: Table 'device_data' - stores sensor data which is sent from devices, defined by DDL: ``` CREATE TABLE warehouse.device_data ( id INTEGER PRIMARY KEY NOT NULL DEFAULT nextval('device_data_id_seq'::regclass), device_id TEXT NOT NULL, device_data BYTEA NOT NULL, -- contains additional fields which are omitted for brevity received_at TIMESTAMP WITHOUT TIME ZONE DEFAULT now() ); ``` Table currently holds millions of records and queries are taking huge amount of time. Most of queries contain `WHERE device_id='something'` clause. Solution I have in mind is to create table partitions for each `device_id`. Is it possible in Postgres to create table partitions for each `device_id`? I went through Postgres documentation and couple of examples I found but all of them use fixed boundaries to create partitions. My solution would require: - create new table partition on the fly when new `device_id` is first encountered - store to an existing partition if the `device_id` is already known and partition for that `device_id` already exist I would like this to be done using table partitions as it would allow querying across multiple `device_id`s.

Original source