Skip to content

Partitioning by composite key

Dmitry Ivanov edited this page Jun 6, 2017 · 3 revisions

Release 1.4 also supports composite keys (which are expressions too).

Prerequisites

/* create a table we're going to partition */ create table test (logdate date not null, comment text); /* create sequence for automatic partition naming */ select create_naming_sequence('test'); /* we have to manually create a composite partitioning key */ create type test_key as (year float8, month float8); /* register a RANGE-partitioned table 'test' */ select add_to_pathman_config('test', '( extract(year from logdate),  extract(month from logdate) )::test_key', NULL); /* add one partition [(year, month), (year + 10, month)) */ select add_range_partition('test', (extract(year from current_date), 1)::test_key, (extract(year from current_date + '10 years'::interval), 1)::test_key);

Partitions

First of all, let's check the partition we've just created:

select parent, partition, parttype, range_min, range_max from pathman_partition_list ; parent | partition | parttype | range_min | range_max --------+-----------+----------+-----------+----------- test | test_1 | 2 | (2017,1) | (2027,1) (1 row)

We couldn't use neither create_range_partitions() nor create_hash_partitions(), since the first one requires that MAX & MIN aggregates and +/- operators be defined for the expression's type, while the second one cannot work without a hash-function (which we didn't assign).

This means that we have to create new partitions manually. Let's add a few more:

/* [(10, 1), (20, 1)), [(20, 1), (30,1)) ... */ select add_range_partition('test', (extract(year from current_date + format('%s years', i)::interval), 1)::test_key, (extract(year from current_date + format('%s years', i + 10)::interval), 1)::test_key) from generate_series(10, 200, 10) as g(i);

That's way better!

select parent, partition, range_min, range_max from pathman_partition_list where parent = 'test'::regclass; parent | partition | range_min | range_max --------+-----------+-----------+----------- test | test_1 | (2017,1) | (2027,1) test | test_2 | (2027,1) | (2037,1) test | test_3 | (2037,1) | (2047,1) test | test_4 | (2047,1) | (2057,1) test | test_5 | (2057,1) | (2067,1) test | test_6 | (2067,1) | (2077,1) test | test_7 | (2077,1) | (2087,1) test | test_8 | (2087,1) | (2097,1) test | test_9 | (2097,1) | (2107,1) test | test_10 | (2107,1) | (2117,1) test | test_11 | (2117,1) | (2127,1) test | test_12 | (2127,1) | (2137,1) test | test_13 | (2137,1) | (2147,1) test | test_14 | (2147,1) | (2157,1) test | test_15 | (2157,1) | (2167,1) test | test_16 | (2167,1) | (2177,1) test | test_17 | (2177,1) | (2187,1) test | test_18 | (2187,1) | (2197,1) test | test_19 | (2197,1) | (2207,1) test | test_20 | (2207,1) | (2217,1) test | test_21 | (2217,1) | (2227,1) (21 rows)

Note that overlap checks will still work:

/* this command will fail */ select add_range_partition('test', (extract(year from current_date + '30 years'::interval), 1)::test_key, (extract(year from current_date + '50 years'::interval), 1)::test_key); ERROR: specified range [(2047,1), (2067,1)) overlaps with existing partitions

Sample queries

The expression itself looks quite ugly:

ROW(date_part('year'::text, logdate), date_part('month'::text, logdate))::test_key

Unfortunately, we can't do much about it. Let's select all dates prior to (2040, 1):

explain select * from test where ROW(date_part('year'::text, logdate), date_part('month'::text, logdate))::test_key < (2040, 1)::test_key; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Append (cost=0.00..83.98 rows=2963 width=36) -> Seq Scan on test_1 (cost=0.00..22.70 rows=1270 width=36) -> Seq Scan on test_2 (cost=0.00..22.70 rows=1270 width=36) -> Seq Scan on test_3 (cost=0.00..38.58 rows=423 width=36) Filter: (ROW(date_part('year'::text, (logdate)::timestamp without time zone), date_part('month'::text (logdate)::timestamp without time zone))::test_key < ROW('2040'::double precision, '1'::double precision)::test_key) (5 rows)

Limitations

The harshest limitations:

  • The following functions won't work with composite keys by default:
    • create_range_partitions()
    • create_hash_partitions()
    • append_range_partition()
    • prepend_range_partition()
    • generate_range_bounds()
  • Automatic partition creation on INSERT is disabled;
  • All other limitations that are inherent to expressions;

Clone this wiki locally