From 5dc2c3f34207a3608575c5625b209369f96832c3 Mon Sep 17 00:00:00 2001 From: Nitin Date: Wed, 29 Dec 2021 12:56:41 +0530 Subject: [PATCH 2/2] partition-wise join support --- src/backend/partitioning/partbounds.c | 309 ++++--- src/include/partitioning/partbounds.h | 5 +- src/test/regress/expected/partition_join.out | 1257 ++++++++++++++++++++++++++ src/test/regress/sql/partition_join.sql | 257 ++++++ 4 files changed, 1683 insertions(+), 145 deletions(-) diff --git a/src/backend/partitioning/partbounds.c b/src/backend/partitioning/partbounds.c index 2ccee84..258df06 100644 --- a/src/backend/partitioning/partbounds.c +++ b/src/backend/partitioning/partbounds.c @@ -106,7 +106,8 @@ static PartitionBoundInfo create_list_bounds(PartitionBoundSpec **boundspecs, int nparts, PartitionKey key, int **mapping); static PartitionBoundInfo create_range_bounds(PartitionBoundSpec **boundspecs, int nparts, PartitionKey key, int **mapping); -static PartitionBoundInfo merge_list_bounds(FmgrInfo *partsupfunc, +static PartitionBoundInfo merge_list_bounds(int partnatts, + FmgrInfo *partsupfunc, Oid *collations, RelOptInfo *outer_rel, RelOptInfo *inner_rel, @@ -147,15 +148,14 @@ static int process_inner_partition(PartitionMap *outer_map, JoinType jointype, int *next_index, int *default_index); -static void merge_null_partitions(PartitionMap *outer_map, +static int merge_null_partitions(PartitionMap *outer_map, PartitionMap *inner_map, - bool outer_has_null, - bool inner_has_null, + bool consider_outer_null, + bool consider_inner_null, int outer_null, int inner_null, JoinType jointype, - int *next_index, - int *null_index); + int *next_index); static void merge_default_partitions(PartitionMap *outer_map, PartitionMap *inner_map, bool outer_has_default, @@ -371,7 +371,6 @@ create_hash_bounds(PartitionBoundSpec **boundspecs, int nparts, palloc0(sizeof(PartitionBoundInfoData)); boundinfo->strategy = key->strategy; /* No special hash partitions. */ - boundinfo->null_index = -1; boundinfo->isnulls = NULL; boundinfo->default_index = -1; @@ -444,6 +443,34 @@ create_hash_bounds(PartitionBoundSpec **boundspecs, int nparts, } /* + * partition_bound_accepts_nulls + * + * Returns TRUE if any of the partition bounds contains a NULL value, + * FALSE otherwise. + */ +bool +partition_bound_accepts_nulls(PartitionBoundInfo boundinfo, int partnatts) +{ + int i; + + if (!boundinfo->isnulls) + return false; + + for (i = 0; i < boundinfo->ndatums; i++) + { + int j; + + for (j = 0; j < partnatts; j++) + { + if (boundinfo->isnulls[i][j]) + return true; + } + } + + return false; +} + +/* * get_list_datum_count * Returns the total number of datums in all the partitions. */ @@ -474,7 +501,6 @@ create_list_bounds(PartitionBoundSpec **boundspecs, int nparts, int ndatums; int next_index = 0; int default_index = -1; - int null_index = -1; Datum *boundDatums; bool *boundIsNulls; @@ -482,7 +508,6 @@ create_list_bounds(PartitionBoundSpec **boundspecs, int nparts, palloc0(sizeof(PartitionBoundInfoData)); boundinfo->strategy = key->strategy; /* Will be set correctly below. */ - boundinfo->null_index = -1; boundinfo->default_index = -1; ndatums = get_list_datum_count(boundspecs, nparts); @@ -526,10 +551,7 @@ create_list_bounds(PartitionBoundSpec **boundspecs, int nparts, if (!val->constisnull) all_values[j].values[k] = val->constvalue; else - { - null_index = i; all_values[j].isnulls[k] = true; - } k++; } @@ -593,22 +615,6 @@ create_list_bounds(PartitionBoundSpec **boundspecs, int nparts, pfree(all_values); - /* - * Set the canonical value for null_index, if any. - * - * It is possible that the null-accepting partition has not been assigned - * an index yet, which could happen if such partition accepts only null - * and hence not handled in the above loop which only looked at non-null - * values. - */ - if (null_index != -1) - { - Assert(null_index >= 0); - if ((*mapping)[null_index] == -1) - (*mapping)[null_index] = next_index++; - boundinfo->null_index = (*mapping)[null_index]; - } - /* Set the canonical value for default_index, if any. */ if (default_index != -1) { @@ -704,8 +710,6 @@ create_range_bounds(PartitionBoundSpec **boundspecs, int nparts, boundinfo = (PartitionBoundInfoData *) palloc0(sizeof(PartitionBoundInfoData)); boundinfo->strategy = key->strategy; - /* There is no special null-accepting range partition. */ - boundinfo->null_index = -1; boundinfo->isnulls = NULL; /* Will be set correctly below. */ boundinfo->default_index = -1; @@ -1135,7 +1139,6 @@ partition_bounds_copy(PartitionBoundInfo src, dest->indexes = (int *) palloc(sizeof(int) * nindexes); memcpy(dest->indexes, src->indexes, sizeof(int) * nindexes); - dest->null_index = src->null_index; dest->default_index = src->default_index; return dest; @@ -1195,7 +1198,8 @@ partition_bounds_merge(int partnatts, return NULL; case PARTITION_STRATEGY_LIST: - return merge_list_bounds(partsupfunc, + return merge_list_bounds(partnatts, + partsupfunc, partcollation, outer_rel, inner_rel, @@ -1239,7 +1243,8 @@ partition_bounds_merge(int partnatts, * join can't handle. */ static PartitionBoundInfo -merge_list_bounds(FmgrInfo *partsupfunc, Oid *partcollation, +merge_list_bounds(int partnatts, + FmgrInfo *partsupfunc, Oid *partcollation, RelOptInfo *outer_rel, RelOptInfo *inner_rel, JoinType jointype, List **outer_parts, List **inner_parts) @@ -1251,8 +1256,6 @@ merge_list_bounds(FmgrInfo *partsupfunc, Oid *partcollation, bool inner_has_default = partition_bound_has_default(inner_bi); int outer_default = outer_bi->default_index; int inner_default = inner_bi->default_index; - bool outer_has_null = partition_bound_accepts_nulls(outer_bi); - bool inner_has_null = partition_bound_accepts_nulls(inner_bi); PartitionMap outer_map; PartitionMap inner_map; int outer_pos; @@ -1303,6 +1306,11 @@ merge_list_bounds(FmgrInfo *partsupfunc, Oid *partcollation, bool *outer_isnull = NULL; bool *inner_isnull = NULL; bool *merged_isnull = NULL; + bool consider_outer_null = false; + bool consider_inner_null = false; + bool outer_has_null = false; + bool inner_has_null = false; + int i; if (outer_bi->isnulls && outer_pos < outer_bi->ndatums) outer_isnull = outer_bi->isnulls[outer_pos]; @@ -1337,24 +1345,38 @@ merge_list_bounds(FmgrInfo *partsupfunc, Oid *partcollation, } } - if (outer_isnull && outer_isnull[0]) - { - outer_pos++; - continue; - } - - if (inner_isnull && inner_isnull[0]) - { - inner_pos++; - continue; - } - /* Get the list values. */ outer_datums = outer_pos < outer_bi->ndatums ? outer_bi->datums[outer_pos] : NULL; inner_datums = inner_pos < inner_bi->ndatums ? inner_bi->datums[inner_pos] : NULL; + for (i = 0; i < partnatts; i++) + { + if (outer_isnull && outer_isnull[i]) + { + outer_has_null = true; + if (outer_map.merged_indexes[outer_index] == -1) + { + consider_outer_null = true; + break; + } + } + } + + for (i = 0; i < partnatts; i++) + { + if (inner_isnull && inner_isnull[i]) + { + inner_has_null = true; + if (inner_map.merged_indexes[inner_index] == -1) + { + consider_inner_null = true; + break; + } + } + } + /* * We run this loop till both sides finish. This allows us to avoid * duplicating code to handle the remaining values on the side which @@ -1371,10 +1393,10 @@ merge_list_bounds(FmgrInfo *partsupfunc, Oid *partcollation, else { Assert(outer_datums != NULL && inner_datums != NULL); - cmpval = DatumGetInt32(FunctionCall2Coll(&partsupfunc[0], - partcollation[0], - outer_datums[0], - inner_datums[0])); + cmpval = partition_lbound_datum_cmp(partsupfunc, partcollation, + outer_datums, outer_isnull, + inner_datums, inner_isnull, + partnatts); } if (cmpval == 0) @@ -1385,15 +1407,31 @@ merge_list_bounds(FmgrInfo *partsupfunc, Oid *partcollation, Assert(outer_index >= 0); Assert(inner_index >= 0); - /* - * Try merging both partitions. If successful, add the list value - * and index of the merged partition below. - */ - merged_index = merge_matching_partitions(&outer_map, &inner_map, + if (outer_has_null && inner_has_null) + { + /* Merge the NULL partitions. */ + merged_index = merge_null_partitions(&outer_map, &inner_map, + consider_outer_null, + consider_inner_null, outer_index, inner_index, - &next_index); - if (merged_index == -1) - goto cleanup; + jointype, &next_index); + + if (merged_index == -1) + goto cleanup; + } + else + { + /* + * Try merging both partitions. If successful, add the list + * value and index of the merged partition below. + */ + merged_index = merge_matching_partitions(&outer_map, &inner_map, + outer_index, inner_index, + &next_index); + + if (merged_index == -1) + goto cleanup; + } merged_datum = outer_datums; merged_isnull = outer_isnull; @@ -1407,14 +1445,30 @@ merge_list_bounds(FmgrInfo *partsupfunc, Oid *partcollation, /* A list value missing from the inner side. */ Assert(outer_pos < outer_bi->ndatums); - /* - * If the inner side has the default partition, or this is an - * outer join, try to assign a merged partition to the outer - * partition (see process_outer_partition()). Otherwise, the - * outer partition will not contribute to the result. - */ - if (inner_has_default || IS_OUTER_JOIN(jointype)) + if (inner_has_null) { + if (consider_inner_null) + { + /* Merge the NULL partitions. */ + merged_index = merge_null_partitions(&outer_map, &inner_map, + consider_outer_null, + consider_inner_null, + outer_index, inner_index, + jointype, &next_index); + + if (merged_index == -1) + goto cleanup; + } + } + else if (inner_has_default || IS_OUTER_JOIN(jointype)) + { + /* + * If the inner side has the default partition, or this is an + * outer join, try to assign a merged partition to the outer + * partition (see process_outer_partition()). Otherwise, the + * outer partition will not contribute to the result. + */ + /* Get the outer partition. */ outer_index = outer_bi->indexes[outer_pos]; Assert(outer_index >= 0); @@ -1429,10 +1483,11 @@ merge_list_bounds(FmgrInfo *partsupfunc, Oid *partcollation, &default_index); if (merged_index == -1) goto cleanup; - merged_datum = outer_datums; - merged_isnull = outer_isnull; } + merged_datum = outer_datums; + merged_isnull = outer_isnull; + /* Move to the next list value on the outer side. */ outer_pos++; } @@ -1442,14 +1497,30 @@ merge_list_bounds(FmgrInfo *partsupfunc, Oid *partcollation, Assert(cmpval > 0); Assert(inner_pos < inner_bi->ndatums); - /* - * If the outer side has the default partition, or this is a FULL - * join, try to assign a merged partition to the inner partition - * (see process_inner_partition()). Otherwise, the inner - * partition will not contribute to the result. - */ - if (outer_has_default || jointype == JOIN_FULL) + if (outer_has_null || inner_has_null) + { + if (consider_outer_null || consider_inner_null) + { + /* Merge the NULL partitions. */ + merged_index = merge_null_partitions(&outer_map, &inner_map, + consider_outer_null, + consider_inner_null, + outer_index, inner_index, + jointype, &next_index); + + if (merged_index == -1) + goto cleanup; + } + } + else if (outer_has_default || jointype == JOIN_FULL) { + /* + * If the outer side has the default partition, or this is a + * FULL join, try to assign a merged partition to the inner + * partition (see process_inner_partition()). Otherwise, the + * innerpartition will not contribute to the result. + */ + /* Get the inner partition. */ inner_index = inner_bi->indexes[inner_pos]; Assert(inner_index >= 0); @@ -1464,10 +1535,11 @@ merge_list_bounds(FmgrInfo *partsupfunc, Oid *partcollation, &default_index); if (merged_index == -1) goto cleanup; - merged_datum = inner_datums; - merged_isnull = inner_isnull; } + merged_datum = inner_datums; + merged_isnull = inner_isnull; + /* Move to the next list value on the inner side. */ inner_pos++; } @@ -1484,26 +1556,6 @@ merge_list_bounds(FmgrInfo *partsupfunc, Oid *partcollation, } } - /* - * If the NULL partitions (if any) have been proven empty, deem them - * non-existent. - */ - if (outer_has_null && - is_dummy_partition(outer_rel, outer_bi->null_index)) - outer_has_null = false; - if (inner_has_null && - is_dummy_partition(inner_rel, inner_bi->null_index)) - inner_has_null = false; - - /* Merge the NULL partitions if any. */ - if (outer_has_null || inner_has_null) - merge_null_partitions(&outer_map, &inner_map, - outer_has_null, inner_has_null, - outer_bi->null_index, inner_bi->null_index, - jointype, &next_index, &null_index); - else - Assert(null_index == -1); - /* Merge the default partitions if any. */ if (outer_has_default || inner_has_default) merge_default_partitions(&outer_map, &inner_map, @@ -2216,48 +2268,24 @@ process_inner_partition(PartitionMap *outer_map, * be mergejoinable, and we currently assume that mergejoinable operators are * strict (see MJEvalOuterValues()/MJEvalInnerValues()). */ -static void +static int merge_null_partitions(PartitionMap *outer_map, PartitionMap *inner_map, - bool outer_has_null, - bool inner_has_null, + bool consider_outer_null, + bool consider_inner_null, int outer_null, int inner_null, JoinType jointype, - int *next_index, - int *null_index) + int *next_index) { - bool consider_outer_null = false; - bool consider_inner_null = false; - - Assert(outer_has_null || inner_has_null); - Assert(*null_index == -1); - - /* - * Check whether the NULL partitions have already been merged and if so, - * set the consider_outer_null/consider_inner_null flags. - */ - if (outer_has_null) - { - Assert(outer_null >= 0 && outer_null < outer_map->nparts); - if (outer_map->merged_indexes[outer_null] == -1) - consider_outer_null = true; - } - if (inner_has_null) - { - Assert(inner_null >= 0 && inner_null < inner_map->nparts); - if (inner_map->merged_indexes[inner_null] == -1) - consider_inner_null = true; - } + int merged_index = *next_index; /* If both flags are set false, we don't need to do anything. */ if (!consider_outer_null && !consider_inner_null) - return; + return merged_index; if (consider_outer_null && !consider_inner_null) { - Assert(outer_has_null); - /* * If this is an outer join, the NULL partition on the outer side has * to be scanned all the way anyway; merge the NULL partition with a @@ -2269,14 +2297,12 @@ merge_null_partitions(PartitionMap *outer_map, if (IS_OUTER_JOIN(jointype)) { Assert(jointype != JOIN_RIGHT); - *null_index = merge_partition_with_dummy(outer_map, outer_null, - next_index); + merged_index = merge_partition_with_dummy(outer_map, outer_null, + next_index); } } else if (!consider_outer_null && consider_inner_null) { - Assert(inner_has_null); - /* * If this is a FULL join, the NULL partition on the inner side has to * be scanned all the way anyway; merge the NULL partition with a @@ -2286,14 +2312,12 @@ merge_null_partitions(PartitionMap *outer_map, * treat it as the NULL partition of the join relation. */ if (jointype == JOIN_FULL) - *null_index = merge_partition_with_dummy(inner_map, inner_null, - next_index); + merged_index = merge_partition_with_dummy(inner_map, inner_null, + next_index); } else { Assert(consider_outer_null && consider_inner_null); - Assert(outer_has_null); - Assert(inner_has_null); /* * If this is an outer join, the NULL partition on the outer side (and @@ -2311,12 +2335,13 @@ merge_null_partitions(PartitionMap *outer_map, if (IS_OUTER_JOIN(jointype)) { Assert(jointype != JOIN_RIGHT); - *null_index = merge_matching_partitions(outer_map, inner_map, - outer_null, inner_null, - next_index); - Assert(*null_index >= 0); + merged_index = merge_matching_partitions(outer_map, inner_map, + outer_null, inner_null, + next_index); } } + + return merged_index; } /* @@ -2649,7 +2674,6 @@ build_merged_partition_bounds(char strategy, List *merged_datums, foreach(lc, merged_indexes) merged_bounds->indexes[pos++] = lfirst_int(lc); - merged_bounds->null_index = null_index; merged_bounds->default_index = default_index; return merged_bounds; @@ -3144,7 +3168,7 @@ check_new_partition_bound(char *relname, Relation parent, Assert(boundinfo && boundinfo->strategy == PARTITION_STRATEGY_LIST && (boundinfo->ndatums > 0 || - partition_bound_accepts_nulls(boundinfo) || + partition_bound_accepts_nulls(boundinfo, key->partnatts) || partition_bound_has_default(boundinfo))); foreach(cell, spec->listdatums) @@ -4364,7 +4388,8 @@ get_qual_for_list(Relation parent, PartitionBoundSpec *spec) * If default is the only partition, there need not be any partition * constraint on it. */ - if (ndatums == 0 && !partition_bound_accepts_nulls(boundinfo)) + if (ndatums == 0 && + !partition_bound_accepts_nulls(boundinfo, key->partnatts)) return NIL; } diff --git a/src/include/partitioning/partbounds.h b/src/include/partitioning/partbounds.h index 7de5cb3..16af593 100644 --- a/src/include/partitioning/partbounds.h +++ b/src/include/partitioning/partbounds.h @@ -91,15 +91,14 @@ typedef struct PartitionBoundInfoData * only set for LIST partitioned tables */ int nindexes; /* Length of the indexes[] array */ int *indexes; /* Partition indexes */ - int null_index; /* Index of the null-accepting partition; -1 - * if there isn't one */ int default_index; /* Index of the default partition; -1 if there * isn't one */ } PartitionBoundInfoData; -#define partition_bound_accepts_nulls(bi) ((bi)->null_index != -1) #define partition_bound_has_default(bi) ((bi)->default_index != -1) +extern bool partition_bound_accepts_nulls(PartitionBoundInfo boundinfo, int partnatts); + extern int get_hash_partition_greatest_modulus(PartitionBoundInfo b); extern uint64 compute_partition_hash_value(int partnatts, FmgrInfo *partsupfunc, Oid *partcollation, diff --git a/src/test/regress/expected/partition_join.out b/src/test/regress/expected/partition_join.out index 27f7525..84b5b36 100644 --- a/src/test/regress/expected/partition_join.out +++ b/src/test/regress/expected/partition_join.out @@ -4650,6 +4650,1263 @@ SELECT t1.a, t1.c, t2.a, t2.c, t3.a, t3.c FROM (plt1_adv t1 LEFT JOIN plt2_adv t DROP TABLE plt1_adv; DROP TABLE plt2_adv; DROP TABLE plt3_adv; +-- Tests for multi-column list-partitioned tables +CREATE TABLE plt1_adv_m (a int, b int, c text, d int) PARTITION BY LIST (c, d); +CREATE TABLE plt1_adv_m_p1 PARTITION OF plt1_adv_m FOR VALUES IN (('0001', 1), ('0003', 3)); +CREATE TABLE plt1_adv_m_p2 PARTITION OF plt1_adv_m FOR VALUES IN (('0004', 4), ('0006', 6)); +CREATE TABLE plt1_adv_m_p3 PARTITION OF plt1_adv_m FOR VALUES IN (('0008', 8), ('0009', 9)); +INSERT INTO plt1_adv_m SELECT i, i, to_char(i % 10, 'FM0000'), (i % 10) FROM generate_series(1, 299) i WHERE i % 10 IN (1, 3, 4, 6, 8, 9); +ANALYZE plt1_adv_m; +CREATE TABLE plt2_adv_m (a int, b int, c text, d int) PARTITION BY LIST (c, d); +CREATE TABLE plt2_adv_m_p1 PARTITION OF plt2_adv_m FOR VALUES IN (('0002', 2), ('0003', 3)); +CREATE TABLE plt2_adv_m_p2 PARTITION OF plt2_adv_m FOR VALUES IN (('0004', 4), ('0006', 6)); +CREATE TABLE plt2_adv_m_p3 PARTITION OF plt2_adv_m FOR VALUES IN (('0007', 7), ('0009', 9)); +INSERT INTO plt2_adv_m SELECT i, i, to_char(i % 10, 'FM0000'), (i % 10) FROM generate_series(1, 299) i WHERE i % 10 IN (2, 3, 4, 6, 7, 9); +ANALYZE plt2_adv_m; +-- inner join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 INNER JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.a < 10 ORDER BY t1.a; + QUERY PLAN +------------------------------------------------------------------------------------------ + Sort + Sort Key: t1.a + -> Append + -> Hash Join + Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c) AND (t2_1.d = t1_1.d)) + -> Seq Scan on plt2_adv_m_p1 t2_1 + -> Hash + -> Seq Scan on plt1_adv_m_p1 t1_1 + Filter: (a < 10) + -> Hash Join + Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c) AND (t2_2.d = t1_2.d)) + -> Seq Scan on plt2_adv_m_p2 t2_2 + -> Hash + -> Seq Scan on plt1_adv_m_p2 t1_2 + Filter: (a < 10) + -> Hash Join + Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c) AND (t2_3.d = t1_3.d)) + -> Seq Scan on plt2_adv_m_p3 t2_3 + -> Hash + -> Seq Scan on plt1_adv_m_p3 t1_3 + Filter: (a < 10) +(21 rows) + +SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 INNER JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.a < 10 ORDER BY t1.a; + a | c | d | a | c | d +---+------+---+---+------+--- + 3 | 0003 | 3 | 3 | 0003 | 3 + 4 | 0004 | 4 | 4 | 0004 | 4 + 6 | 0006 | 6 | 6 | 0006 | 6 + 9 | 0009 | 9 | 9 | 0009 | 9 +(4 rows) + +-- semi join +EXPLAIN (COSTS OFF) +SELECT t1.* FROM plt1_adv_m t1 WHERE EXISTS (SELECT 1 FROM plt2_adv_m t2 WHERE t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) AND t1.a < 10 ORDER BY t1.a; + QUERY PLAN +-------------------------------------------------------------------------------------------- + Sort + Sort Key: t1.a + -> Append + -> Nested Loop Semi Join + Join Filter: ((t1_1.a = t2_1.a) AND (t1_1.c = t2_1.c) AND (t1_1.d = t2_1.d)) + -> Seq Scan on plt1_adv_m_p1 t1_1 + Filter: (a < 10) + -> Seq Scan on plt2_adv_m_p1 t2_1 + -> Nested Loop Semi Join + Join Filter: ((t1_2.a = t2_2.a) AND (t1_2.c = t2_2.c) AND (t1_2.d = t2_2.d)) + -> Seq Scan on plt1_adv_m_p2 t1_2 + Filter: (a < 10) + -> Seq Scan on plt2_adv_m_p2 t2_2 + -> Nested Loop Semi Join + Join Filter: ((t1_3.a = t2_3.a) AND (t1_3.c = t2_3.c) AND (t1_3.d = t2_3.d)) + -> Seq Scan on plt1_adv_m_p3 t1_3 + Filter: (a < 10) + -> Seq Scan on plt2_adv_m_p3 t2_3 +(18 rows) + +SELECT t1.* FROM plt1_adv_m t1 WHERE EXISTS (SELECT 1 FROM plt2_adv_m t2 WHERE t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) AND t1.a < 10 ORDER BY t1.a; + a | b | c | d +---+---+------+--- + 3 | 3 | 0003 | 3 + 4 | 4 | 0004 | 4 + 6 | 6 | 0006 | 6 + 9 | 9 | 0009 | 9 +(4 rows) + +-- left join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 LEFT JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.a < 10 ORDER BY t1.a; + QUERY PLAN +------------------------------------------------------------------------------------------ + Sort + Sort Key: t1.a + -> Append + -> Hash Right Join + Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c) AND (t2_1.d = t1_1.d)) + -> Seq Scan on plt2_adv_m_p1 t2_1 + -> Hash + -> Seq Scan on plt1_adv_m_p1 t1_1 + Filter: (a < 10) + -> Hash Right Join + Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c) AND (t2_2.d = t1_2.d)) + -> Seq Scan on plt2_adv_m_p2 t2_2 + -> Hash + -> Seq Scan on plt1_adv_m_p2 t1_2 + Filter: (a < 10) + -> Hash Right Join + Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c) AND (t2_3.d = t1_3.d)) + -> Seq Scan on plt2_adv_m_p3 t2_3 + -> Hash + -> Seq Scan on plt1_adv_m_p3 t1_3 + Filter: (a < 10) +(21 rows) + +SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 LEFT JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.a < 10 ORDER BY t1.a; + a | c | d | a | c | d +---+------+---+---+------+--- + 1 | 0001 | 1 | | | + 3 | 0003 | 3 | 3 | 0003 | 3 + 4 | 0004 | 4 | 4 | 0004 | 4 + 6 | 0006 | 6 | 6 | 0006 | 6 + 8 | 0008 | 8 | | | + 9 | 0009 | 9 | 9 | 0009 | 9 +(6 rows) + +-- anti join +EXPLAIN (COSTS OFF) +SELECT t1.* FROM plt1_adv_m t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv_m t2 WHERE t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) AND t1.a < 10 ORDER BY t1.a; + QUERY PLAN +-------------------------------------------------------------------------------------------- + Sort + Sort Key: t1.a + -> Append + -> Nested Loop Anti Join + Join Filter: ((t1_1.a = t2_1.a) AND (t1_1.c = t2_1.c) AND (t1_1.d = t2_1.d)) + -> Seq Scan on plt1_adv_m_p1 t1_1 + Filter: (a < 10) + -> Seq Scan on plt2_adv_m_p1 t2_1 + -> Nested Loop Anti Join + Join Filter: ((t1_2.a = t2_2.a) AND (t1_2.c = t2_2.c) AND (t1_2.d = t2_2.d)) + -> Seq Scan on plt1_adv_m_p2 t1_2 + Filter: (a < 10) + -> Seq Scan on plt2_adv_m_p2 t2_2 + -> Nested Loop Anti Join + Join Filter: ((t1_3.a = t2_3.a) AND (t1_3.c = t2_3.c) AND (t1_3.d = t2_3.d)) + -> Seq Scan on plt1_adv_m_p3 t1_3 + Filter: (a < 10) + -> Seq Scan on plt2_adv_m_p3 t2_3 +(18 rows) + +SELECT t1.* FROM plt1_adv_m t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv_m t2 WHERE t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) AND t1.a < 10 ORDER BY t1.a; + a | b | c | d +---+---+------+--- + 1 | 1 | 0001 | 1 + 8 | 8 | 0008 | 8 +(2 rows) + +-- full join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 FULL JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE coalesce(t1.a, 0) < 10 AND coalesce(t2.b, 0) < 10 ORDER BY t1.a, t2.a; + QUERY PLAN +------------------------------------------------------------------------------------------ + Sort + Sort Key: t1.a, t2.a + -> Append + -> Hash Full Join + Hash Cond: ((t1_1.a = t2_1.a) AND (t1_1.c = t2_1.c) AND (t1_1.d = t2_1.d)) + Filter: ((COALESCE(t1_1.a, 0) < 10) AND (COALESCE(t2_1.b, 0) < 10)) + -> Seq Scan on plt1_adv_m_p1 t1_1 + -> Hash + -> Seq Scan on plt2_adv_m_p1 t2_1 + -> Hash Full Join + Hash Cond: ((t1_2.a = t2_2.a) AND (t1_2.c = t2_2.c) AND (t1_2.d = t2_2.d)) + Filter: ((COALESCE(t1_2.a, 0) < 10) AND (COALESCE(t2_2.b, 0) < 10)) + -> Seq Scan on plt1_adv_m_p2 t1_2 + -> Hash + -> Seq Scan on plt2_adv_m_p2 t2_2 + -> Hash Full Join + Hash Cond: ((t1_3.a = t2_3.a) AND (t1_3.c = t2_3.c) AND (t1_3.d = t2_3.d)) + Filter: ((COALESCE(t1_3.a, 0) < 10) AND (COALESCE(t2_3.b, 0) < 10)) + -> Seq Scan on plt1_adv_m_p3 t1_3 + -> Hash + -> Seq Scan on plt2_adv_m_p3 t2_3 +(21 rows) + +SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 FULL JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE coalesce(t1.a, 0) < 10 AND coalesce(t2.b, 0) < 10 ORDER BY t1.a, t2.a; + a | c | d | a | c | d +---+------+---+---+------+--- + 1 | 0001 | 1 | | | + 3 | 0003 | 3 | 3 | 0003 | 3 + 4 | 0004 | 4 | 4 | 0004 | 4 + 6 | 0006 | 6 | 6 | 0006 | 6 + 8 | 0008 | 8 | | | + 9 | 0009 | 9 | 9 | 0009 | 9 + | | | 2 | 0002 | 2 + | | | 7 | 0007 | 7 +(8 rows) + +-- Test cases where one side has an extra partition +CREATE TABLE plt2_adv_m_extra PARTITION OF plt2_adv_m FOR VALUES IN (('0000', 0)); +INSERT INTO plt2_adv_m_extra VALUES (0, 0, '0000', 0); +ANALYZE plt2_adv_m; +-- inner join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 INNER JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.b < 10 ORDER BY t1.a; + QUERY PLAN +------------------------------------------------------------------------------------------ + Sort + Sort Key: t1.a + -> Append + -> Hash Join + Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c) AND (t2_1.d = t1_1.d)) + -> Seq Scan on plt2_adv_m_p1 t2_1 + -> Hash + -> Seq Scan on plt1_adv_m_p1 t1_1 + Filter: (b < 10) + -> Hash Join + Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c) AND (t2_2.d = t1_2.d)) + -> Seq Scan on plt2_adv_m_p2 t2_2 + -> Hash + -> Seq Scan on plt1_adv_m_p2 t1_2 + Filter: (b < 10) + -> Hash Join + Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c) AND (t2_3.d = t1_3.d)) + -> Seq Scan on plt2_adv_m_p3 t2_3 + -> Hash + -> Seq Scan on plt1_adv_m_p3 t1_3 + Filter: (b < 10) +(21 rows) + +SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 INNER JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.b < 10 ORDER BY t1.a; + a | c | d | a | c | d +---+------+---+---+------+--- + 3 | 0003 | 3 | 3 | 0003 | 3 + 4 | 0004 | 4 | 4 | 0004 | 4 + 6 | 0006 | 6 | 6 | 0006 | 6 + 9 | 0009 | 9 | 9 | 0009 | 9 +(4 rows) + +-- semi join +EXPLAIN (COSTS OFF) +SELECT t1.* FROM plt1_adv_m t1 WHERE EXISTS (SELECT 1 FROM plt2_adv_m t2 WHERE t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) AND t1.b < 10 ORDER BY t1.a; + QUERY PLAN +-------------------------------------------------------------------------------------------- + Sort + Sort Key: t1.a + -> Append + -> Nested Loop Semi Join + Join Filter: ((t1_1.a = t2_1.a) AND (t1_1.c = t2_1.c) AND (t1_1.d = t2_1.d)) + -> Seq Scan on plt1_adv_m_p1 t1_1 + Filter: (b < 10) + -> Seq Scan on plt2_adv_m_p1 t2_1 + -> Nested Loop Semi Join + Join Filter: ((t1_2.a = t2_2.a) AND (t1_2.c = t2_2.c) AND (t1_2.d = t2_2.d)) + -> Seq Scan on plt1_adv_m_p2 t1_2 + Filter: (b < 10) + -> Seq Scan on plt2_adv_m_p2 t2_2 + -> Nested Loop Semi Join + Join Filter: ((t1_3.a = t2_3.a) AND (t1_3.c = t2_3.c) AND (t1_3.d = t2_3.d)) + -> Seq Scan on plt1_adv_m_p3 t1_3 + Filter: (b < 10) + -> Seq Scan on plt2_adv_m_p3 t2_3 +(18 rows) + +SELECT t1.* FROM plt1_adv_m t1 WHERE EXISTS (SELECT 1 FROM plt2_adv_m t2 WHERE t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) AND t1.b < 10 ORDER BY t1.a; + a | b | c | d +---+---+------+--- + 3 | 3 | 0003 | 3 + 4 | 4 | 0004 | 4 + 6 | 6 | 0006 | 6 + 9 | 9 | 0009 | 9 +(4 rows) + +-- left join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 LEFT JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.b < 10 ORDER BY t1.a; + QUERY PLAN +------------------------------------------------------------------------------------------ + Sort + Sort Key: t1.a + -> Append + -> Hash Right Join + Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c) AND (t2_1.d = t1_1.d)) + -> Seq Scan on plt2_adv_m_p1 t2_1 + -> Hash + -> Seq Scan on plt1_adv_m_p1 t1_1 + Filter: (b < 10) + -> Hash Right Join + Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c) AND (t2_2.d = t1_2.d)) + -> Seq Scan on plt2_adv_m_p2 t2_2 + -> Hash + -> Seq Scan on plt1_adv_m_p2 t1_2 + Filter: (b < 10) + -> Hash Right Join + Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c) AND (t2_3.d = t1_3.d)) + -> Seq Scan on plt2_adv_m_p3 t2_3 + -> Hash + -> Seq Scan on plt1_adv_m_p3 t1_3 + Filter: (b < 10) +(21 rows) + +SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 LEFT JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.b < 10 ORDER BY t1.a; + a | c | d | a | c | d +---+------+---+---+------+--- + 1 | 0001 | 1 | | | + 3 | 0003 | 3 | 3 | 0003 | 3 + 4 | 0004 | 4 | 4 | 0004 | 4 + 6 | 0006 | 6 | 6 | 0006 | 6 + 8 | 0008 | 8 | | | + 9 | 0009 | 9 | 9 | 0009 | 9 +(6 rows) + +-- left join; currently we can't do partitioned join if there are no matched +-- partitions on the nullable side +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt2_adv_m t1 LEFT JOIN plt1_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.b < 10 ORDER BY t1.a; + QUERY PLAN +------------------------------------------------------------------------ + Sort + Sort Key: t1.a + -> Hash Right Join + Hash Cond: ((t2.a = t1.a) AND (t2.c = t1.c) AND (t2.d = t1.d)) + -> Append + -> Seq Scan on plt1_adv_m_p1 t2_1 + -> Seq Scan on plt1_adv_m_p2 t2_2 + -> Seq Scan on plt1_adv_m_p3 t2_3 + -> Hash + -> Append + -> Seq Scan on plt2_adv_m_extra t1_1 + Filter: (b < 10) + -> Seq Scan on plt2_adv_m_p1 t1_2 + Filter: (b < 10) + -> Seq Scan on plt2_adv_m_p2 t1_3 + Filter: (b < 10) + -> Seq Scan on plt2_adv_m_p3 t1_4 + Filter: (b < 10) +(18 rows) + +-- anti join +EXPLAIN (COSTS OFF) +SELECT t1.* FROM plt1_adv_m t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv_m t2 WHERE t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) AND t1.b < 10 ORDER BY t1.a; + QUERY PLAN +-------------------------------------------------------------------------------------------- + Sort + Sort Key: t1.a + -> Append + -> Nested Loop Anti Join + Join Filter: ((t1_1.a = t2_1.a) AND (t1_1.c = t2_1.c) AND (t1_1.d = t2_1.d)) + -> Seq Scan on plt1_adv_m_p1 t1_1 + Filter: (b < 10) + -> Seq Scan on plt2_adv_m_p1 t2_1 + -> Nested Loop Anti Join + Join Filter: ((t1_2.a = t2_2.a) AND (t1_2.c = t2_2.c) AND (t1_2.d = t2_2.d)) + -> Seq Scan on plt1_adv_m_p2 t1_2 + Filter: (b < 10) + -> Seq Scan on plt2_adv_m_p2 t2_2 + -> Nested Loop Anti Join + Join Filter: ((t1_3.a = t2_3.a) AND (t1_3.c = t2_3.c) AND (t1_3.d = t2_3.d)) + -> Seq Scan on plt1_adv_m_p3 t1_3 + Filter: (b < 10) + -> Seq Scan on plt2_adv_m_p3 t2_3 +(18 rows) + +SELECT t1.* FROM plt1_adv_m t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv_m t2 WHERE t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) AND t1.b < 10 ORDER BY t1.a; + a | b | c | d +---+---+------+--- + 1 | 1 | 0001 | 1 + 8 | 8 | 0008 | 8 +(2 rows) + +-- anti join; currently we can't do partitioned join if there are no matched +-- partitions on the nullable side +EXPLAIN (COSTS OFF) +SELECT t1.* FROM plt2_adv_m t1 WHERE NOT EXISTS (SELECT 1 FROM plt1_adv_m t2 WHERE t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) AND t1.b < 10 ORDER BY t1.a; + QUERY PLAN +------------------------------------------------------------------------ + Sort + Sort Key: t1.a + -> Hash Anti Join + Hash Cond: ((t1.a = t2.a) AND (t1.c = t2.c) AND (t1.d = t2.d)) + -> Append + -> Seq Scan on plt2_adv_m_extra t1_1 + Filter: (b < 10) + -> Seq Scan on plt2_adv_m_p1 t1_2 + Filter: (b < 10) + -> Seq Scan on plt2_adv_m_p2 t1_3 + Filter: (b < 10) + -> Seq Scan on plt2_adv_m_p3 t1_4 + Filter: (b < 10) + -> Hash + -> Append + -> Seq Scan on plt1_adv_m_p1 t2_1 + -> Seq Scan on plt1_adv_m_p2 t2_2 + -> Seq Scan on plt1_adv_m_p3 t2_3 +(18 rows) + +-- full join; currently we can't do partitioned join if there are no matched +-- partitions on the nullable side +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 FULL JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE coalesce(t1.b, 0) < 10 AND coalesce(t2.b, 0) < 10 ORDER BY t1.a, t2.a; + QUERY PLAN +------------------------------------------------------------------------- + Sort + Sort Key: t1.a, t2.a + -> Hash Full Join + Hash Cond: ((t2.a = t1.a) AND (t2.c = t1.c) AND (t2.d = t1.d)) + Filter: ((COALESCE(t1.b, 0) < 10) AND (COALESCE(t2.b, 0) < 10)) + -> Append + -> Seq Scan on plt2_adv_m_extra t2_1 + -> Seq Scan on plt2_adv_m_p1 t2_2 + -> Seq Scan on plt2_adv_m_p2 t2_3 + -> Seq Scan on plt2_adv_m_p3 t2_4 + -> Hash + -> Append + -> Seq Scan on plt1_adv_m_p1 t1_1 + -> Seq Scan on plt1_adv_m_p2 t1_2 + -> Seq Scan on plt1_adv_m_p3 t1_3 +(15 rows) + +DROP TABLE plt2_adv_m_extra; +-- Test cases where a partition on one side matches multiple partitions on +-- the other side; we currently can't do partitioned join in such cases +ALTER TABLE plt2_adv_m DETACH PARTITION plt2_adv_m_p2; +-- Split plt2_adv_p2 into two partitions so that plt1_adv_p2 matches both +CREATE TABLE plt2_adv_m_p2_1 PARTITION OF plt2_adv_m FOR VALUES IN (('0004', 4)); +CREATE TABLE plt2_adv_m_p2_2 PARTITION OF plt2_adv_m FOR VALUES IN (('0006', 6)); +INSERT INTO plt2_adv_m SELECT i, i, to_char(i % 10, 'FM0000'), (i % 10) FROM generate_series(1, 299) i WHERE i % 10 IN (4, 6); +ANALYZE plt2_adv_m; +-- inner join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 INNER JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.b < 10 ORDER BY t1.a; + QUERY PLAN +------------------------------------------------------------------------ + Sort + Sort Key: t1.a + -> Hash Join + Hash Cond: ((t2.a = t1.a) AND (t2.c = t1.c) AND (t2.d = t1.d)) + -> Append + -> Seq Scan on plt2_adv_m_p1 t2_1 + -> Seq Scan on plt2_adv_m_p2_1 t2_2 + -> Seq Scan on plt2_adv_m_p2_2 t2_3 + -> Seq Scan on plt2_adv_m_p3 t2_4 + -> Hash + -> Append + -> Seq Scan on plt1_adv_m_p1 t1_1 + Filter: (b < 10) + -> Seq Scan on plt1_adv_m_p2 t1_2 + Filter: (b < 10) + -> Seq Scan on plt1_adv_m_p3 t1_3 + Filter: (b < 10) +(17 rows) + +-- semi join +EXPLAIN (COSTS OFF) +SELECT t1.* FROM plt1_adv_m t1 WHERE EXISTS (SELECT 1 FROM plt2_adv_m t2 WHERE t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) AND t1.b < 10 ORDER BY t1.a; + QUERY PLAN +------------------------------------------------------------------------ + Sort + Sort Key: t1.a + -> Hash Semi Join + Hash Cond: ((t1.a = t2.a) AND (t1.c = t2.c) AND (t1.d = t2.d)) + -> Append + -> Seq Scan on plt1_adv_m_p1 t1_1 + Filter: (b < 10) + -> Seq Scan on plt1_adv_m_p2 t1_2 + Filter: (b < 10) + -> Seq Scan on plt1_adv_m_p3 t1_3 + Filter: (b < 10) + -> Hash + -> Append + -> Seq Scan on plt2_adv_m_p1 t2_1 + -> Seq Scan on plt2_adv_m_p2_1 t2_2 + -> Seq Scan on plt2_adv_m_p2_2 t2_3 + -> Seq Scan on plt2_adv_m_p3 t2_4 +(17 rows) + +-- left join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 LEFT JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.b < 10 ORDER BY t1.a; + QUERY PLAN +------------------------------------------------------------------------ + Sort + Sort Key: t1.a + -> Hash Right Join + Hash Cond: ((t2.a = t1.a) AND (t2.c = t1.c) AND (t2.d = t1.d)) + -> Append + -> Seq Scan on plt2_adv_m_p1 t2_1 + -> Seq Scan on plt2_adv_m_p2_1 t2_2 + -> Seq Scan on plt2_adv_m_p2_2 t2_3 + -> Seq Scan on plt2_adv_m_p3 t2_4 + -> Hash + -> Append + -> Seq Scan on plt1_adv_m_p1 t1_1 + Filter: (b < 10) + -> Seq Scan on plt1_adv_m_p2 t1_2 + Filter: (b < 10) + -> Seq Scan on plt1_adv_m_p3 t1_3 + Filter: (b < 10) +(17 rows) + +-- anti join +EXPLAIN (COSTS OFF) +SELECT t1.* FROM plt1_adv_m t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv_m t2 WHERE t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) AND t1.b < 10 ORDER BY t1.a; + QUERY PLAN +------------------------------------------------------------------------ + Sort + Sort Key: t1.a + -> Hash Anti Join + Hash Cond: ((t1.a = t2.a) AND (t1.c = t2.c) AND (t1.d = t2.d)) + -> Append + -> Seq Scan on plt1_adv_m_p1 t1_1 + Filter: (b < 10) + -> Seq Scan on plt1_adv_m_p2 t1_2 + Filter: (b < 10) + -> Seq Scan on plt1_adv_m_p3 t1_3 + Filter: (b < 10) + -> Hash + -> Append + -> Seq Scan on plt2_adv_m_p1 t2_1 + -> Seq Scan on plt2_adv_m_p2_1 t2_2 + -> Seq Scan on plt2_adv_m_p2_2 t2_3 + -> Seq Scan on plt2_adv_m_p3 t2_4 +(17 rows) + +-- full join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 FULL JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE coalesce(t1.b, 0) < 10 AND coalesce(t2.b, 0) < 10 ORDER BY t1.a, t2.a; + QUERY PLAN +------------------------------------------------------------------------- + Sort + Sort Key: t1.a, t2.a + -> Hash Full Join + Hash Cond: ((t2.a = t1.a) AND (t2.c = t1.c) AND (t2.d = t1.d)) + Filter: ((COALESCE(t1.b, 0) < 10) AND (COALESCE(t2.b, 0) < 10)) + -> Append + -> Seq Scan on plt2_adv_m_p1 t2_1 + -> Seq Scan on plt2_adv_m_p2_1 t2_2 + -> Seq Scan on plt2_adv_m_p2_2 t2_3 + -> Seq Scan on plt2_adv_m_p3 t2_4 + -> Hash + -> Append + -> Seq Scan on plt1_adv_m_p1 t1_1 + -> Seq Scan on plt1_adv_m_p2 t1_2 + -> Seq Scan on plt1_adv_m_p3 t1_3 +(15 rows) + +DROP TABLE plt2_adv_m_p2_1; +DROP TABLE plt2_adv_m_p2_2; +-- Restore plt2_adv_p2 +ALTER TABLE plt2_adv_m ATTACH PARTITION plt2_adv_m_p2 FOR VALUES IN (('0004', 4), ('0006', 6)); +-- Test NULL partitions +ALTER TABLE plt1_adv_m DETACH PARTITION plt1_adv_m_p1; +-- Change plt1_adv_p1 to the NULL partition +CREATE TABLE plt1_adv_m_p1_null PARTITION OF plt1_adv_m FOR VALUES IN ((NULL, NULL), ('0001', 1), ('0003', 3)); +INSERT INTO plt1_adv_m SELECT i, i, to_char(i % 10, 'FM0000'), (i % 10) FROM generate_series(1, 299) i WHERE i % 10 IN (1, 3); +INSERT INTO plt1_adv_m VALUES (-1, -1, NULL, NULL); +ANALYZE plt1_adv_m; +ALTER TABLE plt2_adv_m DETACH PARTITION plt2_adv_m_p3; +-- Change plt2_adv_p3 to the NULL partition +CREATE TABLE plt2_adv_m_p3_null PARTITION OF plt2_adv_m FOR VALUES IN ((NULL, NULL), ('0007', 7), ('0009', 9)); +INSERT INTO plt2_adv_m SELECT i, i, to_char(i % 10, 'FM0000'), (i % 10) FROM generate_series(1, 299) i WHERE i % 10 IN (7, 9); +INSERT INTO plt2_adv_m VALUES (-1, -1, NULL, NULL); +ANALYZE plt2_adv_m; +-- inner join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 INNER JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.b < 10 ORDER BY t1.a; + QUERY PLAN +------------------------------------------------------------------------------------------ + Sort + Sort Key: t1.a + -> Append + -> Hash Join + Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c) AND (t2_1.d = t1_1.d)) + -> Seq Scan on plt2_adv_m_p1 t2_1 + -> Hash + -> Seq Scan on plt1_adv_m_p1_null t1_1 + Filter: (b < 10) + -> Hash Join + Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c) AND (t2_2.d = t1_2.d)) + -> Seq Scan on plt2_adv_m_p2 t2_2 + -> Hash + -> Seq Scan on plt1_adv_m_p2 t1_2 + Filter: (b < 10) + -> Hash Join + Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c) AND (t2_3.d = t1_3.d)) + -> Seq Scan on plt2_adv_m_p3_null t2_3 + -> Hash + -> Seq Scan on plt1_adv_m_p3 t1_3 + Filter: (b < 10) +(21 rows) + +SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 INNER JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.b < 10 ORDER BY t1.a; + a | c | d | a | c | d +---+------+---+---+------+--- + 3 | 0003 | 3 | 3 | 0003 | 3 + 4 | 0004 | 4 | 4 | 0004 | 4 + 6 | 0006 | 6 | 6 | 0006 | 6 + 9 | 0009 | 9 | 9 | 0009 | 9 +(4 rows) + +-- semi join +EXPLAIN (COSTS OFF) +SELECT t1.* FROM plt1_adv_m t1 WHERE EXISTS (SELECT 1 FROM plt2_adv_m t2 WHERE t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) AND t1.b < 10 ORDER BY t1.a; + QUERY PLAN +-------------------------------------------------------------------------------------------- + Sort + Sort Key: t1.a + -> Append + -> Hash Semi Join + Hash Cond: ((t1_1.a = t2_1.a) AND (t1_1.c = t2_1.c) AND (t1_1.d = t2_1.d)) + -> Seq Scan on plt1_adv_m_p1_null t1_1 + Filter: (b < 10) + -> Hash + -> Seq Scan on plt2_adv_m_p1 t2_1 + -> Nested Loop Semi Join + Join Filter: ((t1_2.a = t2_2.a) AND (t1_2.c = t2_2.c) AND (t1_2.d = t2_2.d)) + -> Seq Scan on plt1_adv_m_p2 t1_2 + Filter: (b < 10) + -> Seq Scan on plt2_adv_m_p2 t2_2 + -> Nested Loop Semi Join + Join Filter: ((t1_3.a = t2_3.a) AND (t1_3.c = t2_3.c) AND (t1_3.d = t2_3.d)) + -> Seq Scan on plt1_adv_m_p3 t1_3 + Filter: (b < 10) + -> Seq Scan on plt2_adv_m_p3_null t2_3 +(19 rows) + +SELECT t1.* FROM plt1_adv_m t1 WHERE EXISTS (SELECT 1 FROM plt2_adv_m t2 WHERE t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) AND t1.b < 10 ORDER BY t1.a; + a | b | c | d +---+---+------+--- + 3 | 3 | 0003 | 3 + 4 | 4 | 0004 | 4 + 6 | 6 | 0006 | 6 + 9 | 9 | 0009 | 9 +(4 rows) + +-- left join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 LEFT JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.b < 10 ORDER BY t1.a; + QUERY PLAN +------------------------------------------------------------------------------------------ + Sort + Sort Key: t1.a + -> Append + -> Hash Right Join + Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c) AND (t2_1.d = t1_1.d)) + -> Seq Scan on plt2_adv_m_p1 t2_1 + -> Hash + -> Seq Scan on plt1_adv_m_p1_null t1_1 + Filter: (b < 10) + -> Hash Right Join + Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c) AND (t2_2.d = t1_2.d)) + -> Seq Scan on plt2_adv_m_p2 t2_2 + -> Hash + -> Seq Scan on plt1_adv_m_p2 t1_2 + Filter: (b < 10) + -> Hash Right Join + Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c) AND (t2_3.d = t1_3.d)) + -> Seq Scan on plt2_adv_m_p3_null t2_3 + -> Hash + -> Seq Scan on plt1_adv_m_p3 t1_3 + Filter: (b < 10) +(21 rows) + +SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 LEFT JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.b < 10 ORDER BY t1.a; + a | c | d | a | c | d +----+------+---+---+------+--- + -1 | | | | | + 1 | 0001 | 1 | | | + 3 | 0003 | 3 | 3 | 0003 | 3 + 4 | 0004 | 4 | 4 | 0004 | 4 + 6 | 0006 | 6 | 6 | 0006 | 6 + 8 | 0008 | 8 | | | + 9 | 0009 | 9 | 9 | 0009 | 9 +(7 rows) + +-- anti join +EXPLAIN (COSTS OFF) +SELECT t1.* FROM plt1_adv_m t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv_m t2 WHERE t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) AND t1.b < 10 ORDER BY t1.a; + QUERY PLAN +-------------------------------------------------------------------------------------------- + Sort + Sort Key: t1.a + -> Append + -> Hash Anti Join + Hash Cond: ((t1_1.a = t2_1.a) AND (t1_1.c = t2_1.c) AND (t1_1.d = t2_1.d)) + -> Seq Scan on plt1_adv_m_p1_null t1_1 + Filter: (b < 10) + -> Hash + -> Seq Scan on plt2_adv_m_p1 t2_1 + -> Nested Loop Anti Join + Join Filter: ((t1_2.a = t2_2.a) AND (t1_2.c = t2_2.c) AND (t1_2.d = t2_2.d)) + -> Seq Scan on plt1_adv_m_p2 t1_2 + Filter: (b < 10) + -> Seq Scan on plt2_adv_m_p2 t2_2 + -> Nested Loop Anti Join + Join Filter: ((t1_3.a = t2_3.a) AND (t1_3.c = t2_3.c) AND (t1_3.d = t2_3.d)) + -> Seq Scan on plt1_adv_m_p3 t1_3 + Filter: (b < 10) + -> Seq Scan on plt2_adv_m_p3_null t2_3 +(19 rows) + +SELECT t1.* FROM plt1_adv_m t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv_m t2 WHERE t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) AND t1.b < 10 ORDER BY t1.a; + a | b | c | d +----+----+------+--- + -1 | -1 | | + 1 | 1 | 0001 | 1 + 8 | 8 | 0008 | 8 +(3 rows) + +-- full join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 FULL JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE coalesce(t1.b, 0) < 10 AND coalesce(t2.b, 0) < 10 ORDER BY t1.a, t2.a; + QUERY PLAN +------------------------------------------------------------------------------------------ + Sort + Sort Key: t1.a, t2.a + -> Append + -> Hash Full Join + Hash Cond: ((t1_1.a = t2_1.a) AND (t1_1.c = t2_1.c) AND (t1_1.d = t2_1.d)) + Filter: ((COALESCE(t1_1.b, 0) < 10) AND (COALESCE(t2_1.b, 0) < 10)) + -> Seq Scan on plt1_adv_m_p1_null t1_1 + -> Hash + -> Seq Scan on plt2_adv_m_p1 t2_1 + -> Hash Full Join + Hash Cond: ((t1_2.a = t2_2.a) AND (t1_2.c = t2_2.c) AND (t1_2.d = t2_2.d)) + Filter: ((COALESCE(t1_2.b, 0) < 10) AND (COALESCE(t2_2.b, 0) < 10)) + -> Seq Scan on plt1_adv_m_p2 t1_2 + -> Hash + -> Seq Scan on plt2_adv_m_p2 t2_2 + -> Hash Full Join + Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c) AND (t2_3.d = t1_3.d)) + Filter: ((COALESCE(t1_3.b, 0) < 10) AND (COALESCE(t2_3.b, 0) < 10)) + -> Seq Scan on plt2_adv_m_p3_null t2_3 + -> Hash + -> Seq Scan on plt1_adv_m_p3 t1_3 +(21 rows) + +SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 FULL JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE coalesce(t1.b, 0) < 10 AND coalesce(t2.b, 0) < 10 ORDER BY t1.a, t2.a; + a | c | d | a | c | d +----+------+---+----+------+--- + -1 | | | | | + 1 | 0001 | 1 | | | + 3 | 0003 | 3 | 3 | 0003 | 3 + 4 | 0004 | 4 | 4 | 0004 | 4 + 6 | 0006 | 6 | 6 | 0006 | 6 + 8 | 0008 | 8 | | | + 9 | 0009 | 9 | 9 | 0009 | 9 + | | | -1 | | + | | | 2 | 0002 | 2 + | | | 7 | 0007 | 7 +(10 rows) + +DROP TABLE plt1_adv_m_p1_null; +-- Restore plt1_adv_p1 +ALTER TABLE plt1_adv_m ATTACH PARTITION plt1_adv_m_p1 FOR VALUES IN (('0001', 1), ('0003', 3)); +-- Add to plt1_adv the extra NULL partition containing only NULL values as the +-- key values +CREATE TABLE plt1_adv_m_extra PARTITION OF plt1_adv_m FOR VALUES IN ((NULL, NULL)); +INSERT INTO plt1_adv_m VALUES (-1, -1, NULL, NULL); +ANALYZE plt1_adv_m; +DROP TABLE plt2_adv_m_p3_null; +-- Restore plt2_adv_p3 +ALTER TABLE plt2_adv_m ATTACH PARTITION plt2_adv_m_p3 FOR VALUES IN (('0007', 7), ('0009', 9)); +ANALYZE plt2_adv_m; +-- inner join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 INNER JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.b < 10 ORDER BY t1.a; + QUERY PLAN +------------------------------------------------------------------------------------------ + Sort + Sort Key: t1.a + -> Append + -> Hash Join + Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c) AND (t2_1.d = t1_1.d)) + -> Seq Scan on plt2_adv_m_p1 t2_1 + -> Hash + -> Seq Scan on plt1_adv_m_p1 t1_1 + Filter: (b < 10) + -> Hash Join + Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c) AND (t2_2.d = t1_2.d)) + -> Seq Scan on plt2_adv_m_p2 t2_2 + -> Hash + -> Seq Scan on plt1_adv_m_p2 t1_2 + Filter: (b < 10) + -> Hash Join + Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c) AND (t2_3.d = t1_3.d)) + -> Seq Scan on plt2_adv_m_p3 t2_3 + -> Hash + -> Seq Scan on plt1_adv_m_p3 t1_3 + Filter: (b < 10) +(21 rows) + +SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 INNER JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.b < 10 ORDER BY t1.a; + a | c | d | a | c | d +---+------+---+---+------+--- + 3 | 0003 | 3 | 3 | 0003 | 3 + 4 | 0004 | 4 | 4 | 0004 | 4 + 6 | 0006 | 6 | 6 | 0006 | 6 + 9 | 0009 | 9 | 9 | 0009 | 9 +(4 rows) + +-- left join; currently we can't do partitioned join if there are no matched +-- partitions on the nullable side +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 LEFT JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.b < 10 ORDER BY t1.a; + QUERY PLAN +------------------------------------------------------------------------ + Sort + Sort Key: t1.a + -> Hash Right Join + Hash Cond: ((t2.a = t1.a) AND (t2.c = t1.c) AND (t2.d = t1.d)) + -> Append + -> Seq Scan on plt2_adv_m_p1 t2_1 + -> Seq Scan on plt2_adv_m_p2 t2_2 + -> Seq Scan on plt2_adv_m_p3 t2_3 + -> Hash + -> Append + -> Seq Scan on plt1_adv_m_p1 t1_1 + Filter: (b < 10) + -> Seq Scan on plt1_adv_m_p2 t1_2 + Filter: (b < 10) + -> Seq Scan on plt1_adv_m_p3 t1_3 + Filter: (b < 10) + -> Seq Scan on plt1_adv_m_extra t1_4 + Filter: (b < 10) +(18 rows) + +-- full join; currently we can't do partitioned join if there are no matched +-- partitions on the nullable side +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 FULL JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE coalesce(t1.b, 0) < 10 AND coalesce(t2.b, 0) < 10 ORDER BY t1.a, t2.a; + QUERY PLAN +------------------------------------------------------------------------- + Sort + Sort Key: t1.a, t2.a + -> Hash Full Join + Hash Cond: ((t1.a = t2.a) AND (t1.c = t2.c) AND (t1.d = t2.d)) + Filter: ((COALESCE(t1.b, 0) < 10) AND (COALESCE(t2.b, 0) < 10)) + -> Append + -> Seq Scan on plt1_adv_m_p1 t1_1 + -> Seq Scan on plt1_adv_m_p2 t1_2 + -> Seq Scan on plt1_adv_m_p3 t1_3 + -> Seq Scan on plt1_adv_m_extra t1_4 + -> Hash + -> Append + -> Seq Scan on plt2_adv_m_p1 t2_1 + -> Seq Scan on plt2_adv_m_p2 t2_2 + -> Seq Scan on plt2_adv_m_p3 t2_3 +(15 rows) + +-- Add to plt2_adv the extra NULL partition containing only NULL values as the +-- key values +CREATE TABLE plt2_adv_m_extra PARTITION OF plt2_adv_m FOR VALUES IN ((NULL, NULL)); +INSERT INTO plt2_adv_m VALUES (-1, -1, NULL, NULL); +ANALYZE plt2_adv_m; +-- inner join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 INNER JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.b < 10 ORDER BY t1.a; + QUERY PLAN +------------------------------------------------------------------------------------------ + Sort + Sort Key: t1.a + -> Append + -> Hash Join + Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c) AND (t2_1.d = t1_1.d)) + -> Seq Scan on plt2_adv_m_p1 t2_1 + -> Hash + -> Seq Scan on plt1_adv_m_p1 t1_1 + Filter: (b < 10) + -> Hash Join + Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c) AND (t2_2.d = t1_2.d)) + -> Seq Scan on plt2_adv_m_p2 t2_2 + -> Hash + -> Seq Scan on plt1_adv_m_p2 t1_2 + Filter: (b < 10) + -> Hash Join + Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c) AND (t2_3.d = t1_3.d)) + -> Seq Scan on plt2_adv_m_p3 t2_3 + -> Hash + -> Seq Scan on plt1_adv_m_p3 t1_3 + Filter: (b < 10) +(21 rows) + +SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 INNER JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.b < 10 ORDER BY t1.a; + a | c | d | a | c | d +---+------+---+---+------+--- + 3 | 0003 | 3 | 3 | 0003 | 3 + 4 | 0004 | 4 | 4 | 0004 | 4 + 6 | 0006 | 6 | 6 | 0006 | 6 + 9 | 0009 | 9 | 9 | 0009 | 9 +(4 rows) + +-- left join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 LEFT JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.b < 10 ORDER BY t1.a; + QUERY PLAN +-------------------------------------------------------------------------------------------- + Sort + Sort Key: t1.a + -> Append + -> Hash Right Join + Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c) AND (t2_1.d = t1_1.d)) + -> Seq Scan on plt2_adv_m_p1 t2_1 + -> Hash + -> Seq Scan on plt1_adv_m_p1 t1_1 + Filter: (b < 10) + -> Hash Right Join + Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c) AND (t2_2.d = t1_2.d)) + -> Seq Scan on plt2_adv_m_p2 t2_2 + -> Hash + -> Seq Scan on plt1_adv_m_p2 t1_2 + Filter: (b < 10) + -> Hash Right Join + Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c) AND (t2_3.d = t1_3.d)) + -> Seq Scan on plt2_adv_m_p3 t2_3 + -> Hash + -> Seq Scan on plt1_adv_m_p3 t1_3 + Filter: (b < 10) + -> Nested Loop Left Join + Join Filter: ((t1_4.a = t2_4.a) AND (t1_4.c = t2_4.c) AND (t1_4.d = t2_4.d)) + -> Seq Scan on plt1_adv_m_extra t1_4 + Filter: (b < 10) + -> Seq Scan on plt2_adv_m_extra t2_4 +(26 rows) + +SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 LEFT JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.b < 10 ORDER BY t1.a; + a | c | d | a | c | d +----+------+---+---+------+--- + -1 | | | | | + 1 | 0001 | 1 | | | + 3 | 0003 | 3 | 3 | 0003 | 3 + 4 | 0004 | 4 | 4 | 0004 | 4 + 6 | 0006 | 6 | 6 | 0006 | 6 + 8 | 0008 | 8 | | | + 9 | 0009 | 9 | 9 | 0009 | 9 +(7 rows) + +-- full join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 FULL JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE coalesce(t1.b, 0) < 10 AND coalesce(t2.b, 0) < 10 ORDER BY t1.a, t2.a; + QUERY PLAN +------------------------------------------------------------------------------------------ + Sort + Sort Key: t1.a, t2.a + -> Append + -> Hash Full Join + Hash Cond: ((t1_1.a = t2_1.a) AND (t1_1.c = t2_1.c) AND (t1_1.d = t2_1.d)) + Filter: ((COALESCE(t1_1.b, 0) < 10) AND (COALESCE(t2_1.b, 0) < 10)) + -> Seq Scan on plt1_adv_m_p1 t1_1 + -> Hash + -> Seq Scan on plt2_adv_m_p1 t2_1 + -> Hash Full Join + Hash Cond: ((t1_2.a = t2_2.a) AND (t1_2.c = t2_2.c) AND (t1_2.d = t2_2.d)) + Filter: ((COALESCE(t1_2.b, 0) < 10) AND (COALESCE(t2_2.b, 0) < 10)) + -> Seq Scan on plt1_adv_m_p2 t1_2 + -> Hash + -> Seq Scan on plt2_adv_m_p2 t2_2 + -> Hash Full Join + Hash Cond: ((t1_3.a = t2_3.a) AND (t1_3.c = t2_3.c) AND (t1_3.d = t2_3.d)) + Filter: ((COALESCE(t1_3.b, 0) < 10) AND (COALESCE(t2_3.b, 0) < 10)) + -> Seq Scan on plt1_adv_m_p3 t1_3 + -> Hash + -> Seq Scan on plt2_adv_m_p3 t2_3 + -> Hash Full Join + Hash Cond: ((t1_4.a = t2_4.a) AND (t1_4.c = t2_4.c) AND (t1_4.d = t2_4.d)) + Filter: ((COALESCE(t1_4.b, 0) < 10) AND (COALESCE(t2_4.b, 0) < 10)) + -> Seq Scan on plt1_adv_m_extra t1_4 + -> Hash + -> Seq Scan on plt2_adv_m_extra t2_4 +(27 rows) + +SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 FULL JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE coalesce(t1.b, 0) < 10 AND coalesce(t2.b, 0) < 10 ORDER BY t1.a, t2.a; + a | c | d | a | c | d +----+------+---+----+------+--- + -1 | | | | | + 1 | 0001 | 1 | | | + 3 | 0003 | 3 | 3 | 0003 | 3 + 4 | 0004 | 4 | 4 | 0004 | 4 + 6 | 0006 | 6 | 6 | 0006 | 6 + 8 | 0008 | 8 | | | + 9 | 0009 | 9 | 9 | 0009 | 9 + | | | -1 | | + | | | 2 | 0002 | 2 + | | | 7 | 0007 | 7 +(10 rows) + +-- 3-way join to test the NULL partition of a join relation +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d, t3.a, t3.c, t3.d FROM plt1_adv_m t1 LEFT JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) LEFT JOIN plt1_adv_m t3 ON (t1.a = t3.a AND t1.c = t3.c AND t1.d = t3.d) WHERE t1.b < 10 ORDER BY t1.a; + QUERY PLAN +------------------------------------------------------------------------------------------------------ + Sort + Sort Key: t1.a + -> Append + -> Hash Right Join + Hash Cond: ((t3_1.a = t1_1.a) AND (t3_1.c = t1_1.c) AND (t3_1.d = t1_1.d)) + -> Seq Scan on plt1_adv_m_p1 t3_1 + -> Hash + -> Hash Right Join + Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c) AND (t2_1.d = t1_1.d)) + -> Seq Scan on plt2_adv_m_p1 t2_1 + -> Hash + -> Seq Scan on plt1_adv_m_p1 t1_1 + Filter: (b < 10) + -> Hash Right Join + Hash Cond: ((t3_2.a = t1_2.a) AND (t3_2.c = t1_2.c) AND (t3_2.d = t1_2.d)) + -> Seq Scan on plt1_adv_m_p2 t3_2 + -> Hash + -> Hash Right Join + Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c) AND (t2_2.d = t1_2.d)) + -> Seq Scan on plt2_adv_m_p2 t2_2 + -> Hash + -> Seq Scan on plt1_adv_m_p2 t1_2 + Filter: (b < 10) + -> Hash Right Join + Hash Cond: ((t3_3.a = t1_3.a) AND (t3_3.c = t1_3.c) AND (t3_3.d = t1_3.d)) + -> Seq Scan on plt1_adv_m_p3 t3_3 + -> Hash + -> Hash Right Join + Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c) AND (t2_3.d = t1_3.d)) + -> Seq Scan on plt2_adv_m_p3 t2_3 + -> Hash + -> Seq Scan on plt1_adv_m_p3 t1_3 + Filter: (b < 10) + -> Nested Loop Left Join + Join Filter: ((t1_4.a = t3_4.a) AND (t1_4.c = t3_4.c) AND (t1_4.d = t3_4.d)) + -> Nested Loop Left Join + Join Filter: ((t1_4.a = t2_4.a) AND (t1_4.c = t2_4.c) AND (t1_4.d = t2_4.d)) + -> Seq Scan on plt1_adv_m_extra t1_4 + Filter: (b < 10) + -> Seq Scan on plt2_adv_m_extra t2_4 + -> Seq Scan on plt1_adv_m_extra t3_4 +(41 rows) + +SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d, t3.a, t3.c, t3.d FROM plt1_adv_m t1 LEFT JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) LEFT JOIN plt1_adv_m t3 ON (t1.a = t3.a AND t1.c = t3.c AND t1.d = t3.d) WHERE t1.b < 10 ORDER BY t1.a; + a | c | d | a | c | d | a | c | d +----+------+---+---+------+---+---+------+--- + -1 | | | | | | | | + 1 | 0001 | 1 | | | | 1 | 0001 | 1 + 3 | 0003 | 3 | 3 | 0003 | 3 | 3 | 0003 | 3 + 4 | 0004 | 4 | 4 | 0004 | 4 | 4 | 0004 | 4 + 6 | 0006 | 6 | 6 | 0006 | 6 | 6 | 0006 | 6 + 8 | 0008 | 8 | | | | 8 | 0008 | 8 + 9 | 0009 | 9 | 9 | 0009 | 9 | 9 | 0009 | 9 +(7 rows) + +DROP TABLE plt1_adv_m_extra; +DROP TABLE plt2_adv_m_extra; +-- Multiple NULL test +CREATE TABLE plt1_adv_m_p4 PARTITION OF plt1_adv_m FOR VALUES IN (('0005', NULL)); +CREATE TABLE plt1_adv_m_p5 PARTITION OF plt1_adv_m FOR VALUES IN (('0010', NULL), (NULL, 10)); +INSERT INTO plt1_adv_m VALUES (-1, -1, '0005', NULL); +INSERT INTO plt1_adv_m VALUES (-1, -1, '0010', NULL); +INSERT INTO plt1_adv_m VALUES (-1, -1, NULL, 10); +ANALYZE plt1_adv_m; +CREATE TABLE plt2_adv_m_p4 PARTITION OF plt2_adv_m FOR VALUES IN ((NULL, 5)); +CREATE TABLE plt2_adv_m_p5 PARTITION OF plt2_adv_m FOR VALUES IN (('0010', NULL), (NULL, 10)); +INSERT INTO plt2_adv_m VALUES (-1, -1, '0005', NULL); +ERROR: no partition of relation "plt2_adv_m" found for row +DETAIL: Partition key of the failing row contains (c, d) = (0005, null). +INSERT INTO plt2_adv_m VALUES (-1, -1, '0010', NULL); +INSERT INTO plt2_adv_m VALUES (-1, -1, NULL, 10); +ANALYZE plt2_adv_m; +-- inner join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 INNER JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.a < 10 ORDER BY t1.a; + QUERY PLAN +------------------------------------------------------------------------------------------ + Sort + Sort Key: t1.a + -> Append + -> Hash Join + Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c) AND (t2_1.d = t1_1.d)) + -> Seq Scan on plt2_adv_m_p1 t2_1 + -> Hash + -> Seq Scan on plt1_adv_m_p1 t1_1 + Filter: (a < 10) + -> Hash Join + Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c) AND (t2_2.d = t1_2.d)) + -> Seq Scan on plt2_adv_m_p2 t2_2 + -> Hash + -> Seq Scan on plt1_adv_m_p2 t1_2 + Filter: (a < 10) + -> Hash Join + Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.c = t1_3.c) AND (t2_3.d = t1_3.d)) + -> Seq Scan on plt2_adv_m_p3 t2_3 + -> Hash + -> Seq Scan on plt1_adv_m_p3 t1_3 + Filter: (a < 10) +(21 rows) + +SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 INNER JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.a < 10 ORDER BY t1.a; + a | c | d | a | c | d +---+------+---+---+------+--- + 3 | 0003 | 3 | 3 | 0003 | 3 + 4 | 0004 | 4 | 4 | 0004 | 4 + 6 | 0006 | 6 | 6 | 0006 | 6 + 9 | 0009 | 9 | 9 | 0009 | 9 +(4 rows) + +-- semi join +EXPLAIN (COSTS OFF) +SELECT t1.* FROM plt1_adv_m t1 WHERE EXISTS (SELECT 1 FROM plt2_adv_m t2 WHERE t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) AND t1.a < 10 ORDER BY t1.a; + QUERY PLAN +-------------------------------------------------------------------------------------------- + Sort + Sort Key: t1.a + -> Append + -> Nested Loop Semi Join + Join Filter: ((t1_1.a = t2_1.a) AND (t1_1.c = t2_1.c) AND (t1_1.d = t2_1.d)) + -> Seq Scan on plt1_adv_m_p1 t1_1 + Filter: (a < 10) + -> Seq Scan on plt2_adv_m_p1 t2_1 + -> Nested Loop Semi Join + Join Filter: ((t1_2.a = t2_2.a) AND (t1_2.c = t2_2.c) AND (t1_2.d = t2_2.d)) + -> Seq Scan on plt1_adv_m_p2 t1_2 + Filter: (a < 10) + -> Seq Scan on plt2_adv_m_p2 t2_2 + -> Nested Loop Semi Join + Join Filter: ((t1_3.a = t2_3.a) AND (t1_3.c = t2_3.c) AND (t1_3.d = t2_3.d)) + -> Seq Scan on plt1_adv_m_p3 t1_3 + Filter: (a < 10) + -> Seq Scan on plt2_adv_m_p3 t2_3 +(18 rows) + +SELECT t1.* FROM plt1_adv_m t1 WHERE EXISTS (SELECT 1 FROM plt2_adv_m t2 WHERE t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) AND t1.a < 10 ORDER BY t1.a; + a | b | c | d +---+---+------+--- + 3 | 3 | 0003 | 3 + 4 | 4 | 0004 | 4 + 6 | 6 | 0006 | 6 + 9 | 9 | 0009 | 9 +(4 rows) + +-- left join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 LEFT JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.a < 10 ORDER BY t1.a; + QUERY PLAN +------------------------------------------------------------------------ + Sort + Sort Key: t1.a + -> Hash Right Join + Hash Cond: ((t2.a = t1.a) AND (t2.c = t1.c) AND (t2.d = t1.d)) + -> Append + -> Seq Scan on plt2_adv_m_p1 t2_1 + -> Seq Scan on plt2_adv_m_p2 t2_2 + -> Seq Scan on plt2_adv_m_p3 t2_3 + -> Seq Scan on plt2_adv_m_p5 t2_4 + -> Seq Scan on plt2_adv_m_p4 t2_5 + -> Hash + -> Append + -> Seq Scan on plt1_adv_m_p1 t1_1 + Filter: (a < 10) + -> Seq Scan on plt1_adv_m_p2 t1_2 + Filter: (a < 10) + -> Seq Scan on plt1_adv_m_p4 t1_3 + Filter: (a < 10) + -> Seq Scan on plt1_adv_m_p3 t1_4 + Filter: (a < 10) + -> Seq Scan on plt1_adv_m_p5 t1_5 + Filter: (a < 10) +(22 rows) + +SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 LEFT JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.a < 10 ORDER BY t1.a; + a | c | d | a | c | d +----+------+----+---+------+--- + -1 | 0010 | | | | + -1 | | 10 | | | + -1 | 0005 | | | | + 1 | 0001 | 1 | | | + 3 | 0003 | 3 | 3 | 0003 | 3 + 4 | 0004 | 4 | 4 | 0004 | 4 + 6 | 0006 | 6 | 6 | 0006 | 6 + 8 | 0008 | 8 | | | + 9 | 0009 | 9 | 9 | 0009 | 9 +(9 rows) + +-- anti join +EXPLAIN (COSTS OFF) +SELECT t1.* FROM plt1_adv_m t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv_m t2 WHERE t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) AND t1.a < 10 ORDER BY t1.a; + QUERY PLAN +------------------------------------------------------------------------ + Sort + Sort Key: t1.a + -> Hash Anti Join + Hash Cond: ((t1.a = t2.a) AND (t1.c = t2.c) AND (t1.d = t2.d)) + -> Append + -> Seq Scan on plt1_adv_m_p1 t1_1 + Filter: (a < 10) + -> Seq Scan on plt1_adv_m_p2 t1_2 + Filter: (a < 10) + -> Seq Scan on plt1_adv_m_p4 t1_3 + Filter: (a < 10) + -> Seq Scan on plt1_adv_m_p3 t1_4 + Filter: (a < 10) + -> Seq Scan on plt1_adv_m_p5 t1_5 + Filter: (a < 10) + -> Hash + -> Append + -> Seq Scan on plt2_adv_m_p1 t2_1 + -> Seq Scan on plt2_adv_m_p2 t2_2 + -> Seq Scan on plt2_adv_m_p3 t2_3 + -> Seq Scan on plt2_adv_m_p5 t2_4 + -> Seq Scan on plt2_adv_m_p4 t2_5 +(22 rows) + +SELECT t1.* FROM plt1_adv_m t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv_m t2 WHERE t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) AND t1.a < 10 ORDER BY t1.a; + a | b | c | d +----+----+------+---- + -1 | -1 | 0005 | + -1 | -1 | 0010 | + -1 | -1 | | 10 + 1 | 1 | 0001 | 1 + 8 | 8 | 0008 | 8 +(5 rows) + +-- full join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 FULL JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE coalesce(t1.a, 0) < 10 AND coalesce(t2.b, 0) < 10 ORDER BY t1.a, t2.a; + QUERY PLAN +------------------------------------------------------------------------- + Sort + Sort Key: t1.a, t2.a + -> Hash Full Join + Hash Cond: ((t1.a = t2.a) AND (t1.c = t2.c) AND (t1.d = t2.d)) + Filter: ((COALESCE(t1.a, 0) < 10) AND (COALESCE(t2.b, 0) < 10)) + -> Append + -> Seq Scan on plt1_adv_m_p1 t1_1 + -> Seq Scan on plt1_adv_m_p2 t1_2 + -> Seq Scan on plt1_adv_m_p4 t1_3 + -> Seq Scan on plt1_adv_m_p3 t1_4 + -> Seq Scan on plt1_adv_m_p5 t1_5 + -> Hash + -> Append + -> Seq Scan on plt2_adv_m_p1 t2_1 + -> Seq Scan on plt2_adv_m_p2 t2_2 + -> Seq Scan on plt2_adv_m_p3 t2_3 + -> Seq Scan on plt2_adv_m_p5 t2_4 + -> Seq Scan on plt2_adv_m_p4 t2_5 +(18 rows) + +SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 FULL JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE coalesce(t1.a, 0) < 10 AND coalesce(t2.b, 0) < 10 ORDER BY t1.a, t2.a; + a | c | d | a | c | d +----+------+----+----+------+---- + -1 | 0010 | | | | + -1 | 0005 | | | | + -1 | | 10 | | | + 1 | 0001 | 1 | | | + 3 | 0003 | 3 | 3 | 0003 | 3 + 4 | 0004 | 4 | 4 | 0004 | 4 + 6 | 0006 | 6 | 6 | 0006 | 6 + 8 | 0008 | 8 | | | + 9 | 0009 | 9 | 9 | 0009 | 9 + | | | -1 | 0010 | + | | | -1 | | 10 + | | | 2 | 0002 | 2 + | | | 7 | 0007 | 7 +(13 rows) + -- Tests for multi-level partitioned tables CREATE TABLE alpha (a double precision, b int, c text) PARTITION BY RANGE (a); CREATE TABLE alpha_neg PARTITION OF alpha FOR VALUES FROM ('-Infinity') TO (0) PARTITION BY RANGE (b); diff --git a/src/test/regress/sql/partition_join.sql b/src/test/regress/sql/partition_join.sql index d97b5b6..ca0ec38 100644 --- a/src/test/regress/sql/partition_join.sql +++ b/src/test/regress/sql/partition_join.sql @@ -1100,6 +1100,263 @@ DROP TABLE plt2_adv; DROP TABLE plt3_adv; +-- Tests for multi-column list-partitioned tables +CREATE TABLE plt1_adv_m (a int, b int, c text, d int) PARTITION BY LIST (c, d); +CREATE TABLE plt1_adv_m_p1 PARTITION OF plt1_adv_m FOR VALUES IN (('0001', 1), ('0003', 3)); +CREATE TABLE plt1_adv_m_p2 PARTITION OF plt1_adv_m FOR VALUES IN (('0004', 4), ('0006', 6)); +CREATE TABLE plt1_adv_m_p3 PARTITION OF plt1_adv_m FOR VALUES IN (('0008', 8), ('0009', 9)); +INSERT INTO plt1_adv_m SELECT i, i, to_char(i % 10, 'FM0000'), (i % 10) FROM generate_series(1, 299) i WHERE i % 10 IN (1, 3, 4, 6, 8, 9); +ANALYZE plt1_adv_m; + +CREATE TABLE plt2_adv_m (a int, b int, c text, d int) PARTITION BY LIST (c, d); +CREATE TABLE plt2_adv_m_p1 PARTITION OF plt2_adv_m FOR VALUES IN (('0002', 2), ('0003', 3)); +CREATE TABLE plt2_adv_m_p2 PARTITION OF plt2_adv_m FOR VALUES IN (('0004', 4), ('0006', 6)); +CREATE TABLE plt2_adv_m_p3 PARTITION OF plt2_adv_m FOR VALUES IN (('0007', 7), ('0009', 9)); +INSERT INTO plt2_adv_m SELECT i, i, to_char(i % 10, 'FM0000'), (i % 10) FROM generate_series(1, 299) i WHERE i % 10 IN (2, 3, 4, 6, 7, 9); +ANALYZE plt2_adv_m; + +-- inner join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 INNER JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.a < 10 ORDER BY t1.a; +SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 INNER JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.a < 10 ORDER BY t1.a; + +-- semi join +EXPLAIN (COSTS OFF) +SELECT t1.* FROM plt1_adv_m t1 WHERE EXISTS (SELECT 1 FROM plt2_adv_m t2 WHERE t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) AND t1.a < 10 ORDER BY t1.a; +SELECT t1.* FROM plt1_adv_m t1 WHERE EXISTS (SELECT 1 FROM plt2_adv_m t2 WHERE t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) AND t1.a < 10 ORDER BY t1.a; + +-- left join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 LEFT JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.a < 10 ORDER BY t1.a; +SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 LEFT JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.a < 10 ORDER BY t1.a; + +-- anti join +EXPLAIN (COSTS OFF) +SELECT t1.* FROM plt1_adv_m t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv_m t2 WHERE t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) AND t1.a < 10 ORDER BY t1.a; +SELECT t1.* FROM plt1_adv_m t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv_m t2 WHERE t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) AND t1.a < 10 ORDER BY t1.a; + +-- full join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 FULL JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE coalesce(t1.a, 0) < 10 AND coalesce(t2.b, 0) < 10 ORDER BY t1.a, t2.a; +SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 FULL JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE coalesce(t1.a, 0) < 10 AND coalesce(t2.b, 0) < 10 ORDER BY t1.a, t2.a; + +-- Test cases where one side has an extra partition +CREATE TABLE plt2_adv_m_extra PARTITION OF plt2_adv_m FOR VALUES IN (('0000', 0)); +INSERT INTO plt2_adv_m_extra VALUES (0, 0, '0000', 0); +ANALYZE plt2_adv_m; + +-- inner join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 INNER JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.b < 10 ORDER BY t1.a; +SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 INNER JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.b < 10 ORDER BY t1.a; + +-- semi join +EXPLAIN (COSTS OFF) +SELECT t1.* FROM plt1_adv_m t1 WHERE EXISTS (SELECT 1 FROM plt2_adv_m t2 WHERE t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) AND t1.b < 10 ORDER BY t1.a; +SELECT t1.* FROM plt1_adv_m t1 WHERE EXISTS (SELECT 1 FROM plt2_adv_m t2 WHERE t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) AND t1.b < 10 ORDER BY t1.a; + +-- left join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 LEFT JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.b < 10 ORDER BY t1.a; +SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 LEFT JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.b < 10 ORDER BY t1.a; + +-- left join; currently we can't do partitioned join if there are no matched +-- partitions on the nullable side +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt2_adv_m t1 LEFT JOIN plt1_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.b < 10 ORDER BY t1.a; + +-- anti join +EXPLAIN (COSTS OFF) +SELECT t1.* FROM plt1_adv_m t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv_m t2 WHERE t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) AND t1.b < 10 ORDER BY t1.a; +SELECT t1.* FROM plt1_adv_m t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv_m t2 WHERE t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) AND t1.b < 10 ORDER BY t1.a; + +-- anti join; currently we can't do partitioned join if there are no matched +-- partitions on the nullable side +EXPLAIN (COSTS OFF) +SELECT t1.* FROM plt2_adv_m t1 WHERE NOT EXISTS (SELECT 1 FROM plt1_adv_m t2 WHERE t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) AND t1.b < 10 ORDER BY t1.a; + +-- full join; currently we can't do partitioned join if there are no matched +-- partitions on the nullable side +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 FULL JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE coalesce(t1.b, 0) < 10 AND coalesce(t2.b, 0) < 10 ORDER BY t1.a, t2.a; + +DROP TABLE plt2_adv_m_extra; + +-- Test cases where a partition on one side matches multiple partitions on +-- the other side; we currently can't do partitioned join in such cases +ALTER TABLE plt2_adv_m DETACH PARTITION plt2_adv_m_p2; +-- Split plt2_adv_p2 into two partitions so that plt1_adv_p2 matches both +CREATE TABLE plt2_adv_m_p2_1 PARTITION OF plt2_adv_m FOR VALUES IN (('0004', 4)); +CREATE TABLE plt2_adv_m_p2_2 PARTITION OF plt2_adv_m FOR VALUES IN (('0006', 6)); +INSERT INTO plt2_adv_m SELECT i, i, to_char(i % 10, 'FM0000'), (i % 10) FROM generate_series(1, 299) i WHERE i % 10 IN (4, 6); +ANALYZE plt2_adv_m; + +-- inner join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 INNER JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.b < 10 ORDER BY t1.a; + +-- semi join +EXPLAIN (COSTS OFF) +SELECT t1.* FROM plt1_adv_m t1 WHERE EXISTS (SELECT 1 FROM plt2_adv_m t2 WHERE t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) AND t1.b < 10 ORDER BY t1.a; + +-- left join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 LEFT JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.b < 10 ORDER BY t1.a; + +-- anti join +EXPLAIN (COSTS OFF) +SELECT t1.* FROM plt1_adv_m t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv_m t2 WHERE t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) AND t1.b < 10 ORDER BY t1.a; + +-- full join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 FULL JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE coalesce(t1.b, 0) < 10 AND coalesce(t2.b, 0) < 10 ORDER BY t1.a, t2.a; + +DROP TABLE plt2_adv_m_p2_1; +DROP TABLE plt2_adv_m_p2_2; +-- Restore plt2_adv_p2 +ALTER TABLE plt2_adv_m ATTACH PARTITION plt2_adv_m_p2 FOR VALUES IN (('0004', 4), ('0006', 6)); + + +-- Test NULL partitions +ALTER TABLE plt1_adv_m DETACH PARTITION plt1_adv_m_p1; +-- Change plt1_adv_p1 to the NULL partition +CREATE TABLE plt1_adv_m_p1_null PARTITION OF plt1_adv_m FOR VALUES IN ((NULL, NULL), ('0001', 1), ('0003', 3)); +INSERT INTO plt1_adv_m SELECT i, i, to_char(i % 10, 'FM0000'), (i % 10) FROM generate_series(1, 299) i WHERE i % 10 IN (1, 3); +INSERT INTO plt1_adv_m VALUES (-1, -1, NULL, NULL); +ANALYZE plt1_adv_m; + +ALTER TABLE plt2_adv_m DETACH PARTITION plt2_adv_m_p3; +-- Change plt2_adv_p3 to the NULL partition +CREATE TABLE plt2_adv_m_p3_null PARTITION OF plt2_adv_m FOR VALUES IN ((NULL, NULL), ('0007', 7), ('0009', 9)); +INSERT INTO plt2_adv_m SELECT i, i, to_char(i % 10, 'FM0000'), (i % 10) FROM generate_series(1, 299) i WHERE i % 10 IN (7, 9); +INSERT INTO plt2_adv_m VALUES (-1, -1, NULL, NULL); +ANALYZE plt2_adv_m; + +-- inner join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 INNER JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.b < 10 ORDER BY t1.a; +SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 INNER JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.b < 10 ORDER BY t1.a; + +-- semi join +EXPLAIN (COSTS OFF) +SELECT t1.* FROM plt1_adv_m t1 WHERE EXISTS (SELECT 1 FROM plt2_adv_m t2 WHERE t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) AND t1.b < 10 ORDER BY t1.a; +SELECT t1.* FROM plt1_adv_m t1 WHERE EXISTS (SELECT 1 FROM plt2_adv_m t2 WHERE t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) AND t1.b < 10 ORDER BY t1.a; + +-- left join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 LEFT JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.b < 10 ORDER BY t1.a; +SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 LEFT JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.b < 10 ORDER BY t1.a; + +-- anti join +EXPLAIN (COSTS OFF) +SELECT t1.* FROM plt1_adv_m t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv_m t2 WHERE t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) AND t1.b < 10 ORDER BY t1.a; +SELECT t1.* FROM plt1_adv_m t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv_m t2 WHERE t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) AND t1.b < 10 ORDER BY t1.a; + +-- full join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 FULL JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE coalesce(t1.b, 0) < 10 AND coalesce(t2.b, 0) < 10 ORDER BY t1.a, t2.a; +SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 FULL JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE coalesce(t1.b, 0) < 10 AND coalesce(t2.b, 0) < 10 ORDER BY t1.a, t2.a; + +DROP TABLE plt1_adv_m_p1_null; +-- Restore plt1_adv_p1 +ALTER TABLE plt1_adv_m ATTACH PARTITION plt1_adv_m_p1 FOR VALUES IN (('0001', 1), ('0003', 3)); + +-- Add to plt1_adv the extra NULL partition containing only NULL values as the +-- key values +CREATE TABLE plt1_adv_m_extra PARTITION OF plt1_adv_m FOR VALUES IN ((NULL, NULL)); +INSERT INTO plt1_adv_m VALUES (-1, -1, NULL, NULL); +ANALYZE plt1_adv_m; + +DROP TABLE plt2_adv_m_p3_null; +-- Restore plt2_adv_p3 +ALTER TABLE plt2_adv_m ATTACH PARTITION plt2_adv_m_p3 FOR VALUES IN (('0007', 7), ('0009', 9)); +ANALYZE plt2_adv_m; + +-- inner join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 INNER JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.b < 10 ORDER BY t1.a; +SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 INNER JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.b < 10 ORDER BY t1.a; + +-- left join; currently we can't do partitioned join if there are no matched +-- partitions on the nullable side +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 LEFT JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.b < 10 ORDER BY t1.a; + +-- full join; currently we can't do partitioned join if there are no matched +-- partitions on the nullable side +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 FULL JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE coalesce(t1.b, 0) < 10 AND coalesce(t2.b, 0) < 10 ORDER BY t1.a, t2.a; + + +-- Add to plt2_adv the extra NULL partition containing only NULL values as the +-- key values +CREATE TABLE plt2_adv_m_extra PARTITION OF plt2_adv_m FOR VALUES IN ((NULL, NULL)); +INSERT INTO plt2_adv_m VALUES (-1, -1, NULL, NULL); +ANALYZE plt2_adv_m; + +-- inner join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 INNER JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.b < 10 ORDER BY t1.a; +SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 INNER JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.b < 10 ORDER BY t1.a; + +-- left join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 LEFT JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.b < 10 ORDER BY t1.a; +SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 LEFT JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.b < 10 ORDER BY t1.a; + +-- full join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 FULL JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE coalesce(t1.b, 0) < 10 AND coalesce(t2.b, 0) < 10 ORDER BY t1.a, t2.a; +SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 FULL JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE coalesce(t1.b, 0) < 10 AND coalesce(t2.b, 0) < 10 ORDER BY t1.a, t2.a; + +-- 3-way join to test the NULL partition of a join relation +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d, t3.a, t3.c, t3.d FROM plt1_adv_m t1 LEFT JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) LEFT JOIN plt1_adv_m t3 ON (t1.a = t3.a AND t1.c = t3.c AND t1.d = t3.d) WHERE t1.b < 10 ORDER BY t1.a; +SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d, t3.a, t3.c, t3.d FROM plt1_adv_m t1 LEFT JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) LEFT JOIN plt1_adv_m t3 ON (t1.a = t3.a AND t1.c = t3.c AND t1.d = t3.d) WHERE t1.b < 10 ORDER BY t1.a; + +DROP TABLE plt1_adv_m_extra; +DROP TABLE plt2_adv_m_extra; + +-- Multiple NULL test +CREATE TABLE plt1_adv_m_p4 PARTITION OF plt1_adv_m FOR VALUES IN (('0005', NULL)); +CREATE TABLE plt1_adv_m_p5 PARTITION OF plt1_adv_m FOR VALUES IN (('0010', NULL), (NULL, 10)); +INSERT INTO plt1_adv_m VALUES (-1, -1, '0005', NULL); +INSERT INTO plt1_adv_m VALUES (-1, -1, '0010', NULL); +INSERT INTO plt1_adv_m VALUES (-1, -1, NULL, 10); +ANALYZE plt1_adv_m; + +CREATE TABLE plt2_adv_m_p4 PARTITION OF plt2_adv_m FOR VALUES IN ((NULL, 5)); +CREATE TABLE plt2_adv_m_p5 PARTITION OF plt2_adv_m FOR VALUES IN (('0010', NULL), (NULL, 10)); +INSERT INTO plt2_adv_m VALUES (-1, -1, '0005', NULL); +INSERT INTO plt2_adv_m VALUES (-1, -1, '0010', NULL); +INSERT INTO plt2_adv_m VALUES (-1, -1, NULL, 10); +ANALYZE plt2_adv_m; + +-- inner join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 INNER JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.a < 10 ORDER BY t1.a; +SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 INNER JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.a < 10 ORDER BY t1.a; + +-- semi join +EXPLAIN (COSTS OFF) +SELECT t1.* FROM plt1_adv_m t1 WHERE EXISTS (SELECT 1 FROM plt2_adv_m t2 WHERE t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) AND t1.a < 10 ORDER BY t1.a; +SELECT t1.* FROM plt1_adv_m t1 WHERE EXISTS (SELECT 1 FROM plt2_adv_m t2 WHERE t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) AND t1.a < 10 ORDER BY t1.a; + +-- left join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 LEFT JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.a < 10 ORDER BY t1.a; +SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 LEFT JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE t1.a < 10 ORDER BY t1.a; + +-- anti join +EXPLAIN (COSTS OFF) +SELECT t1.* FROM plt1_adv_m t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv_m t2 WHERE t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) AND t1.a < 10 ORDER BY t1.a; +SELECT t1.* FROM plt1_adv_m t1 WHERE NOT EXISTS (SELECT 1 FROM plt2_adv_m t2 WHERE t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) AND t1.a < 10 ORDER BY t1.a; + +-- full join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 FULL JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE coalesce(t1.a, 0) < 10 AND coalesce(t2.b, 0) < 10 ORDER BY t1.a, t2.a; +SELECT t1.a, t1.c, t1.d, t2.a, t2.c, t2.d FROM plt1_adv_m t1 FULL JOIN plt2_adv_m t2 ON (t1.a = t2.a AND t1.c = t2.c AND t1.d = t2.d) WHERE coalesce(t1.a, 0) < 10 AND coalesce(t2.b, 0) < 10 ORDER BY t1.a, t2.a; + -- Tests for multi-level partitioned tables CREATE TABLE alpha (a double precision, b int, c text) PARTITION BY RANGE (a); CREATE TABLE alpha_neg PARTITION OF alpha FOR VALUES FROM ('-Infinity') TO (0) PARTITION BY RANGE (b); -- 1.8.3.1