From 89af632922c18e43e542b98fd540ba792f584013 Mon Sep 17 00:00:00 2001 From: Etsuro Fujita Date: Fri, 22 Nov 2019 21:46:26 +0900 Subject: [PATCH 1/2] Improve partition matching for partitionwise join --- src/backend/nodes/outfuncs.c | 2 + src/backend/optimizer/path/joinrels.c | 246 +- src/backend/optimizer/util/inherit.c | 2 + src/backend/optimizer/util/relnode.c | 45 +- src/backend/partitioning/partbounds.c | 1607 +++++++ src/include/nodes/pathnodes.h | 5 + src/include/partitioning/partbounds.h | 7 + src/test/regress/expected/partition_join.out | 4429 +++++++++++++++--- src/test/regress/sql/partition_join.sql | 482 +- 9 files changed, 6047 insertions(+), 778 deletions(-) diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index b0dcd02ff6..b815157402 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -2281,6 +2281,8 @@ _outRelOptInfo(StringInfo str, const RelOptInfo *node) WRITE_BOOL_FIELD(has_eclass_joins); WRITE_BOOL_FIELD(consider_partitionwise_join); WRITE_BITMAPSET_FIELD(top_parent_relids); + WRITE_BOOL_FIELD(merged); + WRITE_BITMAPSET_FIELD(all_partrels); WRITE_NODE_FIELD(partitioned_child_rels); } diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index 6a480ab764..ed7bc23c7b 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -48,6 +48,9 @@ static SpecialJoinInfo *build_child_join_sjinfo(PlannerInfo *root, Relids left_relids, Relids right_relids); static int match_expr_to_partition_keys(Expr *expr, RelOptInfo *rel, bool strict_op); +static void get_matching_part_pairs(PlannerInfo *root, RelOptInfo *joinrel, + RelOptInfo *rel1, RelOptInfo *rel2, + List **parts1, List **parts2); /* @@ -1357,25 +1360,30 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, { bool rel1_is_simple = IS_SIMPLE_REL(rel1); bool rel2_is_simple = IS_SIMPLE_REL(rel2); - int nparts; + bool merged = false; + List *parts1 = NIL; + List *parts2 = NIL; + ListCell *lcr1 = NULL; + ListCell *lcr2 = NULL; int cnt_parts; /* Guard against stack overflow due to overly deep partition hierarchy. */ check_stack_depth(); /* Nothing to do, if the join relation is not partitioned. */ - if (!IS_PARTITIONED_REL(joinrel)) + if (joinrel->part_scheme == NULL || joinrel->nparts == 0) return; /* The join relation should have consider_partitionwise_join set. */ Assert(joinrel->consider_partitionwise_join); /* - * Since this join relation is partitioned, all the base relations - * participating in this join must be partitioned and so are all the - * intermediate join relations. + * We can not perform partition-wise join if either of the joining + * relations is not partitioned. */ - Assert(IS_PARTITIONED_REL(rel1) && IS_PARTITIONED_REL(rel2)); + if (!IS_PARTITIONED_REL(rel1) || !IS_PARTITIONED_REL(rel2)) + return; + Assert(REL_HAS_ALL_PART_PROPS(rel1) && REL_HAS_ALL_PART_PROPS(rel2)); /* The joining relations should have consider_partitionwise_join set. */ @@ -1390,34 +1398,107 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, joinrel->part_scheme == rel2->part_scheme); /* - * Since we allow partitionwise join only when the partition bounds of the - * joining relations exactly match, the partition bounds of the join - * should match those of the joining relations. + * If we don't have the partition bounds for the join rel yet, try to + * create it along with pairs of partitions to be joined; else generate + * those using the partitioning info for the join rel we already have. */ - Assert(partition_bounds_equal(joinrel->part_scheme->partnatts, - joinrel->part_scheme->parttyplen, - joinrel->part_scheme->parttypbyval, - joinrel->boundinfo, rel1->boundinfo)); - Assert(partition_bounds_equal(joinrel->part_scheme->partnatts, - joinrel->part_scheme->parttyplen, - joinrel->part_scheme->parttypbyval, - joinrel->boundinfo, rel2->boundinfo)); + if (joinrel->nparts == -1) + { + PartitionScheme part_scheme = joinrel->part_scheme; + PartitionBoundInfo boundinfo = NULL; + int nparts = 0; + + Assert(joinrel->boundinfo == NULL); + Assert(joinrel->part_rels == NULL); + + /* + * See if the partition bounds for inputs are exactly the same, in + * which case we don't need to work hard: partitions with the same + * partition indexes will form join pairs, and the join rel will have + * the same partition bounds as inputs; otherwise try to merge the + * partition bounds along with generating join pairs. + * + * Even if one or both inputs have merged partition bounds, it'd be + * possible for the partition bounds to be exactly the same, but it + * seems unlikely to be worth the cycles to check; do this check only + * if both inputs have non-merged partition bounds. + */ + if (!rel1->merged && + !rel2->merged && + rel1->nparts == rel2->nparts && + partition_bounds_equal(part_scheme->partnatts, + part_scheme->parttyplen, + part_scheme->parttypbyval, + rel1->boundinfo, rel2->boundinfo)) + { + boundinfo = rel1->boundinfo; + nparts = rel1->nparts; + } + else + { + boundinfo = partition_bounds_merge(part_scheme->partnatts, + part_scheme->parttyplen, + part_scheme->parttypbyval, + part_scheme->partsupfunc, + part_scheme->partcollation, + rel1, rel2, + parent_sjinfo->jointype, + &parts1, &parts2); + if (boundinfo == NULL) + { + joinrel->nparts = 0; + return; + } + nparts = list_length(parts1); + merged = true; + } + + Assert(nparts > 0); + joinrel->boundinfo = boundinfo; + joinrel->merged = merged; + joinrel->nparts = nparts; + joinrel->part_rels = + (RelOptInfo **) palloc0(sizeof(RelOptInfo *) * nparts); + } + else + { + Assert(joinrel->nparts > 0); + Assert(joinrel->boundinfo); + Assert(joinrel->part_rels); + + /* + * If the partition bounds for the join rel are not merged ones, + * inputs are guaranteed to have the same partition bounds, so + * partitions with the same partition indexes will form join pairs; + * else let get_matching_part_pairs() do the work. + */ + if (joinrel->merged) + { + get_matching_part_pairs(root, joinrel, rel1, rel2, + &parts1, &parts2); + Assert(list_length(parts1) == joinrel->nparts); + Assert(list_length(parts2) == joinrel->nparts); + merged = true; + } + } - nparts = joinrel->nparts; + if (merged) + { + lcr1 = list_head(parts1); + lcr2 = list_head(parts2); + } /* * Create child-join relations for this partitioned join, if those don't * exist. Add paths to child-joins for a pair of child relations * corresponding to the given pair of parent relations. */ - for (cnt_parts = 0; cnt_parts < nparts; cnt_parts++) + for (cnt_parts = 0; cnt_parts < joinrel->nparts; cnt_parts++) { - RelOptInfo *child_rel1 = rel1->part_rels[cnt_parts]; - RelOptInfo *child_rel2 = rel2->part_rels[cnt_parts]; - bool rel1_empty = (child_rel1 == NULL || - IS_DUMMY_REL(child_rel1)); - bool rel2_empty = (child_rel2 == NULL || - IS_DUMMY_REL(child_rel2)); + RelOptInfo *child_rel1; + RelOptInfo *child_rel2; + bool rel1_empty; + bool rel2_empty; SpecialJoinInfo *child_sjinfo; List *child_restrictlist; RelOptInfo *child_joinrel; @@ -1425,6 +1506,22 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, AppendRelInfo **appinfos; int nappinfos; + if (merged) + { + child_rel1 = lfirst_node(RelOptInfo, lcr1); + child_rel2 = lfirst_node(RelOptInfo, lcr2); + lcr1 = lnext(parts1, lcr1); + lcr2 = lnext(parts2, lcr2); + } + else + { + child_rel1 = rel1->part_rels[cnt_parts]; + child_rel2 = rel2->part_rels[cnt_parts]; + } + + rel1_empty = (child_rel1 == NULL || IS_DUMMY_REL(child_rel1)); + rel2_empty = (child_rel2 == NULL || IS_DUMMY_REL(child_rel2)); + /* * Check for cases where we can prove that this segment of the join * returns no rows, due to one or both inputs being empty (including @@ -1522,6 +1619,8 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, child_sjinfo, child_sjinfo->jointype); joinrel->part_rels[cnt_parts] = child_joinrel; + joinrel->all_partrels = bms_add_members(joinrel->all_partrels, + child_joinrel->relids); } Assert(bms_equal(child_joinrel->relids, child_joinrelids)); @@ -1529,6 +1628,7 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, populate_joinrel_with_paths(root, child_rel1, child_rel2, child_joinrel, child_sjinfo, child_restrictlist); + } } @@ -1738,3 +1838,99 @@ match_expr_to_partition_keys(Expr *expr, RelOptInfo *rel, bool strict_op) return -1; } + +/* + * get_matching_part_pairs + * Generate join pairs of partitions for the two inputs + */ +static void +get_matching_part_pairs(PlannerInfo *root, RelOptInfo *joinrel, + RelOptInfo *rel1, RelOptInfo *rel2, + List **parts1, List **parts2) +{ + bool rel1_is_simple = IS_SIMPLE_REL(rel1); + bool rel2_is_simple = IS_SIMPLE_REL(rel2); + int cnt_parts; + + *parts1 = NIL; + *parts2 = NIL; + + for (cnt_parts = 0; cnt_parts < joinrel->nparts; cnt_parts++) + { + RelOptInfo *child_joinrel = joinrel->part_rels[cnt_parts]; + RelOptInfo *child_rel1; + RelOptInfo *child_rel2; + Relids child_relids1; + Relids child_relids2; + + /* + * If this segment of the join is empty, it means that this segment + * was ignored when previously creating child-join paths for it in + * try_partitionwise_join() as it would not contribute to the join + * result, due to one or both inputs being empty; add NULL to each of + * the given lists so that this segment will be ignored again in that + * function. + */ + if (!child_joinrel) + { + *parts1 = lappend(*parts1, NULL); + *parts2 = lappend(*parts2, NULL); + continue; + } + + /* + * Get a relids set of partition(s) involved in this join segment that + * are from the rel1 side. + */ + child_relids1 = bms_intersect(child_joinrel->relids, + rel1->all_partrels); + Assert(bms_num_members(child_relids1) == bms_num_members(rel1->relids)); + + /* + * Get a child rel for rel1 with the relids. Note that we should have + * the child rel even if rel1 is a join rel, because in that case the + * partitions specified in the relids would have matching/overlapping + * boundaries, so those partitions should be considered as ones to be + * joined even when planning partitionwise joins of rel1, meaning that + * the child rel would have been built by the time we get here. + */ + if (rel1_is_simple) + { + int varno = bms_singleton_member(child_relids1); + + child_rel1 = find_base_rel(root, varno); + } + else + child_rel1 = find_join_rel(root, child_relids1); + Assert(child_rel1); + + /* + * Get a relids set of partition(s) involved in this join segment that + * are from the rel2 side. + */ + child_relids2 = bms_intersect(child_joinrel->relids, + rel2->all_partrels); + Assert(bms_num_members(child_relids2) == bms_num_members(rel2->relids)); + + /* + * Get a child rel for rel2 with the relids. See above comments. + */ + if (rel2_is_simple) + { + int varno = bms_singleton_member(child_relids2); + + child_rel2 = find_base_rel(root, varno); + } + else + child_rel2 = find_join_rel(root, child_relids2); + Assert(child_rel2); + + /* + * The join of rel1 and rel2 is legal, so is the join of the child + * rels obtained above; add them to the given lists as a join pair + * producing this join segment. + */ + *parts1 = lappend(*parts1, child_rel1); + *parts2 = lappend(*parts2, child_rel2); + } +} diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c index 38bc61e687..caf6039c10 100644 --- a/src/backend/optimizer/util/inherit.c +++ b/src/backend/optimizer/util/inherit.c @@ -376,6 +376,8 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo, /* Create the otherrel RelOptInfo too. */ childrelinfo = build_simple_rel(root, childRTindex, relinfo); relinfo->part_rels[i] = childrelinfo; + relinfo->all_partrels = bms_add_members(relinfo->all_partrels, + childrelinfo->relids); /* If this child is itself partitioned, recurse */ if (childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index 03e02423b2..a1cf632932 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -240,10 +240,12 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) rel->has_eclass_joins = false; rel->consider_partitionwise_join = false; /* might get changed later */ rel->part_scheme = NULL; - rel->nparts = 0; + rel->nparts = -1; rel->boundinfo = NULL; + rel->merged = false; rel->partition_qual = NIL; rel->part_rels = NULL; + rel->all_partrels = NULL; rel->partexprs = NULL; rel->nullable_partexprs = NULL; rel->partitioned_child_rels = NIL; @@ -653,10 +655,12 @@ build_join_rel(PlannerInfo *root, joinrel->consider_partitionwise_join = false; /* might get changed later */ joinrel->top_parent_relids = NULL; joinrel->part_scheme = NULL; - joinrel->nparts = 0; + joinrel->nparts = -1; joinrel->boundinfo = NULL; + joinrel->merged = false; joinrel->partition_qual = NIL; joinrel->part_rels = NULL; + joinrel->all_partrels = NULL; joinrel->partexprs = NULL; joinrel->nullable_partexprs = NULL; joinrel->partitioned_child_rels = NIL; @@ -829,10 +833,12 @@ build_child_join_rel(PlannerInfo *root, RelOptInfo *outer_rel, joinrel->consider_partitionwise_join = false; /* might get changed later */ joinrel->top_parent_relids = NULL; joinrel->part_scheme = NULL; - joinrel->nparts = 0; + joinrel->nparts = -1; joinrel->boundinfo = NULL; + joinrel->merged = false; joinrel->partition_qual = NIL; joinrel->part_rels = NULL; + joinrel->all_partrels = NULL; joinrel->partexprs = NULL; joinrel->nullable_partexprs = NULL; joinrel->partitioned_child_rels = NIL; @@ -1639,7 +1645,7 @@ build_joinrel_partition_info(RelOptInfo *joinrel, RelOptInfo *outer_rel, * of the way the query planner deduces implied equalities and reorders * the joins. Please see optimizer/README for details. */ - if (!IS_PARTITIONED_REL(outer_rel) || !IS_PARTITIONED_REL(inner_rel) || + if (outer_rel->part_scheme == NULL || inner_rel->part_scheme == NULL || !outer_rel->consider_partitionwise_join || !inner_rel->consider_partitionwise_join || outer_rel->part_scheme != inner_rel->part_scheme || @@ -1652,24 +1658,6 @@ build_joinrel_partition_info(RelOptInfo *joinrel, RelOptInfo *outer_rel, part_scheme = outer_rel->part_scheme; - Assert(REL_HAS_ALL_PART_PROPS(outer_rel) && - REL_HAS_ALL_PART_PROPS(inner_rel)); - - /* - * For now, our partition matching algorithm can match partitions only - * when the partition bounds of the joining relations are exactly same. - * So, bail out otherwise. - */ - if (outer_rel->nparts != inner_rel->nparts || - !partition_bounds_equal(part_scheme->partnatts, - part_scheme->parttyplen, - part_scheme->parttypbyval, - outer_rel->boundinfo, inner_rel->boundinfo)) - { - Assert(!IS_PARTITIONED_REL(joinrel)); - return; - } - /* * This function will be called only once for each joinrel, hence it * should not have partition scheme, partition bounds, partition key @@ -1681,17 +1669,20 @@ build_joinrel_partition_info(RelOptInfo *joinrel, RelOptInfo *outer_rel, /* * Join relation is partitioned using the same partitioning scheme as the - * joining relations and has same bounds. + * joining relations. + * + * Because of restrictions in partition_bounds_merge(), not every pair of + * joining relations (including the one presented to this function) for the + * same joinrel can use partition-wise join or has both the relations + * partitioned. Hence we calculate the partition bounds, number of + * partitions and child-join relations of the join relation when and if we + * find a suitable pair in try_partition_wise_join(). */ joinrel->part_scheme = part_scheme; - joinrel->boundinfo = outer_rel->boundinfo; partnatts = joinrel->part_scheme->partnatts; joinrel->partexprs = (List **) palloc0(sizeof(List *) * partnatts); joinrel->nullable_partexprs = (List **) palloc0(sizeof(List *) * partnatts); - joinrel->nparts = outer_rel->nparts; - joinrel->part_rels = - (RelOptInfo **) palloc0(sizeof(RelOptInfo *) * joinrel->nparts); /* * Set the consider_partitionwise_join flag. diff --git a/src/backend/partitioning/partbounds.c b/src/backend/partitioning/partbounds.c index cfb44e23e2..a362998e57 100644 --- a/src/backend/partitioning/partbounds.c +++ b/src/backend/partitioning/partbounds.c @@ -25,6 +25,7 @@ #include "miscadmin.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" +#include "nodes/pathnodes.h" #include "parser/parse_coerce.h" #include "partitioning/partbounds.h" #include "partitioning/partdesc.h" @@ -69,6 +70,23 @@ typedef struct PartitionRangeBound bool lower; /* this is the lower (vs upper) bound */ } PartitionRangeBound; +typedef struct PartitionMap +{ + int nparts; /* number of partitions */ + int *merged_indexes; /* indexes of merged partitions */ + bool *merged; /* flags to indicate whether partitions are + * merged with non-dummy partitions */ + bool did_remapping; /* did we remap partitions? */ + int *old_indexes; /* old indexes of merged partitions if + * did_remapping */ +} PartitionMap; + +#define compare_range_bounds(partnatts, partsupfunc, partcollations, \ + bound1, bound2) \ + (partition_rbound_cmp(partnatts, partsupfunc, partcollations, \ + (bound1)->datums, (bound1)->kind, (bound1)->lower, \ + bound2)) + static int32 qsort_partition_hbound_cmp(const void *a, const void *b); static int32 qsort_partition_list_value_cmp(const void *a, const void *b, void *arg); @@ -108,6 +126,95 @@ static void get_range_key_properties(PartitionKey key, int keynum, Expr **keyCol, Const **lower_val, Const **upper_val); static List *get_range_nulltest(PartitionKey key); +static PartitionBoundInfo partition_range_bounds_merge(int partnatts, FmgrInfo *partsupfuncs, + Oid *partcollations, + RelOptInfo *outer_rel, RelOptInfo *inner_rel, + JoinType jointype, + List **outer_parts, List **inner_parts); +static PartitionBoundInfo partition_list_bounds_merge(FmgrInfo *partsupfunc, Oid *collations, + RelOptInfo *outer_rel, RelOptInfo *inner_rel, + JoinType jointype, + List **outer_parts, List **inner_parts); +static void init_partition_map(RelOptInfo *rel, PartitionMap *map); +static void free_partition_map(PartitionMap *map); +static int map_and_merge_partitions(PartitionMap *outer_map, PartitionMap *inner_map, + int outer_part, int inner_part, int *next_index); +static int merge_partition_with_dummy(PartitionMap *map, int index, + int *next_index); +static bool process_outer_partition(PartitionMap *outer_map, + PartitionMap *inner_map, + bool outer_has_default, + bool inner_has_default, + int outer_index, + int inner_default, + JoinType jointype, + int *next_index, + int *default_index, + int *merged_index); +static bool process_inner_partition(PartitionMap *outer_map, + PartitionMap *inner_map, + bool outer_has_default, + bool inner_has_default, + int inner_index, + int outer_default, + JoinType jointype, + int *next_index, + int *default_index, + int *merged_index); +static void fix_merged_indexes(PartitionMap *outer_map, PartitionMap *inner_map, + int nmerged, List *merged_indexes); +static void generate_matching_part_pairs(RelOptInfo *outer_rel, + RelOptInfo *inner_rel, + PartitionMap *outer_map, + PartitionMap *inner_map, + int nmerged, + List **outer_part_list, + List **inner_part_list); +static PartitionBoundInfo build_merged_partition_bounds(char strategy, + List *merged_datums, List *merged_indexes, + List *merged_contents, int null_index, + int default_index); +static int get_range_partition(PartitionBoundInfo bi, int *lb_pos, + PartitionRangeBound *lb, PartitionRangeBound *ub); +static bool compare_range_partitions(int partnatts, FmgrInfo *partsupfuncs, + Oid *partcollations, + PartitionRangeBound *outer_lb, + PartitionRangeBound *outer_ub, + PartitionRangeBound *inner_lb, + PartitionRangeBound *inner_ub, + int *lb_cmpval, int *ub_cmpval); +static void get_merged_range_bounds(int partnatts, FmgrInfo *partsupfuncs, + Oid *partcollations, JoinType jointype, + PartitionRangeBound *outer_lb, + PartitionRangeBound *outer_ub, + PartitionRangeBound *inner_lb, + PartitionRangeBound *inner_ub, + int lb_cmpval, int ub_cmpval, + PartitionRangeBound *merged_lb, + PartitionRangeBound *merged_ub); +static void add_merged_range_bounds(int partnatts, FmgrInfo *partsupfuncs, + Oid *partcollations, + PartitionRangeBound *merged_lb, + PartitionRangeBound *merged_ub, + int merged_index, + List **merged_datums, + List **merged_kinds, + List **merged_indexes); +static bool merge_default_partitions(PartitionMap *outer_map, + PartitionMap *inner_map, + bool outer_has_default, + int outer_default, + bool inner_has_default, + int inner_default, + JoinType jointype, + int *next_index, + int *default_index); +static bool merge_null_partitions(PartitionBoundInfo outer_bi, PartitionBoundInfo inner_bi, + PartitionMap *outer_map, PartitionMap *inner_map, + bool outer_has_default, bool inner_has_default, + bool outer_has_null, bool inner_has_null, + JoinType jointype, int *next_index, + int *default_index, int *null_index); /* * get_qual_from_partbound @@ -2995,3 +3102,1503 @@ satisfies_hash_partition(PG_FUNCTION_ARGS) PG_RETURN_BOOL(rowHash % modulus == remainder); } + +/* + * partition_bounds_merge + * + * This function builds and returns the partition bounds for a join relation + * between input relations, creating two lists of partitions, which are + * returned to *outer_parts and *inner_parts respectively. The lists contain + * the same number of partitions, and the partitions at the same positions in + * the lists indicate join pairs used for partitioned join. + * + * This function returns NULL, setting *outer_parts and *inner_parts to NIL, + * if a partition on one side matches multiple partitions on the other side, + * in which case we currently don't support partitioned join. + */ +PartitionBoundInfo +partition_bounds_merge(int partnatts, + int16 *parttyplen, bool *parttypbyval, + FmgrInfo *partsupfunc, Oid *partcollation, + RelOptInfo *outer_rel, RelOptInfo *inner_rel, + JoinType jointype, List **outer_parts, + List **inner_parts) +{ + PartitionBoundInfo merged_bounds; + PartitionBoundInfo outer_binfo = outer_rel->boundinfo, + inner_binfo = inner_rel->boundinfo; + char strategy = outer_binfo->strategy; + + /* Bail out if partitioning strategies are different. */ + if (outer_binfo->strategy != inner_binfo->strategy) + return NULL; + + if (jointype != JOIN_LEFT && jointype != JOIN_INNER && + jointype != JOIN_SEMI && jointype != JOIN_ANTI && + jointype != JOIN_FULL) + elog(ERROR, "unexpected join type %d", jointype); + + *outer_parts = NIL; + *inner_parts = NIL; + switch (strategy) + { + case PARTITION_STRATEGY_HASH: + merged_bounds = NULL; + + break; + + case PARTITION_STRATEGY_LIST: + merged_bounds = partition_list_bounds_merge(partsupfunc, + partcollation, + outer_rel, + inner_rel, + jointype, + outer_parts, + inner_parts); + break; + + case PARTITION_STRATEGY_RANGE: + merged_bounds = partition_range_bounds_merge(partnatts, + partsupfunc, + partcollation, + outer_rel, + inner_rel, + jointype, + outer_parts, + inner_parts); + break; + + default: + elog(ERROR, "unexpected partition strategy: %d", strategy); + } + + Assert(merged_bounds || (*outer_parts == NIL && *inner_parts == NIL)); + + Assert(list_length(*outer_parts) == list_length(*inner_parts)); + + return merged_bounds; +} + +/* + * get_range_partition + * Returns the index of the range partition with the given lower bound + * + * *lb and *ub are set to the lower and upper bounds of the range partition + * respectively, and *lb_index is advanced to the next lower bound, if any. + */ +static int +get_range_partition(PartitionBoundInfo bi, int *lb_index, + PartitionRangeBound *lb, PartitionRangeBound *ub) +{ + /* Return the index as -1 if we've exhausted all the lower bounds. */ + if (*lb_index >= bi->ndatums) + return -1; + + /* A lower bound should have at least one more bound after it. */ + Assert(*lb_index + 1 < bi->ndatums); + + lb->index = bi->indexes[*lb_index]; + lb->kind = bi->kind[*lb_index]; + lb->datums = bi->datums[*lb_index]; + lb->lower = true; + ub->index = bi->indexes[*lb_index + 1]; + ub->kind = bi->kind[*lb_index + 1]; + ub->datums = bi->datums[*lb_index + 1]; + ub->lower = false; + + /* The partition index of an upper bound should be valid. */ + Assert(ub->index >= 0); + + /* + * Advance the lower bound; if there are no bounds left beyond the upper + * bound, we have reached the last lower bound. + */ + if (*lb_index + 2 >= bi->ndatums) + *lb_index = bi->ndatums; + else + { + /* + * If the index assigned to the bound next to the upper bound isn't + * valid, that is the lower bound of the next range partition; else, + * the upper bound of the current range partition is also the lower + * bound of the next range partition. + */ + if (bi->indexes[*lb_index + 2] < 0) + *lb_index = *lb_index + 2; + else + *lb_index = *lb_index + 1; + } + + return ub->index; +} + +/* + * compare_range_partitions + * Compares the bounds of two range partitions, and returns true if the + * ranges of the partitions overlap, false otherwise + * + * *lb_cmpval is set to -1, 0 or 1 if the outer partition's lower bound is + * lower than, equal to or higher than the inner partition's lower bound + * respectively. Likewise, *ub_cmpval is set to -1, 0 or 1 if the outer + * partition's upper bound is lower than, equal to or higher than the inner + * partition's upper bound respectively. + */ +static bool +compare_range_partitions(int partnatts, FmgrInfo *partsupfuncs, + Oid *partcollations, + PartitionRangeBound *outer_lb, + PartitionRangeBound *outer_ub, + PartitionRangeBound *inner_lb, + PartitionRangeBound *inner_ub, + int *lb_cmpval, int *ub_cmpval) +{ + /* + * Check to see if the upper bound of the outer partition is lower than + * the lower bound of the inner partition; in which case the partitions + * aren't overlapping. + */ + if (compare_range_bounds(partnatts, partsupfuncs, partcollations, + outer_ub, inner_lb) < 0) + { + *lb_cmpval = -1; + *ub_cmpval = -1; + return false; + } + + /* + * Check to see if the lower bound of the outer partition is higher than + * the upper bound of the inner partition; in which case the partitions + * aren't overlapping. + */ + if (compare_range_bounds(partnatts, partsupfuncs, partcollations, + outer_lb, inner_ub) > 0) + { + *lb_cmpval = 1; + *ub_cmpval = 1; + return false; + } + + /* All other cases indicate overlapping partitions. */ + *lb_cmpval = compare_range_bounds(partnatts, partsupfuncs, partcollations, + outer_lb, inner_lb); + *ub_cmpval = compare_range_bounds(partnatts, partsupfuncs, partcollations, + outer_ub, inner_ub); + return true; +} + +/* + * get_merged_range_bounds + * Given the bounds of range partitions to be join, determine the range + * bounds of the merged partition produced from the range partitions + * + * *merged_lb and *merged_ub are set to the lower and upper bounds of the + * merged partition. + */ +static void +get_merged_range_bounds(int partnatts, FmgrInfo *partsupfuncs, + Oid *partcollations, JoinType jointype, + PartitionRangeBound *outer_lb, + PartitionRangeBound *outer_ub, + PartitionRangeBound *inner_lb, + PartitionRangeBound *inner_ub, + int lb_cmpval, int ub_cmpval, + PartitionRangeBound *merged_lb, + PartitionRangeBound *merged_ub) +{ + Assert(compare_range_bounds(partnatts, partsupfuncs, partcollations, + outer_lb, inner_lb) == lb_cmpval); + Assert(compare_range_bounds(partnatts, partsupfuncs, partcollations, + outer_ub, inner_ub) == ub_cmpval); + + /* + * An outer join will have all the rows from the outer side, so merged + * bounds will be same as the outer bounds. An inner join will have rows + * that fit both the bounds, thus lower merged bound will be higher of two + * lower bounds and upper merged bound will be lower of the two upper + * bounds. + */ + switch (jointype) + { + case JOIN_INNER: + case JOIN_SEMI: + *merged_lb = (lb_cmpval > 0) ? *outer_lb : *inner_lb; + *merged_ub = (ub_cmpval < 0) ? *outer_ub : *inner_ub; + break; + + case JOIN_LEFT: + case JOIN_ANTI: + *merged_ub = *outer_ub; + *merged_lb = *outer_lb; + break; + + case JOIN_FULL: + *merged_lb = (lb_cmpval < 0) ? *outer_lb : *inner_lb; + *merged_ub = (ub_cmpval > 0) ? *outer_ub : *inner_ub; + break; + + default: + elog(ERROR, "unexpected join type %d", jointype); + } +} + +/* + * add_merged_range_bounds + * Add the range bounds of a merged partition to the lists of range + * bounds + */ +static void +add_merged_range_bounds(int partnatts, FmgrInfo *partsupfuncs, + Oid *partcollations, + PartitionRangeBound *merged_lb, + PartitionRangeBound *merged_ub, + int merged_index, + List **merged_datums, + List **merged_kinds, + List **merged_indexes) +{ + int cmpval; + + if (!*merged_datums) + { + /* First merged partition */ + Assert(!*merged_kinds && !*merged_indexes); + cmpval = 1; + } + else + { + PartitionRangeBound prev_ub; + + Assert(*merged_kinds && *merged_indexes); + + /* Get the last upper bound. */ + prev_ub.index = llast_int(*merged_indexes); + prev_ub.datums = (Datum *) llast(*merged_datums); + prev_ub.kind = (PartitionRangeDatumKind *) llast(*merged_kinds); + prev_ub.lower = false; + + /* + * We pass to partition_rbound_cmp() lower1 as false to prevent it + * from considering the last upper bound to be smaller than the lower + * bound of the merged partition when the values of the two range + * bounds compare equal. + */ + cmpval = partition_rbound_cmp(partnatts, partsupfuncs, partcollations, + merged_lb->datums, merged_lb->kind, + false, &prev_ub); + Assert(cmpval >= 0); + } + + /* + * If the lower bound is higher than the last upper bound, add the lower + * bound with the index as -1 indicating that that is a lower bound; else, + * the last upper bound will be reused as the lower bound of the merged + * partition, so skip this. + */ + if (cmpval > 0) + { + *merged_datums = lappend(*merged_datums, merged_lb->datums); + *merged_kinds = lappend(*merged_kinds, merged_lb->kind); + *merged_indexes = lappend_int(*merged_indexes, -1); + } + + /* Add the upper bound and index of the merged partition. */ + *merged_datums = lappend(*merged_datums, merged_ub->datums); + *merged_kinds = lappend(*merged_kinds, merged_ub->kind); + *merged_indexes = lappend_int(*merged_indexes, merged_index); +} + +/* + * partition_range_bounds_merge + * + * partition_bounds_merge()'s arm for range partitioned tables. + */ +static PartitionBoundInfo +partition_range_bounds_merge(int partnatts, FmgrInfo *partsupfuncs, + Oid *partcollations, + RelOptInfo *outer_rel, RelOptInfo *inner_rel, + JoinType jointype, + List **outer_parts, List **inner_parts) +{ + PartitionBoundInfo merged_bounds = NULL; + PartitionBoundInfo outer_bi = outer_rel->boundinfo; + PartitionBoundInfo inner_bi = inner_rel->boundinfo; + bool outer_has_default = partition_bound_has_default(outer_bi); + int outer_default = outer_bi->default_index; + bool inner_has_default = partition_bound_has_default(inner_bi); + int inner_default = inner_bi->default_index; + PartitionMap outer_map; + PartitionMap inner_map; + int outer_part; + int inner_part; + int outer_lb_index; + int inner_lb_index; + PartitionRangeBound outer_lb; + PartitionRangeBound outer_ub; + PartitionRangeBound inner_lb; + PartitionRangeBound inner_ub; + int next_index = 0; + int default_index = -1; + List *merged_datums = NIL; + List *merged_kinds = NIL; + List *merged_indexes = NIL; + + Assert(outer_bi->strategy == inner_bi->strategy && + outer_bi->strategy == PARTITION_STRATEGY_RANGE); + + Assert(*outer_parts == NIL); + Assert(*inner_parts == NIL); + + init_partition_map(outer_rel, &outer_map); + init_partition_map(inner_rel, &inner_map); + + /* + * Merge the ranges (partitions) from both sides. Every iteration compares + * a pair of ranges, one from each side, advancing to the next range from + * the side with smaller upper range bound. If upper bounds of ranges from + * both sides match exactly, both the sides are advanced. For a given pair + * of ranges, we decide whether the corresponding partition match or not. + * lb_index, for inner or outer side, keeps track of the index of lower bound + * datum in PartitionBoundInfo::datums of that side. + */ + outer_lb_index = inner_lb_index = 0; + outer_part = get_range_partition(outer_bi, &outer_lb_index, + &outer_lb, &outer_ub); + inner_part = get_range_partition(inner_bi, &inner_lb_index, + &inner_lb, &inner_ub); + while (outer_part >= 0 || inner_part >= 0) + { + PartitionRangeBound merged_lb; + PartitionRangeBound merged_ub; + int merged_index = -1; + bool overlap; + int ub_cmpval; + int lb_cmpval; + + if (outer_part >= 0) + Assert(outer_map.merged_indexes[outer_part] == -1 && + outer_map.merged[outer_part] == false); + if (inner_part >= 0) + Assert(inner_map.merged_indexes[inner_part] == -1 && + inner_map.merged[inner_part] == false); + + /* + * We run this loop till both the sides finish. This allows to avoid + * duplicating code to handle the remaining partitions on the side + * which finishes later. For that we set the comparison parameters + * overlap, ub_cmpval and lb_cmpval in such a way that it appears as if + * the side which finishes earlier has an extra partition with lower + * and upper bounds higher than any other partition of the unfinished + * side. That way we advance the partitions on that side till all of + * them are exhausted. + */ + if (outer_part == -1) + { + overlap = false; + lb_cmpval = 1; + ub_cmpval = 1; + } + else if (inner_part == -1) + { + overlap = false; + lb_cmpval = -1; + ub_cmpval = -1; + } + else + overlap = compare_range_partitions(partnatts, partsupfuncs, + partcollations, + &outer_lb, &outer_ub, + &inner_lb, &inner_ub, + &lb_cmpval, &ub_cmpval); + + if (overlap) + { + /* The ranges of partitions overlap; form a join pair of them. */ + + PartitionRangeBound save_outer_ub; + PartitionRangeBound save_inner_ub; + + /* Get the bounds of the merged partition. */ + get_merged_range_bounds(partnatts, partsupfuncs, + partcollations, jointype, + &outer_lb, &outer_ub, + &inner_lb, &inner_ub, + lb_cmpval, ub_cmpval, + &merged_lb, &merged_ub); + + /* + * Get the index of the merged partition. Both partitions aren't + * merged yet, so the partitions should be merged successfully. + */ + merged_index = map_and_merge_partitions(&outer_map, &inner_map, + outer_part, inner_part, + &next_index); + Assert(merged_index >= 0); + + /* Save the upper bounds of both partitions for use below. */ + save_outer_ub = outer_ub; + save_inner_ub = inner_ub; + + /* Move to the next pair of partitions. */ + outer_part = get_range_partition(outer_bi, &outer_lb_index, + &outer_lb, &outer_ub); + inner_part = get_range_partition(inner_bi, &inner_lb_index, + &inner_lb, &inner_ub); + + /* + * If the range of a partition on one side overlaps the range of + * the next partition on the other side, that will cause the + * partition on one side to match at least two partitions on the + * other side, which is the case that we currently don't support + * partitioned join for; give up. + */ + if (ub_cmpval > 0 && inner_part >= 0 && + compare_range_bounds(partnatts, partsupfuncs, partcollations, + &save_outer_ub, &inner_lb) > 0) + return NULL; + if (ub_cmpval < 0 && outer_part >= 0 && + compare_range_bounds(partnatts, partsupfuncs, partcollations, + &outer_lb, &save_inner_ub) < 0) + return NULL; + + /* + * A row from a non-overlapping portion (if any) of a partition + * on one side might find its join partner in the default + * partition (if any) on the other side, causing the same + * situation as above; if so, give up. + */ + if ((outer_has_default && (lb_cmpval > 0 || ub_cmpval < 0)) || + (inner_has_default && (lb_cmpval < 0 || ub_cmpval > 0))) + return NULL; + } + else if (ub_cmpval < 0) + { + /* Upper bound of inner range higher than that of the outer. */ + + merged_lb = outer_lb; + merged_ub = outer_ub; + + if (inner_has_default || IS_OUTER_JOIN(jointype)) + { + if (!process_outer_partition(&outer_map, + &inner_map, + outer_has_default, + inner_has_default, + outer_part, + inner_default, + jointype, + &next_index, + &default_index, + &merged_index)) + return NULL; + } + + /* Move to the next partition on the outer side. */ + outer_part = get_range_partition(outer_bi, &outer_lb_index, + &outer_lb, &outer_ub); + } + else + { + /* Upper bound of outer range higher than that of the inner. */ + Assert(ub_cmpval > 0); + + merged_lb = inner_lb; + merged_ub = inner_ub; + + if (outer_has_default || jointype == JOIN_FULL) + { + if (!process_inner_partition(&outer_map, + &inner_map, + outer_has_default, + inner_has_default, + inner_part, + outer_default, + jointype, + &next_index, + &default_index, + &merged_index)) + return NULL; + } + + /* Move to the next partition on the inner side. */ + inner_part = get_range_partition(inner_bi, &inner_lb_index, + &inner_lb, &inner_ub); + } + + if (merged_index >= 0) + { + /* Add the range bounds of the merged partition. */ + add_merged_range_bounds(partnatts, partsupfuncs, partcollations, + &merged_lb, &merged_ub, merged_index, + &merged_datums, &merged_kinds, + &merged_indexes); + } + } + + /* Merge default partitions if any. */ + if (outer_has_default || inner_has_default) + { + if (!merge_default_partitions(&outer_map, + &inner_map, + outer_has_default, + outer_default, + inner_has_default, + inner_default, + jointype, + &next_index, + &default_index)) + return NULL; + } + else + Assert(default_index == -1); + + /* + * Unlike the case for list partitioning, we wouldn't have re-merged + * partitions, so did_remapping should be left alone. + */ + Assert(!outer_map.did_remapping && !inner_map.did_remapping); + + /* Use maps to match partition from the joining relations. */ + generate_matching_part_pairs(outer_rel, inner_rel, + &outer_map, &inner_map, + next_index, + outer_parts, inner_parts); + + /* Craft a PartitionBoundInfo to return. */ + if (*outer_parts && *inner_parts) + { + Assert(list_length(*outer_parts) == list_length(*inner_parts)); + Assert(list_length(*outer_parts) == next_index); + merged_bounds = build_merged_partition_bounds(outer_bi->strategy, + merged_datums, + merged_indexes, + merged_kinds, + -1, default_index); + } + + /* Free any memory we used in this function. */ + free_partition_map(&outer_map); + free_partition_map(&inner_map); + list_free(merged_datums); + list_free(merged_indexes); + list_free(merged_kinds); + + return merged_bounds; +} + +/* + * partition_list_bounds_merge + * + * partition_bounds_merge()'s arm for list partitioned tables. + * + */ +static PartitionBoundInfo +partition_list_bounds_merge(FmgrInfo *partsupfunc, Oid *partcollation, + RelOptInfo *outer_rel, RelOptInfo *inner_rel, + JoinType jointype, + List **outer_parts, List **inner_parts) +{ + PartitionBoundInfo merged_bounds = NULL; + PartitionBoundInfo outer_bi = outer_rel->boundinfo; + PartitionBoundInfo inner_bi = inner_rel->boundinfo; + bool outer_has_default = partition_bound_has_default(outer_bi); + int outer_default = outer_bi->default_index; + bool inner_has_default = partition_bound_has_default(inner_bi); + 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 next_index = 0; + int null_index = -1; + int default_index = -1; + List *merged_datums = NIL; + List *merged_indexes = NIL; + int cnto; + int cnti; + + Assert(*outer_parts == NIL); + Assert(*inner_parts == NIL); + + Assert(outer_bi->strategy == inner_bi->strategy && + outer_bi->strategy == PARTITION_STRATEGY_LIST); + + /* List partitions do not require unbounded ranges. */ + Assert(!outer_bi->kind && !inner_bi->kind); + + init_partition_map(outer_rel, &outer_map); + init_partition_map(inner_rel, &inner_map); + + /* + * Merge the list value datums from both sides. Every iteration compares a + * pair of datums, one from each side, advancing to the next datum from the + * side with smaller datum. If datums from both sides match exactly, both + * the sides are advanced. For a given pair of datums, we decide whether + * the corresponding partition match or not. + */ + cnto = cnti = 0; + while (cnto < outer_bi->ndatums || cnti < inner_bi->ndatums) + { + Datum *merged_datum = NULL; + int merged_index = -1; + Datum *odatums; + Datum *idatums; + int cmpval; + + /* Get the list datums of the next pair of partitions. */ + odatums = cnto < outer_bi->ndatums ? outer_bi->datums[cnto] : NULL; + idatums = cnti < inner_bi->ndatums ? inner_bi->datums[cnti] : NULL; + + /* + * We run this loop till both the sides finish. This allows to avoid + * duplicating code to handle the remaining datums on the side which + * finishes later. For that we set the comparison parameter cmpval in + * such a way that it appears as if the side which finishes earlier has + * an extra datum higher than any other datum on the unfinished side. + * That way we advance the datums on the unfinished side till all of + * its datums are exhausted. + */ + if (cnto >= outer_bi->ndatums) + cmpval = 1; + else if (cnti >= inner_bi->ndatums) + cmpval = -1; + else + { + Assert(odatums != NULL && idatums != NULL); + cmpval = DatumGetInt32(FunctionCall2Coll(&partsupfunc[0], + partcollation[0], + odatums[0], + idatums[0])); + } + + if (cmpval == 0) + { + int o_index = outer_bi->indexes[cnto]; + int i_index = inner_bi->indexes[cnti]; + + /* + * Datums match. Rows on either side with these datums as partition + * key value will join and will be part of the partition of the + * join result produced by joining the corresponding partitions. + * Match the corresponding partitions and if successful, add the + * datum to the list of merged datums with index of merged + * partition containing it. + */ + merged_datum = odatums; + Assert(o_index >= 0 && i_index >= 0); + merged_index = map_and_merge_partitions(&outer_map, &inner_map, + o_index, i_index, + &next_index); + + if (merged_index < 0) + return NULL; + + /* Move to the next pair of bounds. */ + cnto++; + cnti++; + } + else if (cmpval < 0) + { + Assert(cnto < outer_bi->ndatums); + + /* A datum missing from the inner side. */ + merged_datum = odatums; + + if (inner_has_default || IS_OUTER_JOIN(jointype)) + { + int o_index = outer_bi->indexes[cnto]; + + Assert(o_index >= 0); + if (!process_outer_partition(&outer_map, + &inner_map, + outer_has_default, + inner_has_default, + o_index, + inner_default, + jointype, + &next_index, + &default_index, + &merged_index)) + return NULL; + } + + /* Move to the next datum on the outer side. */ + cnto++; + } + else + { + Assert(cmpval > 0); + Assert(cnti < inner_bi->ndatums); + + /* A datum missing from the outer side. */ + merged_datum = idatums; + + if (outer_has_default || jointype == JOIN_FULL) + { + int i_index = inner_bi->indexes[cnti]; + + Assert(i_index >= 0); + if (!process_inner_partition(&outer_map, + &inner_map, + outer_has_default, + inner_has_default, + i_index, + outer_default, + jointype, + &next_index, + &default_index, + &merged_index)) + return NULL; + } + + /* Move to the next datum on the inner side. */ + cnti++; + } + + /* + * Add the list value with appropriate index in the list of datums, if + * we have associated a partition with this list value. + */ + if (merged_index >= 0) + { + merged_indexes = lappend_int(merged_indexes, merged_index); + merged_datums = lappend(merged_datums, merged_datum); + } + } + + /* Merge null partitions if any. */ + if (outer_has_null || inner_has_null) + { + if (!merge_null_partitions(outer_bi, inner_bi, + &outer_map, &inner_map, + outer_has_default, inner_has_default, + outer_has_null, inner_has_null, + jointype, &next_index, &default_index, + &null_index)) + return NULL; + } + else + Assert(null_index == -1); + + /* Merge default partitions if any. */ + if (outer_has_default || inner_has_default) + { + if (!merge_default_partitions(&outer_map, &inner_map, + outer_has_default, outer_default, + inner_has_default, inner_default, + jointype, &next_index, &default_index)) + return NULL; + } + else + Assert(default_index == -1); + + /* Fix the merged_indexes list if necessary. */ + if (outer_map.did_remapping || inner_map.did_remapping) + { + Assert(jointype == JOIN_FULL); + fix_merged_indexes(&outer_map, &inner_map, + next_index, merged_indexes); + } + + /* Use maps to match partition from the joining relations. */ + generate_matching_part_pairs(outer_rel, inner_rel, + &outer_map, &inner_map, + next_index, + outer_parts, inner_parts); + + /* Craft a PartitionBoundInfo to return. */ + if (*outer_parts && *inner_parts) + { + Assert(list_length(*outer_parts) == list_length(*inner_parts)); + Assert(list_length(*outer_parts) <= next_index); + merged_bounds = build_merged_partition_bounds(outer_bi->strategy, + merged_datums, + merged_indexes, NIL, + null_index, default_index); + } + + /* Free up all extra memory before returning from this function. */ + free_partition_map(&outer_map); + free_partition_map(&inner_map); + list_free(merged_datums); + list_free(merged_indexes); + + return merged_bounds; +} + +/* + * init_partition_map + * + * Initialize a PartitionMap struct for given relation. + */ +static void +init_partition_map(RelOptInfo *rel, PartitionMap *map) +{ + int nparts = rel->nparts; + int i; + + map->nparts = nparts; + map->merged_indexes = (int *) palloc(sizeof(int) * nparts); + map->merged = (bool *) palloc(sizeof(bool) * nparts); + map->did_remapping = false; + map->old_indexes = (int *) palloc(sizeof(int) * nparts); + for (i = 0; i < nparts; i++) + { + map->merged_indexes[i] = map->old_indexes[i] = -1; + map->merged[i] = false; + } +} + +/* + * free_partition_map + */ +static void +free_partition_map(PartitionMap *map) +{ + pfree(map->merged_indexes); + pfree(map->merged); + pfree(map->old_indexes); +} + +/* + * map_and_merge_partitions + * + * *next_index is incremented when creating a new merged partition associated + * with the given partitions. + */ +static int +map_and_merge_partitions(PartitionMap *outer_map, PartitionMap *inner_map, + int outer_index, int inner_index, int *next_index) +{ + int outer_merged_index; + bool outer_merged; + int inner_merged_index; + bool inner_merged; + + Assert(outer_index >= 0 && outer_index < outer_map->nparts); + outer_merged_index = outer_map->merged_indexes[outer_index]; + outer_merged = outer_map->merged[outer_index]; + Assert(inner_index >= 0 && inner_index < inner_map->nparts); + inner_merged_index = inner_map->merged_indexes[inner_index]; + inner_merged = inner_map->merged[inner_index]; + + /* + * Handle cases where both partitions are mapped to merged partitions. + */ + if (outer_merged_index >= 0 && inner_merged_index >= 0) + { + /* + * If the mereged partitions are the same, no need to do anything; + * return the index of the merged partition. Otherwise, if both + * partitions are merged with dummy partitions, re-merge them; map + * them to the merged partition with the smaller of the two merged + * indexes and return the smaller index. Otherwise they can't be + * merged, so return -1. + */ + if (outer_merged_index == inner_merged_index) + { + Assert(outer_merged); + Assert(inner_merged); + return outer_merged_index; + } + if (!outer_merged && !inner_merged) + { + /* + * Note that we will fix the larger index that have been added to + * the merged_indexes list so far in fix_merged_indexes(). + */ + if (outer_merged_index < inner_merged_index) + { + outer_map->merged[outer_index] = true; + inner_map->merged_indexes[inner_index] = outer_merged_index; + inner_map->merged[inner_index] = true; + inner_map->did_remapping = true; + inner_map->old_indexes[inner_index] = inner_merged_index; + return outer_merged_index; + } + else + { + inner_map->merged[inner_index] = true; + outer_map->merged_indexes[outer_index] = inner_merged_index; + outer_map->merged[outer_index] = true; + outer_map->did_remapping = true; + outer_map->old_indexes[outer_index] = outer_merged_index; + return inner_merged_index; + } + } + return -1; + } + + /* At least one partition isn't mapped to a merged partition. */ + Assert(outer_merged_index == -1 || inner_merged_index == -1); + + /* + * If neither of partitions isn't mapped, assign them a new merged + * partition and return the index of the merged partition. Otherwise, if + * one of partitions is merged with a dummy relation (and the other isn't + * merged), re-merge it with the other, with the same index, and return + * the index. Otherwise they can't be merged, so return -1. + */ + if (outer_merged_index == -1 && inner_merged_index == -1) + { + int merged_index = *next_index; + + Assert(!outer_merged); + Assert(!inner_merged); + outer_map->merged_indexes[outer_index] = merged_index; + outer_map->merged[outer_index] = true; + inner_map->merged_indexes[inner_index] = merged_index; + inner_map->merged[inner_index] = true; + *next_index = *next_index + 1; + return merged_index; + } + if (outer_merged_index >= 0 && !outer_map->merged[outer_index]) + { + Assert(inner_merged_index == -1); + Assert(!inner_merged); + inner_map->merged_indexes[inner_index] = outer_merged_index; + inner_map->merged[inner_index] = true; + outer_map->merged[outer_index] = true; + return outer_merged_index; + } + if (inner_merged_index >= 0 && !inner_map->merged[inner_index]) + { + Assert(outer_merged_index == -1); + Assert(!outer_merged); + outer_map->merged_indexes[outer_index] = inner_merged_index; + outer_map->merged[outer_index] = true; + inner_map->merged[inner_index] = true; + return inner_merged_index; + } + return -1; +} + +/* + * merge_partition_with_dummy + * + * *next_index is incremented. + */ +static int +merge_partition_with_dummy(PartitionMap *map, int index, int *next_index) +{ + int merged_index = *next_index; + + Assert(index >= 0 && index < map->nparts); + Assert(map->merged_indexes[index] == -1); + Assert(!map->merged[index]); + map->merged_indexes[index] = merged_index; + /* Leave the merged flag alone! */ + *next_index = *next_index + 1; + return merged_index; +} + +/* + * process_outer_partition + * + * Determine the merged partition associated with the given outer partition. + * + * *next_index is incremented when creating a new merged partition associated + * with the given outer partition. + */ +static bool +process_outer_partition(PartitionMap *outer_map, + PartitionMap *inner_map, + bool outer_has_default, + bool inner_has_default, + int outer_index, + int inner_default, + JoinType jointype, + int *next_index, + int *default_index, + int *merged_index) +{ + Assert(outer_index >= 0); + + /* + * If the inner side has the default partition, the outer partition has to + * be joined with the default partition; try merging them. Otherwise, we + * should in an outer join, in which case the outer partition has to be + * scanned all the way anyway; if the outer partition is already mapped to + * a merged partition, get it, otherwise create a new merged partition by + * merging the outer partition with a dummy partition. + */ + if (inner_has_default) + { + Assert(inner_default >= 0); + + /* + * If the outer side has the default partition as well, we need to + * merge the default partitions (see merge_default_partitions()); give + * up on it. + */ + if (outer_has_default) + return false; + + *merged_index = map_and_merge_partitions(outer_map, inner_map, + outer_index, inner_default, + next_index); + if (*merged_index == -1) + return false; + + /* + * If this is a FULL join, the merged partition would act as the + * default partition of the join; record the index in *default_index + * if not done yet. + */ + if (jointype == JOIN_FULL) + { + if (*default_index == -1) + *default_index = *merged_index; + else + Assert(*merged_index == *default_index); + /* Don't add this index to the list of merged indexes. */ + *merged_index = -1; + } + } + else + { + Assert(IS_OUTER_JOIN(jointype)); + Assert(jointype != JOIN_RIGHT); + + if (outer_map->merged_indexes[outer_index] >= 0) + *merged_index = outer_map->merged_indexes[outer_index]; + else + *merged_index = merge_partition_with_dummy(outer_map, outer_index, + next_index); + } + return true; +} + +/* + * process_inner_partition + * + * Determine the merged partition associated with the given inner partition. + * + * *next_index is incremented when creating a new merged partition associated + * with the given inner partition. + */ +static bool +process_inner_partition(PartitionMap *outer_map, + PartitionMap *inner_map, + bool outer_has_default, + bool inner_has_default, + int inner_index, + int outer_default, + JoinType jointype, + int *next_index, + int *default_index, + int *merged_index) +{ + Assert(inner_index >= 0); + + /* + * If the outer side has the default partition, the inner partition has to + * be joined with the default partition; try merging them. Otherwise, we + * should in an FULL join, in which case the inner partition has to be + * scanned all the way anyway; if the inner partition is already mapped to + * a merged partition, get it, otherwise create a new merged partition by + * merging the inner partition with a dummy partition. + */ + if (outer_has_default) + { + Assert(outer_default >= 0); + + /* + * If the inner side has the default partition as well, we need to + * merge the default partitions (see merge_default_partitions()); give + * up on it. + */ + if (inner_has_default) + return false; + + *merged_index = map_and_merge_partitions(outer_map, inner_map, + outer_default, inner_index, + next_index); + if (*merged_index == -1) + return false; + + /* + * If this is an outer join, the merged partition would act as the + * default partition of the join; record the index in *default_index + * if not done yet. + */ + if (IS_OUTER_JOIN(jointype)) + { + Assert(jointype != JOIN_RIGHT); + if (*default_index == -1) + *default_index = *merged_index; + else + Assert(*merged_index == *default_index); + /* Don't add this index to the list of merged indexes. */ + *merged_index = -1; + } + } + else + { + Assert(jointype == JOIN_FULL); + + if (inner_map->merged_indexes[inner_index] >= 0) + *merged_index = inner_map->merged_indexes[inner_index]; + else + *merged_index = merge_partition_with_dummy(inner_map, inner_index, + next_index); + } + return true; +} + +/* + * fix_merged_indexes + */ +static void +fix_merged_indexes(PartitionMap *outer_map, PartitionMap *inner_map, + int nmerged, List *merged_indexes) +{ + int *new_indexes; + int merged_index; + int i; + ListCell *lc; + + new_indexes = (int *) palloc(sizeof(int) * nmerged); + for (i = 0; i < nmerged; i++) + new_indexes[i] = -1; + + /* Build the mapping of old merged indexes to new merged indexes. */ + if (outer_map->did_remapping) + { + for (i = 0; i < outer_map->nparts; i++) + { + merged_index = outer_map->old_indexes[i]; + if (merged_index >= 0) + new_indexes[merged_index] = outer_map->merged_indexes[i]; + } + } + if (inner_map->did_remapping) + { + for (i = 0; i < inner_map->nparts; i++) + { + merged_index = inner_map->old_indexes[i]; + if (merged_index >= 0) + new_indexes[merged_index] = inner_map->merged_indexes[i]; + } + } + + /* Fix the merged_indexes list using the mapping. */ + foreach(lc, merged_indexes) + { + merged_index = lfirst_int(lc); + Assert(merged_index >= 0); + if (new_indexes[merged_index] >= 0) + lfirst_int(lc) = new_indexes[merged_index]; + } + + pfree(new_indexes); +} + +/* + * generate_matching_part_pairs + * + * This function produces the list pairs of partitions which produce merged + * partitions in the order of merged partition indexes. + * + * nmerged is the number of merged partitions. + * + * If successful, the list pairs are returned as two separate lists, + * outer_part_list and inner_part_list, one for each side. Otherwise, those + * lists will be set to NIL. + */ +static void +generate_matching_part_pairs(RelOptInfo *outer_rel, RelOptInfo *inner_rel, + PartitionMap *outer_map, PartitionMap *inner_map, + int nmerged, + List **outer_part_list, List **inner_part_list) +{ + int outer_nparts = outer_map->nparts; + int inner_nparts = inner_map->nparts; + int *outer_part_array; + int *inner_part_array; + int max_nparts; + int i; + + Assert(outer_nparts == outer_rel->nparts); + Assert(inner_nparts == inner_rel->nparts); + + outer_part_array = (int *) palloc(sizeof(int) * nmerged); + inner_part_array = (int *) palloc(sizeof(int) * nmerged); + for (i = 0; i < nmerged; i++) + outer_part_array[i] = inner_part_array[i] = -1; + + /* Set pairs of matching partitions. */ + max_nparts = Max(outer_nparts, inner_nparts); + for (i = 0; i < max_nparts; i++) + { + if (i < outer_nparts) + { + int merged_index = outer_map->merged_indexes[i]; + + if (merged_index >= 0) + { + Assert(merged_index < nmerged); + outer_part_array[merged_index] = i; + } + } + if (i < inner_nparts) + { + int merged_index = inner_map->merged_indexes[i]; + + if (merged_index >= 0) + { + Assert(merged_index < nmerged); + inner_part_array[merged_index] = i; + } + } + } + + *outer_part_list = NIL; + *inner_part_list = NIL; + + /* Build the list pairs. */ + for (i = 0; i < nmerged; i++) + { + int outer_part = outer_part_array[i]; + int inner_part = inner_part_array[i]; + + /* + * If both partitions are dummy, it means the merged partition was + * removed by partition re-merging in map_and_merge_partitions(); + * ignore the merged partition + */ + if (outer_part == -1 && inner_part == -1) + continue; + + *outer_part_list = lappend(*outer_part_list, outer_part >= 0 ? + outer_rel->part_rels[outer_part] : NULL); + *inner_part_list = lappend(*inner_part_list, inner_part >= 0 ? + inner_rel->part_rels[inner_part] : NULL); + } + + pfree(outer_part_array); + pfree(inner_part_array); +} + +static PartitionBoundInfo +build_merged_partition_bounds(char strategy, List *merged_datums, + List *merged_indexes, List *merged_kinds, + int null_index, int default_index) +{ + int cnt; + PartitionBoundInfo merged_bounds; + ListCell *lc; + + /* We expect the same number of elements in datums and indexes lists. */ + Assert(list_length(merged_datums) == list_length(merged_indexes)); + + merged_bounds = (PartitionBoundInfo) palloc(sizeof(PartitionBoundInfoData)); + merged_bounds->strategy = strategy; + merged_bounds->ndatums = list_length(merged_datums); + + if (strategy == PARTITION_STRATEGY_RANGE) + { + Assert(list_length(merged_datums) == list_length(merged_kinds)); + merged_bounds->kind = + (PartitionRangeDatumKind **) palloc(sizeof(PartitionRangeDatumKind *) * + list_length(merged_kinds)); + cnt = 0; + foreach(lc, merged_kinds) + merged_bounds->kind[cnt++] = lfirst(lc); + + /* There are ndatums+1 indexes in case of range partitions */ + merged_indexes = lappend_int(merged_indexes, -1); + } + else + merged_bounds->kind = NULL; + + cnt = 0; + merged_bounds->datums = (Datum **) palloc(sizeof(Datum *) * + list_length(merged_datums)); + foreach(lc, merged_datums) + merged_bounds->datums[cnt++] = lfirst(lc); + + merged_bounds->indexes = (int *) palloc(sizeof(int) * + list_length(merged_indexes)); + cnt = 0; + foreach(lc, merged_indexes) + merged_bounds->indexes[cnt++] = lfirst_int(lc); + + merged_bounds->null_index = null_index; + merged_bounds->default_index = default_index; + + return merged_bounds; +} + +/* + * Merge default partitions from both sides, if any, and assign the default + * partition for the join result, if necessary. + * + * If both the relations have default partitions, try mapping those to each + * other. If the mapping succeeds corresponding merged partition will act as + * the default partition of the join result. + * + * If inner side of the join has default but not the outer side, rows in it + * won't appear in the join result. So don't create a default partition. If + * outer side of the join has default but not the inner side, rows in it will + * appear in the join result, so create a default merged partition. + */ +static bool +merge_default_partitions(PartitionMap *outer_map, PartitionMap *inner_map, + bool outer_has_default, int outer_default, + bool inner_has_default, int inner_default, + JoinType jointype, int *next_index, + int *default_index) +{ + Assert(outer_has_default || inner_has_default); + + if (outer_has_default && !inner_has_default) + { + if (IS_OUTER_JOIN(jointype)) + { + int merged_index; + + Assert(jointype != JOIN_RIGHT); + Assert(outer_default >= 0 && outer_default < outer_map->nparts); + merged_index = outer_map->merged_indexes[outer_default]; + if (merged_index == -1) + { + Assert(*default_index == -1); + *default_index = merge_partition_with_dummy(outer_map, + outer_default, + next_index); + } + else + Assert(*default_index == merged_index); + } + else + Assert(*default_index < 0); + } + else if (!outer_has_default && inner_has_default) + { + if (jointype == JOIN_FULL) + { + int merged_index; + + Assert(inner_default >= 0 && inner_default < inner_map->nparts); + merged_index = inner_map->merged_indexes[inner_default]; + if (merged_index == -1) + { + Assert(*default_index == -1); + *default_index = merge_partition_with_dummy(inner_map, + inner_default, + next_index); + } + else + Assert(*default_index == merged_index); + } + else + Assert(*default_index < 0); + } + else + { + Assert(outer_has_default && inner_has_default); + + *default_index = map_and_merge_partitions(outer_map, + inner_map, + outer_default, + inner_default, + next_index); + if (*default_index == -1) + return false; + } + + return true; +} + +/* + * merge_null_partitions + * + * Merge NULL partitions, i.e. a partition that can hold NULL values for a list + * partitioned table, if any. Find the index of merged partition to which the + * NULL values would belong in the join result. If one joining relation has a + * NULL partition but not the other, try matching it with the default partition + * from the other relation since the default partition may have rows with NULL + * partition key. We can eliminate a NULL partition when it appears only on the + * inner side of the join and the outer side doesn't have a default partition. + * + * When the equality operator used for join is strict, two NULL values will not + * be considered as equal, and thus a NULL partition can be eliminated for an + * inner join. But we don't check the strictness operator here. + */ +static bool +merge_null_partitions(PartitionBoundInfo outer_bi, PartitionBoundInfo inner_bi, + PartitionMap *outer_map, PartitionMap *inner_map, + bool outer_has_default, bool inner_has_default, + bool outer_has_null, bool inner_has_null, + JoinType jointype, int *next_index, + int *default_index, int *null_index) +{ + Assert(outer_has_null || inner_has_null); + Assert(*null_index == -1); + + if (outer_has_null && !inner_has_null) + { + int merged_index = -1; + + /* + * If the NULL partition was missing from the inner side of the join, + * the partition of the join to which the NULL partition matches will + * contain the NULL values and thus become the NULL partition of the + * the join. + */ + if (inner_has_default || IS_OUTER_JOIN(jointype)) + { + if (!process_outer_partition(outer_map, + inner_map, + outer_has_default, + inner_has_default, + outer_bi->null_index, + inner_bi->default_index, + jointype, + next_index, + default_index, + &merged_index)) + return false; + } + *null_index = merged_index; + } + else if (!outer_has_null && inner_has_null) + { + int merged_index = -1; + + /* + * If the NULL partition was missing from the outer side of the join, + * the partition of the join to which the NULL partition matches will + * contain the NULL values and thus become the NULL partition of the + * the join. + */ + if (outer_has_default || jointype == JOIN_FULL) + { + if (!process_inner_partition(outer_map, + inner_map, + outer_has_default, + inner_has_default, + inner_bi->null_index, + outer_bi->default_index, + jointype, + next_index, + default_index, + &merged_index)) + return false; + } + *null_index = merged_index; + } + else + { + /* Both the relations have NULL partitions, try merging them. */ + *null_index = map_and_merge_partitions(outer_map, + inner_map, + outer_bi->null_index, + inner_bi->null_index, + next_index); + if (*null_index == -1) + return false; + } + + return true; +} diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h index 23a06d718e..e06eb9aaae 100644 --- a/src/include/nodes/pathnodes.h +++ b/src/include/nodes/pathnodes.h @@ -577,8 +577,10 @@ typedef struct PartitionSchemeData *PartitionScheme; * part_scheme - Partitioning scheme of the relation * nparts - Number of partitions * boundinfo - Partition bounds + * merged - true if partition bounds are merged ones * partition_qual - Partition constraint if not the root * part_rels - RelOptInfos for each partition + * all_partrels - Relids set of all partition relids * partexprs, nullable_partexprs - Partition key expressions * partitioned_child_rels - RT indexes of unpruned partitions of * this relation that are partitioned tables @@ -718,9 +720,12 @@ typedef struct RelOptInfo PartitionScheme part_scheme; /* Partitioning scheme. */ int nparts; /* number of partitions */ struct PartitionBoundInfoData *boundinfo; /* Partition bounds */ + bool merged; /* true if partition bounds were created by + * partition_bounds_merge() */ List *partition_qual; /* partition constraint */ struct RelOptInfo **part_rels; /* Array of RelOptInfos of partitions, * stored in the same order of bounds */ + Relids all_partrels; /* Relids set of all partition relids */ List **partexprs; /* Non-nullable partition key expressions. */ List **nullable_partexprs; /* Nullable partition key expressions. */ List *partitioned_child_rels; /* List of RT indexes. */ diff --git a/src/include/partitioning/partbounds.h b/src/include/partitioning/partbounds.h index 0d0fd42b18..9292aa11e5 100644 --- a/src/include/partitioning/partbounds.h +++ b/src/include/partitioning/partbounds.h @@ -16,6 +16,7 @@ #include "nodes/pg_list.h" #include "partitioning/partdefs.h" #include "utils/relcache.h" +struct RelOptInfo; /* avoid including pathnodes.h here */ /* @@ -108,5 +109,11 @@ extern int partition_range_datum_bsearch(FmgrInfo *partsupfunc, int nvalues, Datum *values, bool *is_equal); extern int partition_hash_bsearch(PartitionBoundInfo boundinfo, int modulus, int remainder); +extern PartitionBoundInfo partition_bounds_merge(int partnatts, + int16 *parttyplen, bool *parttypbyval, + FmgrInfo *partsupfunc, Oid *partcollation, + struct RelOptInfo *outer_rel, struct RelOptInfo *inner_rel, + JoinType jointype, + List **outer_parts, List **inner_parts); #endif /* PARTBOUNDS_H */ diff --git a/src/test/regress/expected/partition_join.out b/src/test/regress/expected/partition_join.out index 975bf6765c..1675abb1e7 100644 --- a/src/test/regress/expected/partition_join.out +++ b/src/test/regress/expected/partition_join.out @@ -8,59 +8,86 @@ SET enable_partitionwise_join to true; -- partitioned by a single column -- CREATE TABLE prt1 (a int, b int, c varchar) PARTITION BY RANGE(a); +CREATE TABLE prt1_p0 PARTITION OF prt1 FOR VALUES FROM (MINVALUE) TO (0); CREATE TABLE prt1_p1 PARTITION OF prt1 FOR VALUES FROM (0) TO (250); CREATE TABLE prt1_p3 PARTITION OF prt1 FOR VALUES FROM (500) TO (600); CREATE TABLE prt1_p2 PARTITION OF prt1 FOR VALUES FROM (250) TO (500); -INSERT INTO prt1 SELECT i, i % 25, to_char(i, 'FM0000') FROM generate_series(0, 599) i WHERE i % 2 = 0; +CREATE TABLE prt1_p4 PARTITION OF prt1 FOR VALUES FROM (600) TO (800); +INSERT INTO prt1 SELECT i, i % 25, to_char(i, 'FM0000') FROM generate_series(-250, 799) i WHERE i % 2 = 0; +CREATE INDEX iprt1_p0_a on prt1_p0(a); CREATE INDEX iprt1_p1_a on prt1_p1(a); CREATE INDEX iprt1_p2_a on prt1_p2(a); CREATE INDEX iprt1_p3_a on prt1_p3(a); +CREATE INDEX iprt1_p4_a on prt1_p4(a); ANALYZE prt1; +-- prt2 have missing starting MINVALUE to -250 range and +-- extra bounds from 800 to MAXVALUE CREATE TABLE prt2 (a int, b int, c varchar) PARTITION BY RANGE(b); +CREATE TABLE prt2_p0 PARTITION OF prt2 FOR VALUES FROM (-250) TO (0); CREATE TABLE prt2_p1 PARTITION OF prt2 FOR VALUES FROM (0) TO (250); CREATE TABLE prt2_p2 PARTITION OF prt2 FOR VALUES FROM (250) TO (500); CREATE TABLE prt2_p3 PARTITION OF prt2 FOR VALUES FROM (500) TO (600); -INSERT INTO prt2 SELECT i % 25, i, to_char(i, 'FM0000') FROM generate_series(0, 599) i WHERE i % 3 = 0; +CREATE TABLE prt2_p4 PARTITION OF prt2 FOR VALUES FROM (600) TO (MAXVALUE); +INSERT INTO prt2 SELECT i % 25, i, to_char(i, 'FM0000') FROM generate_series(-250, 799) i WHERE i % 3 = 0; +CREATE INDEX iprt2_p0_b on prt2_p0(b); CREATE INDEX iprt2_p1_b on prt2_p1(b); CREATE INDEX iprt2_p2_b on prt2_p2(b); CREATE INDEX iprt2_p3_b on prt2_p3(b); +CREATE INDEX iprt2_p4_b on prt2_p4(b); ANALYZE prt2; +-- Partition-wise-join is possible with some partition bounds overlap +-- with each other completely and some partialy for inner,left,right, +-- full, semi and anti joins -- inner join EXPLAIN (COSTS OFF) SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.b = 0 ORDER BY t1.a, t2.b; - QUERY PLAN --------------------------------------------------- + QUERY PLAN +--------------------------------------------------------------- Sort Sort Key: t1.a -> Append -> Hash Join Hash Cond: (t2.b = t1.a) - -> Seq Scan on prt2_p1 t2 + -> Seq Scan on prt2_p0 t2 -> Hash - -> Seq Scan on prt1_p1 t1 + -> Seq Scan on prt1_p0 t1 Filter: (b = 0) -> Hash Join Hash Cond: (t2_1.b = t1_1.a) - -> Seq Scan on prt2_p2 t2_1 + -> Seq Scan on prt2_p1 t2_1 -> Hash - -> Seq Scan on prt1_p2 t1_1 + -> Seq Scan on prt1_p1 t1_1 Filter: (b = 0) -> Hash Join Hash Cond: (t2_2.b = t1_2.a) - -> Seq Scan on prt2_p3 t2_2 + -> Seq Scan on prt2_p2 t2_2 -> Hash - -> Seq Scan on prt1_p3 t1_2 + -> Seq Scan on prt1_p2 t1_2 Filter: (b = 0) -(21 rows) + -> Nested Loop + -> Seq Scan on prt1_p3 t1_3 + Filter: (b = 0) + -> Index Scan using iprt2_p3_b on prt2_p3 t2_3 + Index Cond: (b = t1_3.a) + -> Hash Join + Hash Cond: (t2_4.b = t1_4.a) + -> Seq Scan on prt2_p4 t2_4 + -> Hash + -> Seq Scan on prt1_p4 t1_4 + Filter: (b = 0) +(32 rows) SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.b = 0 ORDER BY t1.a, t2.b; - a | c | b | c ------+------+-----+------ - 0 | 0000 | 0 | 0000 - 150 | 0150 | 150 | 0150 - 300 | 0300 | 300 | 0300 - 450 | 0450 | 450 | 0450 -(4 rows) + a | c | b | c +------+-------+------+------- + -150 | -0150 | -150 | -0150 + 0 | 0000 | 0 | 0000 + 150 | 0150 | 150 | 0150 + 300 | 0300 | 300 | 0300 + 450 | 0450 | 450 | 0450 + 600 | 0600 | 600 | 0600 + 750 | 0750 | 750 | 0750 +(7 rows) -- left outer join, with whole-row reference; partitionwise join does not apply EXPLAIN (COSTS OFF) @@ -72,35 +99,50 @@ SELECT t1, t2 FROM prt1 t1 LEFT JOIN prt2 t2 ON t1.a = t2.b WHERE t1.b = 0 ORDER -> Hash Right Join Hash Cond: (t2.b = t1.a) -> Append - -> Seq Scan on prt2_p1 t2 - -> Seq Scan on prt2_p2 t2_1 - -> Seq Scan on prt2_p3 t2_2 + -> Seq Scan on prt2_p0 t2 + -> Seq Scan on prt2_p1 t2_1 + -> Seq Scan on prt2_p2 t2_2 + -> Seq Scan on prt2_p3 t2_3 + -> Seq Scan on prt2_p4 t2_4 -> Hash -> Append - -> Seq Scan on prt1_p1 t1 + -> Seq Scan on prt1_p0 t1 Filter: (b = 0) - -> Seq Scan on prt1_p2 t1_1 + -> Seq Scan on prt1_p1 t1_1 Filter: (b = 0) - -> Seq Scan on prt1_p3 t1_2 + -> Seq Scan on prt1_p2 t1_2 Filter: (b = 0) -(16 rows) + -> Seq Scan on prt1_p3 t1_3 + Filter: (b = 0) + -> Seq Scan on prt1_p4 t1_4 + Filter: (b = 0) +(22 rows) SELECT t1, t2 FROM prt1 t1 LEFT JOIN prt2 t2 ON t1.a = t2.b WHERE t1.b = 0 ORDER BY t1.a, t2.b; - t1 | t2 ---------------+-------------- - (0,0,0000) | (0,0,0000) - (50,0,0050) | - (100,0,0100) | - (150,0,0150) | (0,150,0150) - (200,0,0200) | - (250,0,0250) | - (300,0,0300) | (0,300,0300) - (350,0,0350) | - (400,0,0400) | - (450,0,0450) | (0,450,0450) - (500,0,0500) | - (550,0,0550) | -(12 rows) + t1 | t2 +----------------+---------------- + (-250,0,-0250) | + (-200,0,-0200) | + (-150,0,-0150) | (0,-150,-0150) + (-100,0,-0100) | + (-50,0,-0050) | + (0,0,0000) | (0,0,0000) + (50,0,0050) | + (100,0,0100) | + (150,0,0150) | (0,150,0150) + (200,0,0200) | + (250,0,0250) | + (300,0,0300) | (0,300,0300) + (350,0,0350) | + (400,0,0400) | + (450,0,0450) | (0,450,0450) + (500,0,0500) | + (550,0,0550) | + (600,0,0600) | (0,600,0600) + (650,0,0650) | + (700,0,0700) | + (750,0,0750) | (0,750,0750) +(21 rows) -- right outer join EXPLAIN (COSTS OFF) @@ -112,35 +154,53 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1 RIGHT JOIN prt2 t2 ON t1.a = t2.b WHE -> Append -> Hash Right Join Hash Cond: (t1.a = t2.b) - -> Seq Scan on prt1_p1 t1 + -> Seq Scan on prt1_p0 t1 -> Hash - -> Seq Scan on prt2_p1 t2 + -> Seq Scan on prt2_p0 t2 Filter: (a = 0) -> Hash Right Join Hash Cond: (t1_1.a = t2_1.b) - -> Seq Scan on prt1_p2 t1_1 + -> Seq Scan on prt1_p1 t1_1 + -> Hash + -> Seq Scan on prt2_p1 t2_1 + Filter: (a = 0) + -> Hash Right Join + Hash Cond: (t1_2.a = t2_2.b) + -> Seq Scan on prt1_p2 t1_2 -> Hash - -> Seq Scan on prt2_p2 t2_1 + -> Seq Scan on prt2_p2 t2_2 Filter: (a = 0) -> Nested Loop Left Join - -> Seq Scan on prt2_p3 t2_2 + -> Seq Scan on prt2_p3 t2_3 Filter: (a = 0) - -> Index Scan using iprt1_p3_a on prt1_p3 t1_2 - Index Cond: (a = t2_2.b) -(20 rows) + -> Index Scan using iprt1_p3_a on prt1_p3 t1_3 + Index Cond: (a = t2_3.b) + -> Hash Right Join + Hash Cond: (t1_4.a = t2_4.b) + -> Seq Scan on prt1_p4 t1_4 + -> Hash + -> Seq Scan on prt2_p4 t2_4 + Filter: (a = 0) +(32 rows) SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1 RIGHT JOIN prt2 t2 ON t1.a = t2.b WHERE t2.a = 0 ORDER BY t1.a, t2.b; - a | c | b | c ------+------+-----+------ - 0 | 0000 | 0 | 0000 - 150 | 0150 | 150 | 0150 - 300 | 0300 | 300 | 0300 - 450 | 0450 | 450 | 0450 - | | 75 | 0075 - | | 225 | 0225 - | | 375 | 0375 - | | 525 | 0525 -(8 rows) + a | c | b | c +------+-------+------+------- + -150 | -0150 | -150 | -0150 + 0 | 0000 | 0 | 0000 + 150 | 0150 | 150 | 0150 + 300 | 0300 | 300 | 0300 + 450 | 0450 | 450 | 0450 + 600 | 0600 | 600 | 0600 + 750 | 0750 | 750 | 0750 + | | -225 | -0225 + | | -75 | -0075 + | | 75 | 0075 + | | 225 | 0225 + | | 375 | 0375 + | | 525 | 0525 + | | 675 | 0675 +(14 rows) -- full outer join, with placeholder vars EXPLAIN (COSTS OFF) @@ -148,8 +208,16 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT 50 phv, * FROM prt1 WHERE prt1.b = 0) QUERY PLAN ------------------------------------------------------------------ Sort - Sort Key: prt1_p1.a, prt2_p1.b + Sort Key: prt1_p0.a, prt2_p0.b -> Append + -> Hash Full Join + Hash Cond: (prt1_p0.a = prt2_p0.b) + Filter: (((50) = prt1_p0.a) OR ((75) = prt2_p0.b)) + -> Seq Scan on prt1_p0 + Filter: (b = 0) + -> Hash + -> Seq Scan on prt2_p0 + Filter: (a = 0) -> Hash Full Join Hash Cond: (prt1_p1.a = prt2_p1.b) Filter: (((50) = prt1_p1.a) OR ((75) = prt2_p1.b)) @@ -174,7 +242,15 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT 50 phv, * FROM prt1 WHERE prt1.b = 0) -> Hash -> Seq Scan on prt2_p3 Filter: (a = 0) -(27 rows) + -> Hash Full Join + Hash Cond: (prt1_p4.a = prt2_p4.b) + Filter: (((50) = prt1_p4.a) OR ((75) = prt2_p4.b)) + -> Seq Scan on prt1_p4 + Filter: (b = 0) + -> Hash + -> Seq Scan on prt2_p4 + Filter: (a = 0) +(43 rows) SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT 50 phv, * FROM prt1 WHERE prt1.b = 0) t1 FULL JOIN (SELECT 75 phv, * FROM prt2 WHERE prt2.a = 0) t2 ON (t1.a = t2.b) WHERE t1.phv = t1.a OR t2.phv = t2.b ORDER BY t1.a, t2.b; a | c | b | c @@ -211,35 +287,44 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1 WHERE a < 450) t1 LEFT JO QUERY PLAN ----------------------------------------------------------- Sort - Sort Key: prt1_p1.a, prt2_p2.b + Sort Key: prt1_p0.a, prt2_p2.b -> Hash Right Join - Hash Cond: (prt2_p2.b = prt1_p1.a) + Hash Cond: (prt2_p2.b = prt1_p0.a) -> Append -> Seq Scan on prt2_p2 Filter: (b > 250) -> Seq Scan on prt2_p3 Filter: (b > 250) + -> Seq Scan on prt2_p4 + Filter: (b > 250) -> Hash -> Append + -> Seq Scan on prt1_p0 + Filter: ((a < 450) AND (b = 0)) -> Seq Scan on prt1_p1 Filter: ((a < 450) AND (b = 0)) -> Seq Scan on prt1_p2 Filter: ((a < 450) AND (b = 0)) -(15 rows) +(19 rows) SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1 WHERE a < 450) t1 LEFT JOIN (SELECT * FROM prt2 WHERE b > 250) t2 ON t1.a = t2.b WHERE t1.b = 0 ORDER BY t1.a, t2.b; - a | c | b | c ------+------+-----+------ - 0 | 0000 | | - 50 | 0050 | | - 100 | 0100 | | - 150 | 0150 | | - 200 | 0200 | | - 250 | 0250 | | - 300 | 0300 | 300 | 0300 - 350 | 0350 | | - 400 | 0400 | | -(9 rows) + a | c | b | c +------+-------+-----+------ + -250 | -0250 | | + -200 | -0200 | | + -150 | -0150 | | + -100 | -0100 | | + -50 | -0050 | | + 0 | 0000 | | + 50 | 0050 | | + 100 | 0100 | | + 150 | 0150 | | + 200 | 0200 | | + 250 | 0250 | | + 300 | 0300 | 300 | 0300 + 350 | 0350 | | + 400 | 0400 | | +(14 rows) -- Currently we can't do partitioned join if nullable-side partitions are pruned EXPLAIN (COSTS OFF) @@ -247,11 +332,13 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1 WHERE a < 450) t1 FULL JO QUERY PLAN ------------------------------------------------------ Sort - Sort Key: prt1_p1.a, prt2_p2.b + Sort Key: prt1_p0.a, prt2_p2.b -> Hash Full Join - Hash Cond: (prt1_p1.a = prt2_p2.b) - Filter: ((prt1_p1.b = 0) OR (prt2_p2.a = 0)) + Hash Cond: (prt1_p0.a = prt2_p2.b) + Filter: ((prt1_p0.b = 0) OR (prt2_p2.a = 0)) -> Append + -> Seq Scan on prt1_p0 + Filter: (a < 450) -> Seq Scan on prt1_p1 Filter: (a < 450) -> Seq Scan on prt1_p2 @@ -262,64 +349,147 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1 WHERE a < 450) t1 FULL JO Filter: (b > 250) -> Seq Scan on prt2_p3 Filter: (b > 250) -(16 rows) + -> Seq Scan on prt2_p4 + Filter: (b > 250) +(20 rows) SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1 WHERE a < 450) t1 FULL JOIN (SELECT * FROM prt2 WHERE b > 250) t2 ON t1.a = t2.b WHERE t1.b = 0 OR t2.a = 0 ORDER BY t1.a, t2.b; - a | c | b | c ------+------+-----+------ - 0 | 0000 | | - 50 | 0050 | | - 100 | 0100 | | - 150 | 0150 | | - 200 | 0200 | | - 250 | 0250 | | - 300 | 0300 | 300 | 0300 - 350 | 0350 | | - 400 | 0400 | | - | | 375 | 0375 - | | 450 | 0450 - | | 525 | 0525 -(12 rows) + a | c | b | c +------+-------+-----+------ + -250 | -0250 | | + -200 | -0200 | | + -150 | -0150 | | + -100 | -0100 | | + -50 | -0050 | | + 0 | 0000 | | + 50 | 0050 | | + 100 | 0100 | | + 150 | 0150 | | + 200 | 0200 | | + 250 | 0250 | | + 300 | 0300 | 300 | 0300 + 350 | 0350 | | + 400 | 0400 | | + | | 375 | 0375 + | | 450 | 0450 + | | 525 | 0525 + | | 600 | 0600 + | | 675 | 0675 + | | 750 | 0750 +(20 rows) -- Semi-join EXPLAIN (COSTS OFF) SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t2.b FROM prt2 t2 WHERE t2.a = 0) AND t1.b = 0 ORDER BY t1.a; - QUERY PLAN --------------------------------------------------- + QUERY PLAN +--------------------------------------------------------------- Sort Sort Key: t1.a -> Append -> Hash Semi Join Hash Cond: (t1.a = t2.b) - -> Seq Scan on prt1_p1 t1 + -> Seq Scan on prt1_p0 t1 Filter: (b = 0) -> Hash - -> Seq Scan on prt2_p1 t2 + -> Seq Scan on prt2_p0 t2 Filter: (a = 0) -> Hash Semi Join Hash Cond: (t1_1.a = t2_1.b) - -> Seq Scan on prt1_p2 t1_1 + -> Seq Scan on prt1_p1 t1_1 Filter: (b = 0) -> Hash - -> Seq Scan on prt2_p2 t2_1 + -> Seq Scan on prt2_p1 t2_1 Filter: (a = 0) - -> Nested Loop Semi Join - Join Filter: (t1_2.a = t2_2.b) - -> Seq Scan on prt1_p3 t1_2 + -> Hash Semi Join + Hash Cond: (t1_2.a = t2_2.b) + -> Seq Scan on prt1_p2 t1_2 Filter: (b = 0) - -> Materialize - -> Seq Scan on prt2_p3 t2_2 + -> Hash + -> Seq Scan on prt2_p2 t2_2 Filter: (a = 0) -(24 rows) + -> Nested Loop + -> HashAggregate + Group Key: t2_3.b + -> Seq Scan on prt2_p3 t2_3 + Filter: (a = 0) + -> Index Scan using iprt1_p3_a on prt1_p3 t1_3 + Index Cond: (a = t2_3.b) + Filter: (b = 0) + -> Hash Semi Join + Hash Cond: (t1_4.a = t2_4.b) + -> Seq Scan on prt1_p4 t1_4 + Filter: (b = 0) + -> Hash + -> Seq Scan on prt2_p4 t2_4 + Filter: (a = 0) +(39 rows) SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t2.b FROM prt2 t2 WHERE t2.a = 0) AND t1.b = 0 ORDER BY t1.a; - a | b | c ------+---+------ - 0 | 0 | 0000 - 150 | 0 | 0150 - 300 | 0 | 0300 - 450 | 0 | 0450 -(4 rows) + a | b | c +------+---+------- + -150 | 0 | -0150 + 0 | 0 | 0000 + 150 | 0 | 0150 + 300 | 0 | 0300 + 450 | 0 | 0450 + 600 | 0 | 0600 + 750 | 0 | 0750 +(7 rows) + +EXPLAIN (COSTS OFF) +SELECT t1.* FROM prt2 t1 WHERE t1.b IN (SELECT t2.a FROM prt1 t2 WHERE t2.b = 0) AND t1.a = 0 ORDER BY t1.b; + QUERY PLAN +-------------------------------------------------- + Sort + Sort Key: t1.b + -> Append + -> Hash Semi Join + Hash Cond: (t1.b = t2.a) + -> Seq Scan on prt2_p0 t1 + Filter: (a = 0) + -> Hash + -> Seq Scan on prt1_p0 t2 + Filter: (b = 0) + -> Hash Semi Join + Hash Cond: (t1_1.b = t2_1.a) + -> Seq Scan on prt2_p1 t1_1 + Filter: (a = 0) + -> Hash + -> Seq Scan on prt1_p1 t2_1 + Filter: (b = 0) + -> Hash Semi Join + Hash Cond: (t1_2.b = t2_2.a) + -> Seq Scan on prt2_p2 t1_2 + Filter: (a = 0) + -> Hash + -> Seq Scan on prt1_p2 t2_2 + Filter: (b = 0) + -> Nested Loop Semi Join + Join Filter: (t1_3.b = t2_3.a) + -> Seq Scan on prt2_p3 t1_3 + Filter: (a = 0) + -> Seq Scan on prt1_p3 t2_3 + Filter: (b = 0) + -> Hash Semi Join + Hash Cond: (t1_4.b = t2_4.a) + -> Seq Scan on prt2_p4 t1_4 + Filter: (a = 0) + -> Hash + -> Seq Scan on prt1_p4 t2_4 + Filter: (b = 0) +(37 rows) + +SELECT t1.* FROM prt2 t1 WHERE t1.b IN (SELECT t2.a FROM prt1 t2 WHERE t2.b = 0) AND t1.a = 0 ORDER BY t1.b; + a | b | c +---+------+------- + 0 | -150 | -0150 + 0 | 0 | 0000 + 0 | 150 | 0150 + 0 | 300 | 0300 + 0 | 450 | 0450 + 0 | 600 | 0600 + 0 | 750 | 0750 +(7 rows) -- Anti-join with aggregates EXPLAIN (COSTS OFF) @@ -330,27 +500,82 @@ SELECT sum(t1.a), avg(t1.a), sum(t1.b), avg(t1.b) FROM prt1 t1 WHERE NOT EXISTS -> Append -> Hash Anti Join Hash Cond: (t1.a = t2.b) - -> Seq Scan on prt1_p1 t1 + -> Seq Scan on prt1_p0 t1 -> Hash - -> Seq Scan on prt2_p1 t2 + -> Seq Scan on prt2_p0 t2 -> Hash Anti Join Hash Cond: (t1_1.a = t2_1.b) - -> Seq Scan on prt1_p2 t1_1 + -> Seq Scan on prt1_p1 t1_1 -> Hash - -> Seq Scan on prt2_p2 t2_1 + -> Seq Scan on prt2_p1 t2_1 -> Hash Anti Join Hash Cond: (t1_2.a = t2_2.b) - -> Seq Scan on prt1_p3 t1_2 + -> Seq Scan on prt1_p2 t1_2 -> Hash - -> Seq Scan on prt2_p3 t2_2 -(17 rows) + -> Seq Scan on prt2_p2 t2_2 + -> Hash Anti Join + Hash Cond: (t1_3.a = t2_3.b) + -> Seq Scan on prt1_p3 t1_3 + -> Hash + -> Seq Scan on prt2_p3 t2_3 + -> Hash Anti Join + Hash Cond: (t1_4.a = t2_4.b) + -> Seq Scan on prt1_p4 t1_4 + -> Hash + -> Seq Scan on prt2_p4 t2_4 +(27 rows) SELECT sum(t1.a), avg(t1.a), sum(t1.b), avg(t1.b) FROM prt1 t1 WHERE NOT EXISTS (SELECT 1 FROM prt2 t2 WHERE t1.a = t2.b); - sum | avg | sum | avg --------+----------------------+------+--------------------- - 60000 | 300.0000000000000000 | 2400 | 12.0000000000000000 + sum | avg | sum | avg +-------+----------------------+------+-------------------- + 95550 | 273.0000000000000000 | 2200 | 6.2857142857142857 (1 row) +EXPLAIN (COSTS OFF) +SELECT t1.b, t1.c FROM prt2 t1 WHERE NOT EXISTS (SELECT 1 FROM prt1 t2 WHERE t1.b = t2.a) and t1.a = 0; + QUERY PLAN +-------------------------------------------------------------- + Append + -> Nested Loop Anti Join + -> Seq Scan on prt2_p0 t1 + Filter: (a = 0) + -> Index Only Scan using iprt1_p0_a on prt1_p0 t2 + Index Cond: (a = t1.b) + -> Hash Anti Join + Hash Cond: (t1_1.b = t2_1.a) + -> Seq Scan on prt2_p1 t1_1 + Filter: (a = 0) + -> Hash + -> Seq Scan on prt1_p1 t2_1 + -> Nested Loop Anti Join + -> Seq Scan on prt2_p2 t1_2 + Filter: (a = 0) + -> Index Only Scan using iprt1_p2_a on prt1_p2 t2_2 + Index Cond: (a = t1_2.b) + -> Nested Loop Anti Join + -> Seq Scan on prt2_p3 t1_3 + Filter: (a = 0) + -> Index Only Scan using iprt1_p3_a on prt1_p3 t2_3 + Index Cond: (a = t1_3.b) + -> Nested Loop Anti Join + -> Seq Scan on prt2_p4 t1_4 + Filter: (a = 0) + -> Index Only Scan using iprt1_p4_a on prt1_p4 t2_4 + Index Cond: (a = t1_4.b) +(27 rows) + +SELECT t1.b, t1.c FROM prt2 t1 WHERE NOT EXISTS (SELECT 1 FROM prt1 t2 WHERE t1.b = t2.a) and t1.a = 0; + b | c +------+------- + -225 | -0225 + -75 | -0075 + 75 | 0075 + 225 | 0225 + 375 | 0375 + 525 | 0525 + 675 | 0675 +(7 rows) + -- lateral reference EXPLAIN (COSTS OFF) SELECT * FROM prt1 t1 LEFT JOIN LATERAL @@ -362,49 +587,74 @@ SELECT * FROM prt1 t1 LEFT JOIN LATERAL Sort Key: t1.a -> Append -> Nested Loop Left Join - -> Seq Scan on prt1_p1 t1 + -> Seq Scan on prt1_p0 t1 Filter: (b = 0) -> Nested Loop - -> Index Only Scan using iprt1_p1_a on prt1_p1 t2 + -> Index Only Scan using iprt1_p0_a on prt1_p0 t2 Index Cond: (a = t1.a) - -> Index Scan using iprt2_p1_b on prt2_p1 t3 + -> Index Scan using iprt2_p0_b on prt2_p0 t3 Index Cond: (b = t2.a) -> Nested Loop Left Join - -> Seq Scan on prt1_p2 t1_1 + -> Seq Scan on prt1_p1 t1_1 Filter: (b = 0) -> Nested Loop - -> Index Only Scan using iprt1_p2_a on prt1_p2 t2_1 + -> Index Only Scan using iprt1_p1_a on prt1_p1 t2_1 Index Cond: (a = t1_1.a) - -> Index Scan using iprt2_p2_b on prt2_p2 t3_1 + -> Index Scan using iprt2_p1_b on prt2_p1 t3_1 Index Cond: (b = t2_1.a) -> Nested Loop Left Join - -> Seq Scan on prt1_p3 t1_2 + -> Seq Scan on prt1_p2 t1_2 Filter: (b = 0) -> Nested Loop - -> Index Only Scan using iprt1_p3_a on prt1_p3 t2_2 + -> Index Only Scan using iprt1_p2_a on prt1_p2 t2_2 Index Cond: (a = t1_2.a) - -> Index Scan using iprt2_p3_b on prt2_p3 t3_2 + -> Index Scan using iprt2_p2_b on prt2_p2 t3_2 Index Cond: (b = t2_2.a) -(27 rows) + -> Nested Loop Left Join + -> Seq Scan on prt1_p3 t1_3 + Filter: (b = 0) + -> Nested Loop + -> Index Only Scan using iprt1_p3_a on prt1_p3 t2_3 + Index Cond: (a = t1_3.a) + -> Index Scan using iprt2_p3_b on prt2_p3 t3_3 + Index Cond: (b = t2_3.a) + -> Nested Loop Left Join + -> Seq Scan on prt1_p4 t1_4 + Filter: (b = 0) + -> Nested Loop + -> Index Only Scan using iprt1_p4_a on prt1_p4 t2_4 + Index Cond: (a = t1_4.a) + -> Index Scan using iprt2_p4_b on prt2_p4 t3_4 + Index Cond: (b = t2_4.a) +(43 rows) SELECT * FROM prt1 t1 LEFT JOIN LATERAL (SELECT t2.a AS t2a, t3.a AS t3a, least(t1.a,t2.a,t3.b) FROM prt1 t2 JOIN prt2 t3 ON (t2.a = t3.b)) ss ON t1.a = ss.t2a WHERE t1.b = 0 ORDER BY t1.a; - a | b | c | t2a | t3a | least ------+---+------+-----+-----+------- - 0 | 0 | 0000 | 0 | 0 | 0 - 50 | 0 | 0050 | | | - 100 | 0 | 0100 | | | - 150 | 0 | 0150 | 150 | 0 | 150 - 200 | 0 | 0200 | | | - 250 | 0 | 0250 | | | - 300 | 0 | 0300 | 300 | 0 | 300 - 350 | 0 | 0350 | | | - 400 | 0 | 0400 | | | - 450 | 0 | 0450 | 450 | 0 | 450 - 500 | 0 | 0500 | | | - 550 | 0 | 0550 | | | -(12 rows) + a | b | c | t2a | t3a | least +------+---+-------+------+-----+------- + -250 | 0 | -0250 | | | + -200 | 0 | -0200 | | | + -150 | 0 | -0150 | -150 | 0 | -150 + -100 | 0 | -0100 | | | + -50 | 0 | -0050 | | | + 0 | 0 | 0000 | 0 | 0 | 0 + 50 | 0 | 0050 | | | + 100 | 0 | 0100 | | | + 150 | 0 | 0150 | 150 | 0 | 150 + 200 | 0 | 0200 | | | + 250 | 0 | 0250 | | | + 300 | 0 | 0300 | 300 | 0 | 300 + 350 | 0 | 0350 | | | + 400 | 0 | 0400 | | | + 450 | 0 | 0450 | 450 | 0 | 450 + 500 | 0 | 0500 | | | + 550 | 0 | 0550 | | | + 600 | 0 | 0600 | 600 | 0 | 600 + 650 | 0 | 0650 | | | + 700 | 0 | 0700 | | | + 750 | 0 | 0750 | 750 | 0 | 750 +(21 rows) EXPLAIN (COSTS OFF) SELECT t1.a, ss.t2a, ss.t2c FROM prt1 t1 LEFT JOIN LATERAL @@ -418,46 +668,67 @@ SELECT t1.a, ss.t2a, ss.t2c FROM prt1 t1 LEFT JOIN LATERAL Hash Cond: ((t1.c)::text = (t2.c)::text) Filter: ((t1.b + COALESCE(t2.b, 0)) = 0) -> Append - -> Seq Scan on prt1_p1 t1 - -> Seq Scan on prt1_p2 t1_1 - -> Seq Scan on prt1_p3 t1_2 + -> Seq Scan on prt1_p0 t1 + -> Seq Scan on prt1_p1 t1_1 + -> Seq Scan on prt1_p2 t1_2 + -> Seq Scan on prt1_p3 t1_3 + -> Seq Scan on prt1_p4 t1_4 -> Hash -> Append -> Hash Join Hash Cond: (t2.a = t3.b) - -> Seq Scan on prt1_p1 t2 + -> Seq Scan on prt1_p0 t2 -> Hash - -> Seq Scan on prt2_p1 t3 + -> Seq Scan on prt2_p0 t3 -> Hash Join Hash Cond: (t2_1.a = t3_1.b) - -> Seq Scan on prt1_p2 t2_1 + -> Seq Scan on prt1_p1 t2_1 -> Hash - -> Seq Scan on prt2_p2 t3_1 + -> Seq Scan on prt2_p1 t3_1 -> Hash Join Hash Cond: (t2_2.a = t3_2.b) - -> Seq Scan on prt1_p3 t2_2 + -> Seq Scan on prt1_p2 t2_2 -> Hash - -> Seq Scan on prt2_p3 t3_2 -(26 rows) + -> Seq Scan on prt2_p2 t3_2 + -> Hash Join + Hash Cond: (t2_3.a = t3_3.b) + -> Seq Scan on prt1_p3 t2_3 + -> Hash + -> Seq Scan on prt2_p3 t3_3 + -> Hash Join + Hash Cond: (t2_4.a = t3_4.b) + -> Seq Scan on prt1_p4 t2_4 + -> Hash + -> Seq Scan on prt2_p4 t3_4 +(38 rows) SELECT t1.a, ss.t2a, ss.t2c FROM prt1 t1 LEFT JOIN LATERAL (SELECT t2.a AS t2a, t3.a AS t3a, t2.b t2b, t2.c t2c, least(t1.a,t2.a,t3.a) FROM prt1 t2 JOIN prt2 t3 ON (t2.a = t3.b)) ss ON t1.c = ss.t2c WHERE (t1.b + coalesce(ss.t2b, 0)) = 0 ORDER BY t1.a; - a | t2a | t2c ------+-----+------ - 0 | 0 | 0000 - 50 | | - 100 | | - 150 | 150 | 0150 - 200 | | - 250 | | - 300 | 300 | 0300 - 350 | | - 400 | | - 450 | 450 | 0450 - 500 | | - 550 | | -(12 rows) + a | t2a | t2c +------+------+------- + -250 | | + -200 | | + -150 | -150 | -0150 + -100 | | + -50 | | + 0 | 0 | 0000 + 50 | | + 100 | | + 150 | 150 | 0150 + 200 | | + 250 | | + 300 | 300 | 0300 + 350 | | + 400 | | + 450 | 450 | 0450 + 500 | | + 550 | | + 600 | 600 | 0600 + 650 | | + 700 | | + 750 | 750 | 0750 +(21 rows) -- bug with inadequate sort key representation SET enable_partitionwise_aggregate TO true; @@ -469,49 +740,75 @@ SELECT a, b FROM prt1 FULL JOIN prt2 p2(b,a,c) USING(a,b) QUERY PLAN ------------------------------------------------------------------------------------------------------------------- Group - Group Key: (COALESCE(prt1_p1.a, p2.a)), (COALESCE(prt1_p1.b, p2.b)) + Group Key: (COALESCE(prt1_p0.a, p2.a)), (COALESCE(prt1_p0.b, p2.b)) -> Merge Append - Sort Key: (COALESCE(prt1_p1.a, p2.a)), (COALESCE(prt1_p1.b, p2.b)) + Sort Key: (COALESCE(prt1_p0.a, p2.a)), (COALESCE(prt1_p0.b, p2.b)) + -> Group + Group Key: (COALESCE(prt1_p0.a, p2.a)), (COALESCE(prt1_p0.b, p2.b)) + -> Sort + Sort Key: (COALESCE(prt1_p0.a, p2.a)), (COALESCE(prt1_p0.b, p2.b)) + -> Merge Full Join + Merge Cond: ((prt1_p0.a = p2.a) AND (prt1_p0.b = p2.b)) + Filter: ((COALESCE(prt1_p0.a, p2.a) >= 490) AND (COALESCE(prt1_p0.a, p2.a) <= 510)) + -> Sort + Sort Key: prt1_p0.a, prt1_p0.b + -> Seq Scan on prt1_p0 + -> Sort + Sort Key: p2.a, p2.b + -> Seq Scan on prt2_p0 p2 -> Group - Group Key: (COALESCE(prt1_p1.a, p2.a)), (COALESCE(prt1_p1.b, p2.b)) + Group Key: (COALESCE(prt1_p1.a, p2_1.a)), (COALESCE(prt1_p1.b, p2_1.b)) -> Sort - Sort Key: (COALESCE(prt1_p1.a, p2.a)), (COALESCE(prt1_p1.b, p2.b)) + Sort Key: (COALESCE(prt1_p1.a, p2_1.a)), (COALESCE(prt1_p1.b, p2_1.b)) -> Merge Full Join - Merge Cond: ((prt1_p1.a = p2.a) AND (prt1_p1.b = p2.b)) - Filter: ((COALESCE(prt1_p1.a, p2.a) >= 490) AND (COALESCE(prt1_p1.a, p2.a) <= 510)) + Merge Cond: ((prt1_p1.a = p2_1.a) AND (prt1_p1.b = p2_1.b)) + Filter: ((COALESCE(prt1_p1.a, p2_1.a) >= 490) AND (COALESCE(prt1_p1.a, p2_1.a) <= 510)) -> Sort Sort Key: prt1_p1.a, prt1_p1.b -> Seq Scan on prt1_p1 -> Sort - Sort Key: p2.a, p2.b - -> Seq Scan on prt2_p1 p2 + Sort Key: p2_1.a, p2_1.b + -> Seq Scan on prt2_p1 p2_1 -> Group - Group Key: (COALESCE(prt1_p2.a, p2_1.a)), (COALESCE(prt1_p2.b, p2_1.b)) + Group Key: (COALESCE(prt1_p2.a, p2_2.a)), (COALESCE(prt1_p2.b, p2_2.b)) -> Sort - Sort Key: (COALESCE(prt1_p2.a, p2_1.a)), (COALESCE(prt1_p2.b, p2_1.b)) + Sort Key: (COALESCE(prt1_p2.a, p2_2.a)), (COALESCE(prt1_p2.b, p2_2.b)) -> Merge Full Join - Merge Cond: ((prt1_p2.a = p2_1.a) AND (prt1_p2.b = p2_1.b)) - Filter: ((COALESCE(prt1_p2.a, p2_1.a) >= 490) AND (COALESCE(prt1_p2.a, p2_1.a) <= 510)) + Merge Cond: ((prt1_p2.a = p2_2.a) AND (prt1_p2.b = p2_2.b)) + Filter: ((COALESCE(prt1_p2.a, p2_2.a) >= 490) AND (COALESCE(prt1_p2.a, p2_2.a) <= 510)) -> Sort Sort Key: prt1_p2.a, prt1_p2.b -> Seq Scan on prt1_p2 -> Sort - Sort Key: p2_1.a, p2_1.b - -> Seq Scan on prt2_p2 p2_1 + Sort Key: p2_2.a, p2_2.b + -> Seq Scan on prt2_p2 p2_2 -> Group - Group Key: (COALESCE(prt1_p3.a, p2_2.a)), (COALESCE(prt1_p3.b, p2_2.b)) + Group Key: (COALESCE(prt1_p3.a, p2_3.a)), (COALESCE(prt1_p3.b, p2_3.b)) -> Sort - Sort Key: (COALESCE(prt1_p3.a, p2_2.a)), (COALESCE(prt1_p3.b, p2_2.b)) + Sort Key: (COALESCE(prt1_p3.a, p2_3.a)), (COALESCE(prt1_p3.b, p2_3.b)) -> Merge Full Join - Merge Cond: ((prt1_p3.a = p2_2.a) AND (prt1_p3.b = p2_2.b)) - Filter: ((COALESCE(prt1_p3.a, p2_2.a) >= 490) AND (COALESCE(prt1_p3.a, p2_2.a) <= 510)) + Merge Cond: ((prt1_p3.a = p2_3.a) AND (prt1_p3.b = p2_3.b)) + Filter: ((COALESCE(prt1_p3.a, p2_3.a) >= 490) AND (COALESCE(prt1_p3.a, p2_3.a) <= 510)) -> Sort Sort Key: prt1_p3.a, prt1_p3.b -> Seq Scan on prt1_p3 -> Sort - Sort Key: p2_2.a, p2_2.b - -> Seq Scan on prt2_p3 p2_2 -(43 rows) + Sort Key: p2_3.a, p2_3.b + -> Seq Scan on prt2_p3 p2_3 + -> Group + Group Key: (COALESCE(prt1_p4.a, p2_4.a)), (COALESCE(prt1_p4.b, p2_4.b)) + -> Sort + Sort Key: (COALESCE(prt1_p4.a, p2_4.a)), (COALESCE(prt1_p4.b, p2_4.b)) + -> Merge Full Join + Merge Cond: ((prt1_p4.a = p2_4.a) AND (prt1_p4.b = p2_4.b)) + Filter: ((COALESCE(prt1_p4.a, p2_4.a) >= 490) AND (COALESCE(prt1_p4.a, p2_4.a) <= 510)) + -> Sort + Sort Key: prt1_p4.a, prt1_p4.b + -> Seq Scan on prt1_p4 + -> Sort + Sort Key: p2_4.a, p2_4.b + -> Seq Scan on prt2_p4 p2_4 +(69 rows) SELECT a, b FROM prt1 FULL JOIN prt2 p2(b,a,c) USING(a,b) WHERE a BETWEEN 490 AND 510 @@ -540,19 +837,29 @@ RESET enable_hashjoin; -- partitioned by expression -- CREATE TABLE prt1_e (a int, b int, c int) PARTITION BY RANGE(((a + b)/2)); +CREATE TABLE prt1_e_p0 PARTITION OF prt1_e FOR VALUES FROM (MINVALUE) TO (0); CREATE TABLE prt1_e_p1 PARTITION OF prt1_e FOR VALUES FROM (0) TO (250); CREATE TABLE prt1_e_p2 PARTITION OF prt1_e FOR VALUES FROM (250) TO (500); CREATE TABLE prt1_e_p3 PARTITION OF prt1_e FOR VALUES FROM (500) TO (600); +CREATE TABLE prt1_e_p4 PARTITION OF prt1_e FOR VALUES FROM (600) TO (MAXVALUE); INSERT INTO prt1_e SELECT i, i, i % 25 FROM generate_series(0, 599, 2) i; +INSERT INTO prt1_e SELECT i, i, i % 25 FROM generate_series(-250, 0, 2) i; +INSERT INTO prt1_e SELECT i, i, i % 25 FROM generate_series(600, 799, 2) i; +CREATE INDEX iprt1_e_p0_ab2 on prt1_e_p1(((a+b)/2)); CREATE INDEX iprt1_e_p1_ab2 on prt1_e_p1(((a+b)/2)); CREATE INDEX iprt1_e_p2_ab2 on prt1_e_p2(((a+b)/2)); CREATE INDEX iprt1_e_p3_ab2 on prt1_e_p3(((a+b)/2)); +CREATE INDEX iprt1_e_p4_ab2 on prt1_e_p1(((a+b)/2)); ANALYZE prt1_e; CREATE TABLE prt2_e (a int, b int, c int) PARTITION BY RANGE(((b + a)/2)); +CREATE TABLE prt2_e_p0 PARTITION OF prt2_e FOR VALUES FROM (MINVALUE) TO (0); CREATE TABLE prt2_e_p1 PARTITION OF prt2_e FOR VALUES FROM (0) TO (250); CREATE TABLE prt2_e_p2 PARTITION OF prt2_e FOR VALUES FROM (250) TO (500); CREATE TABLE prt2_e_p3 PARTITION OF prt2_e FOR VALUES FROM (500) TO (600); +CREATE TABLE prt2_e_p4 PARTITION OF prt2_e FOR VALUES FROM (600) TO (MAXVALUE); INSERT INTO prt2_e SELECT i, i, i % 25 FROM generate_series(0, 599, 3) i; +INSERT INTO prt2_e SELECT i, i, i % 25 FROM generate_series(-250, 0, 3) i; +INSERT INTO prt2_e SELECT i, i, i % 25 FROM generate_series(600, 799, 3) i; ANALYZE prt2_e; EXPLAIN (COSTS OFF) SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_e t1, prt2_e t2 WHERE (t1.a + t1.b)/2 = (t2.b + t2.a)/2 AND t1.c = 0 ORDER BY t1.a, t2.b; @@ -563,32 +870,49 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_e t1, prt2_e t2 WHERE (t1.a + t1.b)/2 = -> Append -> Hash Join Hash Cond: (((t2.b + t2.a) / 2) = ((t1.a + t1.b) / 2)) - -> Seq Scan on prt2_e_p1 t2 + -> Seq Scan on prt2_e_p0 t2 -> Hash - -> Seq Scan on prt1_e_p1 t1 + -> Seq Scan on prt1_e_p0 t1 Filter: (c = 0) -> Hash Join - Hash Cond: (((t2_1.b + t2_1.a) / 2) = ((t1_1.a + t1_1.b) / 2)) - -> Seq Scan on prt2_e_p2 t2_1 + Hash Cond: (((t1_1.a + t1_1.b) / 2) = ((t2_1.b + t2_1.a) / 2)) + -> Seq Scan on prt1_e_p1 t1_1 + Filter: (c = 0) -> Hash - -> Seq Scan on prt1_e_p2 t1_1 - Filter: (c = 0) + -> Seq Scan on prt2_e_p1 t2_1 -> Hash Join Hash Cond: (((t2_2.b + t2_2.a) / 2) = ((t1_2.a + t1_2.b) / 2)) - -> Seq Scan on prt2_e_p3 t2_2 + -> Seq Scan on prt2_e_p2 t2_2 -> Hash - -> Seq Scan on prt1_e_p3 t1_2 + -> Seq Scan on prt1_e_p2 t1_2 Filter: (c = 0) -(21 rows) + -> Hash Join + Hash Cond: (((t2_3.b + t2_3.a) / 2) = ((t1_3.a + t1_3.b) / 2)) + -> Seq Scan on prt2_e_p3 t2_3 + -> Hash + -> Seq Scan on prt1_e_p3 t1_3 + Filter: (c = 0) + -> Hash Join + Hash Cond: (((t2_4.b + t2_4.a) / 2) = ((t1_4.a + t1_4.b) / 2)) + -> Seq Scan on prt2_e_p4 t2_4 + -> Hash + -> Seq Scan on prt1_e_p4 t1_4 + Filter: (c = 0) +(33 rows) SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_e t1, prt2_e t2 WHERE (t1.a + t1.b)/2 = (t2.b + t2.a)/2 AND t1.c = 0 ORDER BY t1.a, t2.b; - a | c | b | c ------+---+-----+--- - 0 | 0 | 0 | 0 - 150 | 0 | 150 | 0 - 300 | 0 | 300 | 0 - 450 | 0 | 450 | 0 -(4 rows) + a | c | b | c +------+---+------+--- + -250 | 0 | -250 | 0 + -100 | 0 | -100 | 0 + 0 | 0 | 0 | 0 + 0 | 0 | 0 | 0 + 150 | 0 | 150 | 0 + 300 | 0 | 300 | 0 + 450 | 0 | 450 | 0 + 600 | 0 | 600 | 0 + 750 | 0 | 750 | 0 +(9 rows) -- -- N-way join @@ -601,154 +925,232 @@ SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM prt1 t1, prt2 t2, prt1_e t Sort Key: t1.a -> Append -> Nested Loop - Join Filter: (t1.a = ((t3.a + t3.b) / 2)) + Join Filter: (t1.a = t2.b) -> Hash Join - Hash Cond: (t2.b = t1.a) - -> Seq Scan on prt2_p1 t2 + Hash Cond: (((t3.a + t3.b) / 2) = t1.a) + -> Seq Scan on prt1_e_p0 t3 -> Hash - -> Seq Scan on prt1_p1 t1 + -> Seq Scan on prt1_p0 t1 Filter: (b = 0) - -> Index Scan using iprt1_e_p1_ab2 on prt1_e_p1 t3 - Index Cond: (((a + b) / 2) = t2.b) + -> Index Scan using iprt2_p0_b on prt2_p0 t2 + Index Cond: (b = ((t3.a + t3.b) / 2)) -> Nested Loop Join Filter: (t1_1.a = ((t3_1.a + t3_1.b) / 2)) -> Hash Join Hash Cond: (t2_1.b = t1_1.a) - -> Seq Scan on prt2_p2 t2_1 + -> Seq Scan on prt2_p1 t2_1 -> Hash - -> Seq Scan on prt1_p2 t1_1 + -> Seq Scan on prt1_p1 t1_1 Filter: (b = 0) - -> Index Scan using iprt1_e_p2_ab2 on prt1_e_p2 t3_1 + -> Index Scan using iprt1_e_p4_ab2 on prt1_e_p1 t3_1 Index Cond: (((a + b) / 2) = t2_1.b) -> Nested Loop Join Filter: (t1_2.a = ((t3_2.a + t3_2.b) / 2)) -> Hash Join Hash Cond: (t2_2.b = t1_2.a) - -> Seq Scan on prt2_p3 t2_2 + -> Seq Scan on prt2_p2 t2_2 -> Hash - -> Seq Scan on prt1_p3 t1_2 + -> Seq Scan on prt1_p2 t1_2 Filter: (b = 0) - -> Index Scan using iprt1_e_p3_ab2 on prt1_e_p3 t3_2 + -> Index Scan using iprt1_e_p2_ab2 on prt1_e_p2 t3_2 Index Cond: (((a + b) / 2) = t2_2.b) -(33 rows) + -> Nested Loop + Join Filter: (t1_3.a = ((t3_3.a + t3_3.b) / 2)) + -> Nested Loop + -> Seq Scan on prt1_p3 t1_3 + Filter: (b = 0) + -> Index Scan using iprt2_p3_b on prt2_p3 t2_3 + Index Cond: (b = t1_3.a) + -> Index Scan using iprt1_e_p3_ab2 on prt1_e_p3 t3_3 + Index Cond: (((a + b) / 2) = t2_3.b) + -> Nested Loop + Join Filter: (t1_4.a = t2_4.b) + -> Hash Join + Hash Cond: (((t3_4.a + t3_4.b) / 2) = t1_4.a) + -> Seq Scan on prt1_e_p4 t3_4 + -> Hash + -> Seq Scan on prt1_p4 t1_4 + Filter: (b = 0) + -> Index Scan using iprt2_p4_b on prt2_p4 t2_4 + Index Cond: (b = ((t3_4.a + t3_4.b) / 2)) +(52 rows) SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM prt1 t1, prt2 t2, prt1_e t3 WHERE t1.a = t2.b AND t1.a = (t3.a + t3.b)/2 AND t1.b = 0 ORDER BY t1.a, t2.b; - a | c | b | c | ?column? | c ------+------+-----+------+----------+--- - 0 | 0000 | 0 | 0000 | 0 | 0 - 150 | 0150 | 150 | 0150 | 300 | 0 - 300 | 0300 | 300 | 0300 | 600 | 0 - 450 | 0450 | 450 | 0450 | 900 | 0 -(4 rows) + a | c | b | c | ?column? | c +------+-------+------+-------+----------+--- + -150 | -0150 | -150 | -0150 | -300 | 0 + 0 | 0000 | 0 | 0000 | 0 | 0 + 0 | 0000 | 0 | 0000 | 0 | 0 + 150 | 0150 | 150 | 0150 | 300 | 0 + 300 | 0300 | 300 | 0300 | 600 | 0 + 450 | 0450 | 450 | 0450 | 900 | 0 + 600 | 0600 | 600 | 0600 | 1200 | 0 + 750 | 0750 | 750 | 0750 | 1500 | 0 +(8 rows) EXPLAIN (COSTS OFF) SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2 ON t1.a = t2.b) LEFT JOIN prt1_e t3 ON (t1.a = (t3.a + t3.b)/2) WHERE t1.b = 0 ORDER BY t1.a, t2.b, t3.a + t3.b; - QUERY PLAN --------------------------------------------------------------- + QUERY PLAN +--------------------------------------------------------------------- Sort Sort Key: t1.a, t2.b, ((t3.a + t3.b)) -> Append -> Hash Right Join Hash Cond: (((t3.a + t3.b) / 2) = t1.a) - -> Seq Scan on prt1_e_p1 t3 + -> Seq Scan on prt1_e_p0 t3 -> Hash -> Hash Right Join Hash Cond: (t2.b = t1.a) - -> Seq Scan on prt2_p1 t2 + -> Seq Scan on prt2_p0 t2 -> Hash - -> Seq Scan on prt1_p1 t1 + -> Seq Scan on prt1_p0 t1 Filter: (b = 0) -> Hash Right Join Hash Cond: (((t3_1.a + t3_1.b) / 2) = t1_1.a) - -> Seq Scan on prt1_e_p2 t3_1 + -> Seq Scan on prt1_e_p1 t3_1 -> Hash -> Hash Right Join Hash Cond: (t2_1.b = t1_1.a) - -> Seq Scan on prt2_p2 t2_1 + -> Seq Scan on prt2_p1 t2_1 -> Hash - -> Seq Scan on prt1_p2 t1_1 + -> Seq Scan on prt1_p1 t1_1 Filter: (b = 0) -> Hash Right Join Hash Cond: (((t3_2.a + t3_2.b) / 2) = t1_2.a) - -> Seq Scan on prt1_e_p3 t3_2 + -> Seq Scan on prt1_e_p2 t3_2 -> Hash -> Hash Right Join Hash Cond: (t2_2.b = t1_2.a) - -> Seq Scan on prt2_p3 t2_2 + -> Seq Scan on prt2_p2 t2_2 -> Hash - -> Seq Scan on prt1_p3 t1_2 + -> Seq Scan on prt1_p2 t1_2 Filter: (b = 0) -(33 rows) + -> Nested Loop Left Join + -> Nested Loop Left Join + -> Seq Scan on prt1_p3 t1_3 + Filter: (b = 0) + -> Index Scan using iprt2_p3_b on prt2_p3 t2_3 + Index Cond: (b = t1_3.a) + -> Index Scan using iprt1_e_p3_ab2 on prt1_e_p3 t3_3 + Index Cond: (((a + b) / 2) = t1_3.a) + -> Hash Right Join + Hash Cond: (((t3_4.a + t3_4.b) / 2) = t1_4.a) + -> Seq Scan on prt1_e_p4 t3_4 + -> Hash + -> Hash Right Join + Hash Cond: (t2_4.b = t1_4.a) + -> Seq Scan on prt2_p4 t2_4 + -> Hash + -> Seq Scan on prt1_p4 t1_4 + Filter: (b = 0) +(51 rows) SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2 ON t1.a = t2.b) LEFT JOIN prt1_e t3 ON (t1.a = (t3.a + t3.b)/2) WHERE t1.b = 0 ORDER BY t1.a, t2.b, t3.a + t3.b; - a | c | b | c | ?column? | c ------+------+-----+------+----------+--- - 0 | 0000 | 0 | 0000 | 0 | 0 - 50 | 0050 | | | 100 | 0 - 100 | 0100 | | | 200 | 0 - 150 | 0150 | 150 | 0150 | 300 | 0 - 200 | 0200 | | | 400 | 0 - 250 | 0250 | | | 500 | 0 - 300 | 0300 | 300 | 0300 | 600 | 0 - 350 | 0350 | | | 700 | 0 - 400 | 0400 | | | 800 | 0 - 450 | 0450 | 450 | 0450 | 900 | 0 - 500 | 0500 | | | 1000 | 0 - 550 | 0550 | | | 1100 | 0 -(12 rows) + a | c | b | c | ?column? | c +------+-------+------+-------+----------+--- + -250 | -0250 | | | -500 | 0 + -200 | -0200 | | | -400 | 0 + -150 | -0150 | -150 | -0150 | -300 | 0 + -100 | -0100 | | | -200 | 0 + -50 | -0050 | | | -100 | 0 + 0 | 0000 | 0 | 0000 | 0 | 0 + 0 | 0000 | 0 | 0000 | 0 | 0 + 50 | 0050 | | | 100 | 0 + 100 | 0100 | | | 200 | 0 + 150 | 0150 | 150 | 0150 | 300 | 0 + 200 | 0200 | | | 400 | 0 + 250 | 0250 | | | 500 | 0 + 300 | 0300 | 300 | 0300 | 600 | 0 + 350 | 0350 | | | 700 | 0 + 400 | 0400 | | | 800 | 0 + 450 | 0450 | 450 | 0450 | 900 | 0 + 500 | 0500 | | | 1000 | 0 + 550 | 0550 | | | 1100 | 0 + 600 | 0600 | 600 | 0600 | 1200 | 0 + 650 | 0650 | | | 1300 | 0 + 700 | 0700 | | | 1400 | 0 + 750 | 0750 | 750 | 0750 | 1500 | 0 +(22 rows) EXPLAIN (COSTS OFF) SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2 ON t1.a = t2.b) RIGHT JOIN prt1_e t3 ON (t1.a = (t3.a + t3.b)/2) WHERE t3.c = 0 ORDER BY t1.a, t2.b, t3.a + t3.b; - QUERY PLAN -------------------------------------------------------------------- + QUERY PLAN +--------------------------------------------------------------------- Sort Sort Key: t1.a, t2.b, ((t3.a + t3.b)) -> Append -> Nested Loop Left Join -> Hash Right Join Hash Cond: (t1.a = ((t3.a + t3.b) / 2)) - -> Seq Scan on prt1_p1 t1 + -> Seq Scan on prt1_p0 t1 -> Hash - -> Seq Scan on prt1_e_p1 t3 + -> Seq Scan on prt1_e_p0 t3 Filter: (c = 0) - -> Index Scan using iprt2_p1_b on prt2_p1 t2 + -> Index Scan using iprt2_p0_b on prt2_p0 t2 Index Cond: (b = t1.a) -> Nested Loop Left Join -> Hash Right Join Hash Cond: (t1_1.a = ((t3_1.a + t3_1.b) / 2)) - -> Seq Scan on prt1_p2 t1_1 + -> Seq Scan on prt1_p1 t1_1 -> Hash - -> Seq Scan on prt1_e_p2 t3_1 + -> Seq Scan on prt1_e_p1 t3_1 Filter: (c = 0) - -> Index Scan using iprt2_p2_b on prt2_p2 t2_1 + -> Index Scan using iprt2_p1_b on prt2_p1 t2_1 Index Cond: (b = t1_1.a) -> Nested Loop Left Join -> Hash Right Join Hash Cond: (t1_2.a = ((t3_2.a + t3_2.b) / 2)) - -> Seq Scan on prt1_p3 t1_2 + -> Seq Scan on prt1_p2 t1_2 -> Hash - -> Seq Scan on prt1_e_p3 t3_2 + -> Seq Scan on prt1_e_p2 t3_2 Filter: (c = 0) - -> Index Scan using iprt2_p3_b on prt2_p3 t2_2 + -> Index Scan using iprt2_p2_b on prt2_p2 t2_2 Index Cond: (b = t1_2.a) -(30 rows) + -> Nested Loop Left Join + -> Nested Loop Left Join + -> Seq Scan on prt1_e_p3 t3_3 + Filter: (c = 0) + -> Index Scan using iprt1_p3_a on prt1_p3 t1_3 + Index Cond: (a = ((t3_3.a + t3_3.b) / 2)) + -> Index Scan using iprt2_p3_b on prt2_p3 t2_3 + Index Cond: (b = t1_3.a) + -> Nested Loop Left Join + -> Hash Right Join + Hash Cond: (t1_4.a = ((t3_4.a + t3_4.b) / 2)) + -> Seq Scan on prt1_p4 t1_4 + -> Hash + -> Seq Scan on prt1_e_p4 t3_4 + Filter: (c = 0) + -> Index Scan using iprt2_p4_b on prt2_p4 t2_4 + Index Cond: (b = t1_4.a) +(47 rows) SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2 ON t1.a = t2.b) RIGHT JOIN prt1_e t3 ON (t1.a = (t3.a + t3.b)/2) WHERE t3.c = 0 ORDER BY t1.a, t2.b, t3.a + t3.b; - a | c | b | c | ?column? | c ------+------+-----+------+----------+--- - 0 | 0000 | 0 | 0000 | 0 | 0 - 50 | 0050 | | | 100 | 0 - 100 | 0100 | | | 200 | 0 - 150 | 0150 | 150 | 0150 | 300 | 0 - 200 | 0200 | | | 400 | 0 - 250 | 0250 | | | 500 | 0 - 300 | 0300 | 300 | 0300 | 600 | 0 - 350 | 0350 | | | 700 | 0 - 400 | 0400 | | | 800 | 0 - 450 | 0450 | 450 | 0450 | 900 | 0 - 500 | 0500 | | | 1000 | 0 - 550 | 0550 | | | 1100 | 0 -(12 rows) + a | c | b | c | ?column? | c +------+-------+------+-------+----------+--- + -250 | -0250 | | | -500 | 0 + -200 | -0200 | | | -400 | 0 + -150 | -0150 | -150 | -0150 | -300 | 0 + -100 | -0100 | | | -200 | 0 + -50 | -0050 | | | -100 | 0 + 0 | 0000 | 0 | 0000 | 0 | 0 + 0 | 0000 | 0 | 0000 | 0 | 0 + 50 | 0050 | | | 100 | 0 + 100 | 0100 | | | 200 | 0 + 150 | 0150 | 150 | 0150 | 300 | 0 + 200 | 0200 | | | 400 | 0 + 250 | 0250 | | | 500 | 0 + 300 | 0300 | 300 | 0300 | 600 | 0 + 350 | 0350 | | | 700 | 0 + 400 | 0400 | | | 800 | 0 + 450 | 0450 | 450 | 0450 | 900 | 0 + 500 | 0500 | | | 1000 | 0 + 550 | 0550 | | | 1100 | 0 + 600 | 0600 | 600 | 0600 | 1200 | 0 + 650 | 0650 | | | 1300 | 0 + 700 | 0700 | | | 1400 | 0 + 750 | 0750 | 750 | 0750 | 1500 | 0 +(22 rows) -- Cases with non-nullable expressions in subquery results; -- make sure these go to null as expected @@ -757,21 +1159,34 @@ SELECT t1.a, t1.phv, t2.b, t2.phv, t3.a + t3.b, t3.phv FROM ((SELECT 50 phv, * F QUERY PLAN ---------------------------------------------------------------------------------------------------------------- Sort - Sort Key: prt1_p1.a, prt2_p1.b, ((prt1_e_p1.a + prt1_e_p1.b)) + Sort Key: prt1_p0.a, prt2_p0.b, ((prt1_e_p0.a + prt1_e_p0.b)) -> Append -> Hash Full Join - Hash Cond: (prt1_p1.a = ((prt1_e_p1.a + prt1_e_p1.b) / 2)) - Filter: ((prt1_p1.a = (50)) OR (prt2_p1.b = (75)) OR (((prt1_e_p1.a + prt1_e_p1.b) / 2) = (50))) + Hash Cond: (prt1_p0.a = ((prt1_e_p0.a + prt1_e_p0.b) / 2)) + Filter: ((prt1_p0.a = (50)) OR (prt2_p0.b = (75)) OR (((prt1_e_p0.a + prt1_e_p0.b) / 2) = (50))) -> Hash Full Join - Hash Cond: (prt1_p1.a = prt2_p1.b) - -> Seq Scan on prt1_p1 + Hash Cond: (prt1_p0.a = prt2_p0.b) + -> Seq Scan on prt1_p0 Filter: (b = 0) -> Hash - -> Seq Scan on prt2_p1 + -> Seq Scan on prt2_p0 Filter: (a = 0) -> Hash - -> Seq Scan on prt1_e_p1 + -> Seq Scan on prt1_e_p0 Filter: (c = 0) + -> Hash Full Join + Hash Cond: (((prt1_e_p1.a + prt1_e_p1.b) / 2) = prt1_p1.a) + Filter: ((prt1_p1.a = (50)) OR (prt2_p1.b = (75)) OR (((prt1_e_p1.a + prt1_e_p1.b) / 2) = (50))) + -> Seq Scan on prt1_e_p1 + Filter: (c = 0) + -> Hash + -> Hash Full Join + Hash Cond: (prt1_p1.a = prt2_p1.b) + -> Seq Scan on prt1_p1 + Filter: (b = 0) + -> Hash + -> Seq Scan on prt2_p1 + Filter: (a = 0) -> Hash Full Join Hash Cond: (prt1_p2.a = ((prt1_e_p2.a + prt1_e_p2.b) / 2)) Filter: ((prt1_p2.a = (50)) OR (prt2_p2.b = (75)) OR (((prt1_e_p2.a + prt1_e_p2.b) / 2) = (50))) @@ -798,7 +1213,20 @@ SELECT t1.a, t1.phv, t2.b, t2.phv, t3.a + t3.b, t3.phv FROM ((SELECT 50 phv, * F -> Hash -> Seq Scan on prt1_e_p3 Filter: (c = 0) -(42 rows) + -> Hash Full Join + Hash Cond: (prt1_p4.a = ((prt1_e_p4.a + prt1_e_p4.b) / 2)) + Filter: ((prt1_p4.a = (50)) OR (prt2_p4.b = (75)) OR (((prt1_e_p4.a + prt1_e_p4.b) / 2) = (50))) + -> Hash Full Join + Hash Cond: (prt1_p4.a = prt2_p4.b) + -> Seq Scan on prt1_p4 + Filter: (b = 0) + -> Hash + -> Seq Scan on prt2_p4 + Filter: (a = 0) + -> Hash + -> Seq Scan on prt1_e_p4 + Filter: (c = 0) +(68 rows) SELECT t1.a, t1.phv, t2.b, t2.phv, t3.a + t3.b, t3.phv FROM ((SELECT 50 phv, * FROM prt1 WHERE prt1.b = 0) t1 FULL JOIN (SELECT 75 phv, * FROM prt2 WHERE prt2.a = 0) t2 ON (t1.a = t2.b)) FULL JOIN (SELECT 50 phv, * FROM prt1_e WHERE prt1_e.c = 0) t3 ON (t1.a = (t3.a + t3.b)/2) WHERE t1.a = t1.phv OR t2.b = t2.phv OR (t3.a + t3.b)/2 = t3.phv ORDER BY t1.a, t2.b, t3.a + t3.b; a | phv | b | phv | ?column? | phv @@ -816,172 +1244,260 @@ SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t1.b FROM prt2 t1, prt1_e t2 WHER Sort Key: t1.a -> Append -> Nested Loop - Join Filter: (t1.a = t1_3.b) + Join Filter: (t1.a = t1_5.b) -> HashAggregate - Group Key: t1_3.b + Group Key: t1_5.b -> Hash Join - Hash Cond: (((t2.a + t2.b) / 2) = t1_3.b) - -> Seq Scan on prt1_e_p1 t2 + Hash Cond: (((t2.a + t2.b) / 2) = t1_5.b) + -> Seq Scan on prt1_e_p0 t2 -> Hash - -> Seq Scan on prt2_p1 t1_3 + -> Seq Scan on prt2_p0 t1_5 Filter: (a = 0) - -> Index Scan using iprt1_p1_a on prt1_p1 t1 + -> Index Scan using iprt1_p0_a on prt1_p0 t1 Index Cond: (a = ((t2.a + t2.b) / 2)) Filter: (b = 0) -> Nested Loop - Join Filter: (t1_1.a = t1_4.b) + Join Filter: (t1_1.a = t1_6.b) -> HashAggregate - Group Key: t1_4.b + Group Key: t1_6.b -> Hash Join - Hash Cond: (((t2_1.a + t2_1.b) / 2) = t1_4.b) - -> Seq Scan on prt1_e_p2 t2_1 + Hash Cond: (((t2_1.a + t2_1.b) / 2) = t1_6.b) + -> Seq Scan on prt1_e_p1 t2_1 -> Hash - -> Seq Scan on prt2_p2 t1_4 + -> Seq Scan on prt2_p1 t1_6 Filter: (a = 0) - -> Index Scan using iprt1_p2_a on prt1_p2 t1_1 + -> Index Scan using iprt1_p1_a on prt1_p1 t1_1 Index Cond: (a = ((t2_1.a + t2_1.b) / 2)) Filter: (b = 0) -> Nested Loop - Join Filter: (t1_2.a = t1_5.b) + Join Filter: (t1_2.a = t1_7.b) -> HashAggregate - Group Key: t1_5.b + Group Key: t1_7.b -> Nested Loop - -> Seq Scan on prt2_p3 t1_5 + -> Seq Scan on prt2_p2 t1_7 Filter: (a = 0) - -> Index Scan using iprt1_e_p3_ab2 on prt1_e_p3 t2_2 - Index Cond: (((a + b) / 2) = t1_5.b) - -> Index Scan using iprt1_p3_a on prt1_p3 t1_2 + -> Index Scan using iprt1_e_p2_ab2 on prt1_e_p2 t2_2 + Index Cond: (((a + b) / 2) = t1_7.b) + -> Index Scan using iprt1_p2_a on prt1_p2 t1_2 Index Cond: (a = ((t2_2.a + t2_2.b) / 2)) Filter: (b = 0) -(41 rows) + -> Nested Loop + Join Filter: (t1_3.a = t1_8.b) + -> HashAggregate + Group Key: t1_8.b + -> Nested Loop + -> Seq Scan on prt2_p3 t1_8 + Filter: (a = 0) + -> Index Scan using iprt1_e_p3_ab2 on prt1_e_p3 t2_3 + Index Cond: (((a + b) / 2) = t1_8.b) + -> Index Scan using iprt1_p3_a on prt1_p3 t1_3 + Index Cond: (a = ((t2_3.a + t2_3.b) / 2)) + Filter: (b = 0) + -> Nested Loop + Join Filter: (t1_4.a = t1_9.b) + -> HashAggregate + Group Key: t1_9.b + -> Hash Join + Hash Cond: (((t2_4.a + t2_4.b) / 2) = t1_9.b) + -> Seq Scan on prt1_e_p4 t2_4 + -> Hash + -> Seq Scan on prt2_p4 t1_9 + Filter: (a = 0) + -> Index Scan using iprt1_p4_a on prt1_p4 t1_4 + Index Cond: (a = ((t2_4.a + t2_4.b) / 2)) + Filter: (b = 0) +(66 rows) SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t1.b FROM prt2 t1, prt1_e t2 WHERE t1.a = 0 AND t1.b = (t2.a + t2.b)/2) AND t1.b = 0 ORDER BY t1.a; - a | b | c ------+---+------ - 0 | 0 | 0000 - 150 | 0 | 0150 - 300 | 0 | 0300 - 450 | 0 | 0450 -(4 rows) + a | b | c +------+---+------- + -150 | 0 | -0150 + 0 | 0 | 0000 + 150 | 0 | 0150 + 300 | 0 | 0300 + 450 | 0 | 0450 + 600 | 0 | 0600 + 750 | 0 | 0750 +(7 rows) EXPLAIN (COSTS OFF) SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t1.b FROM prt2 t1 WHERE t1.b IN (SELECT (t1.a + t1.b)/2 FROM prt1_e t1 WHERE t1.c = 0)) AND t1.b = 0 ORDER BY t1.a; - QUERY PLAN -------------------------------------------------------------------------- + QUERY PLAN +--------------------------------------------------------------------------- Sort Sort Key: t1.a -> Append -> Nested Loop -> HashAggregate - Group Key: t1_3.b + Group Key: t1_5.b -> Hash Semi Join - Hash Cond: (t1_3.b = ((t1_6.a + t1_6.b) / 2)) - -> Seq Scan on prt2_p1 t1_3 + Hash Cond: (t1_5.b = ((t1_10.a + t1_10.b) / 2)) + -> Seq Scan on prt2_p0 t1_5 -> Hash - -> Seq Scan on prt1_e_p1 t1_6 + -> Seq Scan on prt1_e_p0 t1_10 Filter: (c = 0) - -> Index Scan using iprt1_p1_a on prt1_p1 t1 - Index Cond: (a = t1_3.b) + -> Index Scan using iprt1_p0_a on prt1_p0 t1 + Index Cond: (a = t1_5.b) Filter: (b = 0) -> Nested Loop -> HashAggregate - Group Key: t1_4.b + Group Key: t1_6.b -> Hash Semi Join - Hash Cond: (t1_4.b = ((t1_7.a + t1_7.b) / 2)) - -> Seq Scan on prt2_p2 t1_4 + Hash Cond: (t1_6.b = ((t1_11.a + t1_11.b) / 2)) + -> Seq Scan on prt2_p1 t1_6 -> Hash - -> Seq Scan on prt1_e_p2 t1_7 + -> Seq Scan on prt1_e_p1 t1_11 Filter: (c = 0) - -> Index Scan using iprt1_p2_a on prt1_p2 t1_1 - Index Cond: (a = t1_4.b) + -> Index Scan using iprt1_p1_a on prt1_p1 t1_1 + Index Cond: (a = t1_6.b) Filter: (b = 0) -> Nested Loop -> HashAggregate - Group Key: t1_5.b + Group Key: t1_7.b -> Hash Semi Join - Hash Cond: (t1_5.b = ((t1_8.a + t1_8.b) / 2)) - -> Seq Scan on prt2_p3 t1_5 + Hash Cond: (t1_7.b = ((t1_12.a + t1_12.b) / 2)) + -> Seq Scan on prt2_p2 t1_7 -> Hash - -> Seq Scan on prt1_e_p3 t1_8 + -> Seq Scan on prt1_e_p2 t1_12 Filter: (c = 0) - -> Index Scan using iprt1_p3_a on prt1_p3 t1_2 - Index Cond: (a = t1_5.b) + -> Index Scan using iprt1_p2_a on prt1_p2 t1_2 + Index Cond: (a = t1_7.b) Filter: (b = 0) -(39 rows) - + -> Nested Loop + -> HashAggregate + Group Key: t1_8.b + -> Hash Semi Join + Hash Cond: (t1_8.b = ((t1_13.a + t1_13.b) / 2)) + -> Seq Scan on prt2_p3 t1_8 + -> Hash + -> Seq Scan on prt1_e_p3 t1_13 + Filter: (c = 0) + -> Index Scan using iprt1_p3_a on prt1_p3 t1_3 + Index Cond: (a = t1_8.b) + Filter: (b = 0) + -> Nested Loop + -> HashAggregate + Group Key: t1_9.b + -> Hash Semi Join + Hash Cond: (t1_9.b = ((t1_14.a + t1_14.b) / 2)) + -> Seq Scan on prt2_p4 t1_9 + -> Hash + -> Seq Scan on prt1_e_p4 t1_14 + Filter: (c = 0) + -> Index Scan using iprt1_p4_a on prt1_p4 t1_4 + Index Cond: (a = t1_9.b) + Filter: (b = 0) +(63 rows) + SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t1.b FROM prt2 t1 WHERE t1.b IN (SELECT (t1.a + t1.b)/2 FROM prt1_e t1 WHERE t1.c = 0)) AND t1.b = 0 ORDER BY t1.a; - a | b | c ------+---+------ - 0 | 0 | 0000 - 150 | 0 | 0150 - 300 | 0 | 0300 - 450 | 0 | 0450 -(4 rows) + a | b | c +------+---+------- + -150 | 0 | -0150 + 0 | 0 | 0000 + 150 | 0 | 0150 + 300 | 0 | 0300 + 450 | 0 | 0450 + 600 | 0 | 0600 + 750 | 0 | 0750 +(7 rows) -- test merge joins SET enable_hashjoin TO off; SET enable_nestloop TO off; EXPLAIN (COSTS OFF) SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t1.b FROM prt2 t1 WHERE t1.b IN (SELECT (t1.a + t1.b)/2 FROM prt1_e t1 WHERE t1.c = 0)) AND t1.b = 0 ORDER BY t1.a; - QUERY PLAN ----------------------------------------------------------------- + QUERY PLAN +------------------------------------------------------------------ Merge Append Sort Key: t1.a -> Merge Semi Join - Merge Cond: (t1.a = t1_3.b) + Merge Cond: (t1.a = t1_5.b) -> Sort Sort Key: t1.a - -> Seq Scan on prt1_p1 t1 + -> Seq Scan on prt1_p0 t1 Filter: (b = 0) -> Merge Semi Join - Merge Cond: (t1_3.b = (((t1_6.a + t1_6.b) / 2))) + Merge Cond: (t1_5.b = (((t1_10.a + t1_10.b) / 2))) -> Sort - Sort Key: t1_3.b - -> Seq Scan on prt2_p1 t1_3 + Sort Key: t1_5.b + -> Seq Scan on prt2_p0 t1_5 -> Sort - Sort Key: (((t1_6.a + t1_6.b) / 2)) - -> Seq Scan on prt1_e_p1 t1_6 + Sort Key: (((t1_10.a + t1_10.b) / 2)) + -> Seq Scan on prt1_e_p0 t1_10 Filter: (c = 0) -> Merge Semi Join - Merge Cond: (t1_1.a = t1_4.b) + Merge Cond: (t1_1.a = t1_6.b) -> Sort Sort Key: t1_1.a - -> Seq Scan on prt1_p2 t1_1 + -> Seq Scan on prt1_p1 t1_1 Filter: (b = 0) -> Merge Semi Join - Merge Cond: (t1_4.b = (((t1_7.a + t1_7.b) / 2))) + Merge Cond: (t1_6.b = (((t1_11.a + t1_11.b) / 2))) -> Sort - Sort Key: t1_4.b - -> Seq Scan on prt2_p2 t1_4 + Sort Key: t1_6.b + -> Seq Scan on prt2_p1 t1_6 -> Sort - Sort Key: (((t1_7.a + t1_7.b) / 2)) - -> Seq Scan on prt1_e_p2 t1_7 + Sort Key: (((t1_11.a + t1_11.b) / 2)) + -> Seq Scan on prt1_e_p1 t1_11 Filter: (c = 0) -> Merge Semi Join - Merge Cond: (t1_2.a = t1_5.b) + Merge Cond: (t1_2.a = t1_7.b) -> Sort Sort Key: t1_2.a - -> Seq Scan on prt1_p3 t1_2 + -> Seq Scan on prt1_p2 t1_2 Filter: (b = 0) -> Merge Semi Join - Merge Cond: (t1_5.b = (((t1_8.a + t1_8.b) / 2))) + Merge Cond: (t1_7.b = (((t1_12.a + t1_12.b) / 2))) -> Sort - Sort Key: t1_5.b - -> Seq Scan on prt2_p3 t1_5 + Sort Key: t1_7.b + -> Seq Scan on prt2_p2 t1_7 -> Sort - Sort Key: (((t1_8.a + t1_8.b) / 2)) - -> Seq Scan on prt1_e_p3 t1_8 + Sort Key: (((t1_12.a + t1_12.b) / 2)) + -> Seq Scan on prt1_e_p2 t1_12 Filter: (c = 0) -(47 rows) + -> Merge Semi Join + Merge Cond: (t1_3.a = t1_8.b) + -> Sort + Sort Key: t1_3.a + -> Seq Scan on prt1_p3 t1_3 + Filter: (b = 0) + -> Merge Semi Join + Merge Cond: (t1_8.b = (((t1_13.a + t1_13.b) / 2))) + -> Sort + Sort Key: t1_8.b + -> Seq Scan on prt2_p3 t1_8 + -> Sort + Sort Key: (((t1_13.a + t1_13.b) / 2)) + -> Seq Scan on prt1_e_p3 t1_13 + Filter: (c = 0) + -> Merge Semi Join + Merge Cond: (t1_4.a = t1_9.b) + -> Sort + Sort Key: t1_4.a + -> Seq Scan on prt1_p4 t1_4 + Filter: (b = 0) + -> Merge Semi Join + Merge Cond: (t1_9.b = (((t1_14.a + t1_14.b) / 2))) + -> Sort + Sort Key: t1_9.b + -> Seq Scan on prt2_p4 t1_9 + -> Sort + Sort Key: (((t1_14.a + t1_14.b) / 2)) + -> Seq Scan on prt1_e_p4 t1_14 + Filter: (c = 0) +(77 rows) SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t1.b FROM prt2 t1 WHERE t1.b IN (SELECT (t1.a + t1.b)/2 FROM prt1_e t1 WHERE t1.c = 0)) AND t1.b = 0 ORDER BY t1.a; - a | b | c ------+---+------ - 0 | 0 | 0000 - 150 | 0 | 0150 - 300 | 0 | 0300 - 450 | 0 | 0450 -(4 rows) + a | b | c +------+---+------- + -150 | 0 | -0150 + 0 | 0 | 0000 + 150 | 0 | 0150 + 300 | 0 | 0300 + 450 | 0 | 0450 + 600 | 0 | 0600 + 750 | 0 | 0750 +(7 rows) EXPLAIN (COSTS OFF) SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2 ON t1.a = t2.b) RIGHT JOIN prt1_e t3 ON (t1.a = (t3.a + t3.b)/2) WHERE t3.c = 0 ORDER BY t1.a, t2.b, t3.a + t3.b; @@ -998,14 +1514,14 @@ SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2 Merge Cond: ((((t3.a + t3.b) / 2)) = t1.a) -> Sort Sort Key: (((t3.a + t3.b) / 2)) - -> Seq Scan on prt1_e_p1 t3 + -> Seq Scan on prt1_e_p0 t3 Filter: (c = 0) -> Sort Sort Key: t1.a - -> Seq Scan on prt1_p1 t1 + -> Seq Scan on prt1_p0 t1 -> Sort Sort Key: t2.b - -> Seq Scan on prt2_p1 t2 + -> Seq Scan on prt2_p0 t2 -> Merge Left Join Merge Cond: (t1_1.a = t2_1.b) -> Sort @@ -1014,14 +1530,14 @@ SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2 Merge Cond: ((((t3_1.a + t3_1.b) / 2)) = t1_1.a) -> Sort Sort Key: (((t3_1.a + t3_1.b) / 2)) - -> Seq Scan on prt1_e_p2 t3_1 + -> Seq Scan on prt1_e_p1 t3_1 Filter: (c = 0) -> Sort Sort Key: t1_1.a - -> Seq Scan on prt1_p2 t1_1 + -> Seq Scan on prt1_p1 t1_1 -> Sort Sort Key: t2_1.b - -> Seq Scan on prt2_p2 t2_1 + -> Seq Scan on prt2_p1 t2_1 -> Merge Left Join Merge Cond: (t1_2.a = t2_2.b) -> Sort @@ -1030,32 +1546,74 @@ SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2 Merge Cond: ((((t3_2.a + t3_2.b) / 2)) = t1_2.a) -> Sort Sort Key: (((t3_2.a + t3_2.b) / 2)) - -> Seq Scan on prt1_e_p3 t3_2 + -> Seq Scan on prt1_e_p2 t3_2 Filter: (c = 0) -> Sort Sort Key: t1_2.a - -> Seq Scan on prt1_p3 t1_2 + -> Seq Scan on prt1_p2 t1_2 -> Sort Sort Key: t2_2.b - -> Seq Scan on prt2_p3 t2_2 -(51 rows) + -> Seq Scan on prt2_p2 t2_2 + -> Merge Left Join + Merge Cond: (t1_3.a = t2_3.b) + -> Sort + Sort Key: t1_3.a + -> Merge Left Join + Merge Cond: ((((t3_3.a + t3_3.b) / 2)) = t1_3.a) + -> Sort + Sort Key: (((t3_3.a + t3_3.b) / 2)) + -> Seq Scan on prt1_e_p3 t3_3 + Filter: (c = 0) + -> Sort + Sort Key: t1_3.a + -> Seq Scan on prt1_p3 t1_3 + -> Sort + Sort Key: t2_3.b + -> Seq Scan on prt2_p3 t2_3 + -> Merge Left Join + Merge Cond: (t1_4.a = t2_4.b) + -> Sort + Sort Key: t1_4.a + -> Merge Left Join + Merge Cond: ((((t3_4.a + t3_4.b) / 2)) = t1_4.a) + -> Sort + Sort Key: (((t3_4.a + t3_4.b) / 2)) + -> Seq Scan on prt1_e_p4 t3_4 + Filter: (c = 0) + -> Sort + Sort Key: t1_4.a + -> Seq Scan on prt1_p4 t1_4 + -> Sort + Sort Key: t2_4.b + -> Seq Scan on prt2_p4 t2_4 +(83 rows) SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2 ON t1.a = t2.b) RIGHT JOIN prt1_e t3 ON (t1.a = (t3.a + t3.b)/2) WHERE t3.c = 0 ORDER BY t1.a, t2.b, t3.a + t3.b; - a | c | b | c | ?column? | c ------+------+-----+------+----------+--- - 0 | 0000 | 0 | 0000 | 0 | 0 - 50 | 0050 | | | 100 | 0 - 100 | 0100 | | | 200 | 0 - 150 | 0150 | 150 | 0150 | 300 | 0 - 200 | 0200 | | | 400 | 0 - 250 | 0250 | | | 500 | 0 - 300 | 0300 | 300 | 0300 | 600 | 0 - 350 | 0350 | | | 700 | 0 - 400 | 0400 | | | 800 | 0 - 450 | 0450 | 450 | 0450 | 900 | 0 - 500 | 0500 | | | 1000 | 0 - 550 | 0550 | | | 1100 | 0 -(12 rows) + a | c | b | c | ?column? | c +------+-------+------+-------+----------+--- + -250 | -0250 | | | -500 | 0 + -200 | -0200 | | | -400 | 0 + -150 | -0150 | -150 | -0150 | -300 | 0 + -100 | -0100 | | | -200 | 0 + -50 | -0050 | | | -100 | 0 + 0 | 0000 | 0 | 0000 | 0 | 0 + 0 | 0000 | 0 | 0000 | 0 | 0 + 50 | 0050 | | | 100 | 0 + 100 | 0100 | | | 200 | 0 + 150 | 0150 | 150 | 0150 | 300 | 0 + 200 | 0200 | | | 400 | 0 + 250 | 0250 | | | 500 | 0 + 300 | 0300 | 300 | 0300 | 600 | 0 + 350 | 0350 | | | 700 | 0 + 400 | 0400 | | | 800 | 0 + 450 | 0450 | 450 | 0450 | 900 | 0 + 500 | 0500 | | | 1000 | 0 + 550 | 0550 | | | 1100 | 0 + 600 | 0600 | 600 | 0600 | 1200 | 0 + 650 | 0650 | | | 1300 | 0 + 700 | 0700 | | | 1400 | 0 + 750 | 0750 | 750 | 0750 | 1500 | 0 +(22 rows) -- MergeAppend on nullable column -- This should generate a partitionwise join, but currently fails to @@ -1064,12 +1622,14 @@ SELECT t1.a, t2.b FROM (SELECT * FROM prt1 WHERE a < 450) t1 LEFT JOIN (SELECT * QUERY PLAN ----------------------------------------------------------- Sort - Sort Key: prt1_p1.a, prt2_p2.b + Sort Key: prt1_p0.a, prt2_p2.b -> Merge Left Join - Merge Cond: (prt1_p1.a = prt2_p2.b) + Merge Cond: (prt1_p0.a = prt2_p2.b) -> Sort - Sort Key: prt1_p1.a + Sort Key: prt1_p0.a -> Append + -> Seq Scan on prt1_p0 + Filter: ((a < 450) AND (b = 0)) -> Seq Scan on prt1_p1 Filter: ((a < 450) AND (b = 0)) -> Seq Scan on prt1_p2 @@ -1081,21 +1641,28 @@ SELECT t1.a, t2.b FROM (SELECT * FROM prt1 WHERE a < 450) t1 LEFT JOIN (SELECT * Filter: (b > 250) -> Seq Scan on prt2_p3 Filter: (b > 250) -(18 rows) + -> Seq Scan on prt2_p4 + Filter: (b > 250) +(22 rows) SELECT t1.a, t2.b FROM (SELECT * FROM prt1 WHERE a < 450) t1 LEFT JOIN (SELECT * FROM prt2 WHERE b > 250) t2 ON t1.a = t2.b WHERE t1.b = 0 ORDER BY t1.a, t2.b; - a | b ------+----- - 0 | - 50 | - 100 | - 150 | - 200 | - 250 | - 300 | 300 - 350 | - 400 | -(9 rows) + a | b +------+----- + -250 | + -200 | + -150 | + -100 | + -50 | + 0 | + 50 | + 100 | + 150 | + 200 | + 250 | + 300 | 300 + 350 | + 400 | +(14 rows) -- merge join when expression with whole-row reference needs to be sorted; -- partitionwise join does not apply @@ -1109,175 +1676,2403 @@ SELECT t1.a, t2.b FROM prt1 t1, prt2 t2 WHERE t1::text = t2::text AND t1.a = t2. Sort Key: t1.a, ((((t1.*)::prt1))::text) -> Result -> Append - -> Seq Scan on prt1_p1 t1 - -> Seq Scan on prt1_p2 t1_1 - -> Seq Scan on prt1_p3 t1_2 + -> Seq Scan on prt1_p0 t1 + -> Seq Scan on prt1_p1 t1_1 + -> Seq Scan on prt1_p2 t1_2 + -> Seq Scan on prt1_p3 t1_3 + -> Seq Scan on prt1_p4 t1_4 -> Sort Sort Key: t2.b, ((((t2.*)::prt2))::text) -> Result -> Append - -> Seq Scan on prt2_p1 t2 - -> Seq Scan on prt2_p2 t2_1 - -> Seq Scan on prt2_p3 t2_2 -(16 rows) + -> Seq Scan on prt2_p0 t2 + -> Seq Scan on prt2_p1 t2_1 + -> Seq Scan on prt2_p2 t2_2 + -> Seq Scan on prt2_p3 t2_3 + -> Seq Scan on prt2_p4 t2_4 +(20 rows) SELECT t1.a, t2.b FROM prt1 t1, prt2 t2 WHERE t1::text = t2::text AND t1.a = t2.b ORDER BY t1.a; - a | b -----+---- - 0 | 0 - 6 | 6 - 12 | 12 - 18 | 18 - 24 | 24 -(5 rows) + a | b +-----+----- + -24 | -24 + -18 | -18 + -12 | -12 + -6 | -6 + 0 | 0 + 6 | 6 + 12 | 12 + 18 | 18 + 24 | 24 +(9 rows) RESET enable_hashjoin; RESET enable_nestloop; --- --- partitioned by multiple columns --- -CREATE TABLE prt1_m (a int, b int, c int) PARTITION BY RANGE(a, ((a + b)/2)); -CREATE TABLE prt1_m_p1 PARTITION OF prt1_m FOR VALUES FROM (0, 0) TO (250, 250); -CREATE TABLE prt1_m_p2 PARTITION OF prt1_m FOR VALUES FROM (250, 250) TO (500, 500); -CREATE TABLE prt1_m_p3 PARTITION OF prt1_m FOR VALUES FROM (500, 500) TO (600, 600); -INSERT INTO prt1_m SELECT i, i, i % 25 FROM generate_series(0, 599, 2) i; -ANALYZE prt1_m; -CREATE TABLE prt2_m (a int, b int, c int) PARTITION BY RANGE(((b + a)/2), b); -CREATE TABLE prt2_m_p1 PARTITION OF prt2_m FOR VALUES FROM (0, 0) TO (250, 250); -CREATE TABLE prt2_m_p2 PARTITION OF prt2_m FOR VALUES FROM (250, 250) TO (500, 500); -CREATE TABLE prt2_m_p3 PARTITION OF prt2_m FOR VALUES FROM (500, 500) TO (600, 600); -INSERT INTO prt2_m SELECT i, i, i % 25 FROM generate_series(0, 599, 3) i; -ANALYZE prt2_m; +-- test default partition behavior for range, partition-wise join is not +-- possible since more than one partition on one side matches default partition +-- on the other side. Default partition from prt1 matches prt2_p3 and +-- prt2_p4 partition from prt2. +ALTER TABLE prt1 DETACH PARTITION prt1_p3; +ALTER TABLE prt1 ATTACH PARTITION prt1_p3 DEFAULT; +ANALYZE prt1; EXPLAIN (COSTS OFF) -SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1_m WHERE prt1_m.c = 0) t1 FULL JOIN (SELECT * FROM prt2_m WHERE prt2_m.c = 0) t2 ON (t1.a = (t2.b + t2.a)/2 AND t2.b = (t1.a + t1.b)/2) ORDER BY t1.a, t2.b; - QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------- +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.b = 0 ORDER BY t1.a, t2.b; + QUERY PLAN +-------------------------------------------------- Sort - Sort Key: prt1_m_p1.a, prt2_m_p1.b + Sort Key: t1.a + -> Hash Join + Hash Cond: (t2.b = t1.a) + -> Append + -> Seq Scan on prt2_p0 t2 + -> Seq Scan on prt2_p1 t2_1 + -> Seq Scan on prt2_p2 t2_2 + -> Seq Scan on prt2_p3 t2_3 + -> Seq Scan on prt2_p4 t2_4 + -> Hash + -> Append + -> Seq Scan on prt1_p0 t1 + Filter: (b = 0) + -> Seq Scan on prt1_p1 t1_1 + Filter: (b = 0) + -> Seq Scan on prt1_p2 t1_2 + Filter: (b = 0) + -> Seq Scan on prt1_p4 t1_3 + Filter: (b = 0) + -> Seq Scan on prt1_p3 t1_4 + Filter: (b = 0) +(22 rows) + +-- partitionwise join should be possible when we drop prt2_p4 from prt2. +ALTER TABLE prt2 DETACH PARTITION prt2_p4; +ANALYZE prt2; +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.b = 0 ORDER BY t1.a, t2.b; + QUERY PLAN +--------------------------------------------------------------- + Sort + Sort Key: t1.a -> Append - -> Hash Full Join - Hash Cond: ((prt1_m_p1.a = ((prt2_m_p1.b + prt2_m_p1.a) / 2)) AND (((prt1_m_p1.a + prt1_m_p1.b) / 2) = prt2_m_p1.b)) - -> Seq Scan on prt1_m_p1 - Filter: (c = 0) + -> Hash Join + Hash Cond: (t2.b = t1.a) + -> Seq Scan on prt2_p0 t2 -> Hash - -> Seq Scan on prt2_m_p1 - Filter: (c = 0) - -> Hash Full Join - Hash Cond: ((prt1_m_p2.a = ((prt2_m_p2.b + prt2_m_p2.a) / 2)) AND (((prt1_m_p2.a + prt1_m_p2.b) / 2) = prt2_m_p2.b)) - -> Seq Scan on prt1_m_p2 - Filter: (c = 0) + -> Seq Scan on prt1_p0 t1 + Filter: (b = 0) + -> Hash Join + Hash Cond: (t2_1.b = t1_1.a) + -> Seq Scan on prt2_p1 t2_1 -> Hash - -> Seq Scan on prt2_m_p2 - Filter: (c = 0) - -> Hash Full Join - Hash Cond: ((prt1_m_p3.a = ((prt2_m_p3.b + prt2_m_p3.a) / 2)) AND (((prt1_m_p3.a + prt1_m_p3.b) / 2) = prt2_m_p3.b)) - -> Seq Scan on prt1_m_p3 - Filter: (c = 0) + -> Seq Scan on prt1_p1 t1_1 + Filter: (b = 0) + -> Hash Join + Hash Cond: (t2_2.b = t1_2.a) + -> Seq Scan on prt2_p2 t2_2 -> Hash - -> Seq Scan on prt2_m_p3 - Filter: (c = 0) -(24 rows) + -> Seq Scan on prt1_p2 t1_2 + Filter: (b = 0) + -> Nested Loop + -> Seq Scan on prt1_p3 t1_3 + Filter: (b = 0) + -> Index Scan using iprt2_p3_b on prt2_p3 t2_3 + Index Cond: (b = t1_3.a) +(26 rows) -SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1_m WHERE prt1_m.c = 0) t1 FULL JOIN (SELECT * FROM prt2_m WHERE prt2_m.c = 0) t2 ON (t1.a = (t2.b + t2.a)/2 AND t2.b = (t1.a + t1.b)/2) ORDER BY t1.a, t2.b; - a | c | b | c ------+---+-----+--- - 0 | 0 | 0 | 0 - 50 | 0 | | - 100 | 0 | | - 150 | 0 | 150 | 0 - 200 | 0 | | - 250 | 0 | | - 300 | 0 | 300 | 0 - 350 | 0 | | - 400 | 0 | | - 450 | 0 | 450 | 0 - 500 | 0 | | - 550 | 0 | | - | | 75 | 0 - | | 225 | 0 - | | 375 | 0 - | | 525 | 0 +-- restore the partitioned tables for rest of the tests +ALTER TABLE prt1 DETACH PARTITION prt1_p3; +ALTER TABLE prt1 ATTACH PARTITION prt1_p3 FOR VALUES FROM (500) TO (600); +ANALYZE prt1; +ALTER TABLE prt2 ATTACH PARTITION prt2_p4 FOR VALUES FROM (600) TO (MAXVALUE); +ANALYZE prt2; +-- Add an extra partition to prt2 , Partition-wise join is possible with +-- extra partitions on inner side are allowed +DROP TABLE prt2_p4; +CREATE TABLE prt2_p4 PARTITION OF prt2 FOR VALUES FROM (600) TO (800); +CREATE TABLE prt2_p5 PARTITION OF prt2 FOR VALUES FROM (800) TO (1000); +INSERT INTO prt2 SELECT i % 25, i, to_char(i, 'FM0000') FROM generate_series(600, 999) i WHERE i % 3 = 0; +ANALYZE prt2; +-- inner join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM prt1 t1 INNER JOIN prt2 t2 ON t1.a = t2.b WHERE t1.b = 0 ORDER BY t1.a; + QUERY PLAN +--------------------------------------------------------------- + Sort + Sort Key: t1.a + -> Append + -> Hash Join + Hash Cond: (t2.b = t1.a) + -> Seq Scan on prt2_p0 t2 + -> Hash + -> Seq Scan on prt1_p0 t1 + Filter: (b = 0) + -> Hash Join + Hash Cond: (t2_1.b = t1_1.a) + -> Seq Scan on prt2_p1 t2_1 + -> Hash + -> Seq Scan on prt1_p1 t1_1 + Filter: (b = 0) + -> Hash Join + Hash Cond: (t2_2.b = t1_2.a) + -> Seq Scan on prt2_p2 t2_2 + -> Hash + -> Seq Scan on prt1_p2 t1_2 + Filter: (b = 0) + -> Nested Loop + -> Seq Scan on prt1_p3 t1_3 + Filter: (b = 0) + -> Index Scan using iprt2_p3_b on prt2_p3 t2_3 + Index Cond: (b = t1_3.a) + -> Hash Join + Hash Cond: (t2_4.b = t1_4.a) + -> Seq Scan on prt2_p4 t2_4 + -> Hash + -> Seq Scan on prt1_p4 t1_4 + Filter: (b = 0) +(32 rows) + +SELECT t1.a, t1.c, t2.a, t2.c FROM prt1 t1 INNER JOIN prt2 t2 ON t1.a = t2.b WHERE t1.b = 0 ORDER BY t1.a; + a | c | a | c +------+-------+---+------- + -150 | -0150 | 0 | -0150 + 0 | 0000 | 0 | 0000 + 150 | 0150 | 0 | 0150 + 300 | 0300 | 0 | 0300 + 450 | 0450 | 0 | 0450 + 600 | 0600 | 0 | 0600 + 750 | 0750 | 0 | 0750 +(7 rows) + +-- left join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM prt1 t1 LEFT JOIN prt2 t2 ON t1.a = t2.b WHERE t1.b = 0 ORDER BY t1.a; + QUERY PLAN +--------------------------------------------------------------- + Sort + Sort Key: t1.a + -> Append + -> Hash Right Join + Hash Cond: (t2.b = t1.a) + -> Seq Scan on prt2_p0 t2 + -> Hash + -> Seq Scan on prt1_p0 t1 + Filter: (b = 0) + -> Hash Right Join + Hash Cond: (t2_1.b = t1_1.a) + -> Seq Scan on prt2_p1 t2_1 + -> Hash + -> Seq Scan on prt1_p1 t1_1 + Filter: (b = 0) + -> Hash Right Join + Hash Cond: (t2_2.b = t1_2.a) + -> Seq Scan on prt2_p2 t2_2 + -> Hash + -> Seq Scan on prt1_p2 t1_2 + Filter: (b = 0) + -> Nested Loop Left Join + -> Seq Scan on prt1_p3 t1_3 + Filter: (b = 0) + -> Index Scan using iprt2_p3_b on prt2_p3 t2_3 + Index Cond: (b = t1_3.a) + -> Hash Right Join + Hash Cond: (t2_4.b = t1_4.a) + -> Seq Scan on prt2_p4 t2_4 + -> Hash + -> Seq Scan on prt1_p4 t1_4 + Filter: (b = 0) +(32 rows) + +SELECT t1.a, t1.c, t2.a, t2.c FROM prt1 t1 LEFT JOIN prt2 t2 ON t1.a = t2.b WHERE t1.b = 0 ORDER BY t1.a; + a | c | a | c +------+-------+---+------- + -250 | -0250 | | + -200 | -0200 | | + -150 | -0150 | 0 | -0150 + -100 | -0100 | | + -50 | -0050 | | + 0 | 0000 | 0 | 0000 + 50 | 0050 | | + 100 | 0100 | | + 150 | 0150 | 0 | 0150 + 200 | 0200 | | + 250 | 0250 | | + 300 | 0300 | 0 | 0300 + 350 | 0350 | | + 400 | 0400 | | + 450 | 0450 | 0 | 0450 + 500 | 0500 | | + 550 | 0550 | | + 600 | 0600 | 0 | 0600 + 650 | 0650 | | + 700 | 0700 | | + 750 | 0750 | 0 | 0750 +(21 rows) + +-- semi join +EXPLAIN (COSTS OFF) +select t1.a, t1.b, t1.c from prt1 t1 where exists (select 1 from prt2 t2 WHERE t1.a = t2.b) and t1.b = 0 order by t1.a, t1.b, t1.c; + QUERY PLAN +-------------------------------------------------------------------- + Sort + Sort Key: t1.a, t1.c + -> Append + -> Hash Semi Join + Hash Cond: (t1.a = t2.b) + -> Seq Scan on prt1_p0 t1 + Filter: (b = 0) + -> Hash + -> Seq Scan on prt2_p0 t2 + -> Hash Semi Join + Hash Cond: (t1_1.a = t2_1.b) + -> Seq Scan on prt1_p1 t1_1 + Filter: (b = 0) + -> Hash + -> Seq Scan on prt2_p1 t2_1 + -> Hash Semi Join + Hash Cond: (t1_2.a = t2_2.b) + -> Seq Scan on prt1_p2 t1_2 + Filter: (b = 0) + -> Hash + -> Seq Scan on prt2_p2 t2_2 + -> Nested Loop Semi Join + -> Seq Scan on prt1_p3 t1_3 + Filter: (b = 0) + -> Index Only Scan using iprt2_p3_b on prt2_p3 t2_3 + Index Cond: (b = t1_3.a) + -> Hash Semi Join + Hash Cond: (t1_4.a = t2_4.b) + -> Seq Scan on prt1_p4 t1_4 + Filter: (b = 0) + -> Hash + -> Seq Scan on prt2_p4 t2_4 +(32 rows) + +select t1.a, t1.b, t1.c from prt1 t1 where exists (select 1 from prt2 t2 WHERE t1.a = t2.b) and t1.b = 0 order by t1.a, t1.b, t1.c; + a | b | c +------+---+------- + -150 | 0 | -0150 + 0 | 0 | 0000 + 150 | 0 | 0150 + 300 | 0 | 0300 + 450 | 0 | 0450 + 600 | 0 | 0600 + 750 | 0 | 0750 +(7 rows) + +EXPLAIN (COSTS OFF) +select t1.a, t1.b, t1.c from prt2 t1 where exists (select 1 from prt1 t2 WHERE t1.b = t2.a) and t1.a = 0 order by t1.a, t1.b, t1.c; + QUERY PLAN +-------------------------------------------------------------------- + Sort + Sort Key: t1.b, t1.c + -> Append + -> Nested Loop Semi Join + -> Seq Scan on prt2_p0 t1 + Filter: (a = 0) + -> Index Only Scan using iprt1_p0_a on prt1_p0 t2 + Index Cond: (a = t1.b) + -> Nested Loop Semi Join + -> Seq Scan on prt2_p1 t1_1 + Filter: (a = 0) + -> Index Only Scan using iprt1_p1_a on prt1_p1 t2_1 + Index Cond: (a = t1_1.b) + -> Nested Loop Semi Join + -> Seq Scan on prt2_p2 t1_2 + Filter: (a = 0) + -> Index Only Scan using iprt1_p2_a on prt1_p2 t2_2 + Index Cond: (a = t1_2.b) + -> Nested Loop Semi Join + -> Seq Scan on prt2_p3 t1_3 + Filter: (a = 0) + -> Index Only Scan using iprt1_p3_a on prt1_p3 t2_3 + Index Cond: (a = t1_3.b) + -> Nested Loop Semi Join + -> Seq Scan on prt2_p4 t1_4 + Filter: (a = 0) + -> Index Only Scan using iprt1_p4_a on prt1_p4 t2_4 + Index Cond: (a = t1_4.b) +(28 rows) + +select t1.a, t1.b, t1.c from prt2 t1 where exists (select 1 from prt1 t2 WHERE t1.b = t2.a) and t1.a = 0 order by t1.a, t1.b, t1.c; + a | b | c +---+------+------- + 0 | -150 | -0150 + 0 | 0 | 0000 + 0 | 150 | 0150 + 0 | 300 | 0300 + 0 | 450 | 0450 + 0 | 600 | 0600 + 0 | 750 | 0750 +(7 rows) + +-- anti join +EXPLAIN (COSTS OFF) +select t1.a, t1.b, t1.c from prt1 t1 where not exists (select 1 from prt2 t2 WHERE t1.a = t2.b) and t1.b = 0 order by t1.a, t1.b, t1.c; + QUERY PLAN +-------------------------------------------------------------------- + Sort + Sort Key: t1.a, t1.c + -> Append + -> Hash Anti Join + Hash Cond: (t1.a = t2.b) + -> Seq Scan on prt1_p0 t1 + Filter: (b = 0) + -> Hash + -> Seq Scan on prt2_p0 t2 + -> Hash Anti Join + Hash Cond: (t1_1.a = t2_1.b) + -> Seq Scan on prt1_p1 t1_1 + Filter: (b = 0) + -> Hash + -> Seq Scan on prt2_p1 t2_1 + -> Hash Anti Join + Hash Cond: (t1_2.a = t2_2.b) + -> Seq Scan on prt1_p2 t1_2 + Filter: (b = 0) + -> Hash + -> Seq Scan on prt2_p2 t2_2 + -> Nested Loop Anti Join + -> Seq Scan on prt1_p3 t1_3 + Filter: (b = 0) + -> Index Only Scan using iprt2_p3_b on prt2_p3 t2_3 + Index Cond: (b = t1_3.a) + -> Hash Anti Join + Hash Cond: (t1_4.a = t2_4.b) + -> Seq Scan on prt1_p4 t1_4 + Filter: (b = 0) + -> Hash + -> Seq Scan on prt2_p4 t2_4 +(32 rows) + +select t1.a, t1.b, t1.c from prt1 t1 where not exists (select 1 from prt2 t2 WHERE t1.a = t2.b) and t1.b = 0 order by t1.a, t1.b, t1.c; + a | b | c +------+---+------- + -250 | 0 | -0250 + -200 | 0 | -0200 + -100 | 0 | -0100 + -50 | 0 | -0050 + 50 | 0 | 0050 + 100 | 0 | 0100 + 200 | 0 | 0200 + 250 | 0 | 0250 + 350 | 0 | 0350 + 400 | 0 | 0400 + 500 | 0 | 0500 + 550 | 0 | 0550 + 650 | 0 | 0650 + 700 | 0 | 0700 +(14 rows) + +-- 3-way join when not every pair of joining relation can use partition-wise +-- join +EXPLAIN (COSTS OFF) +SELECT t1.a, t2.a, t3.c FROM prt1 t1 RIGHT JOIN prt2 t2 ON (t1.a = t2.b) INNER JOIN prt1 t3 ON (t2.b = t3.a) WHERE t2.a = 0 ORDER BY t1.a, t2.a, t3.c; + QUERY PLAN +--------------------------------------------------------------------- + Sort + Sort Key: t1.a, t3.c + -> Append + -> Nested Loop Left Join + -> Nested Loop + -> Seq Scan on prt2_p0 t2 + Filter: (a = 0) + -> Index Scan using iprt1_p0_a on prt1_p0 t3 + Index Cond: (a = t2.b) + -> Index Only Scan using iprt1_p0_a on prt1_p0 t1 + Index Cond: (a = t2.b) + -> Hash Right Join + Hash Cond: (t1_1.a = t2_1.b) + -> Seq Scan on prt1_p1 t1_1 + -> Hash + -> Hash Join + Hash Cond: (t3_1.a = t2_1.b) + -> Seq Scan on prt1_p1 t3_1 + -> Hash + -> Seq Scan on prt2_p1 t2_1 + Filter: (a = 0) + -> Nested Loop Left Join + -> Nested Loop + -> Seq Scan on prt2_p2 t2_2 + Filter: (a = 0) + -> Index Scan using iprt1_p2_a on prt1_p2 t3_2 + Index Cond: (a = t2_2.b) + -> Index Only Scan using iprt1_p2_a on prt1_p2 t1_2 + Index Cond: (a = t2_2.b) + -> Nested Loop Left Join + -> Nested Loop + -> Seq Scan on prt2_p3 t2_3 + Filter: (a = 0) + -> Index Scan using iprt1_p3_a on prt1_p3 t3_3 + Index Cond: (a = t2_3.b) + -> Index Only Scan using iprt1_p3_a on prt1_p3 t1_3 + Index Cond: (a = t2_3.b) + -> Hash Right Join + Hash Cond: (t1_4.a = t2_4.b) + -> Seq Scan on prt1_p4 t1_4 + -> Hash + -> Hash Join + Hash Cond: (t3_4.a = t2_4.b) + -> Seq Scan on prt1_p4 t3_4 + -> Hash + -> Seq Scan on prt2_p4 t2_4 + Filter: (a = 0) +(47 rows) + +SELECT t1.a, t2.a, t3.c FROM prt1 t1 RIGHT JOIN prt2 t2 ON (t1.a = t2.b) INNER JOIN prt1 t3 ON (t2.b = t3.a) WHERE t2.a = 0 ORDER BY t1.a, t2.a, t3.c; + a | a | c +------+---+------- + -150 | 0 | -0150 + 0 | 0 | 0000 + 150 | 0 | 0150 + 300 | 0 | 0300 + 450 | 0 | 0450 + 600 | 0 | 0600 + 750 | 0 | 0750 +(7 rows) + +-- partition-wise join can not handle missing partition on the inner side +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM prt1 t1 RIGHT JOIN prt2 t2 ON t1.a = t2.b WHERE t2.a = 0 ORDER BY t2.b; + QUERY PLAN +-------------------------------------------------- + Sort + Sort Key: t2.b + -> Hash Right Join + Hash Cond: (t1.a = t2.b) + -> Append + -> Seq Scan on prt1_p0 t1 + -> Seq Scan on prt1_p1 t1_1 + -> Seq Scan on prt1_p2 t1_2 + -> Seq Scan on prt1_p3 t1_3 + -> Seq Scan on prt1_p4 t1_4 + -> Hash + -> Append + -> Seq Scan on prt2_p0 t2 + Filter: (a = 0) + -> Seq Scan on prt2_p1 t2_1 + Filter: (a = 0) + -> Seq Scan on prt2_p2 t2_2 + Filter: (a = 0) + -> Seq Scan on prt2_p3 t2_3 + Filter: (a = 0) + -> Seq Scan on prt2_p4 t2_4 + Filter: (a = 0) + -> Seq Scan on prt2_p5 t2_5 + Filter: (a = 0) +(24 rows) + +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM prt1 t1 FULL JOIN prt2 t2 ON t1.a = t2.b WHERE coalesce(t1.b, 0) + coalesce(t2.a, 0) = 0 ORDER BY t1.a, t2.a; + QUERY PLAN +--------------------------------------------------------------- + Sort + Sort Key: t1.a, t2.a + -> Hash Full Join + Hash Cond: (t1.a = t2.b) + Filter: ((COALESCE(t1.b, 0) + COALESCE(t2.a, 0)) = 0) + -> Append + -> Seq Scan on prt1_p0 t1 + -> Seq Scan on prt1_p1 t1_1 + -> Seq Scan on prt1_p2 t1_2 + -> Seq Scan on prt1_p3 t1_3 + -> Seq Scan on prt1_p4 t1_4 + -> Hash + -> Append + -> Seq Scan on prt2_p0 t2 + -> Seq Scan on prt2_p1 t2_1 + -> Seq Scan on prt2_p2 t2_2 + -> Seq Scan on prt2_p3 t2_3 + -> Seq Scan on prt2_p4 t2_4 + -> Seq Scan on prt2_p5 t2_5 +(19 rows) + +EXPLAIN (COSTS OFF) +select t1.a, t1.b, t1.c from prt2 t1 where not exists (select 1 from prt1 t2 WHERE t1.b = t2.a) and t1.a = 0 order by t1.a, t1.b, t1.c; + QUERY PLAN +-------------------------------------------------- + Sort + Sort Key: t1.b, t1.c + -> Hash Anti Join + Hash Cond: (t1.b = t2.a) + -> Append + -> Seq Scan on prt2_p0 t1 + Filter: (a = 0) + -> Seq Scan on prt2_p1 t1_1 + Filter: (a = 0) + -> Seq Scan on prt2_p2 t1_2 + Filter: (a = 0) + -> Seq Scan on prt2_p3 t1_3 + Filter: (a = 0) + -> Seq Scan on prt2_p4 t1_4 + Filter: (a = 0) + -> Seq Scan on prt2_p5 t1_5 + Filter: (a = 0) + -> Hash + -> Append + -> Seq Scan on prt1_p0 t2 + -> Seq Scan on prt1_p1 t2_1 + -> Seq Scan on prt1_p2 t2_2 + -> Seq Scan on prt1_p3 t2_3 + -> Seq Scan on prt1_p4 t2_4 +(24 rows) + +-- Partition-wise join can not handle the case when one partition from one side +-- matches with multiple partitions on the other side +DROP TABLE prt2_p4; +DROP TABLE prt2_p5; +CREATE TABLE prt2_p4 PARTITION OF prt2 FOR VALUES FROM (600) TO (700); +CREATE TABLE prt2_p5 PARTITION OF prt2 FOR VALUES FROM (700) TO (1000); +INSERT INTO prt2 SELECT i % 25, i, to_char(i, 'FM0000') FROM generate_series(600, 999, 3) i; +ANALYZE prt2; +-- inner join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM prt1 t1 INNER JOIN prt2 t2 ON t1.a = t2.b WHERE t1.b = 0 ORDER BY t1.a; + QUERY PLAN +-------------------------------------------------- + Sort + Sort Key: t1.a + -> Hash Join + Hash Cond: (t2.b = t1.a) + -> Append + -> Seq Scan on prt2_p0 t2 + -> Seq Scan on prt2_p1 t2_1 + -> Seq Scan on prt2_p2 t2_2 + -> Seq Scan on prt2_p3 t2_3 + -> Seq Scan on prt2_p4 t2_4 + -> Seq Scan on prt2_p5 t2_5 + -> Hash + -> Append + -> Seq Scan on prt1_p0 t1 + Filter: (b = 0) + -> Seq Scan on prt1_p1 t1_1 + Filter: (b = 0) + -> Seq Scan on prt1_p2 t1_2 + Filter: (b = 0) + -> Seq Scan on prt1_p3 t1_3 + Filter: (b = 0) + -> Seq Scan on prt1_p4 t1_4 + Filter: (b = 0) +(23 rows) + +-- left join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM prt1 t1 LEFT JOIN prt2 t2 ON t1.a = t2.b WHERE t1.b = 0 ORDER BY t1.a; + QUERY PLAN +-------------------------------------------------- + Sort + Sort Key: t1.a + -> Hash Right Join + Hash Cond: (t2.b = t1.a) + -> Append + -> Seq Scan on prt2_p0 t2 + -> Seq Scan on prt2_p1 t2_1 + -> Seq Scan on prt2_p2 t2_2 + -> Seq Scan on prt2_p3 t2_3 + -> Seq Scan on prt2_p4 t2_4 + -> Seq Scan on prt2_p5 t2_5 + -> Hash + -> Append + -> Seq Scan on prt1_p0 t1 + Filter: (b = 0) + -> Seq Scan on prt1_p1 t1_1 + Filter: (b = 0) + -> Seq Scan on prt1_p2 t1_2 + Filter: (b = 0) + -> Seq Scan on prt1_p3 t1_3 + Filter: (b = 0) + -> Seq Scan on prt1_p4 t1_4 + Filter: (b = 0) +(23 rows) + +-- right join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM prt1 t1 RIGHT JOIN prt2 t2 ON t1.a = t2.b WHERE t2.a = 0 ORDER BY t2.a; + QUERY PLAN +-------------------------------------------- + Hash Right Join + Hash Cond: (t1.a = t2.b) + -> Append + -> Seq Scan on prt1_p0 t1 + -> Seq Scan on prt1_p1 t1_1 + -> Seq Scan on prt1_p2 t1_2 + -> Seq Scan on prt1_p3 t1_3 + -> Seq Scan on prt1_p4 t1_4 + -> Hash + -> Append + -> Seq Scan on prt2_p0 t2 + Filter: (a = 0) + -> Seq Scan on prt2_p1 t2_1 + Filter: (a = 0) + -> Seq Scan on prt2_p2 t2_2 + Filter: (a = 0) + -> Seq Scan on prt2_p3 t2_3 + Filter: (a = 0) + -> Seq Scan on prt2_p4 t2_4 + Filter: (a = 0) + -> Seq Scan on prt2_p5 t2_5 + Filter: (a = 0) +(22 rows) + +-- full join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM prt1 t1 FULL JOIN prt2 t2 ON t1.a = t2.b WHERE t1.b + t2.a = 0 ORDER BY t1.a, t2.a; + QUERY PLAN +-------------------------------------------------- + Sort + Sort Key: t1.a, t2.a + -> Hash Join + Hash Cond: (t1.a = t2.b) + Join Filter: ((t1.b + t2.a) = 0) + -> Append + -> Seq Scan on prt1_p0 t1 + -> Seq Scan on prt1_p1 t1_1 + -> Seq Scan on prt1_p2 t1_2 + -> Seq Scan on prt1_p3 t1_3 + -> Seq Scan on prt1_p4 t1_4 + -> Hash + -> Append + -> Seq Scan on prt2_p0 t2 + -> Seq Scan on prt2_p1 t2_1 + -> Seq Scan on prt2_p2 t2_2 + -> Seq Scan on prt2_p3 t2_3 + -> Seq Scan on prt2_p4 t2_4 + -> Seq Scan on prt2_p5 t2_5 +(19 rows) + +-- semi join +EXPLAIN (COSTS OFF) +select t1.a, t1.b, t1.c from prt1 t1 where exists (select 1 from prt2 t2 WHERE t1.a = t2.b) and t1.b = 0 order by t1.a, t1.b, t1.c; + QUERY PLAN +-------------------------------------------------- + Sort + Sort Key: t1.a, t1.c + -> Hash Semi Join + Hash Cond: (t1.a = t2.b) + -> Append + -> Seq Scan on prt1_p0 t1 + Filter: (b = 0) + -> Seq Scan on prt1_p1 t1_1 + Filter: (b = 0) + -> Seq Scan on prt1_p2 t1_2 + Filter: (b = 0) + -> Seq Scan on prt1_p3 t1_3 + Filter: (b = 0) + -> Seq Scan on prt1_p4 t1_4 + Filter: (b = 0) + -> Hash + -> Append + -> Seq Scan on prt2_p0 t2 + -> Seq Scan on prt2_p1 t2_1 + -> Seq Scan on prt2_p2 t2_2 + -> Seq Scan on prt2_p3 t2_3 + -> Seq Scan on prt2_p4 t2_4 + -> Seq Scan on prt2_p5 t2_5 +(23 rows) + +EXPLAIN (COSTS OFF) +select t1.a, t1.b, t1.c from prt2 t1 where exists (select 1 from prt1 t2 WHERE t1.b = t2.a) and t1.a = 0 order by t1.a, t1.b, t1.c; + QUERY PLAN +-------------------------------------------------- + Sort + Sort Key: t1.b, t1.c + -> Hash Semi Join + Hash Cond: (t1.b = t2.a) + -> Append + -> Seq Scan on prt2_p0 t1 + Filter: (a = 0) + -> Seq Scan on prt2_p1 t1_1 + Filter: (a = 0) + -> Seq Scan on prt2_p2 t1_2 + Filter: (a = 0) + -> Seq Scan on prt2_p3 t1_3 + Filter: (a = 0) + -> Seq Scan on prt2_p4 t1_4 + Filter: (a = 0) + -> Seq Scan on prt2_p5 t1_5 + Filter: (a = 0) + -> Hash + -> Append + -> Seq Scan on prt1_p0 t2 + -> Seq Scan on prt1_p1 t2_1 + -> Seq Scan on prt1_p2 t2_2 + -> Seq Scan on prt1_p3 t2_3 + -> Seq Scan on prt1_p4 t2_4 +(24 rows) + +-- anti join +EXPLAIN (COSTS OFF) +select t1.a, t1.b, t1.c from prt1 t1 where not exists (select 1 from prt2 t2 WHERE t1.a = t2.b) and t1.b = 0 order by t1.a, t1.b, t1.c; + QUERY PLAN +-------------------------------------------------- + Sort + Sort Key: t1.a, t1.c + -> Hash Anti Join + Hash Cond: (t1.a = t2.b) + -> Append + -> Seq Scan on prt1_p0 t1 + Filter: (b = 0) + -> Seq Scan on prt1_p1 t1_1 + Filter: (b = 0) + -> Seq Scan on prt1_p2 t1_2 + Filter: (b = 0) + -> Seq Scan on prt1_p3 t1_3 + Filter: (b = 0) + -> Seq Scan on prt1_p4 t1_4 + Filter: (b = 0) + -> Hash + -> Append + -> Seq Scan on prt2_p0 t2 + -> Seq Scan on prt2_p1 t2_1 + -> Seq Scan on prt2_p2 t2_2 + -> Seq Scan on prt2_p3 t2_3 + -> Seq Scan on prt2_p4 t2_4 + -> Seq Scan on prt2_p5 t2_5 +(23 rows) + +EXPLAIN (COSTS OFF) +select t1.a, t1.b, t1.c from prt2 t1 where not exists (select 1 from prt1 t2 WHERE t1.b = t2.a) and t1.a = 0 order by t1.a, t1.b, t1.c; + QUERY PLAN +-------------------------------------------------- + Sort + Sort Key: t1.b, t1.c + -> Hash Anti Join + Hash Cond: (t1.b = t2.a) + -> Append + -> Seq Scan on prt2_p0 t1 + Filter: (a = 0) + -> Seq Scan on prt2_p1 t1_1 + Filter: (a = 0) + -> Seq Scan on prt2_p2 t1_2 + Filter: (a = 0) + -> Seq Scan on prt2_p3 t1_3 + Filter: (a = 0) + -> Seq Scan on prt2_p4 t1_4 + Filter: (a = 0) + -> Seq Scan on prt2_p5 t1_5 + Filter: (a = 0) + -> Hash + -> Append + -> Seq Scan on prt1_p0 t2 + -> Seq Scan on prt1_p1 t2_1 + -> Seq Scan on prt1_p2 t2_2 + -> Seq Scan on prt1_p3 t2_3 + -> Seq Scan on prt1_p4 t2_4 +(24 rows) + +-- +-- partitioned by multiple columns +-- +CREATE TABLE prt1_m (a int, b int, c int) PARTITION BY RANGE(a, ((a + b)/2)); +CREATE TABLE prt1_m_p1 PARTITION OF prt1_m FOR VALUES FROM (0, 0) TO (250, 250); +CREATE TABLE prt1_m_p2 PARTITION OF prt1_m FOR VALUES FROM (250, 250) TO (500, 500); +CREATE TABLE prt1_m_p3 PARTITION OF prt1_m FOR VALUES FROM (500, 500) TO (600, 600); +INSERT INTO prt1_m SELECT i, i, i % 25 FROM generate_series(0, 599, 2) i; +ANALYZE prt1_m; +CREATE TABLE prt2_m (a int, b int, c int) PARTITION BY RANGE(((b + a)/2), b); +CREATE TABLE prt2_m_p1 PARTITION OF prt2_m FOR VALUES FROM (0, 0) TO (250, 250); +CREATE TABLE prt2_m_p2 PARTITION OF prt2_m FOR VALUES FROM (250, 250) TO (500, 500); +CREATE TABLE prt2_m_p3 PARTITION OF prt2_m FOR VALUES FROM (500, 500) TO (600, 600); +INSERT INTO prt2_m SELECT i, i, i % 25 FROM generate_series(0, 599, 3) i; +ANALYZE prt2_m; +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1_m WHERE prt1_m.c = 0) t1 FULL JOIN (SELECT * FROM prt2_m WHERE prt2_m.c = 0) t2 ON (t1.a = (t2.b + t2.a)/2 AND t2.b = (t1.a + t1.b)/2) ORDER BY t1.a, t2.b; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------ + Sort + Sort Key: prt1_m_p1.a, prt2_m_p1.b + -> Append + -> Hash Full Join + Hash Cond: ((prt1_m_p1.a = ((prt2_m_p1.b + prt2_m_p1.a) / 2)) AND (((prt1_m_p1.a + prt1_m_p1.b) / 2) = prt2_m_p1.b)) + -> Seq Scan on prt1_m_p1 + Filter: (c = 0) + -> Hash + -> Seq Scan on prt2_m_p1 + Filter: (c = 0) + -> Hash Full Join + Hash Cond: ((prt1_m_p2.a = ((prt2_m_p2.b + prt2_m_p2.a) / 2)) AND (((prt1_m_p2.a + prt1_m_p2.b) / 2) = prt2_m_p2.b)) + -> Seq Scan on prt1_m_p2 + Filter: (c = 0) + -> Hash + -> Seq Scan on prt2_m_p2 + Filter: (c = 0) + -> Hash Full Join + Hash Cond: ((prt1_m_p3.a = ((prt2_m_p3.b + prt2_m_p3.a) / 2)) AND (((prt1_m_p3.a + prt1_m_p3.b) / 2) = prt2_m_p3.b)) + -> Seq Scan on prt1_m_p3 + Filter: (c = 0) + -> Hash + -> Seq Scan on prt2_m_p3 + Filter: (c = 0) +(24 rows) + +SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1_m WHERE prt1_m.c = 0) t1 FULL JOIN (SELECT * FROM prt2_m WHERE prt2_m.c = 0) t2 ON (t1.a = (t2.b + t2.a)/2 AND t2.b = (t1.a + t1.b)/2) ORDER BY t1.a, t2.b; + a | c | b | c +-----+---+-----+--- + 0 | 0 | 0 | 0 + 50 | 0 | | + 100 | 0 | | + 150 | 0 | 150 | 0 + 200 | 0 | | + 250 | 0 | | + 300 | 0 | 300 | 0 + 350 | 0 | | + 400 | 0 | | + 450 | 0 | 450 | 0 + 500 | 0 | | + 550 | 0 | | + | | 75 | 0 + | | 225 | 0 + | | 375 | 0 + | | 525 | 0 +(16 rows) + +-- +-- tests for list partitioned tables. +-- +\set part_mod 17 +\set cond_mod 47 +\set num_rows 500 +CREATE TABLE plt1 (a int, b int, c varchar) PARTITION BY LIST(c); +CREATE TABLE plt1_p1 PARTITION OF plt1 FOR VALUES IN ('0001','0002','0003'); +CREATE TABLE plt1_p2 PARTITION OF plt1 FOR VALUES IN ('0004','0005','0006'); +CREATE TABLE plt1_p3 PARTITION OF plt1 FOR VALUES IN ('0008','0009'); +CREATE TABLE plt1_p4 PARTITION OF plt1 FOR VALUES IN ('0000','0010'); +INSERT INTO plt1 SELECT i, i % :cond_mod, to_char(i % :part_mod, 'FM0000') FROM generate_series(0, :num_rows) i WHERE i % :part_mod NOT IN (7, 11, 12, 13, 14, 15, 16); +ANALYSE plt1; +-- plt2 have missing starting 0001, additional 0007, missing ending 0010 +-- and additional 0011 and 0012 bounds +CREATE TABLE plt2 (a int, b int, c varchar) PARTITION BY LIST(c); +CREATE TABLE plt2_p1 PARTITION OF plt2 FOR VALUES IN ('0002','0003'); +CREATE TABLE plt2_p2 PARTITION OF plt2 FOR VALUES IN ('0004','0005','0006'); +CREATE TABLE plt2_p3 PARTITION OF plt2 FOR VALUES IN ('0007','0008','0009'); +CREATE TABLE plt2_p4 PARTITION OF plt2 FOR VALUES IN ('0000','0011','0012'); +INSERT INTO plt2 SELECT i, i % :cond_mod, to_char(i % :part_mod, 'FM0000') FROM generate_series(0, :num_rows) i WHERE i % :part_mod NOT IN (1, 10, 13, 14, 15, 16); +ANALYSE plt2; +-- Partition-wise-join is possible with some partition bounds overlap +-- with each other completely and some partialy for inner,left,right, +-- full, semi and anti joins +-- inner join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1 t1 INNER JOIN plt2 t2 ON t1.c = t2.c WHERE t1.b + t2.b = 0 ORDER BY t1.a; + QUERY PLAN +------------------------------------------------------------ + Sort + Sort Key: t1.a + -> Append + -> Hash Join + Hash Cond: ((t1.c)::text = (t2.c)::text) + Join Filter: ((t1.b + t2.b) = 0) + -> Seq Scan on plt1_p4 t1 + -> Hash + -> Seq Scan on plt2_p4 t2 + -> Hash Join + Hash Cond: ((t2_1.c)::text = (t1_1.c)::text) + Join Filter: ((t1_1.b + t2_1.b) = 0) + -> Seq Scan on plt2_p1 t2_1 + -> Hash + -> Seq Scan on plt1_p1 t1_1 + -> Hash Join + Hash Cond: ((t1_2.c)::text = (t2_2.c)::text) + Join Filter: ((t1_2.b + t2_2.b) = 0) + -> Seq Scan on plt1_p2 t1_2 + -> Hash + -> Seq Scan on plt2_p2 t2_2 + -> Hash Join + Hash Cond: ((t1_3.c)::text = (t2_3.c)::text) + Join Filter: ((t1_3.b + t2_3.b) = 0) + -> Seq Scan on plt1_p3 t1_3 + -> Hash + -> Seq Scan on plt2_p3 t2_3 +(27 rows) + +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1 t1 INNER JOIN plt2 t2 ON t1.c = t2.c WHERE t1.b + t2.b = 0 ORDER BY t1.a; + a | c | a | c +-----+------+-----+------ + 0 | 0000 | 0 | 0000 + 94 | 0009 | 94 | 0009 + 141 | 0005 | 141 | 0005 + 329 | 0006 | 329 | 0006 + 376 | 0002 | 376 | 0002 +(5 rows) + +-- left join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1 t1 LEFT JOIN plt2 t2 ON t1.c = t2.c WHERE t1.b + coalesce(t2.b, 0) = 0 ORDER BY t1.a; + QUERY PLAN +------------------------------------------------------------ + Sort + Sort Key: t1.a + -> Append + -> Hash Left Join + Hash Cond: ((t1.c)::text = (t2.c)::text) + Filter: ((t1.b + COALESCE(t2.b, 0)) = 0) + -> Seq Scan on plt1_p4 t1 + -> Hash + -> Seq Scan on plt2_p4 t2 + -> Hash Right Join + Hash Cond: ((t2_1.c)::text = (t1_1.c)::text) + Filter: ((t1_1.b + COALESCE(t2_1.b, 0)) = 0) + -> Seq Scan on plt2_p1 t2_1 + -> Hash + -> Seq Scan on plt1_p1 t1_1 + -> Hash Left Join + Hash Cond: ((t1_2.c)::text = (t2_2.c)::text) + Filter: ((t1_2.b + COALESCE(t2_2.b, 0)) = 0) + -> Seq Scan on plt1_p2 t1_2 + -> Hash + -> Seq Scan on plt2_p2 t2_2 + -> Hash Left Join + Hash Cond: ((t1_3.c)::text = (t2_3.c)::text) + Filter: ((t1_3.b + COALESCE(t2_3.b, 0)) = 0) + -> Seq Scan on plt1_p3 t1_3 + -> Hash + -> Seq Scan on plt2_p3 t2_3 +(27 rows) + +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1 t1 LEFT JOIN plt2 t2 ON t1.c = t2.c WHERE t1.b + coalesce(t2.b, 0) = 0 ORDER BY t1.a; + a | c | a | c +-----+------+-----+------ + 0 | 0000 | 0 | 0000 + 94 | 0009 | 94 | 0009 + 141 | 0005 | 141 | 0005 + 188 | 0001 | | + 282 | 0010 | | + 329 | 0006 | 329 | 0006 + 376 | 0002 | 376 | 0002 +(7 rows) + +-- right join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1 t1 RIGHT JOIN plt2 t2 ON t1.c = t2.c WHERE coalesce(t1.b, 0) + t2.b = 0 ORDER BY t2.a; + QUERY PLAN +------------------------------------------------------------ + Sort + Sort Key: t2.a + -> Append + -> Hash Right Join + Hash Cond: ((t1.c)::text = (t2.c)::text) + Filter: ((COALESCE(t1.b, 0) + t2.b) = 0) + -> Seq Scan on plt1_p4 t1 + -> Hash + -> Seq Scan on plt2_p4 t2 + -> Hash Left Join + Hash Cond: ((t2_1.c)::text = (t1_1.c)::text) + Filter: ((COALESCE(t1_1.b, 0) + t2_1.b) = 0) + -> Seq Scan on plt2_p1 t2_1 + -> Hash + -> Seq Scan on plt1_p1 t1_1 + -> Hash Left Join + Hash Cond: ((t2_2.c)::text = (t1_2.c)::text) + Filter: ((COALESCE(t1_2.b, 0) + t2_2.b) = 0) + -> Seq Scan on plt2_p2 t2_2 + -> Hash + -> Seq Scan on plt1_p2 t1_2 + -> Hash Right Join + Hash Cond: ((t1_3.c)::text = (t2_3.c)::text) + Filter: ((COALESCE(t1_3.b, 0) + t2_3.b) = 0) + -> Seq Scan on plt1_p3 t1_3 + -> Hash + -> Seq Scan on plt2_p3 t2_3 +(27 rows) + +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1 t1 RIGHT JOIN plt2 t2 ON t1.c = t2.c WHERE coalesce(t1.b, 0) + t2.b = 0 ORDER BY t1.a; + a | c | a | c +-----+------+-----+------ + 0 | 0000 | 0 | 0000 + 94 | 0009 | 94 | 0009 + 141 | 0005 | 141 | 0005 + 329 | 0006 | 329 | 0006 + 376 | 0002 | 376 | 0002 + | | 470 | 0011 +(6 rows) + +-- full join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1 t1 FULL JOIN plt2 t2 ON t1.c = t2.c WHERE coalesce(t1.b, 0) + coalesce(t2.b, 0) = 0 ORDER BY t1.a, t2.a; + QUERY PLAN +------------------------------------------------------------------------- + Sort + Sort Key: t1.a, t2.a + -> Append + -> Hash Full Join + Hash Cond: ((t1.c)::text = (t2.c)::text) + Filter: ((COALESCE(t1.b, 0) + COALESCE(t2.b, 0)) = 0) + -> Seq Scan on plt1_p4 t1 + -> Hash + -> Seq Scan on plt2_p4 t2 + -> Hash Full Join + Hash Cond: ((t2_1.c)::text = (t1_1.c)::text) + Filter: ((COALESCE(t1_1.b, 0) + COALESCE(t2_1.b, 0)) = 0) + -> Seq Scan on plt2_p1 t2_1 + -> Hash + -> Seq Scan on plt1_p1 t1_1 + -> Hash Full Join + Hash Cond: ((t1_2.c)::text = (t2_2.c)::text) + Filter: ((COALESCE(t1_2.b, 0) + COALESCE(t2_2.b, 0)) = 0) + -> Seq Scan on plt1_p2 t1_2 + -> Hash + -> Seq Scan on plt2_p2 t2_2 + -> Hash Full Join + Hash Cond: ((t1_3.c)::text = (t2_3.c)::text) + Filter: ((COALESCE(t1_3.b, 0) + COALESCE(t2_3.b, 0)) = 0) + -> Seq Scan on plt1_p3 t1_3 + -> Hash + -> Seq Scan on plt2_p3 t2_3 +(27 rows) + +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1 t1 FULL JOIN plt2 t2 ON t1.c = t2.c WHERE coalesce(t1.b, 0) + coalesce(t2.b, 0) = 0 ORDER BY t1.a, t2.a; + a | c | a | c +-----+------+-----+------ + 0 | 0000 | 0 | 0000 + 94 | 0009 | 94 | 0009 + 141 | 0005 | 141 | 0005 + 188 | 0001 | | + 282 | 0010 | | + 329 | 0006 | 329 | 0006 + 376 | 0002 | 376 | 0002 + | | 470 | 0011 +(8 rows) + +-- semi join +EXPLAIN (COSTS OFF) +select t1.a, t1.b, t1.c from plt1 t1 where exists (select 1 from plt2 t2 WHERE t1.c = t2.c) and t1.b = 0 order by t1.a, t1.b, t1.c; + QUERY PLAN +-------------------------------------------------------------- + Sort + Sort Key: t1.a, t1.c + -> Append + -> Hash Join + Hash Cond: ((t2.c)::text = (t1.c)::text) + -> HashAggregate + Group Key: (t2.c)::text + -> Seq Scan on plt2_p4 t2 + -> Hash + -> Seq Scan on plt1_p4 t1 + Filter: (b = 0) + -> Nested Loop + Join Filter: ((t1_1.c)::text = (t2_1.c)::text) + -> HashAggregate + Group Key: (t2_1.c)::text + -> Seq Scan on plt2_p1 t2_1 + -> Materialize + -> Seq Scan on plt1_p1 t1_1 + Filter: (b = 0) + -> Nested Loop Semi Join + Join Filter: ((t1_2.c)::text = (t2_2.c)::text) + -> Seq Scan on plt1_p2 t1_2 + Filter: (b = 0) + -> Seq Scan on plt2_p2 t2_2 + -> Nested Loop Semi Join + Join Filter: ((t1_3.c)::text = (t2_3.c)::text) + -> Seq Scan on plt1_p3 t1_3 + Filter: (b = 0) + -> Seq Scan on plt2_p3 t2_3 +(29 rows) + +select t1.a, t1.b, t1.c from plt1 t1 where exists (select 1 from plt2 t2 WHERE t1.c = t2.c) and t1.b = 0 order by t1.a, t1.b, t1.c; + a | b | c +-----+---+------ + 0 | 0 | 0000 + 94 | 0 | 0009 + 141 | 0 | 0005 + 329 | 0 | 0006 + 376 | 0 | 0002 +(5 rows) + +EXPLAIN (COSTS OFF) +select t1.a, t1.b, t1.c from plt2 t1 where exists (select 1 from plt1 t2 WHERE t1.c = t2.c) and t1.b = 0 order by t1.a, t1.b, t1.c; + QUERY PLAN +-------------------------------------------------------------- + Sort + Sort Key: t1.a, t1.c + -> Append + -> Nested Loop + Join Filter: ((t1.c)::text = (t2.c)::text) + -> HashAggregate + Group Key: (t2.c)::text + -> Seq Scan on plt1_p4 t2 + -> Materialize + -> Seq Scan on plt2_p4 t1 + Filter: (b = 0) + -> Nested Loop Semi Join + Join Filter: ((t1_1.c)::text = (t2_1.c)::text) + -> Seq Scan on plt2_p1 t1_1 + Filter: (b = 0) + -> Seq Scan on plt1_p1 t2_1 + -> Nested Loop Semi Join + Join Filter: ((t1_2.c)::text = (t2_2.c)::text) + -> Seq Scan on plt2_p2 t1_2 + Filter: (b = 0) + -> Seq Scan on plt1_p2 t2_2 + -> Nested Loop Semi Join + Join Filter: ((t1_3.c)::text = (t2_3.c)::text) + -> Seq Scan on plt2_p3 t1_3 + Filter: (b = 0) + -> Seq Scan on plt1_p3 t2_3 +(26 rows) + +select t1.a, t1.b, t1.c from plt2 t1 where exists (select 1 from plt1 t2 WHERE t1.c = t2.c) and t1.b = 0 order by t1.a, t1.b, t1.c; + a | b | c +-----+---+------ + 0 | 0 | 0000 + 94 | 0 | 0009 + 141 | 0 | 0005 + 329 | 0 | 0006 + 376 | 0 | 0002 +(5 rows) + +-- anti join +EXPLAIN (COSTS OFF) +select t1.a, t1.b, t1.c from plt1 t1 where not exists (select 1 from plt2 t2 WHERE t1.c = t2.c) and t1.b = 0 order by t1.a, t1.b, t1.c; + QUERY PLAN +-------------------------------------------------------------- + Sort + Sort Key: t1.a, t1.c + -> Append + -> Nested Loop Anti Join + Join Filter: ((t1.c)::text = (t2.c)::text) + -> Seq Scan on plt1_p4 t1 + Filter: (b = 0) + -> Seq Scan on plt2_p4 t2 + -> Hash Anti Join + Hash Cond: ((t1_1.c)::text = (t2_1.c)::text) + -> Seq Scan on plt1_p1 t1_1 + Filter: (b = 0) + -> Hash + -> Seq Scan on plt2_p1 t2_1 + -> Nested Loop Anti Join + Join Filter: ((t1_2.c)::text = (t2_2.c)::text) + -> Seq Scan on plt1_p2 t1_2 + Filter: (b = 0) + -> Seq Scan on plt2_p2 t2_2 + -> Nested Loop Anti Join + Join Filter: ((t1_3.c)::text = (t2_3.c)::text) + -> Seq Scan on plt1_p3 t1_3 + Filter: (b = 0) + -> Seq Scan on plt2_p3 t2_3 +(24 rows) + +select t1.a, t1.b, t1.c from plt1 t1 where not exists (select 1 from plt2 t2 WHERE t1.c = t2.c) and t1.b = 0 order by t1.a, t1.b, t1.c; + a | b | c +-----+---+------ + 188 | 0 | 0001 + 282 | 0 | 0010 +(2 rows) + +EXPLAIN (COSTS OFF) +select t1.a, t1.b, t1.c from plt2 t1 where not exists (select 1 from plt1 t2 WHERE t1.c = t2.c) and t1.b = 0 order by t1.a, t1.b, t1.c; + QUERY PLAN +-------------------------------------------------------------- + Sort + Sort Key: t1.a, t1.c + -> Append + -> Hash Anti Join + Hash Cond: ((t1.c)::text = (t2.c)::text) + -> Seq Scan on plt2_p4 t1 + Filter: (b = 0) + -> Hash + -> Seq Scan on plt1_p4 t2 + -> Nested Loop Anti Join + Join Filter: ((t1_1.c)::text = (t2_1.c)::text) + -> Seq Scan on plt2_p1 t1_1 + Filter: (b = 0) + -> Seq Scan on plt1_p1 t2_1 + -> Nested Loop Anti Join + Join Filter: ((t1_2.c)::text = (t2_2.c)::text) + -> Seq Scan on plt2_p2 t1_2 + Filter: (b = 0) + -> Seq Scan on plt1_p2 t2_2 + -> Nested Loop Anti Join + Join Filter: ((t1_3.c)::text = (t2_3.c)::text) + -> Seq Scan on plt2_p3 t1_3 + Filter: (b = 0) + -> Seq Scan on plt1_p3 t2_3 +(24 rows) + +select t1.a, t1.b, t1.c from plt2 t1 where not exists (select 1 from plt1 t2 WHERE t1.c = t2.c) and t1.b = 0 order by t1.a, t1.b, t1.c; + a | b | c +-----+---+------ + 470 | 0 | 0011 +(1 row) + +-- +-- list partitioned by expression +-- +CREATE TABLE plt1_e (a int, b int, c text) PARTITION BY LIST(ltrim(c, 'A')); +CREATE TABLE plt1_e_p1 PARTITION OF plt1_e FOR VALUES IN ('0002', '0003'); +CREATE TABLE plt1_e_p2 PARTITION OF plt1_e FOR VALUES IN ('0004', '0005', '0006'); +CREATE TABLE plt1_e_p3 PARTITION OF plt1_e FOR VALUES IN ('0008', '0009'); +CREATE TABLE plt1_e_p4 PARTITION OF plt1_e FOR VALUES IN ('0000'); +INSERT INTO plt1_e SELECT i, i % :cond_mod, to_char(i % :part_mod, 'FM0000') FROM generate_series(0, :num_rows) i WHERE i % :part_mod NOT IN (1, 7, 10, 11, 12, 13, 14, 15, 16); +ANALYZE plt1_e; +-- test partition matching with N-way join +EXPLAIN (COSTS OFF) +SELECT avg(t1.a), avg(t2.b), avg(t3.a + t3.b), t1.c, t2.c, t3.c FROM plt1 t1, plt2 t2, plt1_e t3 WHERE t1.b = t2.b AND t1.c = t2.c AND ltrim(t3.c, 'A') = t1.c GROUP BY t1.c, t2.c, t3.c ORDER BY t1.c, t2.c, t3.c; + QUERY PLAN +------------------------------------------------------------------------------------------------ + GroupAggregate + Group Key: t1.c, t2.c, t3.c + -> Sort + Sort Key: t1.c, t3.c + -> Append + -> Hash Join + Hash Cond: ((t1.c)::text = ltrim(t3.c, 'A'::text)) + -> Hash Join + Hash Cond: ((t2.b = t1.b) AND ((t2.c)::text = (t1.c)::text)) + -> Seq Scan on plt2_p4 t2 + -> Hash + -> Seq Scan on plt1_p4 t1 + -> Hash + -> Seq Scan on plt1_e_p4 t3 + -> Hash Join + Hash Cond: ((t1_1.c)::text = ltrim(t3_1.c, 'A'::text)) + -> Hash Join + Hash Cond: ((t1_1.b = t2_1.b) AND ((t1_1.c)::text = (t2_1.c)::text)) + -> Seq Scan on plt1_p1 t1_1 + -> Hash + -> Seq Scan on plt2_p1 t2_1 + -> Hash + -> Seq Scan on plt1_e_p1 t3_1 + -> Hash Join + Hash Cond: ((t1_2.c)::text = ltrim(t3_2.c, 'A'::text)) + -> Hash Join + Hash Cond: ((t1_2.b = t2_2.b) AND ((t1_2.c)::text = (t2_2.c)::text)) + -> Seq Scan on plt1_p2 t1_2 + -> Hash + -> Seq Scan on plt2_p2 t2_2 + -> Hash + -> Seq Scan on plt1_e_p2 t3_2 + -> Hash Join + Hash Cond: ((t1_3.c)::text = ltrim(t3_3.c, 'A'::text)) + -> Hash Join + Hash Cond: ((t2_3.b = t1_3.b) AND ((t2_3.c)::text = (t1_3.c)::text)) + -> Seq Scan on plt2_p3 t2_3 + -> Hash + -> Seq Scan on plt1_p3 t1_3 + -> Hash + -> Seq Scan on plt1_e_p3 t3_3 +(41 rows) + +SELECT avg(t1.a), avg(t2.b), avg(t3.a + t3.b), t1.c, t2.c, t3.c FROM plt1 t1, plt2 t2, plt1_e t3 WHERE t1.b = t2.b AND t1.c = t2.c AND ltrim(t3.c, 'A') = t1.c GROUP BY t1.c, t2.c, t3.c ORDER BY t1.c, t2.c, t3.c; + avg | avg | avg | c | c | c +----------------------+---------------------+----------------------+------+------+------ + 246.5000000000000000 | 22.4666666666666667 | 268.9666666666666667 | 0000 | 0000 | 0000 + 248.5000000000000000 | 21.3333333333333333 | 269.8333333333333333 | 0002 | 0002 | 0002 + 249.5000000000000000 | 22.3333333333333333 | 271.8333333333333333 | 0003 | 0003 | 0003 + 250.5000000000000000 | 23.3333333333333333 | 273.8333333333333333 | 0004 | 0004 | 0004 + 251.5000000000000000 | 22.7666666666666667 | 274.2666666666666667 | 0005 | 0005 | 0005 + 252.5000000000000000 | 22.2000000000000000 | 274.7000000000000000 | 0006 | 0006 | 0006 + 246.0000000000000000 | 23.9655172413793103 | 269.9655172413793103 | 0008 | 0008 | 0008 + 247.0000000000000000 | 23.3448275862068966 | 270.3448275862068966 | 0009 | 0009 | 0009 +(8 rows) + +-- Add an extra partition to plt2 , Partition-wise join is possible with +-- partitions on inner side are allowed +CREATE TABLE plt2_p5 PARTITION OF plt2 FOR VALUES IN ('0013','0014'); +INSERT INTO plt2 SELECT i, i % :cond_mod, to_char(i % :part_mod, 'FM0000') FROM generate_series(0, :num_rows) i WHERE i % :part_mod IN (13, 14); +ANALYZE plt2; +-- inner join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1 t1 INNER JOIN plt2 t2 ON t1.c = t2.c WHERE t1.b + t2.b = 0 ORDER BY t1.a; + QUERY PLAN +------------------------------------------------------------ + Sort + Sort Key: t1.a + -> Append + -> Hash Join + Hash Cond: ((t1.c)::text = (t2.c)::text) + Join Filter: ((t1.b + t2.b) = 0) + -> Seq Scan on plt1_p4 t1 + -> Hash + -> Seq Scan on plt2_p4 t2 + -> Hash Join + Hash Cond: ((t2_1.c)::text = (t1_1.c)::text) + Join Filter: ((t1_1.b + t2_1.b) = 0) + -> Seq Scan on plt2_p1 t2_1 + -> Hash + -> Seq Scan on plt1_p1 t1_1 + -> Hash Join + Hash Cond: ((t1_2.c)::text = (t2_2.c)::text) + Join Filter: ((t1_2.b + t2_2.b) = 0) + -> Seq Scan on plt1_p2 t1_2 + -> Hash + -> Seq Scan on plt2_p2 t2_2 + -> Hash Join + Hash Cond: ((t1_3.c)::text = (t2_3.c)::text) + Join Filter: ((t1_3.b + t2_3.b) = 0) + -> Seq Scan on plt1_p3 t1_3 + -> Hash + -> Seq Scan on plt2_p3 t2_3 +(27 rows) + +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1 t1 INNER JOIN plt2 t2 ON t1.c = t2.c WHERE t1.b + t2.b = 0 ORDER BY t1.a; + a | c | a | c +-----+------+-----+------ + 0 | 0000 | 0 | 0000 + 94 | 0009 | 94 | 0009 + 141 | 0005 | 141 | 0005 + 329 | 0006 | 329 | 0006 + 376 | 0002 | 376 | 0002 +(5 rows) + +-- left join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1 t1 LEFT JOIN plt2 t2 ON t1.c = t2.c WHERE t1.b + coalesce(t2.b, 0) = 0 ORDER BY t1.a; + QUERY PLAN +------------------------------------------------------------ + Sort + Sort Key: t1.a + -> Append + -> Hash Left Join + Hash Cond: ((t1.c)::text = (t2.c)::text) + Filter: ((t1.b + COALESCE(t2.b, 0)) = 0) + -> Seq Scan on plt1_p4 t1 + -> Hash + -> Seq Scan on plt2_p4 t2 + -> Hash Right Join + Hash Cond: ((t2_1.c)::text = (t1_1.c)::text) + Filter: ((t1_1.b + COALESCE(t2_1.b, 0)) = 0) + -> Seq Scan on plt2_p1 t2_1 + -> Hash + -> Seq Scan on plt1_p1 t1_1 + -> Hash Left Join + Hash Cond: ((t1_2.c)::text = (t2_2.c)::text) + Filter: ((t1_2.b + COALESCE(t2_2.b, 0)) = 0) + -> Seq Scan on plt1_p2 t1_2 + -> Hash + -> Seq Scan on plt2_p2 t2_2 + -> Hash Left Join + Hash Cond: ((t1_3.c)::text = (t2_3.c)::text) + Filter: ((t1_3.b + COALESCE(t2_3.b, 0)) = 0) + -> Seq Scan on plt1_p3 t1_3 + -> Hash + -> Seq Scan on plt2_p3 t2_3 +(27 rows) + +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1 t1 LEFT JOIN plt2 t2 ON t1.c = t2.c WHERE t1.b + coalesce(t2.b, 0) = 0 ORDER BY t1.a; + a | c | a | c +-----+------+-----+------ + 0 | 0000 | 0 | 0000 + 94 | 0009 | 94 | 0009 + 141 | 0005 | 141 | 0005 + 188 | 0001 | | + 282 | 0010 | | + 329 | 0006 | 329 | 0006 + 376 | 0002 | 376 | 0002 +(7 rows) + +-- right join, partition-wise join can not handle extra partition on the outer +-- side +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1 t1 RIGHT JOIN plt2 t2 ON t1.c = t2.c WHERE coalesce(t1.b, 0) + t2.b = 0 ORDER BY t2.a; + QUERY PLAN +-------------------------------------------------- + Sort + Sort Key: t2.a + -> Hash Right Join + Hash Cond: ((t1.c)::text = (t2.c)::text) + Filter: ((COALESCE(t1.b, 0) + t2.b) = 0) + -> Append + -> Seq Scan on plt1_p4 t1 + -> Seq Scan on plt1_p1 t1_1 + -> Seq Scan on plt1_p2 t1_2 + -> Seq Scan on plt1_p3 t1_3 + -> Hash + -> Append + -> Seq Scan on plt2_p4 t2 + -> Seq Scan on plt2_p1 t2_1 + -> Seq Scan on plt2_p2 t2_2 + -> Seq Scan on plt2_p3 t2_3 + -> Seq Scan on plt2_p5 t2_4 +(17 rows) + +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1 t1 RIGHT JOIN plt2 t2 ON t1.c = t2.c WHERE coalesce(t1.b, 0) + t2.b = 0 ORDER BY t1.a; + a | c | a | c +-----+------+-----+------ + 0 | 0000 | 0 | 0000 + 94 | 0009 | 94 | 0009 + 141 | 0005 | 141 | 0005 + 329 | 0006 | 329 | 0006 + 376 | 0002 | 376 | 0002 + | | 47 | 0013 + | | 470 | 0011 + | | 235 | 0014 +(8 rows) + +-- full join, partition-wise join can not handle extra partition on the outer +-- side +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1 t1 FULL JOIN plt2 t2 ON t1.c = t2.c WHERE coalesce(t1.b, 0) + coalesce(t2.b, 0) = 0 ORDER BY t1.a, t2.a; + QUERY PLAN +--------------------------------------------------------------- + Sort + Sort Key: t1.a, t2.a + -> Hash Full Join + Hash Cond: ((t1.c)::text = (t2.c)::text) + Filter: ((COALESCE(t1.b, 0) + COALESCE(t2.b, 0)) = 0) + -> Append + -> Seq Scan on plt1_p4 t1 + -> Seq Scan on plt1_p1 t1_1 + -> Seq Scan on plt1_p2 t1_2 + -> Seq Scan on plt1_p3 t1_3 + -> Hash + -> Append + -> Seq Scan on plt2_p4 t2 + -> Seq Scan on plt2_p1 t2_1 + -> Seq Scan on plt2_p2 t2_2 + -> Seq Scan on plt2_p3 t2_3 + -> Seq Scan on plt2_p5 t2_4 +(17 rows) + +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1 t1 FULL JOIN plt2 t2 ON t1.c = t2.c WHERE coalesce(t1.b, 0) + coalesce(t2.b, 0) = 0 ORDER BY t1.a, t2.a; + a | c | a | c +-----+------+-----+------ + 0 | 0000 | 0 | 0000 + 94 | 0009 | 94 | 0009 + 141 | 0005 | 141 | 0005 + 188 | 0001 | | + 282 | 0010 | | + 329 | 0006 | 329 | 0006 + 376 | 0002 | 376 | 0002 + | | 47 | 0013 + | | 235 | 0014 + | | 470 | 0011 +(10 rows) + +-- semi join +EXPLAIN (COSTS OFF) +select t1.a, t1.b, t1.c from plt1 t1 where exists (select 1 from plt2 t2 WHERE t1.c = t2.c) and t1.b = 0 order by t1.a, t1.b, t1.c; + QUERY PLAN +-------------------------------------------------------------- + Sort + Sort Key: t1.a, t1.c + -> Append + -> Hash Join + Hash Cond: ((t2.c)::text = (t1.c)::text) + -> HashAggregate + Group Key: (t2.c)::text + -> Seq Scan on plt2_p4 t2 + -> Hash + -> Seq Scan on plt1_p4 t1 + Filter: (b = 0) + -> Nested Loop + Join Filter: ((t1_1.c)::text = (t2_1.c)::text) + -> HashAggregate + Group Key: (t2_1.c)::text + -> Seq Scan on plt2_p1 t2_1 + -> Materialize + -> Seq Scan on plt1_p1 t1_1 + Filter: (b = 0) + -> Nested Loop Semi Join + Join Filter: ((t1_2.c)::text = (t2_2.c)::text) + -> Seq Scan on plt1_p2 t1_2 + Filter: (b = 0) + -> Seq Scan on plt2_p2 t2_2 + -> Nested Loop Semi Join + Join Filter: ((t1_3.c)::text = (t2_3.c)::text) + -> Seq Scan on plt1_p3 t1_3 + Filter: (b = 0) + -> Seq Scan on plt2_p3 t2_3 +(29 rows) + +select t1.a, t1.b, t1.c from plt1 t1 where exists (select 1 from plt2 t2 WHERE t1.c = t2.c) and t1.b = 0 order by t1.a, t1.b, t1.c; + a | b | c +-----+---+------ + 0 | 0 | 0000 + 94 | 0 | 0009 + 141 | 0 | 0005 + 329 | 0 | 0006 + 376 | 0 | 0002 +(5 rows) + +EXPLAIN (COSTS OFF) +select t1.a, t1.b, t1.c from plt2 t1 where exists (select 1 from plt1 t2 WHERE t1.c = t2.c) and t1.b = 0 order by t1.a, t1.b, t1.c; + QUERY PLAN +-------------------------------------------------------------- + Sort + Sort Key: t1.a, t1.c + -> Append + -> Nested Loop + Join Filter: ((t1.c)::text = (t2.c)::text) + -> HashAggregate + Group Key: (t2.c)::text + -> Seq Scan on plt1_p4 t2 + -> Materialize + -> Seq Scan on plt2_p4 t1 + Filter: (b = 0) + -> Nested Loop Semi Join + Join Filter: ((t1_1.c)::text = (t2_1.c)::text) + -> Seq Scan on plt2_p1 t1_1 + Filter: (b = 0) + -> Seq Scan on plt1_p1 t2_1 + -> Nested Loop Semi Join + Join Filter: ((t1_2.c)::text = (t2_2.c)::text) + -> Seq Scan on plt2_p2 t1_2 + Filter: (b = 0) + -> Seq Scan on plt1_p2 t2_2 + -> Nested Loop Semi Join + Join Filter: ((t1_3.c)::text = (t2_3.c)::text) + -> Seq Scan on plt2_p3 t1_3 + Filter: (b = 0) + -> Seq Scan on plt1_p3 t2_3 +(26 rows) + +select t1.a, t1.b, t1.c from plt2 t1 where exists (select 1 from plt1 t2 WHERE t1.c = t2.c) and t1.b = 0 order by t1.a, t1.b, t1.c; + a | b | c +-----+---+------ + 0 | 0 | 0000 + 94 | 0 | 0009 + 141 | 0 | 0005 + 329 | 0 | 0006 + 376 | 0 | 0002 +(5 rows) + +-- anti join +EXPLAIN (COSTS OFF) +select t1.a, t1.b, t1.c from plt1 t1 where not exists (select 1 from plt2 t2 WHERE t1.c = t2.c) and t1.b = 0 order by t1.a, t1.b, t1.c; + QUERY PLAN +-------------------------------------------------------------- + Sort + Sort Key: t1.a, t1.c + -> Append + -> Nested Loop Anti Join + Join Filter: ((t1.c)::text = (t2.c)::text) + -> Seq Scan on plt1_p4 t1 + Filter: (b = 0) + -> Seq Scan on plt2_p4 t2 + -> Hash Anti Join + Hash Cond: ((t1_1.c)::text = (t2_1.c)::text) + -> Seq Scan on plt1_p1 t1_1 + Filter: (b = 0) + -> Hash + -> Seq Scan on plt2_p1 t2_1 + -> Nested Loop Anti Join + Join Filter: ((t1_2.c)::text = (t2_2.c)::text) + -> Seq Scan on plt1_p2 t1_2 + Filter: (b = 0) + -> Seq Scan on plt2_p2 t2_2 + -> Nested Loop Anti Join + Join Filter: ((t1_3.c)::text = (t2_3.c)::text) + -> Seq Scan on plt1_p3 t1_3 + Filter: (b = 0) + -> Seq Scan on plt2_p3 t2_3 +(24 rows) + +select t1.a, t1.b, t1.c from plt1 t1 where not exists (select 1 from plt2 t2 WHERE t1.c = t2.c) and t1.b = 0 order by t1.a, t1.b, t1.c; + a | b | c +-----+---+------ + 188 | 0 | 0001 + 282 | 0 | 0010 +(2 rows) + +EXPLAIN (COSTS OFF) +select t1.a, t1.b, t1.c from plt2 t1 where not exists (select 1 from plt1 t2 WHERE t1.c = t2.c) and t1.b = 0 order by t1.a, t1.b, t1.c; + QUERY PLAN +-------------------------------------------------- + Sort + Sort Key: t1.a, t1.c + -> Hash Anti Join + Hash Cond: ((t1.c)::text = (t2.c)::text) + -> Append + -> Seq Scan on plt2_p4 t1 + Filter: (b = 0) + -> Seq Scan on plt2_p1 t1_1 + Filter: (b = 0) + -> Seq Scan on plt2_p2 t1_2 + Filter: (b = 0) + -> Seq Scan on plt2_p3 t1_3 + Filter: (b = 0) + -> Seq Scan on plt2_p5 t1_4 + Filter: (b = 0) + -> Hash + -> Append + -> Seq Scan on plt1_p4 t2 + -> Seq Scan on plt1_p1 t2_1 + -> Seq Scan on plt1_p2 t2_2 + -> Seq Scan on plt1_p3 t2_3 +(21 rows) + +select t1.a, t1.b, t1.c from plt2 t1 where not exists (select 1 from plt1 t2 WHERE t1.c = t2.c) and t1.b = 0 order by t1.a, t1.b, t1.c; + a | b | c +-----+---+------ + 47 | 0 | 0013 + 235 | 0 | 0014 + 470 | 0 | 0011 +(3 rows) + +-- Partition-wise join can not handle the case when one partition from one side +-- matches with multiple partitions on the other side +DROP TABLE plt2_p5; +CREATE TABLE plt2_p5 PARTITION OF plt2 FOR VALUES IN ('0001','0013','0014'); +INSERT INTO plt2 SELECT i, i % :cond_mod, to_char(i % :part_mod, 'FM0000') FROM generate_series(0, :num_rows) i WHERE i % :part_mod IN (1, 13, 14); +ANALYZE plt2; +-- inner join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1 t1 INNER JOIN plt2 t2 ON t1.c = t2.c WHERE t1.b + t2.b = 0 ORDER BY t1.a; + QUERY PLAN +-------------------------------------------------- + Sort + Sort Key: t1.a + -> Hash Join + Hash Cond: ((t1.c)::text = (t2.c)::text) + Join Filter: ((t1.b + t2.b) = 0) + -> Append + -> Seq Scan on plt1_p4 t1 + -> Seq Scan on plt1_p1 t1_1 + -> Seq Scan on plt1_p2 t1_2 + -> Seq Scan on plt1_p3 t1_3 + -> Hash + -> Append + -> Seq Scan on plt2_p4 t2 + -> Seq Scan on plt2_p5 t2_1 + -> Seq Scan on plt2_p1 t2_2 + -> Seq Scan on plt2_p2 t2_3 + -> Seq Scan on plt2_p3 t2_4 +(17 rows) + +-- left join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1 t1 LEFT JOIN plt2 t2 ON t1.c = t2.c WHERE t1.b + coalesce(t2.b, 0) = 0 ORDER BY t1.a; + QUERY PLAN +-------------------------------------------------- + Sort + Sort Key: t1.a + -> Hash Left Join + Hash Cond: ((t1.c)::text = (t2.c)::text) + Filter: ((t1.b + COALESCE(t2.b, 0)) = 0) + -> Append + -> Seq Scan on plt1_p4 t1 + -> Seq Scan on plt1_p1 t1_1 + -> Seq Scan on plt1_p2 t1_2 + -> Seq Scan on plt1_p3 t1_3 + -> Hash + -> Append + -> Seq Scan on plt2_p4 t2 + -> Seq Scan on plt2_p5 t2_1 + -> Seq Scan on plt2_p1 t2_2 + -> Seq Scan on plt2_p2 t2_3 + -> Seq Scan on plt2_p3 t2_4 +(17 rows) + +-- right join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1 t1 RIGHT JOIN plt2 t2 ON t1.c = t2.c WHERE coalesce(t1.b, 0) + t2.b = 0 ORDER BY t2.a; + QUERY PLAN +-------------------------------------------------- + Sort + Sort Key: t2.a + -> Hash Right Join + Hash Cond: ((t1.c)::text = (t2.c)::text) + Filter: ((COALESCE(t1.b, 0) + t2.b) = 0) + -> Append + -> Seq Scan on plt1_p4 t1 + -> Seq Scan on plt1_p1 t1_1 + -> Seq Scan on plt1_p2 t1_2 + -> Seq Scan on plt1_p3 t1_3 + -> Hash + -> Append + -> Seq Scan on plt2_p4 t2 + -> Seq Scan on plt2_p5 t2_1 + -> Seq Scan on plt2_p1 t2_2 + -> Seq Scan on plt2_p2 t2_3 + -> Seq Scan on plt2_p3 t2_4 +(17 rows) + +-- full join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1 t1 FULL JOIN plt2 t2 ON t1.c = t2.c WHERE coalesce(t1.b, 0) + coalesce(t2.b, 0) = 0 ORDER BY t1.a, t2.a; + QUERY PLAN +--------------------------------------------------------------- + Sort + Sort Key: t1.a, t2.a + -> Hash Full Join + Hash Cond: ((t1.c)::text = (t2.c)::text) + Filter: ((COALESCE(t1.b, 0) + COALESCE(t2.b, 0)) = 0) + -> Append + -> Seq Scan on plt1_p4 t1 + -> Seq Scan on plt1_p1 t1_1 + -> Seq Scan on plt1_p2 t1_2 + -> Seq Scan on plt1_p3 t1_3 + -> Hash + -> Append + -> Seq Scan on plt2_p4 t2 + -> Seq Scan on plt2_p5 t2_1 + -> Seq Scan on plt2_p1 t2_2 + -> Seq Scan on plt2_p2 t2_3 + -> Seq Scan on plt2_p3 t2_4 +(17 rows) + +-- semi join +EXPLAIN (COSTS OFF) +select t1.a, t1.b, t1.c from plt1 t1 where exists (select 1 from plt2 t2 WHERE t1.c = t2.c) and t1.b = 0 order by t1.a, t1.b, t1.c; + QUERY PLAN +-------------------------------------------------------------- + Sort + Sort Key: t1.a, t1.c + -> Hash Join + Hash Cond: ((t1.c)::text = (t2.c)::text) + -> Append + -> Seq Scan on plt1_p4 t1 + Filter: (b = 0) + -> Seq Scan on plt1_p1 t1_1 + Filter: (b = 0) + -> Seq Scan on plt1_p2 t1_2 + Filter: (b = 0) + -> Seq Scan on plt1_p3 t1_3 + Filter: (b = 0) + -> Hash + -> HashAggregate + Group Key: (t2.c)::text + -> Result + -> Append + -> Seq Scan on plt2_p4 t2 + -> Seq Scan on plt2_p5 t2_1 + -> Seq Scan on plt2_p1 t2_2 + -> Seq Scan on plt2_p2 t2_3 + -> Seq Scan on plt2_p3 t2_4 +(23 rows) + +EXPLAIN (COSTS OFF) +select t1.a, t1.b, t1.c from plt2 t1 where exists (select 1 from plt1 t2 WHERE t1.c = t2.c) and t1.b = 0 order by t1.a, t1.b, t1.c; + QUERY PLAN +-------------------------------------------------------------- + Sort + Sort Key: t1.a, t1.c + -> Hash Join + Hash Cond: ((t1.c)::text = (t2.c)::text) + -> Append + -> Seq Scan on plt2_p4 t1 + Filter: (b = 0) + -> Seq Scan on plt2_p5 t1_1 + Filter: (b = 0) + -> Seq Scan on plt2_p1 t1_2 + Filter: (b = 0) + -> Seq Scan on plt2_p2 t1_3 + Filter: (b = 0) + -> Seq Scan on plt2_p3 t1_4 + Filter: (b = 0) + -> Hash + -> HashAggregate + Group Key: (t2.c)::text + -> Result + -> Append + -> Seq Scan on plt1_p4 t2 + -> Seq Scan on plt1_p1 t2_1 + -> Seq Scan on plt1_p2 t2_2 + -> Seq Scan on plt1_p3 t2_3 +(24 rows) + +-- anti join +EXPLAIN (COSTS OFF) +select t1.a, t1.b, t1.c from plt1 t1 where not exists (select 1 from plt2 t2 WHERE t1.c = t2.c) and t1.b = 0 order by t1.a, t1.b, t1.c; + QUERY PLAN +---------------------------------------------------- + Sort + Sort Key: t1.a, t1.c + -> Nested Loop Anti Join + Join Filter: ((t1.c)::text = (t2.c)::text) + -> Append + -> Seq Scan on plt1_p4 t1 + Filter: (b = 0) + -> Seq Scan on plt1_p1 t1_1 + Filter: (b = 0) + -> Seq Scan on plt1_p2 t1_2 + Filter: (b = 0) + -> Seq Scan on plt1_p3 t1_3 + Filter: (b = 0) + -> Materialize + -> Append + -> Seq Scan on plt2_p4 t2 + -> Seq Scan on plt2_p5 t2_1 + -> Seq Scan on plt2_p1 t2_2 + -> Seq Scan on plt2_p2 t2_3 + -> Seq Scan on plt2_p3 t2_4 +(20 rows) + +EXPLAIN (COSTS OFF) +select t1.a, t1.b, t1.c from plt2 t1 where not exists (select 1 from plt1 t2 WHERE t1.c = t2.c) and t1.b = 0 order by t1.a, t1.b, t1.c; + QUERY PLAN +-------------------------------------------------- + Sort + Sort Key: t1.a, t1.c + -> Hash Anti Join + Hash Cond: ((t1.c)::text = (t2.c)::text) + -> Append + -> Seq Scan on plt2_p4 t1 + Filter: (b = 0) + -> Seq Scan on plt2_p5 t1_1 + Filter: (b = 0) + -> Seq Scan on plt2_p1 t1_2 + Filter: (b = 0) + -> Seq Scan on plt2_p2 t1_3 + Filter: (b = 0) + -> Seq Scan on plt2_p3 t1_4 + Filter: (b = 0) + -> Hash + -> Append + -> Seq Scan on plt1_p4 t2 + -> Seq Scan on plt1_p1 t2_1 + -> Seq Scan on plt1_p2 t2_2 + -> Seq Scan on plt1_p3 t2_3 +(21 rows) + +-- partition have a NULL on one side, Partition-wise join is possible with +-- NULL when NULL comparision is not strict i.e. NULL=NULL allowed +-- in this case NULL will be treated as addition partition bounds. +DROP TABLE plt2_p5; +DROP TABLE plt2_p4; +CREATE TABLE plt2_p4 PARTITION OF plt2 FOR VALUES IN ('0000',NULL,'0012'); +INSERT INTO plt2 SELECT i, i % :cond_mod, case when i % :part_mod = 11 then NULL else to_char(i % :part_mod, 'FM0000') end FROM generate_series(0, :num_rows) i WHERE i % :part_mod IN (0,11,12); +ANALYZE plt2; +-- inner join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1 t1 INNER JOIN plt2 t2 ON t1.c = t2.c WHERE t1.b + t2.b = 0 ORDER BY t1.a; + QUERY PLAN +------------------------------------------------------------ + Sort + Sort Key: t1.a + -> Append + -> Hash Join + Hash Cond: ((t2.c)::text = (t1.c)::text) + Join Filter: ((t1.b + t2.b) = 0) + -> Seq Scan on plt2_p4 t2 + -> Hash + -> Seq Scan on plt1_p4 t1 + -> Hash Join + Hash Cond: ((t2_1.c)::text = (t1_1.c)::text) + Join Filter: ((t1_1.b + t2_1.b) = 0) + -> Seq Scan on plt2_p1 t2_1 + -> Hash + -> Seq Scan on plt1_p1 t1_1 + -> Hash Join + Hash Cond: ((t1_2.c)::text = (t2_2.c)::text) + Join Filter: ((t1_2.b + t2_2.b) = 0) + -> Seq Scan on plt1_p2 t1_2 + -> Hash + -> Seq Scan on plt2_p2 t2_2 + -> Hash Join + Hash Cond: ((t1_3.c)::text = (t2_3.c)::text) + Join Filter: ((t1_3.b + t2_3.b) = 0) + -> Seq Scan on plt1_p3 t1_3 + -> Hash + -> Seq Scan on plt2_p3 t2_3 +(27 rows) + +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1 t1 INNER JOIN plt2 t2 ON t1.c = t2.c WHERE t1.b + t2.b = 0 ORDER BY t1.a; + a | c | a | c +-----+------+-----+------ + 0 | 0000 | 0 | 0000 + 94 | 0009 | 94 | 0009 + 141 | 0005 | 141 | 0005 + 329 | 0006 | 329 | 0006 + 376 | 0002 | 376 | 0002 +(5 rows) + +-- left join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1 t1 LEFT JOIN plt2 t2 ON t1.c = t2.c WHERE t1.b + coalesce(t2.b, 0) = 0 ORDER BY t1.a; + QUERY PLAN +------------------------------------------------------------ + Sort + Sort Key: t1.a + -> Append + -> Hash Right Join + Hash Cond: ((t2.c)::text = (t1.c)::text) + Filter: ((t1.b + COALESCE(t2.b, 0)) = 0) + -> Seq Scan on plt2_p4 t2 + -> Hash + -> Seq Scan on plt1_p4 t1 + -> Hash Right Join + Hash Cond: ((t2_1.c)::text = (t1_1.c)::text) + Filter: ((t1_1.b + COALESCE(t2_1.b, 0)) = 0) + -> Seq Scan on plt2_p1 t2_1 + -> Hash + -> Seq Scan on plt1_p1 t1_1 + -> Hash Left Join + Hash Cond: ((t1_2.c)::text = (t2_2.c)::text) + Filter: ((t1_2.b + COALESCE(t2_2.b, 0)) = 0) + -> Seq Scan on plt1_p2 t1_2 + -> Hash + -> Seq Scan on plt2_p2 t2_2 + -> Hash Left Join + Hash Cond: ((t1_3.c)::text = (t2_3.c)::text) + Filter: ((t1_3.b + COALESCE(t2_3.b, 0)) = 0) + -> Seq Scan on plt1_p3 t1_3 + -> Hash + -> Seq Scan on plt2_p3 t2_3 +(27 rows) + +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1 t1 LEFT JOIN plt2 t2 ON t1.c = t2.c WHERE t1.b + coalesce(t2.b, 0) = 0 ORDER BY t1.a; + a | c | a | c +-----+------+-----+------ + 0 | 0000 | 0 | 0000 + 94 | 0009 | 94 | 0009 + 141 | 0005 | 141 | 0005 + 188 | 0001 | | + 282 | 0010 | | + 329 | 0006 | 329 | 0006 + 376 | 0002 | 376 | 0002 +(7 rows) + +-- right join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1 t1 RIGHT JOIN plt2 t2 ON t1.c = t2.c WHERE coalesce(t1.b, 0) + t2.b = 0 ORDER BY t2.a; + QUERY PLAN +------------------------------------------------------------ + Sort + Sort Key: t2.a + -> Append + -> Hash Left Join + Hash Cond: ((t2.c)::text = (t1.c)::text) + Filter: ((COALESCE(t1.b, 0) + t2.b) = 0) + -> Seq Scan on plt2_p4 t2 + -> Hash + -> Seq Scan on plt1_p4 t1 + -> Hash Left Join + Hash Cond: ((t2_1.c)::text = (t1_1.c)::text) + Filter: ((COALESCE(t1_1.b, 0) + t2_1.b) = 0) + -> Seq Scan on plt2_p1 t2_1 + -> Hash + -> Seq Scan on plt1_p1 t1_1 + -> Hash Left Join + Hash Cond: ((t2_2.c)::text = (t1_2.c)::text) + Filter: ((COALESCE(t1_2.b, 0) + t2_2.b) = 0) + -> Seq Scan on plt2_p2 t2_2 + -> Hash + -> Seq Scan on plt1_p2 t1_2 + -> Hash Right Join + Hash Cond: ((t1_3.c)::text = (t2_3.c)::text) + Filter: ((COALESCE(t1_3.b, 0) + t2_3.b) = 0) + -> Seq Scan on plt1_p3 t1_3 + -> Hash + -> Seq Scan on plt2_p3 t2_3 +(27 rows) + +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1 t1 RIGHT JOIN plt2 t2 ON t1.c = t2.c WHERE coalesce(t1.b, 0) + t2.b = 0 ORDER BY t1.a; + a | c | a | c +-----+------+-----+------ + 0 | 0000 | 0 | 0000 + 94 | 0009 | 94 | 0009 + 141 | 0005 | 141 | 0005 + 329 | 0006 | 329 | 0006 + 376 | 0002 | 376 | 0002 + | | 470 | +(6 rows) + +-- full join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1 t1 FULL JOIN plt2 t2 ON t1.c = t2.c WHERE coalesce(t1.b, 0) + coalesce(t2.b, 0) = 0 ORDER BY t1.a, t2.a; + QUERY PLAN +------------------------------------------------------------------------- + Sort + Sort Key: t1.a, t2.a + -> Append + -> Hash Full Join + Hash Cond: ((t2.c)::text = (t1.c)::text) + Filter: ((COALESCE(t1.b, 0) + COALESCE(t2.b, 0)) = 0) + -> Seq Scan on plt2_p4 t2 + -> Hash + -> Seq Scan on plt1_p4 t1 + -> Hash Full Join + Hash Cond: ((t2_1.c)::text = (t1_1.c)::text) + Filter: ((COALESCE(t1_1.b, 0) + COALESCE(t2_1.b, 0)) = 0) + -> Seq Scan on plt2_p1 t2_1 + -> Hash + -> Seq Scan on plt1_p1 t1_1 + -> Hash Full Join + Hash Cond: ((t1_2.c)::text = (t2_2.c)::text) + Filter: ((COALESCE(t1_2.b, 0) + COALESCE(t2_2.b, 0)) = 0) + -> Seq Scan on plt1_p2 t1_2 + -> Hash + -> Seq Scan on plt2_p2 t2_2 + -> Hash Full Join + Hash Cond: ((t1_3.c)::text = (t2_3.c)::text) + Filter: ((COALESCE(t1_3.b, 0) + COALESCE(t2_3.b, 0)) = 0) + -> Seq Scan on plt1_p3 t1_3 + -> Hash + -> Seq Scan on plt2_p3 t2_3 +(27 rows) + +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1 t1 FULL JOIN plt2 t2 ON t1.c = t2.c WHERE coalesce(t1.b, 0) + coalesce(t2.b, 0) = 0 ORDER BY t1.a, t2.a; + a | c | a | c +-----+------+-----+------ + 0 | 0000 | 0 | 0000 + 94 | 0009 | 94 | 0009 + 141 | 0005 | 141 | 0005 + 188 | 0001 | | + 282 | 0010 | | + 329 | 0006 | 329 | 0006 + 376 | 0002 | 376 | 0002 + | | 470 | +(8 rows) + +-- semi join +EXPLAIN (COSTS OFF) +select t1.a, t1.b, t1.c from plt1 t1 where exists (select 1 from plt2 t2 WHERE t1.c = t2.c) and t1.b = 0 order by t1.a, t1.b, t1.c; + QUERY PLAN +-------------------------------------------------------------- + Sort + Sort Key: t1.a, t1.c + -> Append + -> Nested Loop + Join Filter: ((t1.c)::text = (t2.c)::text) + -> HashAggregate + Group Key: (t2.c)::text + -> Seq Scan on plt2_p4 t2 + -> Materialize + -> Seq Scan on plt1_p4 t1 + Filter: (b = 0) + -> Nested Loop + Join Filter: ((t1_1.c)::text = (t2_1.c)::text) + -> HashAggregate + Group Key: (t2_1.c)::text + -> Seq Scan on plt2_p1 t2_1 + -> Materialize + -> Seq Scan on plt1_p1 t1_1 + Filter: (b = 0) + -> Nested Loop Semi Join + Join Filter: ((t1_2.c)::text = (t2_2.c)::text) + -> Seq Scan on plt1_p2 t1_2 + Filter: (b = 0) + -> Seq Scan on plt2_p2 t2_2 + -> Nested Loop Semi Join + Join Filter: ((t1_3.c)::text = (t2_3.c)::text) + -> Seq Scan on plt1_p3 t1_3 + Filter: (b = 0) + -> Seq Scan on plt2_p3 t2_3 +(29 rows) + +select t1.a, t1.b, t1.c from plt1 t1 where exists (select 1 from plt2 t2 WHERE t1.c = t2.c) and t1.b = 0 order by t1.a, t1.b, t1.c; + a | b | c +-----+---+------ + 0 | 0 | 0000 + 94 | 0 | 0009 + 141 | 0 | 0005 + 329 | 0 | 0006 + 376 | 0 | 0002 +(5 rows) + +EXPLAIN (COSTS OFF) +select t1.a, t1.b, t1.c from plt2 t1 where exists (select 1 from plt1 t2 WHERE t1.c = t2.c) and t1.b = 0 order by t1.a, t1.b, t1.c; + QUERY PLAN +-------------------------------------------------------------- + Sort + Sort Key: t1.a, t1.c + -> Append + -> Nested Loop + Join Filter: ((t1.c)::text = (t2.c)::text) + -> HashAggregate + Group Key: (t2.c)::text + -> Seq Scan on plt1_p4 t2 + -> Materialize + -> Seq Scan on plt2_p4 t1 + Filter: (b = 0) + -> Nested Loop Semi Join + Join Filter: ((t1_1.c)::text = (t2_1.c)::text) + -> Seq Scan on plt2_p1 t1_1 + Filter: (b = 0) + -> Seq Scan on plt1_p1 t2_1 + -> Nested Loop Semi Join + Join Filter: ((t1_2.c)::text = (t2_2.c)::text) + -> Seq Scan on plt2_p2 t1_2 + Filter: (b = 0) + -> Seq Scan on plt1_p2 t2_2 + -> Nested Loop Semi Join + Join Filter: ((t1_3.c)::text = (t2_3.c)::text) + -> Seq Scan on plt2_p3 t1_3 + Filter: (b = 0) + -> Seq Scan on plt1_p3 t2_3 +(26 rows) + +select t1.a, t1.b, t1.c from plt2 t1 where exists (select 1 from plt1 t2 WHERE t1.c = t2.c) and t1.b = 0 order by t1.a, t1.b, t1.c; + a | b | c +-----+---+------ + 0 | 0 | 0000 + 94 | 0 | 0009 + 141 | 0 | 0005 + 329 | 0 | 0006 + 376 | 0 | 0002 +(5 rows) + +-- anti join +EXPLAIN (COSTS OFF) +select t1.a, t1.b, t1.c from plt1 t1 where not exists (select 1 from plt2 t2 WHERE t1.c = t2.c) and t1.b = 0 order by t1.a, t1.b, t1.c; + QUERY PLAN +-------------------------------------------------------------- + Sort + Sort Key: t1.a, t1.c + -> Append + -> Nested Loop Anti Join + Join Filter: ((t1.c)::text = (t2.c)::text) + -> Seq Scan on plt1_p4 t1 + Filter: (b = 0) + -> Seq Scan on plt2_p4 t2 + -> Hash Anti Join + Hash Cond: ((t1_1.c)::text = (t2_1.c)::text) + -> Seq Scan on plt1_p1 t1_1 + Filter: (b = 0) + -> Hash + -> Seq Scan on plt2_p1 t2_1 + -> Nested Loop Anti Join + Join Filter: ((t1_2.c)::text = (t2_2.c)::text) + -> Seq Scan on plt1_p2 t1_2 + Filter: (b = 0) + -> Seq Scan on plt2_p2 t2_2 + -> Nested Loop Anti Join + Join Filter: ((t1_3.c)::text = (t2_3.c)::text) + -> Seq Scan on plt1_p3 t1_3 + Filter: (b = 0) + -> Seq Scan on plt2_p3 t2_3 +(24 rows) + +select t1.a, t1.b, t1.c from plt1 t1 where not exists (select 1 from plt2 t2 WHERE t1.c = t2.c) and t1.b = 0 order by t1.a, t1.b, t1.c; + a | b | c +-----+---+------ + 188 | 0 | 0001 + 282 | 0 | 0010 +(2 rows) + +EXPLAIN (COSTS OFF) +select t1.a, t1.b, t1.c from plt2 t1 where not exists (select 1 from plt1 t2 WHERE t1.c = t2.c) and t1.b = 0 order by t1.a, t1.b, t1.c; + QUERY PLAN +-------------------------------------------------------------- + Sort + Sort Key: t1.a, t1.c + -> Append + -> Hash Anti Join + Hash Cond: ((t1.c)::text = (t2.c)::text) + -> Seq Scan on plt2_p4 t1 + Filter: (b = 0) + -> Hash + -> Seq Scan on plt1_p4 t2 + -> Nested Loop Anti Join + Join Filter: ((t1_1.c)::text = (t2_1.c)::text) + -> Seq Scan on plt2_p1 t1_1 + Filter: (b = 0) + -> Seq Scan on plt1_p1 t2_1 + -> Nested Loop Anti Join + Join Filter: ((t1_2.c)::text = (t2_2.c)::text) + -> Seq Scan on plt2_p2 t1_2 + Filter: (b = 0) + -> Seq Scan on plt1_p2 t2_2 + -> Nested Loop Anti Join + Join Filter: ((t1_3.c)::text = (t2_3.c)::text) + -> Seq Scan on plt2_p3 t1_3 + Filter: (b = 0) + -> Seq Scan on plt1_p3 t2_3 +(24 rows) + +select t1.a, t1.b, t1.c from plt2 t1 where not exists (select 1 from plt1 t2 WHERE t1.c = t2.c) and t1.b = 0 order by t1.a, t1.b, t1.c; + a | b | c +-----+---+--- + 470 | 0 | +(1 row) + +-- partition have a NULL on both side with different partition bounds w.r.t other side +-- NULL when NULL comparision is not strict i.e. NULL=NULL allowed +-- Partition-wise join can not handle the case when one partition from one side +-- matches with multiple partitions on the other side +DROP TABLE plt1_p3; +CREATE TABLE plt1_p3 PARTITION OF plt1 FOR VALUES IN (NULL,'0008','0009'); +INSERT INTO plt1 SELECT i, i % :cond_mod, case when i % :part_mod = 7 then NULL else to_char(i % :part_mod, 'FM0000') end FROM generate_series(0, :num_rows) i WHERE i % :part_mod IN (7,8,9); +ANALYZE plt1; +-- inner join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1 t1 INNER JOIN plt2 t2 ON t1.c = t2.c WHERE t1.b + t2.b = 0 ORDER BY t1.a; + QUERY PLAN +-------------------------------------------------- + Sort + Sort Key: t1.a + -> Hash Join + Hash Cond: ((t2.c)::text = (t1.c)::text) + Join Filter: ((t1.b + t2.b) = 0) + -> Append + -> Seq Scan on plt2_p4 t2 + -> Seq Scan on plt2_p1 t2_1 + -> Seq Scan on plt2_p2 t2_2 + -> Seq Scan on plt2_p3 t2_3 + -> Hash + -> Append + -> Seq Scan on plt1_p4 t1 + -> Seq Scan on plt1_p1 t1_1 + -> Seq Scan on plt1_p2 t1_2 + -> Seq Scan on plt1_p3 t1_3 +(16 rows) + +-- left join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1 t1 LEFT JOIN plt2 t2 ON t1.c = t2.c WHERE t1.b + coalesce(t2.b, 0) = 0 ORDER BY t1.a; + QUERY PLAN +-------------------------------------------------- + Sort + Sort Key: t1.a + -> Hash Right Join + Hash Cond: ((t2.c)::text = (t1.c)::text) + Filter: ((t1.b + COALESCE(t2.b, 0)) = 0) + -> Append + -> Seq Scan on plt2_p4 t2 + -> Seq Scan on plt2_p1 t2_1 + -> Seq Scan on plt2_p2 t2_2 + -> Seq Scan on plt2_p3 t2_3 + -> Hash + -> Append + -> Seq Scan on plt1_p4 t1 + -> Seq Scan on plt1_p1 t1_1 + -> Seq Scan on plt1_p2 t1_2 + -> Seq Scan on plt1_p3 t1_3 +(16 rows) + +-- right join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1 t1 RIGHT JOIN plt2 t2 ON t1.c = t2.c WHERE coalesce(t1.b, 0) + t2.b = 0 ORDER BY t2.a; + QUERY PLAN +-------------------------------------------------- + Sort + Sort Key: t2.a + -> Hash Left Join + Hash Cond: ((t2.c)::text = (t1.c)::text) + Filter: ((COALESCE(t1.b, 0) + t2.b) = 0) + -> Append + -> Seq Scan on plt2_p4 t2 + -> Seq Scan on plt2_p1 t2_1 + -> Seq Scan on plt2_p2 t2_2 + -> Seq Scan on plt2_p3 t2_3 + -> Hash + -> Append + -> Seq Scan on plt1_p4 t1 + -> Seq Scan on plt1_p1 t1_1 + -> Seq Scan on plt1_p2 t1_2 + -> Seq Scan on plt1_p3 t1_3 +(16 rows) + +-- full join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1 t1 FULL JOIN plt2 t2 ON t1.c = t2.c WHERE coalesce(t1.b, 0) + coalesce(t2.b, 0) = 0 ORDER BY t1.a, t2.a; + QUERY PLAN +--------------------------------------------------------------- + Sort + Sort Key: t1.a, t2.a + -> Hash Full Join + Hash Cond: ((t2.c)::text = (t1.c)::text) + Filter: ((COALESCE(t1.b, 0) + COALESCE(t2.b, 0)) = 0) + -> Append + -> Seq Scan on plt2_p4 t2 + -> Seq Scan on plt2_p1 t2_1 + -> Seq Scan on plt2_p2 t2_2 + -> Seq Scan on plt2_p3 t2_3 + -> Hash + -> Append + -> Seq Scan on plt1_p4 t1 + -> Seq Scan on plt1_p1 t1_1 + -> Seq Scan on plt1_p2 t1_2 + -> Seq Scan on plt1_p3 t1_3 (16 rows) --- --- tests for list partitioned tables. --- -CREATE TABLE plt1 (a int, b int, c text) PARTITION BY LIST(c); -CREATE TABLE plt1_p1 PARTITION OF plt1 FOR VALUES IN ('0000', '0003', '0004', '0010'); -CREATE TABLE plt1_p2 PARTITION OF plt1 FOR VALUES IN ('0001', '0005', '0002', '0009'); -CREATE TABLE plt1_p3 PARTITION OF plt1 FOR VALUES IN ('0006', '0007', '0008', '0011'); -INSERT INTO plt1 SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(0, 599, 2) i; -ANALYZE plt1; -CREATE TABLE plt2 (a int, b int, c text) PARTITION BY LIST(c); -CREATE TABLE plt2_p1 PARTITION OF plt2 FOR VALUES IN ('0000', '0003', '0004', '0010'); -CREATE TABLE plt2_p2 PARTITION OF plt2 FOR VALUES IN ('0001', '0005', '0002', '0009'); -CREATE TABLE plt2_p3 PARTITION OF plt2 FOR VALUES IN ('0006', '0007', '0008', '0011'); -INSERT INTO plt2 SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(0, 599, 3) i; -ANALYZE plt2; --- --- list partitioned by expression --- -CREATE TABLE plt1_e (a int, b int, c text) PARTITION BY LIST(ltrim(c, 'A')); -CREATE TABLE plt1_e_p1 PARTITION OF plt1_e FOR VALUES IN ('0000', '0003', '0004', '0010'); -CREATE TABLE plt1_e_p2 PARTITION OF plt1_e FOR VALUES IN ('0001', '0005', '0002', '0009'); -CREATE TABLE plt1_e_p3 PARTITION OF plt1_e FOR VALUES IN ('0006', '0007', '0008', '0011'); -INSERT INTO plt1_e SELECT i, i, 'A' || to_char(i/50, 'FM0000') FROM generate_series(0, 599, 2) i; -ANALYZE plt1_e; --- test partition matching with N-way join +-- semi join EXPLAIN (COSTS OFF) -SELECT avg(t1.a), avg(t2.b), avg(t3.a + t3.b), t1.c, t2.c, t3.c FROM plt1 t1, plt2 t2, plt1_e t3 WHERE t1.b = t2.b AND t1.c = t2.c AND ltrim(t3.c, 'A') = t1.c GROUP BY t1.c, t2.c, t3.c ORDER BY t1.c, t2.c, t3.c; - QUERY PLAN --------------------------------------------------------------------------------- - GroupAggregate - Group Key: t1.c, t2.c, t3.c - -> Sort - Sort Key: t1.c, t3.c +select t1.a, t1.b, t1.c from plt1 t1 where exists (select 1 from plt2 t2 WHERE t1.c = t2.c) and t1.b = 0 order by t1.a, t1.b, t1.c; + QUERY PLAN +-------------------------------------------------------------- + Sort + Sort Key: t1.a, t1.c + -> Hash Join + Hash Cond: ((t1.c)::text = (t2.c)::text) -> Append - -> Hash Join - Hash Cond: (t1.c = ltrim(t3.c, 'A'::text)) - -> Hash Join - Hash Cond: ((t1.b = t2.b) AND (t1.c = t2.c)) - -> Seq Scan on plt1_p1 t1 - -> Hash - -> Seq Scan on plt2_p1 t2 - -> Hash - -> Seq Scan on plt1_e_p1 t3 - -> Hash Join - Hash Cond: (t1_1.c = ltrim(t3_1.c, 'A'::text)) - -> Hash Join - Hash Cond: ((t1_1.b = t2_1.b) AND (t1_1.c = t2_1.c)) - -> Seq Scan on plt1_p2 t1_1 - -> Hash - -> Seq Scan on plt2_p2 t2_1 - -> Hash - -> Seq Scan on plt1_e_p2 t3_1 - -> Hash Join - Hash Cond: (t1_2.c = ltrim(t3_2.c, 'A'::text)) - -> Hash Join - Hash Cond: ((t1_2.b = t2_2.b) AND (t1_2.c = t2_2.c)) - -> Seq Scan on plt1_p3 t1_2 - -> Hash - -> Seq Scan on plt2_p3 t2_2 - -> Hash - -> Seq Scan on plt1_e_p3 t3_2 -(32 rows) + -> Seq Scan on plt1_p4 t1 + Filter: (b = 0) + -> Seq Scan on plt1_p1 t1_1 + Filter: (b = 0) + -> Seq Scan on plt1_p2 t1_2 + Filter: (b = 0) + -> Seq Scan on plt1_p3 t1_3 + Filter: (b = 0) + -> Hash + -> HashAggregate + Group Key: (t2.c)::text + -> Result + -> Append + -> Seq Scan on plt2_p4 t2 + -> Seq Scan on plt2_p1 t2_1 + -> Seq Scan on plt2_p2 t2_2 + -> Seq Scan on plt2_p3 t2_3 +(22 rows) -SELECT avg(t1.a), avg(t2.b), avg(t3.a + t3.b), t1.c, t2.c, t3.c FROM plt1 t1, plt2 t2, plt1_e t3 WHERE t1.b = t2.b AND t1.c = t2.c AND ltrim(t3.c, 'A') = t1.c GROUP BY t1.c, t2.c, t3.c ORDER BY t1.c, t2.c, t3.c; - avg | avg | avg | c | c | c -----------------------+----------------------+-----------------------+------+------+------- - 24.0000000000000000 | 24.0000000000000000 | 48.0000000000000000 | 0000 | 0000 | A0000 - 75.0000000000000000 | 75.0000000000000000 | 148.0000000000000000 | 0001 | 0001 | A0001 - 123.0000000000000000 | 123.0000000000000000 | 248.0000000000000000 | 0002 | 0002 | A0002 - 174.0000000000000000 | 174.0000000000000000 | 348.0000000000000000 | 0003 | 0003 | A0003 - 225.0000000000000000 | 225.0000000000000000 | 448.0000000000000000 | 0004 | 0004 | A0004 - 273.0000000000000000 | 273.0000000000000000 | 548.0000000000000000 | 0005 | 0005 | A0005 - 324.0000000000000000 | 324.0000000000000000 | 648.0000000000000000 | 0006 | 0006 | A0006 - 375.0000000000000000 | 375.0000000000000000 | 748.0000000000000000 | 0007 | 0007 | A0007 - 423.0000000000000000 | 423.0000000000000000 | 848.0000000000000000 | 0008 | 0008 | A0008 - 474.0000000000000000 | 474.0000000000000000 | 948.0000000000000000 | 0009 | 0009 | A0009 - 525.0000000000000000 | 525.0000000000000000 | 1048.0000000000000000 | 0010 | 0010 | A0010 - 573.0000000000000000 | 573.0000000000000000 | 1148.0000000000000000 | 0011 | 0011 | A0011 -(12 rows) +EXPLAIN (COSTS OFF) +select t1.a, t1.b, t1.c from plt2 t1 where exists (select 1 from plt1 t2 WHERE t1.c = t2.c) and t1.b = 0 order by t1.a, t1.b, t1.c; + QUERY PLAN +-------------------------------------------------------------- + Sort + Sort Key: t1.a, t1.c + -> Hash Join + Hash Cond: ((t1.c)::text = (t2.c)::text) + -> Append + -> Seq Scan on plt2_p4 t1 + Filter: (b = 0) + -> Seq Scan on plt2_p1 t1_1 + Filter: (b = 0) + -> Seq Scan on plt2_p2 t1_2 + Filter: (b = 0) + -> Seq Scan on plt2_p3 t1_3 + Filter: (b = 0) + -> Hash + -> HashAggregate + Group Key: (t2.c)::text + -> Result + -> Append + -> Seq Scan on plt1_p4 t2 + -> Seq Scan on plt1_p1 t2_1 + -> Seq Scan on plt1_p2 t2_2 + -> Seq Scan on plt1_p3 t2_3 +(22 rows) + +-- anti join +EXPLAIN (COSTS OFF) +select t1.a, t1.b, t1.c from plt1 t1 where not exists (select 1 from plt2 t2 WHERE t1.c = t2.c) and t1.b = 0 order by t1.a, t1.b, t1.c; + QUERY PLAN +-------------------------------------------------- + Sort + Sort Key: t1.a, t1.c + -> Hash Anti Join + Hash Cond: ((t1.c)::text = (t2.c)::text) + -> Append + -> Seq Scan on plt1_p4 t1 + Filter: (b = 0) + -> Seq Scan on plt1_p1 t1_1 + Filter: (b = 0) + -> Seq Scan on plt1_p2 t1_2 + Filter: (b = 0) + -> Seq Scan on plt1_p3 t1_3 + Filter: (b = 0) + -> Hash + -> Append + -> Seq Scan on plt2_p4 t2 + -> Seq Scan on plt2_p1 t2_1 + -> Seq Scan on plt2_p2 t2_2 + -> Seq Scan on plt2_p3 t2_3 +(19 rows) + +EXPLAIN (COSTS OFF) +select t1.a, t1.b, t1.c from plt2 t1 where not exists (select 1 from plt1 t2 WHERE t1.c = t2.c) and t1.b = 0 order by t1.a, t1.b, t1.c; + QUERY PLAN +-------------------------------------------------- + Sort + Sort Key: t1.a, t1.c + -> Hash Anti Join + Hash Cond: ((t1.c)::text = (t2.c)::text) + -> Append + -> Seq Scan on plt2_p4 t1 + Filter: (b = 0) + -> Seq Scan on plt2_p1 t1_1 + Filter: (b = 0) + -> Seq Scan on plt2_p2 t1_2 + Filter: (b = 0) + -> Seq Scan on plt2_p3 t1_3 + Filter: (b = 0) + -> Hash + -> Append + -> Seq Scan on plt1_p4 t2 + -> Seq Scan on plt1_p1 t2_1 + -> Seq Scan on plt1_p2 t2_2 + -> Seq Scan on plt1_p3 t2_3 +(19 rows) -- joins where one of the relations is proven empty EXPLAIN (COSTS OFF) @@ -1302,22 +4097,22 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1 WHERE a = 1 AND a = 2) t1 -------------------------------------------------- Hash Left Join Hash Cond: (t2.b = a) - -> Append - -> Hash Join - Hash Cond: (t3.a = t2.b) - -> Seq Scan on prt1_p1 t3 - -> Hash - -> Seq Scan on prt2_p1 t2 - -> Hash Join - Hash Cond: (t3_1.a = t2_1.b) - -> Seq Scan on prt1_p2 t3_1 - -> Hash - -> Seq Scan on prt2_p2 t2_1 - -> Hash Join - Hash Cond: (t3_2.a = t2_2.b) - -> Seq Scan on prt1_p3 t3_2 - -> Hash - -> Seq Scan on prt2_p3 t2_2 + -> Hash Join + Hash Cond: (t3.a = t2.b) + -> Append + -> Seq Scan on prt1_p0 t3 + -> Seq Scan on prt1_p1 t3_1 + -> Seq Scan on prt1_p2 t3_2 + -> Seq Scan on prt1_p3 t3_3 + -> Seq Scan on prt1_p4 t3_4 + -> Hash + -> Append + -> Seq Scan on prt2_p0 t2 + -> Seq Scan on prt2_p1 t2_1 + -> Seq Scan on prt2_p2 t2_2 + -> Seq Scan on prt2_p3 t2_3 + -> Seq Scan on prt2_p4 t2_4 + -> Seq Scan on prt2_p5 t2_5 -> Hash -> Result One-Time Filter: false @@ -1332,16 +4127,22 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1 WHERE a = 1 AND a = 2) t1 -> Hash Left Join Hash Cond: (t2.b = a) -> Append - -> Seq Scan on prt2_p1 t2 + -> Seq Scan on prt2_p0 t2 + Filter: (a = 0) + -> Seq Scan on prt2_p1 t2_1 Filter: (a = 0) - -> Seq Scan on prt2_p2 t2_1 + -> Seq Scan on prt2_p2 t2_2 Filter: (a = 0) - -> Seq Scan on prt2_p3 t2_2 + -> Seq Scan on prt2_p3 t2_3 + Filter: (a = 0) + -> Seq Scan on prt2_p4 t2_4 + Filter: (a = 0) + -> Seq Scan on prt2_p5 t2_5 Filter: (a = 0) -> Hash -> Result One-Time Filter: false -(14 rows) +(20 rows) -- -- tests for hash partitioned tables. @@ -1417,41 +4218,9 @@ SELECT avg(t1.a), avg(t2.b), avg(t3.a + t3.b), t1.c, t2.c, t3.c FROM pht1 t1, ph 273.0000000000000000 | 273.0000000000000000 | 548.0000000000000000 | 0005 | 0005 | A0005 (6 rows) --- test default partition behavior for range -ALTER TABLE prt1 DETACH PARTITION prt1_p3; -ALTER TABLE prt1 ATTACH PARTITION prt1_p3 DEFAULT; -ANALYZE prt1; -ALTER TABLE prt2 DETACH PARTITION prt2_p3; -ALTER TABLE prt2 ATTACH PARTITION prt2_p3 DEFAULT; -ANALYZE prt2; -EXPLAIN (COSTS OFF) -SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.b = 0 ORDER BY t1.a, t2.b; - QUERY PLAN --------------------------------------------------- - Sort - Sort Key: t1.a - -> Append - -> Hash Join - Hash Cond: (t2.b = t1.a) - -> Seq Scan on prt2_p1 t2 - -> Hash - -> Seq Scan on prt1_p1 t1 - Filter: (b = 0) - -> Hash Join - Hash Cond: (t2_1.b = t1_1.a) - -> Seq Scan on prt2_p2 t2_1 - -> Hash - -> Seq Scan on prt1_p2 t1_1 - Filter: (b = 0) - -> Hash Join - Hash Cond: (t2_2.b = t1_2.a) - -> Seq Scan on prt2_p3 t2_2 - -> Hash - -> Seq Scan on prt1_p3 t1_2 - Filter: (b = 0) -(21 rows) - --- test default partition behavior for list +-- test default partition behavior for list, should not use partition-wise join +-- since default partition from one side matches multiple partitions on the +-- other ALTER TABLE plt1 DETACH PARTITION plt1_p3; ALTER TABLE plt1 ATTACH PARTITION plt1_p3 DEFAULT; ANALYZE plt1; @@ -1466,26 +4235,24 @@ SELECT avg(t1.a), avg(t2.b), t1.c, t2.c FROM plt1 t1 RIGHT JOIN plt2 t2 ON t1.c Sort Key: t1.c -> HashAggregate Group Key: t1.c, t2.c - -> Append - -> Hash Join - Hash Cond: (t2.c = t1.c) - -> Seq Scan on plt2_p1 t2 - -> Hash - -> Seq Scan on plt1_p1 t1 + -> Hash Join + Hash Cond: ((t2.c)::text = (t1.c)::text) + -> Append + -> Seq Scan on plt2_p4 t2 + -> Seq Scan on plt2_p1 t2_1 + -> Seq Scan on plt2_p2 t2_2 + -> Seq Scan on plt2_p3 t2_3 + -> Hash + -> Append + -> Seq Scan on plt1_p4 t1 Filter: ((a % 25) = 0) - -> Hash Join - Hash Cond: (t2_1.c = t1_1.c) - -> Seq Scan on plt2_p2 t2_1 - -> Hash - -> Seq Scan on plt1_p2 t1_1 + -> Seq Scan on plt1_p1 t1_1 Filter: ((a % 25) = 0) - -> Hash Join - Hash Cond: (t2_2.c = t1_2.c) - -> Seq Scan on plt2_p3 t2_2 - -> Hash - -> Seq Scan on plt1_p3 t1_2 + -> Seq Scan on plt1_p2 t1_2 Filter: ((a % 25) = 0) -(23 rows) + -> Seq Scan on plt1_p3 t1_3 + Filter: ((a % 25) = 0) +(21 rows) -- -- multiple levels of partitioning @@ -1881,64 +4648,70 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt4_n t2 WHERE t1.a = t2.a; Hash Join Hash Cond: (t1.a = t2.a) -> Append - -> Seq Scan on prt1_p1 t1 - -> Seq Scan on prt1_p2 t1_1 - -> Seq Scan on prt1_p3 t1_2 + -> Seq Scan on prt1_p0 t1 + -> Seq Scan on prt1_p1 t1_1 + -> Seq Scan on prt1_p2 t1_2 + -> Seq Scan on prt1_p3 t1_3 + -> Seq Scan on prt1_p4 t1_4 -> Hash -> Append -> Seq Scan on prt4_n_p1 t2 -> Seq Scan on prt4_n_p2 t2_1 -> Seq Scan on prt4_n_p3 t2_2 -(11 rows) +(13 rows) EXPLAIN (COSTS OFF) SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt4_n t2, prt2 t3 WHERE t1.a = t2.a and t1.a = t3.b; - QUERY PLAN --------------------------------------------------------- + QUERY PLAN +---------------------------------------------------------- Hash Join - Hash Cond: (t2.a = t1.a) + Hash Cond: (t3.b = t1.a) -> Append - -> Seq Scan on prt4_n_p1 t2 - -> Seq Scan on prt4_n_p2 t2_1 - -> Seq Scan on prt4_n_p3 t2_2 + -> Seq Scan on prt2_p0 t3 + -> Seq Scan on prt2_p1 t3_1 + -> Seq Scan on prt2_p2 t3_2 + -> Seq Scan on prt2_p3 t3_3 + -> Seq Scan on prt2_p4 t3_4 + -> Seq Scan on prt2_p5 t3_5 -> Hash - -> Append - -> Hash Join - Hash Cond: (t1.a = t3.b) - -> Seq Scan on prt1_p1 t1 - -> Hash - -> Seq Scan on prt2_p1 t3 - -> Hash Join - Hash Cond: (t1_1.a = t3_1.b) - -> Seq Scan on prt1_p2 t1_1 - -> Hash - -> Seq Scan on prt2_p2 t3_1 - -> Hash Join - Hash Cond: (t1_2.a = t3_2.b) - -> Seq Scan on prt1_p3 t1_2 - -> Hash - -> Seq Scan on prt2_p3 t3_2 + -> Hash Join + Hash Cond: (t1.a = t2.a) + -> Append + -> Seq Scan on prt1_p0 t1 + -> Seq Scan on prt1_p1 t1_1 + -> Seq Scan on prt1_p2 t1_2 + -> Seq Scan on prt1_p3 t1_3 + -> Seq Scan on prt1_p4 t1_4 + -> Hash + -> Append + -> Seq Scan on prt4_n_p1 t2 + -> Seq Scan on prt4_n_p2 t2_1 + -> Seq Scan on prt4_n_p3 t2_2 (23 rows) -- partitionwise join can not be applied if there are no equi-join conditions -- between partition keys EXPLAIN (COSTS OFF) SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1 LEFT JOIN prt2 t2 ON (t1.a < t2.b); - QUERY PLAN ---------------------------------------------------------- + QUERY PLAN +-------------------------------------------- Nested Loop Left Join + Join Filter: (t1.a < t2.b) -> Append - -> Seq Scan on prt1_p1 t1 - -> Seq Scan on prt1_p2 t1_1 - -> Seq Scan on prt1_p3 t1_2 - -> Append - -> Index Scan using iprt2_p1_b on prt2_p1 t2 - Index Cond: (b > t1.a) - -> Index Scan using iprt2_p2_b on prt2_p2 t2_1 - Index Cond: (b > t1.a) - -> Index Scan using iprt2_p3_b on prt2_p3 t2_2 - Index Cond: (b > t1.a) -(12 rows) + -> Seq Scan on prt1_p0 t1 + -> Seq Scan on prt1_p1 t1_1 + -> Seq Scan on prt1_p2 t1_2 + -> Seq Scan on prt1_p3 t1_3 + -> Seq Scan on prt1_p4 t1_4 + -> Materialize + -> Append + -> Seq Scan on prt2_p0 t2 + -> Seq Scan on prt2_p1 t2_1 + -> Seq Scan on prt2_p2 t2_2 + -> Seq Scan on prt2_p3 t2_3 + -> Seq Scan on prt2_p4 t2_4 + -> Seq Scan on prt2_p5 t2_5 +(16 rows) -- equi-join with join condition on partial keys does not qualify for -- partitionwise join @@ -2024,16 +4797,17 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_n t1 JOIN prt2_n t2 ON (t1.c = t2.c) JOI -> Seq Scan on prt2_n_p2 t2_1 -> Hash -> Hash Join - Hash Cond: (t3.c = (t1.c)::text) + Hash Cond: ((t3.c)::text = (t1.c)::text) -> Append - -> Seq Scan on plt1_p1 t3 - -> Seq Scan on plt1_p2 t3_1 - -> Seq Scan on plt1_p3 t3_2 + -> Seq Scan on plt1_p4 t3 + -> Seq Scan on plt1_p1 t3_1 + -> Seq Scan on plt1_p2 t3_2 + -> Seq Scan on plt1_p3 t3_3 -> Hash -> Append -> Seq Scan on prt1_n_p1 t1 -> Seq Scan on prt1_n_p2 t1_1 -(16 rows) +(17 rows) -- partitionwise join can not be applied for a join between list and range -- partitioned tables @@ -2044,14 +4818,16 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_n t1 FULL JOIN prt1 t2 ON (t1.c = t2.c); Hash Full Join Hash Cond: ((t2.c)::text = (t1.c)::text) -> Append - -> Seq Scan on prt1_p1 t2 - -> Seq Scan on prt1_p2 t2_1 - -> Seq Scan on prt1_p3 t2_2 + -> Seq Scan on prt1_p0 t2 + -> Seq Scan on prt1_p1 t2_1 + -> Seq Scan on prt1_p2 t2_2 + -> Seq Scan on prt1_p3 t2_3 + -> Seq Scan on prt1_p4 t2_4 -> Hash -> Append -> Seq Scan on prt1_n_p1 t1 -> Seq Scan on prt1_n_p2 t1_1 -(10 rows) +(12 rows) -- partitionwise join can not be applied if only one of joining tables has -- default partition @@ -2067,16 +4843,279 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.b = -> Hash Join Hash Cond: (t2.b = t1.a) -> Append - -> Seq Scan on prt2_p1 t2 - -> Seq Scan on prt2_p2 t2_1 - -> Seq Scan on prt2_p3 t2_2 + -> Seq Scan on prt2_p0 t2 + -> Seq Scan on prt2_p1 t2_1 + -> Seq Scan on prt2_p2 t2_2 + -> Seq Scan on prt2_p3 t2_3 + -> Seq Scan on prt2_p4 t2_4 + -> Seq Scan on prt2_p5 t2_5 -> Hash -> Append - -> Seq Scan on prt1_p1 t1 + -> Seq Scan on prt1_p0 t1 Filter: (b = 0) - -> Seq Scan on prt1_p2 t1_1 + -> Seq Scan on prt1_p1 t1_1 Filter: (b = 0) - -> Seq Scan on prt1_p3 t1_2 + -> Seq Scan on prt1_p2 t1_2 Filter: (b = 0) -(16 rows) + -> Seq Scan on prt1_p3 t1_3 + Filter: (b = 0) + -> Seq Scan on prt1_p4 t1_4 + Filter: (b = 0) +(23 rows) + +DROP TABLE plt1; +DROP TABLE plt2; +CREATE TABLE plt1 (a int, b int, c text) PARTITION BY LIST(c); +CREATE TABLE plt1_p1 PARTITION OF plt1 FOR VALUES IN ('0000', '0002'); +CREATE TABLE plt1_p2 PARTITION OF plt1 FOR VALUES IN ('0003', '0004'); +CREATE TABLE plt1_p3 PARTITION OF plt1 FOR VALUES IN ('0006', '0007'); +INSERT INTO plt1 SELECT i, i, to_char(i % 8, 'FM0000') FROM generate_series(0, 39) i WHERE i % 8 IN (0, 2, 3, 4, 6, 7); +ANALYZE plt1; +CREATE TABLE plt2 (a int, b int, c text) PARTITION BY LIST(c); +CREATE TABLE plt2_p1 PARTITION OF plt2 FOR VALUES IN ('0001', '0002'); +CREATE TABLE plt2_p2 PARTITION OF plt2 FOR VALUES IN ('0003', '0004'); +CREATE TABLE plt2_p3 PARTITION OF plt2 FOR VALUES IN ('0005', '0007'); +INSERT INTO plt2 SELECT i, i, to_char(i % 8, 'FM0000') FROM generate_series(0, 39) i WHERE i % 8 IN (1, 2, 3, 4, 5, 7); +ANALYZE plt2; +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1 t1 FULL JOIN plt2 t2 ON (t1.c = t2.c) WHERE COALESCE(t1.a, 0) % 8 != 2 AND COALESCE(t1.a, 0) % 8 != 3 AND COALESCE(t1.a, 0) % 8 != 4 AND COALESCE(t1.a, 0) % 8 != 7 ORDER BY t1.c, t2.c, t1.a, t2.a; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Sort + Sort Key: t1.c, t2.c, t1.a, t2.a + -> Append + -> Hash Full Join + Hash Cond: (t1.c = t2.c) + Filter: (((COALESCE(t1.a, 0) % 8) <> 2) AND ((COALESCE(t1.a, 0) % 8) <> 3) AND ((COALESCE(t1.a, 0) % 8) <> 4) AND ((COALESCE(t1.a, 0) % 8) <> 7)) + -> Seq Scan on plt1_p1 t1 + -> Hash + -> Seq Scan on plt2_p1 t2 + -> Hash Full Join + Hash Cond: (t1_1.c = t2_1.c) + Filter: (((COALESCE(t1_1.a, 0) % 8) <> 2) AND ((COALESCE(t1_1.a, 0) % 8) <> 3) AND ((COALESCE(t1_1.a, 0) % 8) <> 4) AND ((COALESCE(t1_1.a, 0) % 8) <> 7)) + -> Seq Scan on plt1_p2 t1_1 + -> Hash + -> Seq Scan on plt2_p2 t2_1 + -> Hash Full Join + Hash Cond: (t1_2.c = t2_2.c) + Filter: (((COALESCE(t1_2.a, 0) % 8) <> 2) AND ((COALESCE(t1_2.a, 0) % 8) <> 3) AND ((COALESCE(t1_2.a, 0) % 8) <> 4) AND ((COALESCE(t1_2.a, 0) % 8) <> 7)) + -> Seq Scan on plt1_p3 t1_2 + -> Hash + -> Seq Scan on plt2_p3 t2_2 +(21 rows) + +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1 t1 FULL JOIN plt2 t2 ON (t1.c = t2.c) WHERE COALESCE(t1.a, 0) % 8 != 2 AND COALESCE(t1.a, 0) % 8 != 3 AND COALESCE(t1.a, 0) % 8 != 4 AND COALESCE(t1.a, 0) % 8 != 7 ORDER BY t1.c, t2.c, t1.a, t2.a; + a | c | a | c +----+------+----+------ + 0 | 0000 | | + 8 | 0000 | | + 16 | 0000 | | + 24 | 0000 | | + 32 | 0000 | | + 6 | 0006 | | + 14 | 0006 | | + 22 | 0006 | | + 30 | 0006 | | + 38 | 0006 | | + | | 1 | 0001 + | | 9 | 0001 + | | 17 | 0001 + | | 25 | 0001 + | | 33 | 0001 + | | 5 | 0005 + | | 13 | 0005 + | | 21 | 0005 + | | 29 | 0005 + | | 37 | 0005 +(20 rows) + +DROP TABLE plt1; +DROP TABLE plt2; +CREATE TABLE plt1 (a int, b int, c text) PARTITION BY LIST(c); +CREATE TABLE plt1_p1 PARTITION OF plt1 FOR VALUES IN (NULL, '0000', '0002'); +CREATE TABLE plt1_p2 PARTITION OF plt1 FOR VALUES IN ('0003', '0004'); +INSERT INTO plt1 SELECT i, i, to_char(i % 5, 'FM0000') FROM generate_series(0, 24) i WHERE i % 5 IN (0, 2, 3, 4); +ANALYZE plt1; +CREATE TABLE plt2 (a int, b int, c text) PARTITION BY LIST(c); +CREATE TABLE plt2_p1 PARTITION OF plt2 DEFAULT; +CREATE TABLE plt2_p2 PARTITION OF plt2 FOR VALUES IN ('0003', '0004'); +INSERT INTO plt2 SELECT i, i, to_char(i % 5, 'FM0000') FROM generate_series(0, 24) i WHERE i % 5 IN (1, 2, 3, 4); +ANALYZE plt2; +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1 t1 FULL JOIN plt2 t2 ON (t1.c = t2.c) WHERE COALESCE(t1.a, 0) % 5 != 2 AND COALESCE(t1.a, 0) % 5 != 3 AND COALESCE(t1.a, 0) % 5 != 4 ORDER BY t1.c, t2.c, t1.a, t2.a; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------ + Sort + Sort Key: t1.c, t2_1.c, t1.a, t2_1.a + -> Append + -> Hash Full Join + Hash Cond: (t1.c = t2_1.c) + Filter: (((COALESCE(t1.a, 0) % 5) <> 2) AND ((COALESCE(t1.a, 0) % 5) <> 3) AND ((COALESCE(t1.a, 0) % 5) <> 4)) + -> Seq Scan on plt1_p1 t1 + -> Hash + -> Seq Scan on plt2_p1 t2_1 + -> Hash Full Join + Hash Cond: (t1_1.c = t2.c) + Filter: (((COALESCE(t1_1.a, 0) % 5) <> 2) AND ((COALESCE(t1_1.a, 0) % 5) <> 3) AND ((COALESCE(t1_1.a, 0) % 5) <> 4)) + -> Seq Scan on plt1_p2 t1_1 + -> Hash + -> Seq Scan on plt2_p2 t2 +(15 rows) + +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1 t1 FULL JOIN plt2 t2 ON (t1.c = t2.c) WHERE COALESCE(t1.a, 0) % 5 != 2 AND COALESCE(t1.a, 0) % 5 != 3 AND COALESCE(t1.a, 0) % 5 != 4 ORDER BY t1.c, t2.c, t1.a, t2.a; + a | c | a | c +----+------+----+------ + 0 | 0000 | | + 5 | 0000 | | + 10 | 0000 | | + 15 | 0000 | | + 20 | 0000 | | + | | 1 | 0001 + | | 6 | 0001 + | | 11 | 0001 + | | 16 | 0001 + | | 21 | 0001 +(10 rows) + +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt2 t2 FULL JOIN plt1 t1 ON (t1.c = t2.c) WHERE COALESCE(t1.a, 0) % 5 != 2 AND COALESCE(t1.a, 0) % 5 != 3 AND COALESCE(t1.a, 0) % 5 != 4 ORDER BY t1.c, t2.c, t1.a, t2.a; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------ + Sort + Sort Key: t1.c, t2_1.c, t1.a, t2_1.a + -> Append + -> Hash Full Join + Hash Cond: (t2_1.c = t1.c) + Filter: (((COALESCE(t1.a, 0) % 5) <> 2) AND ((COALESCE(t1.a, 0) % 5) <> 3) AND ((COALESCE(t1.a, 0) % 5) <> 4)) + -> Seq Scan on plt2_p1 t2_1 + -> Hash + -> Seq Scan on plt1_p1 t1 + -> Hash Full Join + Hash Cond: (t2.c = t1_1.c) + Filter: (((COALESCE(t1_1.a, 0) % 5) <> 2) AND ((COALESCE(t1_1.a, 0) % 5) <> 3) AND ((COALESCE(t1_1.a, 0) % 5) <> 4)) + -> Seq Scan on plt2_p2 t2 + -> Hash + -> Seq Scan on plt1_p2 t1_1 +(15 rows) + +SELECT t1.a, t1.c, t2.a, t2.c FROM plt2 t2 FULL JOIN plt1 t1 ON (t1.c = t2.c) WHERE COALESCE(t1.a, 0) % 5 != 2 AND COALESCE(t1.a, 0) % 5 != 3 AND COALESCE(t1.a, 0) % 5 != 4 ORDER BY t1.c, t2.c, t1.a, t2.a; + a | c | a | c +----+------+----+------ + 0 | 0000 | | + 5 | 0000 | | + 10 | 0000 | | + 15 | 0000 | | + 20 | 0000 | | + | | 1 | 0001 + | | 6 | 0001 + | | 11 | 0001 + | | 16 | 0001 + | | 21 | 0001 +(10 rows) + +DROP TABLE plt1; +DROP TABLE plt2; +CREATE TABLE plt1 (a int, b int, c text) PARTITION BY LIST(c); +CREATE TABLE plt1_p1 PARTITION OF plt1 FOR VALUES IN ('0000', '0001', '0002'); +CREATE TABLE plt1_p2 PARTITION OF plt1 FOR VALUES IN ('0003', '0004'); +INSERT INTO plt1 SELECT i, i, to_char(i % 5, 'FM0000') FROM generate_series(0, 24) i; +ANALYZE plt1; +CREATE TABLE plt2 (a int, b int, c text) PARTITION BY LIST(c); +CREATE TABLE plt2_p1 PARTITION OF plt2 FOR VALUES IN ('0002'); +CREATE TABLE plt2_p2 PARTITION OF plt2 FOR VALUES IN ('0003', '0004'); +INSERT INTO plt2 SELECT i, i, to_char(i % 5, 'FM0000') FROM generate_series(0, 24) i WHERE i % 5 IN (2, 3, 4); +ANALYZE plt2; +CREATE TABLE plt3 (a int, b int, c text) PARTITION BY LIST(c); +CREATE TABLE plt3_p1 PARTITION OF plt3 FOR VALUES IN ('0001'); +CREATE TABLE plt3_p2 PARTITION OF plt3 FOR VALUES IN ('0003', '0004'); +INSERT INTO plt3 SELECT i, i, to_char(i % 5, 'FM0000') FROM generate_series(0, 24) i WHERE i % 5 IN (1, 3, 4); +ANALYZE plt3; +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c, t3.a, t3.c FROM (plt1 t1 LEFT JOIN plt2 t2 ON (t1.c = t2.c)) FULL JOIN plt3 t3 ON (t1.c = t3.c) WHERE COALESCE(t1.a, 0) % 5 != 3 AND coalesce(t1.a, 0) % 5 != 4 ORDER BY t1.c, t1.a, t2.a, t3.a; + QUERY PLAN +----------------------------------------------------------------------------------------------- + Sort + Sort Key: t1.c, t1.a, t2.a, t3.a + -> Append + -> Hash Full Join + Hash Cond: (t1.c = t3.c) + Filter: (((COALESCE(t1.a, 0) % 5) <> 3) AND ((COALESCE(t1.a, 0) % 5) <> 4)) + -> Hash Left Join + Hash Cond: (t1.c = t2.c) + -> Seq Scan on plt1_p1 t1 + -> Hash + -> Seq Scan on plt2_p1 t2 + -> Hash + -> Seq Scan on plt3_p1 t3 + -> Hash Full Join + Hash Cond: (t1_1.c = t3_1.c) + Filter: (((COALESCE(t1_1.a, 0) % 5) <> 3) AND ((COALESCE(t1_1.a, 0) % 5) <> 4)) + -> Hash Left Join + Hash Cond: (t1_1.c = t2_1.c) + -> Seq Scan on plt1_p2 t1_1 + -> Hash + -> Seq Scan on plt2_p2 t2_1 + -> Hash + -> Seq Scan on plt3_p2 t3_1 +(23 rows) + +SELECT t1.a, t1.c, t2.a, t2.c, t3.a, t3.c FROM (plt1 t1 LEFT JOIN plt2 t2 ON (t1.c = t2.c)) FULL JOIN plt3 t3 ON (t1.c = t3.c) WHERE COALESCE(t1.a, 0) % 5 != 3 AND coalesce(t1.a, 0) % 5 != 4 ORDER BY t1.c, t1.a, t2.a, t3.a; + a | c | a | c | a | c +----+------+----+------+----+------ + 0 | 0000 | | | | + 5 | 0000 | | | | + 10 | 0000 | | | | + 15 | 0000 | | | | + 20 | 0000 | | | | + 1 | 0001 | | | 1 | 0001 + 1 | 0001 | | | 6 | 0001 + 1 | 0001 | | | 11 | 0001 + 1 | 0001 | | | 16 | 0001 + 1 | 0001 | | | 21 | 0001 + 6 | 0001 | | | 1 | 0001 + 6 | 0001 | | | 6 | 0001 + 6 | 0001 | | | 11 | 0001 + 6 | 0001 | | | 16 | 0001 + 6 | 0001 | | | 21 | 0001 + 11 | 0001 | | | 1 | 0001 + 11 | 0001 | | | 6 | 0001 + 11 | 0001 | | | 11 | 0001 + 11 | 0001 | | | 16 | 0001 + 11 | 0001 | | | 21 | 0001 + 16 | 0001 | | | 1 | 0001 + 16 | 0001 | | | 6 | 0001 + 16 | 0001 | | | 11 | 0001 + 16 | 0001 | | | 16 | 0001 + 16 | 0001 | | | 21 | 0001 + 21 | 0001 | | | 1 | 0001 + 21 | 0001 | | | 6 | 0001 + 21 | 0001 | | | 11 | 0001 + 21 | 0001 | | | 16 | 0001 + 21 | 0001 | | | 21 | 0001 + 2 | 0002 | 2 | 0002 | | + 2 | 0002 | 7 | 0002 | | + 2 | 0002 | 12 | 0002 | | + 2 | 0002 | 17 | 0002 | | + 2 | 0002 | 22 | 0002 | | + 7 | 0002 | 2 | 0002 | | + 7 | 0002 | 7 | 0002 | | + 7 | 0002 | 12 | 0002 | | + 7 | 0002 | 17 | 0002 | | + 7 | 0002 | 22 | 0002 | | + 12 | 0002 | 2 | 0002 | | + 12 | 0002 | 7 | 0002 | | + 12 | 0002 | 12 | 0002 | | + 12 | 0002 | 17 | 0002 | | + 12 | 0002 | 22 | 0002 | | + 17 | 0002 | 2 | 0002 | | + 17 | 0002 | 7 | 0002 | | + 17 | 0002 | 12 | 0002 | | + 17 | 0002 | 17 | 0002 | | + 17 | 0002 | 22 | 0002 | | + 22 | 0002 | 2 | 0002 | | + 22 | 0002 | 7 | 0002 | | + 22 | 0002 | 12 | 0002 | | + 22 | 0002 | 17 | 0002 | | + 22 | 0002 | 22 | 0002 | | +(55 rows) diff --git a/src/test/regress/sql/partition_join.sql b/src/test/regress/sql/partition_join.sql index 92994b479b..d8434f6b1a 100644 --- a/src/test/regress/sql/partition_join.sql +++ b/src/test/regress/sql/partition_join.sql @@ -10,25 +10,39 @@ SET enable_partitionwise_join to true; -- partitioned by a single column -- CREATE TABLE prt1 (a int, b int, c varchar) PARTITION BY RANGE(a); +CREATE TABLE prt1_p0 PARTITION OF prt1 FOR VALUES FROM (MINVALUE) TO (0); CREATE TABLE prt1_p1 PARTITION OF prt1 FOR VALUES FROM (0) TO (250); CREATE TABLE prt1_p3 PARTITION OF prt1 FOR VALUES FROM (500) TO (600); CREATE TABLE prt1_p2 PARTITION OF prt1 FOR VALUES FROM (250) TO (500); -INSERT INTO prt1 SELECT i, i % 25, to_char(i, 'FM0000') FROM generate_series(0, 599) i WHERE i % 2 = 0; +CREATE TABLE prt1_p4 PARTITION OF prt1 FOR VALUES FROM (600) TO (800); +INSERT INTO prt1 SELECT i, i % 25, to_char(i, 'FM0000') FROM generate_series(-250, 799) i WHERE i % 2 = 0; +CREATE INDEX iprt1_p0_a on prt1_p0(a); CREATE INDEX iprt1_p1_a on prt1_p1(a); CREATE INDEX iprt1_p2_a on prt1_p2(a); CREATE INDEX iprt1_p3_a on prt1_p3(a); +CREATE INDEX iprt1_p4_a on prt1_p4(a); ANALYZE prt1; +-- prt2 have missing starting MINVALUE to -250 range and +-- extra bounds from 800 to MAXVALUE CREATE TABLE prt2 (a int, b int, c varchar) PARTITION BY RANGE(b); +CREATE TABLE prt2_p0 PARTITION OF prt2 FOR VALUES FROM (-250) TO (0); CREATE TABLE prt2_p1 PARTITION OF prt2 FOR VALUES FROM (0) TO (250); CREATE TABLE prt2_p2 PARTITION OF prt2 FOR VALUES FROM (250) TO (500); CREATE TABLE prt2_p3 PARTITION OF prt2 FOR VALUES FROM (500) TO (600); -INSERT INTO prt2 SELECT i % 25, i, to_char(i, 'FM0000') FROM generate_series(0, 599) i WHERE i % 3 = 0; +CREATE TABLE prt2_p4 PARTITION OF prt2 FOR VALUES FROM (600) TO (MAXVALUE); +INSERT INTO prt2 SELECT i % 25, i, to_char(i, 'FM0000') FROM generate_series(-250, 799) i WHERE i % 3 = 0; +CREATE INDEX iprt2_p0_b on prt2_p0(b); CREATE INDEX iprt2_p1_b on prt2_p1(b); CREATE INDEX iprt2_p2_b on prt2_p2(b); CREATE INDEX iprt2_p3_b on prt2_p3(b); +CREATE INDEX iprt2_p4_b on prt2_p4(b); ANALYZE prt2; +-- Partition-wise-join is possible with some partition bounds overlap +-- with each other completely and some partialy for inner,left,right, +-- full, semi and anti joins + -- inner join EXPLAIN (COSTS OFF) SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.b = 0 ORDER BY t1.a, t2.b; @@ -69,11 +83,19 @@ EXPLAIN (COSTS OFF) SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t2.b FROM prt2 t2 WHERE t2.a = 0) AND t1.b = 0 ORDER BY t1.a; SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t2.b FROM prt2 t2 WHERE t2.a = 0) AND t1.b = 0 ORDER BY t1.a; +EXPLAIN (COSTS OFF) +SELECT t1.* FROM prt2 t1 WHERE t1.b IN (SELECT t2.a FROM prt1 t2 WHERE t2.b = 0) AND t1.a = 0 ORDER BY t1.b; +SELECT t1.* FROM prt2 t1 WHERE t1.b IN (SELECT t2.a FROM prt1 t2 WHERE t2.b = 0) AND t1.a = 0 ORDER BY t1.b; + -- Anti-join with aggregates EXPLAIN (COSTS OFF) SELECT sum(t1.a), avg(t1.a), sum(t1.b), avg(t1.b) FROM prt1 t1 WHERE NOT EXISTS (SELECT 1 FROM prt2 t2 WHERE t1.a = t2.b); SELECT sum(t1.a), avg(t1.a), sum(t1.b), avg(t1.b) FROM prt1 t1 WHERE NOT EXISTS (SELECT 1 FROM prt2 t2 WHERE t1.a = t2.b); +EXPLAIN (COSTS OFF) +SELECT t1.b, t1.c FROM prt2 t1 WHERE NOT EXISTS (SELECT 1 FROM prt1 t2 WHERE t1.b = t2.a) and t1.a = 0; +SELECT t1.b, t1.c FROM prt2 t1 WHERE NOT EXISTS (SELECT 1 FROM prt1 t2 WHERE t1.b = t2.a) and t1.a = 0; + -- lateral reference EXPLAIN (COSTS OFF) SELECT * FROM prt1 t1 LEFT JOIN LATERAL @@ -110,20 +132,30 @@ RESET enable_hashjoin; -- partitioned by expression -- CREATE TABLE prt1_e (a int, b int, c int) PARTITION BY RANGE(((a + b)/2)); +CREATE TABLE prt1_e_p0 PARTITION OF prt1_e FOR VALUES FROM (MINVALUE) TO (0); CREATE TABLE prt1_e_p1 PARTITION OF prt1_e FOR VALUES FROM (0) TO (250); CREATE TABLE prt1_e_p2 PARTITION OF prt1_e FOR VALUES FROM (250) TO (500); CREATE TABLE prt1_e_p3 PARTITION OF prt1_e FOR VALUES FROM (500) TO (600); +CREATE TABLE prt1_e_p4 PARTITION OF prt1_e FOR VALUES FROM (600) TO (MAXVALUE); INSERT INTO prt1_e SELECT i, i, i % 25 FROM generate_series(0, 599, 2) i; +INSERT INTO prt1_e SELECT i, i, i % 25 FROM generate_series(-250, 0, 2) i; +INSERT INTO prt1_e SELECT i, i, i % 25 FROM generate_series(600, 799, 2) i; +CREATE INDEX iprt1_e_p0_ab2 on prt1_e_p1(((a+b)/2)); CREATE INDEX iprt1_e_p1_ab2 on prt1_e_p1(((a+b)/2)); CREATE INDEX iprt1_e_p2_ab2 on prt1_e_p2(((a+b)/2)); CREATE INDEX iprt1_e_p3_ab2 on prt1_e_p3(((a+b)/2)); +CREATE INDEX iprt1_e_p4_ab2 on prt1_e_p1(((a+b)/2)); ANALYZE prt1_e; CREATE TABLE prt2_e (a int, b int, c int) PARTITION BY RANGE(((b + a)/2)); +CREATE TABLE prt2_e_p0 PARTITION OF prt2_e FOR VALUES FROM (MINVALUE) TO (0); CREATE TABLE prt2_e_p1 PARTITION OF prt2_e FOR VALUES FROM (0) TO (250); CREATE TABLE prt2_e_p2 PARTITION OF prt2_e FOR VALUES FROM (250) TO (500); CREATE TABLE prt2_e_p3 PARTITION OF prt2_e FOR VALUES FROM (500) TO (600); +CREATE TABLE prt2_e_p4 PARTITION OF prt2_e FOR VALUES FROM (600) TO (MAXVALUE); INSERT INTO prt2_e SELECT i, i, i % 25 FROM generate_series(0, 599, 3) i; +INSERT INTO prt2_e SELECT i, i, i % 25 FROM generate_series(-250, 0, 3) i; +INSERT INTO prt2_e SELECT i, i, i % 25 FROM generate_series(600, 799, 3) i; ANALYZE prt2_e; EXPLAIN (COSTS OFF) @@ -187,6 +219,114 @@ SELECT t1.a, t2.b FROM prt1 t1, prt2 t2 WHERE t1::text = t2::text AND t1.a = t2. RESET enable_hashjoin; RESET enable_nestloop; +-- test default partition behavior for range, partition-wise join is not +-- possible since more than one partition on one side matches default partition +-- on the other side. Default partition from prt1 matches prt2_p3 and +-- prt2_p4 partition from prt2. +ALTER TABLE prt1 DETACH PARTITION prt1_p3; +ALTER TABLE prt1 ATTACH PARTITION prt1_p3 DEFAULT; +ANALYZE prt1; +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.b = 0 ORDER BY t1.a, t2.b; + +-- partitionwise join should be possible when we drop prt2_p4 from prt2. +ALTER TABLE prt2 DETACH PARTITION prt2_p4; +ANALYZE prt2; +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.b = 0 ORDER BY t1.a, t2.b; + +-- restore the partitioned tables for rest of the tests +ALTER TABLE prt1 DETACH PARTITION prt1_p3; +ALTER TABLE prt1 ATTACH PARTITION prt1_p3 FOR VALUES FROM (500) TO (600); +ANALYZE prt1; +ALTER TABLE prt2 ATTACH PARTITION prt2_p4 FOR VALUES FROM (600) TO (MAXVALUE); +ANALYZE prt2; + +-- Add an extra partition to prt2 , Partition-wise join is possible with +-- extra partitions on inner side are allowed +DROP TABLE prt2_p4; +CREATE TABLE prt2_p4 PARTITION OF prt2 FOR VALUES FROM (600) TO (800); +CREATE TABLE prt2_p5 PARTITION OF prt2 FOR VALUES FROM (800) TO (1000); +INSERT INTO prt2 SELECT i % 25, i, to_char(i, 'FM0000') FROM generate_series(600, 999) i WHERE i % 3 = 0; +ANALYZE prt2; + +-- inner join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM prt1 t1 INNER JOIN prt2 t2 ON t1.a = t2.b WHERE t1.b = 0 ORDER BY t1.a; +SELECT t1.a, t1.c, t2.a, t2.c FROM prt1 t1 INNER JOIN prt2 t2 ON t1.a = t2.b WHERE t1.b = 0 ORDER BY t1.a; + +-- left join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM prt1 t1 LEFT JOIN prt2 t2 ON t1.a = t2.b WHERE t1.b = 0 ORDER BY t1.a; +SELECT t1.a, t1.c, t2.a, t2.c FROM prt1 t1 LEFT JOIN prt2 t2 ON t1.a = t2.b WHERE t1.b = 0 ORDER BY t1.a; + +-- semi join +EXPLAIN (COSTS OFF) +select t1.a, t1.b, t1.c from prt1 t1 where exists (select 1 from prt2 t2 WHERE t1.a = t2.b) and t1.b = 0 order by t1.a, t1.b, t1.c; +select t1.a, t1.b, t1.c from prt1 t1 where exists (select 1 from prt2 t2 WHERE t1.a = t2.b) and t1.b = 0 order by t1.a, t1.b, t1.c; + +EXPLAIN (COSTS OFF) +select t1.a, t1.b, t1.c from prt2 t1 where exists (select 1 from prt1 t2 WHERE t1.b = t2.a) and t1.a = 0 order by t1.a, t1.b, t1.c; +select t1.a, t1.b, t1.c from prt2 t1 where exists (select 1 from prt1 t2 WHERE t1.b = t2.a) and t1.a = 0 order by t1.a, t1.b, t1.c; + +-- anti join +EXPLAIN (COSTS OFF) +select t1.a, t1.b, t1.c from prt1 t1 where not exists (select 1 from prt2 t2 WHERE t1.a = t2.b) and t1.b = 0 order by t1.a, t1.b, t1.c; +select t1.a, t1.b, t1.c from prt1 t1 where not exists (select 1 from prt2 t2 WHERE t1.a = t2.b) and t1.b = 0 order by t1.a, t1.b, t1.c; + +-- 3-way join when not every pair of joining relation can use partition-wise +-- join +EXPLAIN (COSTS OFF) +SELECT t1.a, t2.a, t3.c FROM prt1 t1 RIGHT JOIN prt2 t2 ON (t1.a = t2.b) INNER JOIN prt1 t3 ON (t2.b = t3.a) WHERE t2.a = 0 ORDER BY t1.a, t2.a, t3.c; +SELECT t1.a, t2.a, t3.c FROM prt1 t1 RIGHT JOIN prt2 t2 ON (t1.a = t2.b) INNER JOIN prt1 t3 ON (t2.b = t3.a) WHERE t2.a = 0 ORDER BY t1.a, t2.a, t3.c; + +-- partition-wise join can not handle missing partition on the inner side +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM prt1 t1 RIGHT JOIN prt2 t2 ON t1.a = t2.b WHERE t2.a = 0 ORDER BY t2.b; +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM prt1 t1 FULL JOIN prt2 t2 ON t1.a = t2.b WHERE coalesce(t1.b, 0) + coalesce(t2.a, 0) = 0 ORDER BY t1.a, t2.a; +EXPLAIN (COSTS OFF) +select t1.a, t1.b, t1.c from prt2 t1 where not exists (select 1 from prt1 t2 WHERE t1.b = t2.a) and t1.a = 0 order by t1.a, t1.b, t1.c; + +-- Partition-wise join can not handle the case when one partition from one side +-- matches with multiple partitions on the other side +DROP TABLE prt2_p4; +DROP TABLE prt2_p5; +CREATE TABLE prt2_p4 PARTITION OF prt2 FOR VALUES FROM (600) TO (700); +CREATE TABLE prt2_p5 PARTITION OF prt2 FOR VALUES FROM (700) TO (1000); +INSERT INTO prt2 SELECT i % 25, i, to_char(i, 'FM0000') FROM generate_series(600, 999, 3) i; +ANALYZE prt2; + +-- inner join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM prt1 t1 INNER JOIN prt2 t2 ON t1.a = t2.b WHERE t1.b = 0 ORDER BY t1.a; + +-- left join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM prt1 t1 LEFT JOIN prt2 t2 ON t1.a = t2.b WHERE t1.b = 0 ORDER BY t1.a; + +-- right join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM prt1 t1 RIGHT JOIN prt2 t2 ON t1.a = t2.b WHERE t2.a = 0 ORDER BY t2.a; + +-- full join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM prt1 t1 FULL JOIN prt2 t2 ON t1.a = t2.b WHERE t1.b + t2.a = 0 ORDER BY t1.a, t2.a; + +-- semi join +EXPLAIN (COSTS OFF) +select t1.a, t1.b, t1.c from prt1 t1 where exists (select 1 from prt2 t2 WHERE t1.a = t2.b) and t1.b = 0 order by t1.a, t1.b, t1.c; + +EXPLAIN (COSTS OFF) +select t1.a, t1.b, t1.c from prt2 t1 where exists (select 1 from prt1 t2 WHERE t1.b = t2.a) and t1.a = 0 order by t1.a, t1.b, t1.c; + +-- anti join +EXPLAIN (COSTS OFF) +select t1.a, t1.b, t1.c from prt1 t1 where not exists (select 1 from prt2 t2 WHERE t1.a = t2.b) and t1.b = 0 order by t1.a, t1.b, t1.c; + +EXPLAIN (COSTS OFF) +select t1.a, t1.b, t1.c from prt2 t1 where not exists (select 1 from prt1 t2 WHERE t1.b = t2.a) and t1.a = 0 order by t1.a, t1.b, t1.c; + -- -- partitioned by multiple columns -- @@ -211,28 +351,79 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1_m WHERE prt1_m.c = 0) t1 -- -- tests for list partitioned tables. -- -CREATE TABLE plt1 (a int, b int, c text) PARTITION BY LIST(c); -CREATE TABLE plt1_p1 PARTITION OF plt1 FOR VALUES IN ('0000', '0003', '0004', '0010'); -CREATE TABLE plt1_p2 PARTITION OF plt1 FOR VALUES IN ('0001', '0005', '0002', '0009'); -CREATE TABLE plt1_p3 PARTITION OF plt1 FOR VALUES IN ('0006', '0007', '0008', '0011'); -INSERT INTO plt1 SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(0, 599, 2) i; -ANALYZE plt1; +\set part_mod 17 +\set cond_mod 47 +\set num_rows 500 + +CREATE TABLE plt1 (a int, b int, c varchar) PARTITION BY LIST(c); +CREATE TABLE plt1_p1 PARTITION OF plt1 FOR VALUES IN ('0001','0002','0003'); +CREATE TABLE plt1_p2 PARTITION OF plt1 FOR VALUES IN ('0004','0005','0006'); +CREATE TABLE plt1_p3 PARTITION OF plt1 FOR VALUES IN ('0008','0009'); +CREATE TABLE plt1_p4 PARTITION OF plt1 FOR VALUES IN ('0000','0010'); +INSERT INTO plt1 SELECT i, i % :cond_mod, to_char(i % :part_mod, 'FM0000') FROM generate_series(0, :num_rows) i WHERE i % :part_mod NOT IN (7, 11, 12, 13, 14, 15, 16); +ANALYSE plt1; + +-- plt2 have missing starting 0001, additional 0007, missing ending 0010 +-- and additional 0011 and 0012 bounds +CREATE TABLE plt2 (a int, b int, c varchar) PARTITION BY LIST(c); +CREATE TABLE plt2_p1 PARTITION OF plt2 FOR VALUES IN ('0002','0003'); +CREATE TABLE plt2_p2 PARTITION OF plt2 FOR VALUES IN ('0004','0005','0006'); +CREATE TABLE plt2_p3 PARTITION OF plt2 FOR VALUES IN ('0007','0008','0009'); +CREATE TABLE plt2_p4 PARTITION OF plt2 FOR VALUES IN ('0000','0011','0012'); +INSERT INTO plt2 SELECT i, i % :cond_mod, to_char(i % :part_mod, 'FM0000') FROM generate_series(0, :num_rows) i WHERE i % :part_mod NOT IN (1, 10, 13, 14, 15, 16); +ANALYSE plt2; + +-- Partition-wise-join is possible with some partition bounds overlap +-- with each other completely and some partialy for inner,left,right, +-- full, semi and anti joins -CREATE TABLE plt2 (a int, b int, c text) PARTITION BY LIST(c); -CREATE TABLE plt2_p1 PARTITION OF plt2 FOR VALUES IN ('0000', '0003', '0004', '0010'); -CREATE TABLE plt2_p2 PARTITION OF plt2 FOR VALUES IN ('0001', '0005', '0002', '0009'); -CREATE TABLE plt2_p3 PARTITION OF plt2 FOR VALUES IN ('0006', '0007', '0008', '0011'); -INSERT INTO plt2 SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(0, 599, 3) i; -ANALYZE plt2; +-- inner join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1 t1 INNER JOIN plt2 t2 ON t1.c = t2.c WHERE t1.b + t2.b = 0 ORDER BY t1.a; +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1 t1 INNER JOIN plt2 t2 ON t1.c = t2.c WHERE t1.b + t2.b = 0 ORDER BY t1.a; + +-- left join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1 t1 LEFT JOIN plt2 t2 ON t1.c = t2.c WHERE t1.b + coalesce(t2.b, 0) = 0 ORDER BY t1.a; +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1 t1 LEFT JOIN plt2 t2 ON t1.c = t2.c WHERE t1.b + coalesce(t2.b, 0) = 0 ORDER BY t1.a; + +-- right join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1 t1 RIGHT JOIN plt2 t2 ON t1.c = t2.c WHERE coalesce(t1.b, 0) + t2.b = 0 ORDER BY t2.a; +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1 t1 RIGHT JOIN plt2 t2 ON t1.c = t2.c WHERE coalesce(t1.b, 0) + t2.b = 0 ORDER BY t1.a; + +-- full join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1 t1 FULL JOIN plt2 t2 ON t1.c = t2.c WHERE coalesce(t1.b, 0) + coalesce(t2.b, 0) = 0 ORDER BY t1.a, t2.a; +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1 t1 FULL JOIN plt2 t2 ON t1.c = t2.c WHERE coalesce(t1.b, 0) + coalesce(t2.b, 0) = 0 ORDER BY t1.a, t2.a; + +-- semi join +EXPLAIN (COSTS OFF) +select t1.a, t1.b, t1.c from plt1 t1 where exists (select 1 from plt2 t2 WHERE t1.c = t2.c) and t1.b = 0 order by t1.a, t1.b, t1.c; +select t1.a, t1.b, t1.c from plt1 t1 where exists (select 1 from plt2 t2 WHERE t1.c = t2.c) and t1.b = 0 order by t1.a, t1.b, t1.c; + +EXPLAIN (COSTS OFF) +select t1.a, t1.b, t1.c from plt2 t1 where exists (select 1 from plt1 t2 WHERE t1.c = t2.c) and t1.b = 0 order by t1.a, t1.b, t1.c; +select t1.a, t1.b, t1.c from plt2 t1 where exists (select 1 from plt1 t2 WHERE t1.c = t2.c) and t1.b = 0 order by t1.a, t1.b, t1.c; + +-- anti join +EXPLAIN (COSTS OFF) +select t1.a, t1.b, t1.c from plt1 t1 where not exists (select 1 from plt2 t2 WHERE t1.c = t2.c) and t1.b = 0 order by t1.a, t1.b, t1.c; +select t1.a, t1.b, t1.c from plt1 t1 where not exists (select 1 from plt2 t2 WHERE t1.c = t2.c) and t1.b = 0 order by t1.a, t1.b, t1.c; + +EXPLAIN (COSTS OFF) +select t1.a, t1.b, t1.c from plt2 t1 where not exists (select 1 from plt1 t2 WHERE t1.c = t2.c) and t1.b = 0 order by t1.a, t1.b, t1.c; +select t1.a, t1.b, t1.c from plt2 t1 where not exists (select 1 from plt1 t2 WHERE t1.c = t2.c) and t1.b = 0 order by t1.a, t1.b, t1.c; -- -- list partitioned by expression -- CREATE TABLE plt1_e (a int, b int, c text) PARTITION BY LIST(ltrim(c, 'A')); -CREATE TABLE plt1_e_p1 PARTITION OF plt1_e FOR VALUES IN ('0000', '0003', '0004', '0010'); -CREATE TABLE plt1_e_p2 PARTITION OF plt1_e FOR VALUES IN ('0001', '0005', '0002', '0009'); -CREATE TABLE plt1_e_p3 PARTITION OF plt1_e FOR VALUES IN ('0006', '0007', '0008', '0011'); -INSERT INTO plt1_e SELECT i, i, 'A' || to_char(i/50, 'FM0000') FROM generate_series(0, 599, 2) i; +CREATE TABLE plt1_e_p1 PARTITION OF plt1_e FOR VALUES IN ('0002', '0003'); +CREATE TABLE plt1_e_p2 PARTITION OF plt1_e FOR VALUES IN ('0004', '0005', '0006'); +CREATE TABLE plt1_e_p3 PARTITION OF plt1_e FOR VALUES IN ('0008', '0009'); +CREATE TABLE plt1_e_p4 PARTITION OF plt1_e FOR VALUES IN ('0000'); +INSERT INTO plt1_e SELECT i, i % :cond_mod, to_char(i % :part_mod, 'FM0000') FROM generate_series(0, :num_rows) i WHERE i % :part_mod NOT IN (1, 7, 10, 11, 12, 13, 14, 15, 16); ANALYZE plt1_e; -- test partition matching with N-way join @@ -240,6 +431,175 @@ EXPLAIN (COSTS OFF) SELECT avg(t1.a), avg(t2.b), avg(t3.a + t3.b), t1.c, t2.c, t3.c FROM plt1 t1, plt2 t2, plt1_e t3 WHERE t1.b = t2.b AND t1.c = t2.c AND ltrim(t3.c, 'A') = t1.c GROUP BY t1.c, t2.c, t3.c ORDER BY t1.c, t2.c, t3.c; SELECT avg(t1.a), avg(t2.b), avg(t3.a + t3.b), t1.c, t2.c, t3.c FROM plt1 t1, plt2 t2, plt1_e t3 WHERE t1.b = t2.b AND t1.c = t2.c AND ltrim(t3.c, 'A') = t1.c GROUP BY t1.c, t2.c, t3.c ORDER BY t1.c, t2.c, t3.c; +-- Add an extra partition to plt2 , Partition-wise join is possible with +-- partitions on inner side are allowed +CREATE TABLE plt2_p5 PARTITION OF plt2 FOR VALUES IN ('0013','0014'); +INSERT INTO plt2 SELECT i, i % :cond_mod, to_char(i % :part_mod, 'FM0000') FROM generate_series(0, :num_rows) i WHERE i % :part_mod IN (13, 14); +ANALYZE plt2; + +-- inner join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1 t1 INNER JOIN plt2 t2 ON t1.c = t2.c WHERE t1.b + t2.b = 0 ORDER BY t1.a; +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1 t1 INNER JOIN plt2 t2 ON t1.c = t2.c WHERE t1.b + t2.b = 0 ORDER BY t1.a; + +-- left join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1 t1 LEFT JOIN plt2 t2 ON t1.c = t2.c WHERE t1.b + coalesce(t2.b, 0) = 0 ORDER BY t1.a; +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1 t1 LEFT JOIN plt2 t2 ON t1.c = t2.c WHERE t1.b + coalesce(t2.b, 0) = 0 ORDER BY t1.a; + +-- right join, partition-wise join can not handle extra partition on the outer +-- side +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1 t1 RIGHT JOIN plt2 t2 ON t1.c = t2.c WHERE coalesce(t1.b, 0) + t2.b = 0 ORDER BY t2.a; +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1 t1 RIGHT JOIN plt2 t2 ON t1.c = t2.c WHERE coalesce(t1.b, 0) + t2.b = 0 ORDER BY t1.a; + +-- full join, partition-wise join can not handle extra partition on the outer +-- side +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1 t1 FULL JOIN plt2 t2 ON t1.c = t2.c WHERE coalesce(t1.b, 0) + coalesce(t2.b, 0) = 0 ORDER BY t1.a, t2.a; +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1 t1 FULL JOIN plt2 t2 ON t1.c = t2.c WHERE coalesce(t1.b, 0) + coalesce(t2.b, 0) = 0 ORDER BY t1.a, t2.a; + +-- semi join +EXPLAIN (COSTS OFF) +select t1.a, t1.b, t1.c from plt1 t1 where exists (select 1 from plt2 t2 WHERE t1.c = t2.c) and t1.b = 0 order by t1.a, t1.b, t1.c; +select t1.a, t1.b, t1.c from plt1 t1 where exists (select 1 from plt2 t2 WHERE t1.c = t2.c) and t1.b = 0 order by t1.a, t1.b, t1.c; + +EXPLAIN (COSTS OFF) +select t1.a, t1.b, t1.c from plt2 t1 where exists (select 1 from plt1 t2 WHERE t1.c = t2.c) and t1.b = 0 order by t1.a, t1.b, t1.c; +select t1.a, t1.b, t1.c from plt2 t1 where exists (select 1 from plt1 t2 WHERE t1.c = t2.c) and t1.b = 0 order by t1.a, t1.b, t1.c; + +-- anti join +EXPLAIN (COSTS OFF) +select t1.a, t1.b, t1.c from plt1 t1 where not exists (select 1 from plt2 t2 WHERE t1.c = t2.c) and t1.b = 0 order by t1.a, t1.b, t1.c; +select t1.a, t1.b, t1.c from plt1 t1 where not exists (select 1 from plt2 t2 WHERE t1.c = t2.c) and t1.b = 0 order by t1.a, t1.b, t1.c; + +EXPLAIN (COSTS OFF) +select t1.a, t1.b, t1.c from plt2 t1 where not exists (select 1 from plt1 t2 WHERE t1.c = t2.c) and t1.b = 0 order by t1.a, t1.b, t1.c; +select t1.a, t1.b, t1.c from plt2 t1 where not exists (select 1 from plt1 t2 WHERE t1.c = t2.c) and t1.b = 0 order by t1.a, t1.b, t1.c; + +-- Partition-wise join can not handle the case when one partition from one side +-- matches with multiple partitions on the other side +DROP TABLE plt2_p5; +CREATE TABLE plt2_p5 PARTITION OF plt2 FOR VALUES IN ('0001','0013','0014'); +INSERT INTO plt2 SELECT i, i % :cond_mod, to_char(i % :part_mod, 'FM0000') FROM generate_series(0, :num_rows) i WHERE i % :part_mod IN (1, 13, 14); +ANALYZE plt2; + +-- inner join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1 t1 INNER JOIN plt2 t2 ON t1.c = t2.c WHERE t1.b + t2.b = 0 ORDER BY t1.a; + +-- left join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1 t1 LEFT JOIN plt2 t2 ON t1.c = t2.c WHERE t1.b + coalesce(t2.b, 0) = 0 ORDER BY t1.a; + +-- right join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1 t1 RIGHT JOIN plt2 t2 ON t1.c = t2.c WHERE coalesce(t1.b, 0) + t2.b = 0 ORDER BY t2.a; + +-- full join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1 t1 FULL JOIN plt2 t2 ON t1.c = t2.c WHERE coalesce(t1.b, 0) + coalesce(t2.b, 0) = 0 ORDER BY t1.a, t2.a; + +-- semi join +EXPLAIN (COSTS OFF) +select t1.a, t1.b, t1.c from plt1 t1 where exists (select 1 from plt2 t2 WHERE t1.c = t2.c) and t1.b = 0 order by t1.a, t1.b, t1.c; + +EXPLAIN (COSTS OFF) +select t1.a, t1.b, t1.c from plt2 t1 where exists (select 1 from plt1 t2 WHERE t1.c = t2.c) and t1.b = 0 order by t1.a, t1.b, t1.c; + +-- anti join +EXPLAIN (COSTS OFF) +select t1.a, t1.b, t1.c from plt1 t1 where not exists (select 1 from plt2 t2 WHERE t1.c = t2.c) and t1.b = 0 order by t1.a, t1.b, t1.c; + +EXPLAIN (COSTS OFF) +select t1.a, t1.b, t1.c from plt2 t1 where not exists (select 1 from plt1 t2 WHERE t1.c = t2.c) and t1.b = 0 order by t1.a, t1.b, t1.c; + +-- partition have a NULL on one side, Partition-wise join is possible with +-- NULL when NULL comparision is not strict i.e. NULL=NULL allowed +-- in this case NULL will be treated as addition partition bounds. +DROP TABLE plt2_p5; +DROP TABLE plt2_p4; +CREATE TABLE plt2_p4 PARTITION OF plt2 FOR VALUES IN ('0000',NULL,'0012'); +INSERT INTO plt2 SELECT i, i % :cond_mod, case when i % :part_mod = 11 then NULL else to_char(i % :part_mod, 'FM0000') end FROM generate_series(0, :num_rows) i WHERE i % :part_mod IN (0,11,12); +ANALYZE plt2; + +-- inner join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1 t1 INNER JOIN plt2 t2 ON t1.c = t2.c WHERE t1.b + t2.b = 0 ORDER BY t1.a; +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1 t1 INNER JOIN plt2 t2 ON t1.c = t2.c WHERE t1.b + t2.b = 0 ORDER BY t1.a; + +-- left join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1 t1 LEFT JOIN plt2 t2 ON t1.c = t2.c WHERE t1.b + coalesce(t2.b, 0) = 0 ORDER BY t1.a; +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1 t1 LEFT JOIN plt2 t2 ON t1.c = t2.c WHERE t1.b + coalesce(t2.b, 0) = 0 ORDER BY t1.a; + +-- right join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1 t1 RIGHT JOIN plt2 t2 ON t1.c = t2.c WHERE coalesce(t1.b, 0) + t2.b = 0 ORDER BY t2.a; +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1 t1 RIGHT JOIN plt2 t2 ON t1.c = t2.c WHERE coalesce(t1.b, 0) + t2.b = 0 ORDER BY t1.a; + +-- full join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1 t1 FULL JOIN plt2 t2 ON t1.c = t2.c WHERE coalesce(t1.b, 0) + coalesce(t2.b, 0) = 0 ORDER BY t1.a, t2.a; +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1 t1 FULL JOIN plt2 t2 ON t1.c = t2.c WHERE coalesce(t1.b, 0) + coalesce(t2.b, 0) = 0 ORDER BY t1.a, t2.a; + +-- semi join +EXPLAIN (COSTS OFF) +select t1.a, t1.b, t1.c from plt1 t1 where exists (select 1 from plt2 t2 WHERE t1.c = t2.c) and t1.b = 0 order by t1.a, t1.b, t1.c; +select t1.a, t1.b, t1.c from plt1 t1 where exists (select 1 from plt2 t2 WHERE t1.c = t2.c) and t1.b = 0 order by t1.a, t1.b, t1.c; + +EXPLAIN (COSTS OFF) +select t1.a, t1.b, t1.c from plt2 t1 where exists (select 1 from plt1 t2 WHERE t1.c = t2.c) and t1.b = 0 order by t1.a, t1.b, t1.c; +select t1.a, t1.b, t1.c from plt2 t1 where exists (select 1 from plt1 t2 WHERE t1.c = t2.c) and t1.b = 0 order by t1.a, t1.b, t1.c; + +-- anti join +EXPLAIN (COSTS OFF) +select t1.a, t1.b, t1.c from plt1 t1 where not exists (select 1 from plt2 t2 WHERE t1.c = t2.c) and t1.b = 0 order by t1.a, t1.b, t1.c; +select t1.a, t1.b, t1.c from plt1 t1 where not exists (select 1 from plt2 t2 WHERE t1.c = t2.c) and t1.b = 0 order by t1.a, t1.b, t1.c; + +EXPLAIN (COSTS OFF) +select t1.a, t1.b, t1.c from plt2 t1 where not exists (select 1 from plt1 t2 WHERE t1.c = t2.c) and t1.b = 0 order by t1.a, t1.b, t1.c; +select t1.a, t1.b, t1.c from plt2 t1 where not exists (select 1 from plt1 t2 WHERE t1.c = t2.c) and t1.b = 0 order by t1.a, t1.b, t1.c; + +-- partition have a NULL on both side with different partition bounds w.r.t other side +-- NULL when NULL comparision is not strict i.e. NULL=NULL allowed +-- Partition-wise join can not handle the case when one partition from one side +-- matches with multiple partitions on the other side +DROP TABLE plt1_p3; +CREATE TABLE plt1_p3 PARTITION OF plt1 FOR VALUES IN (NULL,'0008','0009'); +INSERT INTO plt1 SELECT i, i % :cond_mod, case when i % :part_mod = 7 then NULL else to_char(i % :part_mod, 'FM0000') end FROM generate_series(0, :num_rows) i WHERE i % :part_mod IN (7,8,9); +ANALYZE plt1; + +-- inner join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1 t1 INNER JOIN plt2 t2 ON t1.c = t2.c WHERE t1.b + t2.b = 0 ORDER BY t1.a; + +-- left join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1 t1 LEFT JOIN plt2 t2 ON t1.c = t2.c WHERE t1.b + coalesce(t2.b, 0) = 0 ORDER BY t1.a; + +-- right join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1 t1 RIGHT JOIN plt2 t2 ON t1.c = t2.c WHERE coalesce(t1.b, 0) + t2.b = 0 ORDER BY t2.a; + +-- full join +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1 t1 FULL JOIN plt2 t2 ON t1.c = t2.c WHERE coalesce(t1.b, 0) + coalesce(t2.b, 0) = 0 ORDER BY t1.a, t2.a; + +-- semi join +EXPLAIN (COSTS OFF) +select t1.a, t1.b, t1.c from plt1 t1 where exists (select 1 from plt2 t2 WHERE t1.c = t2.c) and t1.b = 0 order by t1.a, t1.b, t1.c; + +EXPLAIN (COSTS OFF) +select t1.a, t1.b, t1.c from plt2 t1 where exists (select 1 from plt1 t2 WHERE t1.c = t2.c) and t1.b = 0 order by t1.a, t1.b, t1.c; + +-- anti join +EXPLAIN (COSTS OFF) +select t1.a, t1.b, t1.c from plt1 t1 where not exists (select 1 from plt2 t2 WHERE t1.c = t2.c) and t1.b = 0 order by t1.a, t1.b, t1.c; + +EXPLAIN (COSTS OFF) +select t1.a, t1.b, t1.c from plt2 t1 where not exists (select 1 from plt1 t2 WHERE t1.c = t2.c) and t1.b = 0 order by t1.a, t1.b, t1.c; + -- joins where one of the relations is proven empty EXPLAIN (COSTS OFF) SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.a = 1 AND t1.a = 2; @@ -285,27 +645,18 @@ EXPLAIN (COSTS OFF) SELECT avg(t1.a), avg(t2.b), avg(t3.a + t3.b), t1.c, t2.c, t3.c FROM pht1 t1, pht2 t2, pht1_e t3 WHERE t1.b = t2.b AND t1.c = t2.c AND ltrim(t3.c, 'A') = t1.c GROUP BY t1.c, t2.c, t3.c ORDER BY t1.c, t2.c, t3.c; SELECT avg(t1.a), avg(t2.b), avg(t3.a + t3.b), t1.c, t2.c, t3.c FROM pht1 t1, pht2 t2, pht1_e t3 WHERE t1.b = t2.b AND t1.c = t2.c AND ltrim(t3.c, 'A') = t1.c GROUP BY t1.c, t2.c, t3.c ORDER BY t1.c, t2.c, t3.c; --- test default partition behavior for range -ALTER TABLE prt1 DETACH PARTITION prt1_p3; -ALTER TABLE prt1 ATTACH PARTITION prt1_p3 DEFAULT; -ANALYZE prt1; -ALTER TABLE prt2 DETACH PARTITION prt2_p3; -ALTER TABLE prt2 ATTACH PARTITION prt2_p3 DEFAULT; -ANALYZE prt2; - -EXPLAIN (COSTS OFF) -SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.b = 0 ORDER BY t1.a, t2.b; - --- test default partition behavior for list +-- test default partition behavior for list, should not use partition-wise join +-- since default partition from one side matches multiple partitions on the +-- other ALTER TABLE plt1 DETACH PARTITION plt1_p3; ALTER TABLE plt1 ATTACH PARTITION plt1_p3 DEFAULT; ANALYZE plt1; ALTER TABLE plt2 DETACH PARTITION plt2_p3; ALTER TABLE plt2 ATTACH PARTITION plt2_p3 DEFAULT; ANALYZE plt2; - EXPLAIN (COSTS OFF) SELECT avg(t1.a), avg(t2.b), t1.c, t2.c FROM plt1 t1 RIGHT JOIN plt2 t2 ON t1.c = t2.c WHERE t1.a % 25 = 0 GROUP BY t1.c, t2.c ORDER BY t1.c, t2.c; + -- -- multiple levels of partitioning -- @@ -450,3 +801,72 @@ ANALYZE prt2; EXPLAIN (COSTS OFF) SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.b = 0 ORDER BY t1.a, t2.b; + +DROP TABLE plt1; +DROP TABLE plt2; + +CREATE TABLE plt1 (a int, b int, c text) PARTITION BY LIST(c); +CREATE TABLE plt1_p1 PARTITION OF plt1 FOR VALUES IN ('0000', '0002'); +CREATE TABLE plt1_p2 PARTITION OF plt1 FOR VALUES IN ('0003', '0004'); +CREATE TABLE plt1_p3 PARTITION OF plt1 FOR VALUES IN ('0006', '0007'); +INSERT INTO plt1 SELECT i, i, to_char(i % 8, 'FM0000') FROM generate_series(0, 39) i WHERE i % 8 IN (0, 2, 3, 4, 6, 7); +ANALYZE plt1; + +CREATE TABLE plt2 (a int, b int, c text) PARTITION BY LIST(c); +CREATE TABLE plt2_p1 PARTITION OF plt2 FOR VALUES IN ('0001', '0002'); +CREATE TABLE plt2_p2 PARTITION OF plt2 FOR VALUES IN ('0003', '0004'); +CREATE TABLE plt2_p3 PARTITION OF plt2 FOR VALUES IN ('0005', '0007'); +INSERT INTO plt2 SELECT i, i, to_char(i % 8, 'FM0000') FROM generate_series(0, 39) i WHERE i % 8 IN (1, 2, 3, 4, 5, 7); +ANALYZE plt2; + +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1 t1 FULL JOIN plt2 t2 ON (t1.c = t2.c) WHERE COALESCE(t1.a, 0) % 8 != 2 AND COALESCE(t1.a, 0) % 8 != 3 AND COALESCE(t1.a, 0) % 8 != 4 AND COALESCE(t1.a, 0) % 8 != 7 ORDER BY t1.c, t2.c, t1.a, t2.a; +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1 t1 FULL JOIN plt2 t2 ON (t1.c = t2.c) WHERE COALESCE(t1.a, 0) % 8 != 2 AND COALESCE(t1.a, 0) % 8 != 3 AND COALESCE(t1.a, 0) % 8 != 4 AND COALESCE(t1.a, 0) % 8 != 7 ORDER BY t1.c, t2.c, t1.a, t2.a; + +DROP TABLE plt1; +DROP TABLE plt2; + +CREATE TABLE plt1 (a int, b int, c text) PARTITION BY LIST(c); +CREATE TABLE plt1_p1 PARTITION OF plt1 FOR VALUES IN (NULL, '0000', '0002'); +CREATE TABLE plt1_p2 PARTITION OF plt1 FOR VALUES IN ('0003', '0004'); +INSERT INTO plt1 SELECT i, i, to_char(i % 5, 'FM0000') FROM generate_series(0, 24) i WHERE i % 5 IN (0, 2, 3, 4); +ANALYZE plt1; + +CREATE TABLE plt2 (a int, b int, c text) PARTITION BY LIST(c); +CREATE TABLE plt2_p1 PARTITION OF plt2 DEFAULT; +CREATE TABLE plt2_p2 PARTITION OF plt2 FOR VALUES IN ('0003', '0004'); +INSERT INTO plt2 SELECT i, i, to_char(i % 5, 'FM0000') FROM generate_series(0, 24) i WHERE i % 5 IN (1, 2, 3, 4); +ANALYZE plt2; + +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1 t1 FULL JOIN plt2 t2 ON (t1.c = t2.c) WHERE COALESCE(t1.a, 0) % 5 != 2 AND COALESCE(t1.a, 0) % 5 != 3 AND COALESCE(t1.a, 0) % 5 != 4 ORDER BY t1.c, t2.c, t1.a, t2.a; +SELECT t1.a, t1.c, t2.a, t2.c FROM plt1 t1 FULL JOIN plt2 t2 ON (t1.c = t2.c) WHERE COALESCE(t1.a, 0) % 5 != 2 AND COALESCE(t1.a, 0) % 5 != 3 AND COALESCE(t1.a, 0) % 5 != 4 ORDER BY t1.c, t2.c, t1.a, t2.a; + +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c FROM plt2 t2 FULL JOIN plt1 t1 ON (t1.c = t2.c) WHERE COALESCE(t1.a, 0) % 5 != 2 AND COALESCE(t1.a, 0) % 5 != 3 AND COALESCE(t1.a, 0) % 5 != 4 ORDER BY t1.c, t2.c, t1.a, t2.a; +SELECT t1.a, t1.c, t2.a, t2.c FROM plt2 t2 FULL JOIN plt1 t1 ON (t1.c = t2.c) WHERE COALESCE(t1.a, 0) % 5 != 2 AND COALESCE(t1.a, 0) % 5 != 3 AND COALESCE(t1.a, 0) % 5 != 4 ORDER BY t1.c, t2.c, t1.a, t2.a; + +DROP TABLE plt1; +DROP TABLE plt2; + +CREATE TABLE plt1 (a int, b int, c text) PARTITION BY LIST(c); +CREATE TABLE plt1_p1 PARTITION OF plt1 FOR VALUES IN ('0000', '0001', '0002'); +CREATE TABLE plt1_p2 PARTITION OF plt1 FOR VALUES IN ('0003', '0004'); +INSERT INTO plt1 SELECT i, i, to_char(i % 5, 'FM0000') FROM generate_series(0, 24) i; +ANALYZE plt1; + +CREATE TABLE plt2 (a int, b int, c text) PARTITION BY LIST(c); +CREATE TABLE plt2_p1 PARTITION OF plt2 FOR VALUES IN ('0002'); +CREATE TABLE plt2_p2 PARTITION OF plt2 FOR VALUES IN ('0003', '0004'); +INSERT INTO plt2 SELECT i, i, to_char(i % 5, 'FM0000') FROM generate_series(0, 24) i WHERE i % 5 IN (2, 3, 4); +ANALYZE plt2; + +CREATE TABLE plt3 (a int, b int, c text) PARTITION BY LIST(c); +CREATE TABLE plt3_p1 PARTITION OF plt3 FOR VALUES IN ('0001'); +CREATE TABLE plt3_p2 PARTITION OF plt3 FOR VALUES IN ('0003', '0004'); +INSERT INTO plt3 SELECT i, i, to_char(i % 5, 'FM0000') FROM generate_series(0, 24) i WHERE i % 5 IN (1, 3, 4); +ANALYZE plt3; + +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.c, t2.a, t2.c, t3.a, t3.c FROM (plt1 t1 LEFT JOIN plt2 t2 ON (t1.c = t2.c)) FULL JOIN plt3 t3 ON (t1.c = t3.c) WHERE COALESCE(t1.a, 0) % 5 != 3 AND coalesce(t1.a, 0) % 5 != 4 ORDER BY t1.c, t1.a, t2.a, t3.a; +SELECT t1.a, t1.c, t2.a, t2.c, t3.a, t3.c FROM (plt1 t1 LEFT JOIN plt2 t2 ON (t1.c = t2.c)) FULL JOIN plt3 t3 ON (t1.c = t3.c) WHERE COALESCE(t1.a, 0) % 5 != 3 AND coalesce(t1.a, 0) % 5 != 4 ORDER BY t1.c, t1.a, t2.a, t3.a; -- 2.19.2