Postgres partition by week

partitioning, postgresql, sql, week-number

Solution

Ok, so let's create ourselves a function to handle that!

CREATE OR REPLACE FUNCTION create_partition_measurement( DATE, DATE )
returns void AS $$
DECLARE
create_query text;
BEGIN
    FOR create_query IN SELECT

        'CREATE TABLE measurement_' || TO_CHAR( d, 'YYYY_WW' ) || ' (
        CHECK ( EXTRACT(YEAR FROM logdate) = EXTRACT(YEAR FROM TIMESTAMP ''' || d || ''') AND EXTRACT(WEEK FROM logdate) = EXTRACT(WEEK FROM TIMESTAMP ''' || d || ''') )
        ) INHERITS (measurement);'

        FROM generate_series( $1, $2, '1 week' ) AS d LOOP

        EXECUTE create_query;

    END LOOP;
END;
$$
language plpgsql;

With this you can now call something like

SELECT create_partition_measurement ('2015/02/08','2015/03/01'); 

and have your partitions created. First step to automation, done.

I tested all this in my own database using the following test table:

CREATE TABLE measurement (id INT NOT NULL PRIMARY KEY, id_user INT NOT NULL, logdate TIMESTAMP NOT NULL);

After creating the partitions using the function above, I was able to:

- Insert data into the correct partition;

- Get an error when trying to insert data from one week into another week's partition;

- Automatically create partition for several weeks, and;

- Get an error should I try to create a partition for a week where one already exists.

This should be enough =)

Now, about automating the creation process. I use a simple cron script to call this function for me every month and a couple of monitoring scripts to make sure everything is working as it should. The cron looks like this:

0 0 1 * * /var/lib/postgresql/create_partitions.sh

And the script will run the command using the current date and the current date + 1 month. It looks something like this:

startDate=`date "+%Y/%m/%d"`
endDate=`date -u -d "+1 month -$(date +%d) days" "+%Y/%m/%d"
psql -U "$dbUser" -w -c "SELECT create_partition_measurement('$startDate','$endDate');"

If you need to include indexes, PKs, FKs in the table, or help with the triggers to make all this work, just let me know.

Problem

I can imagine table partition by a date (in particular for logs) is something widely used, but I am not able to find a good answer to my problem. I want to create a table partition by week (the number of records is to big to make it monthly). The reason for it to be weekly is that I need the data for an algorithm that will be looking for dates as part of the process. My problem is I want it to create the partitions considering the week and using the "typical" approach I would have to be creating it manually. Something like this. ``` CREATE TABLE measurement_y2013w01 ( CHECK ( logdate >= DATE '2013-01-07' AND logdate < DATE '2013-01-14' ) ) INHERITS (measurement); CREATE TABLE measurement_y2006w02 ( CHECK ( logdate >= DATE '2013-01-14' AND logdate < DATE '2013-01-21' ) ) INHERITS (measurement); ... ``` But I want it to be made automatically. I don't want to be creating a partition for every week one by one. My rule for name giving would be yYYYYwWW for the naming of the partition or start datadYYYYMMDD. I thought in checking for partitions when inserting using something like this: ``` SELECT nmsp_parent.nspname AS parent_schema, parent.relname AS parent, nmsp_child.nspname AS child, child.relname AS child_schema FROM pg_inherits JOIN pg_class parent ON pg_inherits.inhparent = parent.oid JOIN pg_class child ON pg_inherits.inhrelid = child.oid JOIN pg_namespace nmsp_parent ON nmsp_parent.oid = parent.relnamespace JOIN pg_namespace nmsp_child ON nmsp_child.oid = child.relnamespace ``` and if the partition is not there create it before the insert, but this seams a lot inefficient, considering the number of records inserted. My other alternative is to have an external process running weekly creating this partition, but I was trying to avoid this. Is there a more efficient solution for this that I am missing, that is used, for example, for monthly checks?

Original source