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 85415381fb..248a9e9093 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; @@ -1626,7 +1632,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 || @@ -1639,24 +1645,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 @@ -1668,17 +1656,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 318d8ecae9..1238999c22 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,12 @@ typedef struct PartitionRangeBound bool lower; /* this is the lower (vs upper) bound */ } PartitionRangeBound; +typedef struct PartitionMap +{ + int from; + int to; +} PartitionMap; + 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 +115,57 @@ 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( + RelOptInfo *outer_rel, RelOptInfo *inner_rel, + List **outer_parts, List **inner_parts, + JoinType jointype, int partnatts, + FmgrInfo *supfuncs, Oid *collations); +static PartitionBoundInfo partition_list_bounds_merge(FmgrInfo *partsupfunc, Oid *collations, + RelOptInfo *outer_rel, RelOptInfo *inner_rel, + List **outer_parts, List **inner_parts, + JoinType jointype); +static void generate_matching_part_pairs(RelOptInfo *rel1, + RelOptInfo *rel2, + PartitionMap *partmaps1, + PartitionMap *partmaps2, + int nparts1, int nparts2, + int nparts, + List **matched_parts1, + List **matched_parts2); +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 map_and_merge_partitions(PartitionMap *outer_maps, + PartitionMap *inner_maps, + int index1, int index2, int *next_index); +static int32 partition_range_bound_cmp(int partnatts, FmgrInfo *partsupfunc, + Oid *collations, PartitionRangeBound *bound1, + PartitionRangeBound *bound2); +static bool partition_range_cmp(int partnatts, FmgrInfo *supfuncs, + Oid *collations, PartitionRangeBound *lower_bound1, + PartitionRangeBound *upper_bound1, + PartitionRangeBound *lower_bound2, + PartitionRangeBound *upper_bound2, int *ub_cmpval, + int *lb_cmpval); +static bool partition_range_merge_next_lb(int partnatts, FmgrInfo *supfuncs, + Oid *collations, Datum *next_lb_datums, + PartitionRangeDatumKind *next_lb_kind, + List **merged_datums, List **merged_kinds, + List **merged_indexes); +static bool merge_default_partitions(PartitionBoundInfo outer_bi, + PartitionBoundInfo inner_bi, + PartitionMap *outer_maps, + PartitionMap *inner_maps, + JoinType jointype, + int *next_index, int *default_index); +static bool merge_null_partitions(PartitionBoundInfo outer_bi, + PartitionBoundInfo inner_bi, + PartitionMap *outer_maps, + PartitionMap *inner_maps, + JoinType jointype, + int *next_index, int *null_index, + int *default_index); /* * get_qual_from_partbound @@ -2996,3 +3054,1491 @@ satisfies_hash_partition(PG_FUNCTION_ARGS) PG_RETURN_BOOL(rowHash % modulus == remainder); } + +/* + * partition_bounds_merge + * + * The function produces the partition bounds for a join between two relations + * whose partition bounds are given. The function also returns two lists of + * partition indexes one for each of the joining relations. Both the lists + * contain the same number of elements. The partition indexes at the same + * positions in the lists indicate the pair partitions, one from each side, to + * be joined and the position itself corresponds to the index of partition + * produced by that child-join in the partitioned join. + * + * The function returns NULL if we can not find the matching pair of + * partitions. This happens if 1. multiple partitions on one side match with + * one partition on the other side. 2. a given partition on the outer side + * doesn't have a matching partition on the inner side. We can not support the + * first case since we don't have a way to represent multiple partitions as a + * single relation (RelOptInfo) and then perform join using the ganged + * relation. We can not support the second case since the missing inner + * partition needs to be represented as an empty relation and we don't have a + * way to introduce empty relation during join planning after creating paths + * for all the base relations. + */ +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, + outer_parts, inner_parts, + jointype); + break; + + case PARTITION_STRATEGY_RANGE: + merged_bounds = partition_range_bounds_merge(outer_rel, inner_rel, + outer_parts, inner_parts, + jointype, partnatts, + partsupfunc, + partcollation); + 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; +} + +/* + * partition_get_range_bounds + * + * Given the index of lower bound in datums array, return lower and upper + * bounds and the index of the partition with that lower bound. + */ +static int +partition_get_range_bounds(PartitionBoundInfo bi, int lb_index, + PartitionRangeBound *lower, + PartitionRangeBound *upper) +{ + int part_index; + + /* A lower bound should have at least one more bound after it. */ + Assert(lb_index < bi->ndatums - 1); + + /* The lower bound should correspond to a valid partition. */ + part_index = bi->indexes[lb_index + 1]; + Assert(part_index >= 0); + + lower->kind = bi->kind[lb_index]; + lower->datums = bi->datums[lb_index]; + lower->lower = true; + upper->kind = bi->kind[lb_index + 1]; + upper->datums = bi->datums[lb_index + 1]; + upper->lower = false; + + return part_index; +} + +/* + * partition_range_get_next_lb_index + * + * Given the index of lower bound in datums array return the + * index of lower bound of the next partition. When the given index corresponds + * to the last partition, return number of datums (ndatums). + */ +static int +partition_range_get_next_lb_index(PartitionBoundInfo bi, int lb_index) +{ + /* A lower bound should have at least one more bound after it. */ + Assert(lb_index < bi->ndatums - 1); + + /* The partition index corresponding to the upper bound should be valid. */ + Assert(bi->indexes[lb_index + 1] >= 0); + + /* + * If there are no bounds left beyond the upper bound, we have reached the + * last partition. + */ + if (lb_index + 2 < bi->ndatums) + { + /* + * If the bound next to the upper bound corresponds to no partition, + * that's the next lower bound of the next partition. Otherwise, the + * current upper bound is the lower bound of the next partition. + */ + if (bi->indexes[lb_index + 2] < 0) + return lb_index + 2; + else + return lb_index + 1; + } + else + return bi->ndatums; +} + +static int32 +partition_range_bound_cmp(int partnatts, FmgrInfo *partsupfunc, + Oid *partcollations, PartitionRangeBound *bound1, + PartitionRangeBound *bound2) +{ + return partition_rbound_cmp(partnatts, partsupfunc, partcollations, + bound1->datums, bound1->kind, bound1->lower, + bound2); +} + +/* + * partition_range_cmp + * + * Compare the bounds of two range partitions. Set ub_cmpval <, = or > 0, if the + * first partition's upper bound is lower than, equal to or higher than the + * second partition's upper bound resp. Similarly set lb_cmpval <, = or > 0, + * if the first partition's lower bound is lower than, equal to or higher than + * the second partition's lower bound resp. + * + * Return true, if the ranges overlap, otherwise return false. + */ +static bool +partition_range_cmp(int partnatts, FmgrInfo *partsupfuncs, Oid *partcollations, + PartitionRangeBound *lower_bound1, + PartitionRangeBound *upper_bound1, + PartitionRangeBound *lower_bound2, + PartitionRangeBound *upper_bound2, int *ub_cmpval, + int *lb_cmpval) +{ + bool overlap; + + /* + * Compare upper bound of the first partition with the lower bound of the + * second and vice-versa. If lower bound is higher than the upper bound, + * the partitions are not overlapping. All other cases indicate overlapping + * partitions. + */ + if (partition_range_bound_cmp(partnatts, partsupfuncs, partcollations, + lower_bound1, upper_bound2) > 0) + { + overlap = false; + *ub_cmpval = 1; + *lb_cmpval = 1; + } + else if (partition_range_bound_cmp(partnatts, partsupfuncs, partcollations, + lower_bound2, upper_bound1) > 0) + { + overlap = false; + *ub_cmpval = -1; + *lb_cmpval = -1; + } + else + { + overlap = true; + *ub_cmpval = partition_range_bound_cmp(partnatts, partsupfuncs, + partcollations, upper_bound1, + upper_bound2); + *lb_cmpval = partition_range_bound_cmp(partnatts, partsupfuncs, + partcollations, lower_bound1, + lower_bound2); + } + + return overlap; +} + +/* + * partition_range_merge + * + * Merge the partition bounds of given two partitions such that the join + * between the given two partitions fits merged bounds. + * + * "merged_upper" will be set to one of the given upper bounds and + * "merged_lower" will be set to one of the given lower bounds. + */ +static void +partition_range_merge(int partnatts, FmgrInfo *partsupfuncs, + Oid *partcollations, JoinType jointype, + PartitionRangeBound *left_lb, + PartitionRangeBound *left_ub, + PartitionRangeBound *right_lb, + PartitionRangeBound *right_ub, + PartitionRangeBound **merged_lb, + PartitionRangeBound **merged_ub) +{ + /* + * 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_LEFT: + case JOIN_ANTI: + *merged_ub = left_ub; + *merged_lb = left_lb; + break; + + case JOIN_INNER: + case JOIN_SEMI: + if (partition_range_bound_cmp(partnatts, partsupfuncs, + partcollations, left_ub, + right_ub) < 0) + *merged_ub = left_ub; + else + *merged_ub = right_ub; + + if (partition_range_bound_cmp(partnatts, partsupfuncs, + partcollations, left_lb, + right_lb) > 0) + *merged_lb = left_lb; + else + *merged_lb = right_lb; + break; + + case JOIN_FULL: + if (partition_range_bound_cmp(partnatts, partsupfuncs, + partcollations, left_ub, + right_ub) > 0) + *merged_ub = left_ub; + else + *merged_ub = right_ub; + + if (partition_range_bound_cmp(partnatts, partsupfuncs, + partcollations, left_lb, + right_lb) < 0) + *merged_lb = left_lb; + else + *merged_lb = right_lb; + break; + + default: + elog(ERROR, "unexpected join type %d", jointype); + } + + return; +} + +/* + * Add the lower bound of the next range to the list of bounds, if the lower + * bound is higher or equal to the previous upper bound. If successful return + * true, otherwise false. + */ +static bool +partition_range_merge_next_lb(int partnatts, FmgrInfo *partsupfuncs, + Oid *partcollations, Datum *next_lb_datums, + PartitionRangeDatumKind *next_lb_kind, + List **merged_datums, List **merged_kinds, + List **merged_indexes) +{ + int cmpval; + + if (!*merged_datums) + { + Assert(!*merged_kinds && !*merged_indexes); + cmpval = 1; + } + else + { + PartitionRangeBound prev_ub; + + prev_ub.datums = llast(*merged_datums); + prev_ub.kind = llast(*merged_kinds); + prev_ub.lower = false; + + cmpval = partition_rbound_cmp(partnatts, partsupfuncs, partcollations, + next_lb_datums, next_lb_kind, false, + &prev_ub); + } + + /* + * The lower bound is lower than the last upper bound, thus does not fit + * the bounds created so far and hence can not be merged with the existing + * bounds. + */ + if (cmpval < 0) + return false; + + /* + * Add bounds of the new merged partition. If the next lower bound is + * higher than the last upper bound, add new range with index + * corresponding to the lower bound as -1. If the merged lower bound + * is same as the last merged upper bound, the last upper bound will be + * reused as the lower bound of the next range. + */ + if (cmpval > 0) + { + *merged_datums = lappend(*merged_datums, next_lb_datums); + *merged_kinds = lappend(*merged_kinds, next_lb_kind); + *merged_indexes = lappend_int(*merged_indexes, -1); + } + + return true; +} + +/* + * handle_missing_partition + * + * If a range appears in one of the joining relations but not the other, a row + * in the corresponding partition will not have any join partner in the other + * relation, unless the other relation has a default partition. If a given list + * value is present in one joining relation but not the other, the default + * partition on the other side may contain that value. + * + * In both these cases, such an extra partition forms a joining pair with the + * default partition, if any, on the other side. + * + * If the default partition happens to be on the outer side of the join, the + * resultant partition will act as the default partition of the join relation. + * Otherwise the resultant partition will be associated with the range. + * + * When the default partition is not present in the other relation, the rows in + * the extra partition will be included in the bounds of the join result, if it + * appears on the outer side of the join, since all rows from the outer side + * are included in the join result. + * + * This function handles all these cases. + * + * maps_with_missing and missing_side_default are the partition maps (See + * partition_range/list_bounds_merge() for details) and the index of default + * partition respectively corresponding the side with missing partition. + * + * maps_with_extra and extra_part are the partition maps (See + * partition_range/list_bounds_merge() for details) and the index of extra + * partition respectively corresponding to the side with the extra partition. + * + * It returns true if the matching succeeds, otherwise returns false. + */ +static bool +handle_missing_partition(PartitionMap *maps_with_missing, + PartitionMap *maps_with_extra, + int missing_side_default, + int extra_part, + bool missing_side_outer, + bool missing_side_inner, + int *next_index, int *default_index, + int *merged_index) +{ + bool missing_has_default = (missing_side_default != -1); + + if (missing_has_default) + { + *merged_index = map_and_merge_partitions(maps_with_missing, + maps_with_extra, + missing_side_default, + extra_part, + next_index); + if (*merged_index < 0) + return false; + + if (missing_side_outer) + { + /* + * Default partition on the outer side forms the default + * partition of the join result. + */ + if (*default_index < 0) + *default_index = *merged_index; + else if(*default_index != *merged_index) + { + /* + * Ended up with default partition on the outer side + * being joined with multiple partitions on the inner + * side. We don't support this case. + */ + return false; + } + + /* + * Since the merged partition acts as a default partition, it + * doesn't need a separate index. + */ + *merged_index = -1; + } + } + else if (missing_side_inner) + { + /* + * If this partition has already been mapped (say because we + * found an overlapping range earlier), we know where does it + * fit in the join result. Nothing to do in that case. Else + * create a new merged partition. + */ + PartitionMap *extra_map = &maps_with_extra[extra_part]; + if (extra_map->to < 0) + { + extra_map->to = *next_index; + *next_index = *next_index + 1; + *merged_index = extra_map->to; + } + } + else + *merged_index = -1; + + return true; +} + +static PartitionMap* +init_partition_map(RelOptInfo *rel) +{ + int i, nparts = rel->nparts; + PartitionMap *map; + + map = (PartitionMap *) palloc(sizeof(PartitionMap) * nparts); + + for (i = 0; i < nparts; i++) + { + map[i].from = -1; + map[i].to = -1; + } + + return map; +} + +/* + * partition_range_bounds_merge + * + * partition_bounds_merge()'s arm for range partitioned tables. + */ +static PartitionBoundInfo +partition_range_bounds_merge(RelOptInfo *outer_rel, RelOptInfo *inner_rel, + List **outer_parts, List **inner_parts, + JoinType jointype, int partnatts, + FmgrInfo *partsupfuncs, Oid *partcollations) + +{ + PartitionMap *outer_maps = NULL; + PartitionMap *inner_maps = NULL; + int outer_part = 0; + int inner_part = 0; + PartitionBoundInfo merged_bounds = NULL; + int outer_lb_index; + int inner_lb_index; + int next_index; + int default_index = -1; + List *merged_datums = NIL; + List *merged_indexes = NIL; + List *merged_kinds = NIL; + PartitionBoundInfo outer_bi = outer_rel->boundinfo, + inner_bi = inner_rel->boundinfo; + int inner_default = inner_bi->default_index; + int outer_default = outer_bi->default_index; + bool inner_has_default = partition_bound_has_default(inner_bi); + bool outer_has_default = partition_bound_has_default(outer_bi); + int outer_nparts = outer_rel->nparts, + inner_nparts = inner_rel->nparts; + + Assert(outer_bi->strategy == inner_bi->strategy && + outer_bi->strategy == PARTITION_STRATEGY_RANGE); + + Assert(*outer_parts == NIL); + Assert(*inner_parts == NIL); + + outer_maps = init_partition_map(outer_rel); + inner_maps = init_partition_map(inner_rel); + + /* + * 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 = 0; + inner_lb_index = 0; + next_index = 0; + while (outer_lb_index < outer_bi->ndatums || + inner_lb_index < inner_bi->ndatums) + { + PartitionRangeBound outer_lb, outer_ub, + inner_lb, inner_ub, + *merged_lb = NULL, + *merged_ub = NULL; + + int merged_index = -1; + bool overlap; + bool finished_outer = false; + bool finished_inner = false; + + /* Result of bounds comparison per partition_rbound_cmp(). */ + int ub_cmpval; /* Upper bounds comparison result. */ + int lb_cmpval; /* Lower bounds comparison result. */ + + /* Get the range bounds of the next pair of partitions. */ + if (outer_lb_index < outer_bi->ndatums) + outer_part = partition_get_range_bounds(outer_bi, outer_lb_index, + &outer_lb, &outer_ub); + else + finished_outer = true; + + if (inner_lb_index < inner_bi->ndatums) + inner_part = partition_get_range_bounds(inner_bi, inner_lb_index, + &inner_lb, &inner_ub); + else + finished_inner = true; + + Assert(!finished_outer || !finished_inner); + + /* + * 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 (finished_outer) + { + overlap = false; + ub_cmpval = 1; + lb_cmpval = 1; + } + else if (finished_inner) + { + overlap = false; + ub_cmpval = -1; + lb_cmpval = -1; + } + else + overlap = partition_range_cmp(partnatts, partsupfuncs, partcollations, + &outer_lb, &outer_ub, &inner_lb, + &inner_ub, &ub_cmpval, &lb_cmpval); + + if (overlap) + { + /* + * The rows from overlapping portion of ranges on both sides may + * join, hence the corresponding pair of partitions form a joining + * pair. Match them and produce the bounds of the joint partition + * and its index by merging the bounds according to the type of + * join. + */ + partition_range_merge(partnatts, partsupfuncs, partcollations, + jointype, &outer_lb, &outer_ub, &inner_lb, + &inner_ub, &merged_lb, &merged_ub); + + merged_index = map_and_merge_partitions(outer_maps, inner_maps, + outer_part, inner_part, + &next_index); + + if (merged_index < 0) + { + /* Failed to match the partitions. */ + return NULL; + } + + /* + * If the ranges overlap but don't exactly match, a row from + * non-overlapping portion of the range from one side of join may + * find its join partner in the previous or next overlapping + * partition or default partition on the other side , if such a + * partition exists. All those cases, if true, will cause one + * partition from that side to match at least two partitions on the + * other side; a case that we do not support now. Previous + * partition has been delt with in the previous iteration of this + * loop, next partition will be delt in the next iteration. We will + * deal with the default partition here. + */ + if ((lb_cmpval < 0 && inner_has_default) || + /* Non-overlapping range on the lower side of outer range. */ + (lb_cmpval > 0 && outer_has_default) || + /* Non-overlapping range on the lower side of inner range. */ + (ub_cmpval < 0 && outer_has_default) || + /* Non-overlapping range on the upper side of inner range. */ + (ub_cmpval > 0 && inner_has_default)) + /* Non-overlapping range on the upper side of outer range. */ + return NULL; + } + + if (ub_cmpval == 0) + { + /* Upper bounds of both the ranges match. */ + Assert(overlap); + + /* Move to the next pair of partitions. */ + outer_lb_index = partition_range_get_next_lb_index(outer_bi, + outer_lb_index); + inner_lb_index = partition_range_get_next_lb_index(inner_bi, + inner_lb_index); + } + else if (ub_cmpval < 0) + { + /* Upper bound of inner range higher than that of the outer. */ + + if (overlap) + { + /* We have already dealt with overlapping ranges. */ + } + else + { + /* A range missing from the inner side. */ + bool missing_side_outer; + bool missing_side_inner; + + merged_lb = &outer_lb; + merged_ub = &outer_ub; + + /* + * For a FULL join, inner relation acts as both OUTER and INNER + * relation. For LEFT and ANTI join the inner relation acts as + * INNER relation. For INNER and SEMI join OUTER and INNER + * differentiation is immaterial. + */ + missing_side_inner = (jointype == JOIN_FULL || + jointype == JOIN_LEFT || + jointype == JOIN_ANTI); + missing_side_outer = (jointype == JOIN_FULL); + if (!handle_missing_partition(inner_maps, + outer_maps, + inner_default, + outer_part, + missing_side_outer, + missing_side_inner, + &next_index, + &default_index, + &merged_index)) + return NULL; + } + + /* Move to the next partition on the outer side. */ + Assert(!finished_outer); + outer_lb_index = partition_range_get_next_lb_index(outer_bi, + outer_lb_index); + } + else + { + Assert(ub_cmpval > 0); + + /* Upper bound of outer range higher than that of the inner. */ + if (overlap) + { + /* We have already dealt with overlapping ranges. */ + } + else + { + /* A range missing from the outer side. */ + bool missing_side_outer; + bool missing_side_inner; + + merged_lb = &inner_lb; + merged_ub = &inner_ub; + + /* + * For a FULL join, outer relation acts as both OUTER and INNER + * relation. For LEFT and ANTI join the outer relation acts as + * OUTER relation. For INNER and SEMI join OUTER and INNER + * differentiation is immaterial. + */ + missing_side_outer = (jointype == JOIN_FULL || + jointype == JOIN_LEFT || + jointype == JOIN_ANTI); + missing_side_inner = (jointype == JOIN_FULL); + + if (!handle_missing_partition(outer_maps, + inner_maps, + outer_default, + inner_part, + missing_side_outer, + missing_side_inner, + &next_index, + &default_index, + &merged_index)) + return NULL; + } + + /* Move to the next partition on the inner side. */ + Assert (!finished_inner); + inner_lb_index = partition_range_get_next_lb_index(inner_bi, + inner_lb_index); + } + + if (merged_index < 0) + { + /* We didn't find a new merged partition. */ + continue; + } + + /* + * We have a valid partition index for the next partition of join. The + * partition should have valid range. + */ + Assert(merged_lb && merged_ub); + + /* Try merging new lower bound with the last upper bound. */ + if (!partition_range_merge_next_lb(partnatts, partsupfuncs, + partcollations, + merged_lb->datums, + merged_lb->kind, &merged_datums, + &merged_kinds, &merged_indexes)) + return NULL; + + /* Add upper bound with the merged partition index. */ + merged_datums = lappend(merged_datums, merged_ub->datums); + merged_kinds = lappend(merged_kinds, merged_ub->kind); + merged_indexes = lappend_int(merged_indexes, merged_index); + } + + if (!merge_default_partitions(outer_bi, inner_bi, + outer_maps, inner_maps, + jointype, &next_index, + &default_index)) + return NULL; + + /* Use maps to match partition from the joining relations. */ + generate_matching_part_pairs(outer_rel, inner_rel, + outer_maps, inner_maps, + outer_nparts, inner_nparts, + 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. */ + 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, + List **outer_parts, List **inner_parts, + JoinType jointype) +{ + PartitionMap *outer_maps = NULL; + PartitionMap *inner_maps = NULL; + int cnto; + int cnti; + List *merged_datums = NIL; + List *merged_indexes = NIL; + int next_index = 0; + int null_index = -1; + int default_index = -1; + PartitionBoundInfo merged_bounds = NULL; + PartitionBoundInfo outer_bi = outer_rel->boundinfo, + inner_bi = inner_rel->boundinfo; + int *outer_indexes = outer_bi->indexes; + int *inner_indexes = inner_bi->indexes; + int outer_default = outer_bi->default_index; + int inner_default = inner_bi->default_index; + int outer_nparts = outer_rel->nparts, + inner_nparts = inner_rel->nparts; + + 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); + + outer_maps = init_partition_map(outer_rel); + inner_maps = init_partition_map(inner_rel); + + /* + * 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 *odatums; + Datum *idatums; + int o_index; + int i_index; + int cmpval; + int merged_index = -1; + Datum *merged_datum; + bool finished_inner; + bool finished_outer; + + /* + * 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) + { + finished_outer = true; + odatums = NULL; + o_index = -1; + } + else + { + finished_outer = false; + odatums = outer_bi->datums[cnto]; + o_index = outer_indexes[cnto]; + } + + if (cnti >= inner_bi->ndatums) + { + finished_inner = true; + idatums = NULL; + i_index = -1; + } + else + { + finished_inner = false; + idatums = inner_bi->datums[cnti]; + i_index = inner_indexes[cnti]; + } + + /* If we exhausted both the sides, we won't enter the loop. */ + Assert(!finished_inner || !finished_outer); + + if (finished_outer) + cmpval = 1; + else if (finished_inner) + cmpval = -1; + else + { + /* Every list datum should map to a valid partition index. */ + Assert(o_index >= 0 && i_index >= 0 && + odatums != NULL && idatums != NULL); + + cmpval = DatumGetInt32(FunctionCall2Coll(&partsupfunc[0], + partcollation[0], + odatums[0], idatums[0])); + } + + if (cmpval == 0) + { + /* + * 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; + merged_index = map_and_merge_partitions(outer_maps, inner_maps, + 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) + { + bool missing_side_outer; + bool missing_side_inner; + + /* A datum missing from the inner side. */ + merged_index = -1; + merged_datum = odatums; + + /* + * For a FULL join, inner relation acts as both OUTER and INNER + * relation. For LEFT and ANTI join the inner relation acts as + * INNER relation. For INNER and SEMI join OUTER and INNER + * differentiation is immaterial. + */ + missing_side_inner = (jointype == JOIN_FULL || + jointype == JOIN_LEFT || + jointype == JOIN_ANTI); + missing_side_outer = (jointype == JOIN_FULL); + + if (!handle_missing_partition(inner_maps, + outer_maps, + inner_default, + o_index, + missing_side_outer, + missing_side_inner, + &next_index, + &default_index, + &merged_index)) + return NULL; + + /* Move to the next datum on the outer side. */ + Assert(!finished_outer); + cnto++; + } + else + { + bool missing_side_outer; + bool missing_side_inner; + + Assert(cmpval > 0); + + /* A datum missing from the outer side. */ + merged_index = -1; + merged_datum = idatums; + + /* + * For a FULL join, outer relation acts as both OUTER and INNER + * relation. For LEFT and ANTI join the outer relation acts as + * OUTER relation. For INNER and SEMI join OUTER and INNER + * differentiation is immaterial. + */ + missing_side_outer = (jointype == JOIN_FULL || + jointype == JOIN_LEFT || + jointype == JOIN_ANTI); + missing_side_inner = (jointype == JOIN_FULL); + + if (!handle_missing_partition(outer_maps, + inner_maps, + outer_default, + i_index, + missing_side_outer, + missing_side_inner, + &next_index, + &default_index, + &merged_index)) + return NULL; + + /* Move to the next datum on the right side. */ + Assert(!finished_inner); + 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); + } + } + + if (!merge_null_partitions(outer_bi, inner_bi, + outer_maps, inner_maps, + jointype, &next_index, &null_index, + &default_index)) + return NULL; + + if (!merge_default_partitions(outer_bi, inner_bi, + outer_maps, inner_maps, + jointype, &next_index, + &default_index)) + return NULL; + + /* Use maps to match partition from the joining relations. */ + generate_matching_part_pairs(outer_rel, inner_rel, + outer_maps, inner_maps, + outer_nparts, inner_nparts, + 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. */ + list_free(merged_datums); + list_free(merged_indexes); + + return merged_bounds; +} + +/* + * map_and_merge_partitions + * + * If the two given partitions (given by index1 and index2 resp.) are + * already mapped to each other return the index of corresponding partition in + * the merged set of partitions. If they do not have a merged partition + * associated with them, assign a new merged partition index. If the + * partitions are already mapped and their mapped partitions are different from + * each other, they can not be merged, so return -1. + * + * partmaps1[i] gives the mapping of partitions for both relations. It + * describes which partition of relation 2 matches ith partition of relation 1, + * and which partition in the merged set matches ith partition of relation 1 + * maps to. Similarly for partmap2. + * + * index1 and index2 are the indexes of matching partition from respective + * relations. + * + * *next_index is used and incremented when the given partitions require a new + * merged partition. + */ + +static int +map_and_merge_partitions(PartitionMap *partmaps1, PartitionMap *partmaps2, + int index1, int index2, int *next_index) +{ + PartitionMap *partmap1 = &partmaps1[index1]; + PartitionMap *partmap2 = &partmaps2[index2]; + int merged_index; + + /* + * If both the partitions are not mapped to each other, update the + * maps. + */ + if (partmap1->from < 0 && partmap2->from < 0) + { + partmap1->from = index2; + partmap2->from = index1; + } + + /* + * If the given to partitions map to each other, find the corresponding + * merged partition index . + */ + if (partmap1->from == index2 && partmap2->from == index1) + { + /* + * If both the partitions are mapped to the same merged partition, get + * the index of merged partition. + */ + if (partmap1->to == partmap2->to) + { + merged_index = partmap1->to; + + /* + * If the given two partitions do not have a merged partition + * associated with them, allocate a new merged partition. + */ + if (merged_index < 0) + { + merged_index = *next_index; + *next_index = *next_index + 1; + partmap1->to = merged_index; + partmap2->to = merged_index; + } + } + + /* + * If partition from one relation was mapped to a merged partition but + * not the partition from the other relation, map the same merged + * partition to the partition from other relation, since matching + * partitions map to the same merged partition. + */ + else if (partmap1->to >= 0 && partmap2->to < 0) + { + partmap2->to = partmap1->to; + merged_index = partmap1->to; + } + else if (partmap1->to < 0 && partmap2->to >= 0) + { + partmap1->to = partmap2->to; + merged_index = partmap2->to; + } + else + { + Assert(partmap1->to != partmap2->to && + partmap1->to >= 0 && partmap2->to >= 0); + + /* + * Both the partitions map to different merged partitions. This + * means that multiple partitions from one relation matches to one + * partition from the other relation. Partition-wise join does not + * handle this case right now, since it requires ganging multiple + * partitions together (into one RelOptInfo). + */ + merged_index = -1; + } + } + else + { + /* + * Multiple partitions from one relation map to one partition from the + * other relation. Partition-wise join does not handle this case right + * now, since it requires ganging multiple partitions together (into + * one RelOptInfo). + */ + merged_index = -1; + } + + return merged_index; +} + +/* + * generate_matching_part_pairs + * + * partmaps1 map each partition from either side of the join to a merged + * partition resp. E.g. partmaps1[i].to gives the merged partition to which ith + * partition of first relation maps. Similarly for partmap2. If + * partmaps1[i].to == partmaps2[j].to, i and j form the matching pair of + * partitions. + * + * Given these maps this function produces the list pairs of partitions which + * when joined produce the merged partitions in the order of merged partition + * indexes. + * + * nparts1 and nparts2 are the number of partitions of the joining relations + * resp. + * + * nparts is the number of merged partitions. + * + * If successful, the pairs of partitions are returned as two separate lists, + * parts1 and parts2 resp., one for each side. Otherwise, those lists will be + * set to NIL. + */ +static void +generate_matching_part_pairs(RelOptInfo *rel1, RelOptInfo *rel2, + PartitionMap *partmaps1, PartitionMap *partmaps2, + int nparts1, int nparts2, int nparts, + List **matched_parts1, List **matched_parts2) +{ + int *matching1, + *matching2; + int i; + int max_nparts; + + *matched_parts1 = NIL; + *matched_parts2 = NIL; + + matching1 = (int *) palloc(sizeof(int) * nparts), + matching2 = (int *) palloc(sizeof(int) * nparts); + for (i = 0; i < nparts; i++) + matching1[i] = matching2[i] = -1; + + /* Set pairs of matching partitions. */ + max_nparts = Max(nparts1, nparts2); + for (i = 0; i < max_nparts; i++) + { + if (i < nparts1) + { + PartitionMap outer_map = partmaps1[i]; + + if (outer_map.to >= 0) + { + Assert(outer_map.to < nparts); + matching1[outer_map.to] = i; + } + } + + if (i < nparts2) + { + PartitionMap inner_map = partmaps2[i]; + + if (inner_map.to >= 0) + { + Assert(inner_map.to < nparts); + matching2[inner_map.to] = i; + } + } + } + + for (i = 0; i < nparts; i++) + { + int part1 = matching1[i]; + int part2 = matching2[i]; + + /* At least one of the partitions should exist. */ + Assert(part1 >= 0 || part2 >= 0); + + *matched_parts1 = lappend(*matched_parts1, + part1 >= 0 ? rel1->part_rels[part1] : NULL); + *matched_parts2 = lappend(*matched_parts2, + part2 >= 0 ? rel2->part_rels[part2] : NULL); + } + + pfree(matching1); + pfree(matching2); +} + +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(PartitionBoundInfo outer_bi, PartitionBoundInfo inner_bi, + PartitionMap *outer_maps, PartitionMap *inner_maps, + JoinType jointype, int *next_index, int *default_index) +{ + int outer_default = outer_bi->default_index; + int inner_default = inner_bi->default_index; + bool outer_has_default = partition_bound_has_default(outer_bi); + bool inner_has_default = partition_bound_has_default(inner_bi); + bool merged = true; + PartitionMap *outer_default_map = NULL; + PartitionMap *inner_default_map = NULL; + + if (outer_has_default) + outer_default_map = &outer_maps[outer_default]; + + if (inner_has_default) + inner_default_map = &inner_maps[inner_default]; + + if (!outer_has_default && !inner_has_default) + Assert(*default_index < 0); + else if (outer_default_map != NULL && inner_default_map == NULL) + { + if (jointype == JOIN_LEFT || jointype == JOIN_FULL || + jointype == JOIN_ANTI) + { + if (outer_default_map->to < 0) + { + outer_default_map->to = *next_index; + *next_index = *next_index + 1; + Assert(*default_index < 0); + *default_index = outer_default_map->to; + } + else + Assert(*default_index == outer_default_map->to); + } + else + Assert(*default_index < 0); + } + else if (outer_default_map == NULL && inner_default_map != NULL) + { + if (jointype == JOIN_FULL) + { + if (inner_default_map->to < 0) + { + inner_default_map->to = *next_index; + *next_index = *next_index + 1; + Assert(*default_index < 0); + *default_index = inner_default_map->to; + } + else + Assert(*default_index == inner_default_map->to); + } + else + Assert(*default_index < 0); + } + else + { + Assert(outer_has_default && inner_has_default); + + *default_index = map_and_merge_partitions(outer_maps, inner_maps, + outer_default, inner_default, + next_index); + + if (*default_index < 0) + merged = false; + } + + return merged; +} + +/* + * 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_maps, PartitionMap *inner_maps, + JoinType jointype, int *next_index, + int *null_index, int *default_index) +{ + bool outer_has_null = partition_bound_accepts_nulls(outer_bi); + bool inner_has_null = partition_bound_accepts_nulls(inner_bi); + int outer_ni = outer_bi->null_index; + int inner_ni = inner_bi->null_index; + int outer_default = outer_bi->default_index; + int inner_default = inner_bi->default_index; + bool merged = true; + + if (!outer_has_null && !inner_has_null) + Assert(*null_index < 0); + else if (outer_has_null && !inner_has_null) + { + int merged_index = -1; + bool missing_side_outer; + bool missing_side_inner; + + /* + * For a FULL join, inner relation acts as both OUTER and INNER + * relation. For LEFT and ANTI join the inner relation acts as + * INNER relation. For INNER and SEMI join OUTER and INNER + * differentiation is immaterial. + */ + missing_side_inner = (jointype == JOIN_FULL || + jointype == JOIN_LEFT || + jointype == JOIN_ANTI); + missing_side_outer = (jointype == JOIN_FULL); + + merged = handle_missing_partition(inner_maps, + outer_maps, + inner_default, + outer_ni, + missing_side_outer, + missing_side_inner, next_index, + default_index, &merged_index); + *null_index = merged_index; + + /* + * If the NULL partition was missing from the inner side of the join, + * the partition of the join to which the outer null partition maps + * will contain the NULL values and thus becomes the NULL partition of + * the join. + */ + if (missing_side_inner) + *null_index = outer_maps[outer_ni].to; + } + else if (!outer_has_null && inner_has_null) + { + int merged_index = -1; + bool missing_side_outer; + bool missing_side_inner; + + /* + * For a FULL join, outer relation acts as both OUTER and INNER + * relation. For LEFT and ANTI join the outer relation acts as OUTER + * relation. For INNER and SEMI join OUTER and INNER differentiation is + * immaterial. + */ + missing_side_outer = (jointype == JOIN_FULL || + jointype == JOIN_LEFT || + jointype == JOIN_ANTI); + missing_side_inner = (jointype == JOIN_FULL); + merged = handle_missing_partition(outer_maps, + inner_maps, + outer_default, + inner_ni, + missing_side_outer, + missing_side_inner, + next_index, default_index, + &merged_index); + *null_index = merged_index; + + /* + * If the NULL partition was missing from the inner side of the join, + * the partition of the join, to which the outer side null partition maps, + * will contain the NULL values and thus becomes the NULL partition of + * the join. + */ + if (missing_side_inner) + *null_index = inner_maps[inner_ni].to; + } + else + { + /* Both the relations have NULL partitions, try merging them. */ + *null_index = map_and_merge_partitions(outer_maps, + inner_maps, + outer_ni, + inner_ni, + next_index); + if (*null_index < 0) + merged = false; + } + + return merged; +} 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 cad8dd591a..b5b5c8a260 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,64 +668,95 @@ 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) -- -- 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; @@ -486,32 +767,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 @@ -524,154 +822,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 @@ -680,21 +1056,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))) @@ -721,7 +1110,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 @@ -739,172 +1141,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) - -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) + -> 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 +------+---+------- + -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; @@ -921,14 +1411,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 @@ -937,14 +1427,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 @@ -953,32 +1443,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 @@ -987,12 +1519,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 @@ -1004,21 +1538,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 @@ -1032,175 +1573,2412 @@ 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; +-- 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 default partition and +-- prt2_p4 from prt2 and default partition from prt2 matches default partition +-- and prt1_p0 from prt1 +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 + -> 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_p4 t2_3 + -> Seq Scan on prt2_p3 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) + +-- partition-wise join should be possible when we drop the first and last +-- partitions from both sides +ALTER TABLE prt1 DETACH PARTITION prt1_p0; +ALTER TABLE prt1 DETACH PARTITION prt1_p4; +ANALYZE prt1; +ALTER TABLE prt2 DETACH PARTITION prt2_p0; +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 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) + +-- restore the partitioned tables for rest of the tests +ALTER TABLE prt1 ATTACH PARTITION prt1_p0 FOR VALUES FROM (MINVALUE) TO (0); +ALTER TABLE prt1 ATTACH PARTITION prt1_p4 FOR VALUES FROM (600) TO (800); +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_p0 FOR VALUES FROM (-250) TO (0); +ALTER TABLE prt2 ATTACH PARTITION prt2_p4 FOR VALUES FROM (600) TO (MAXVALUE); +ALTER TABLE prt2 DETACH PARTITION prt2_p3; +ALTER TABLE prt2 ATTACH PARTITION prt2_p3 FOR VALUES FROM (500) TO (600); +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) -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; +-- semi join 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.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: 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) + 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_p1 t2_1 + -> Seq Scan on plt2_p2 t2_2 + -> Seq Scan on plt2_p3 t2_3 +(22 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) +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) --- --- 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 +-- anti 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 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 - -> 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 + -> 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) -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 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) @@ -1225,22 +4003,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 @@ -1255,16 +4033,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_p2 t2_1 + -> Seq Scan on prt2_p1 t2_1 Filter: (a = 0) - -> Seq Scan on prt2_p3 t2_2 + -> 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) -> Hash -> Result One-Time Filter: false -(14 rows) +(20 rows) -- -- tests for hash partitioned tables. @@ -1340,41 +4124,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; @@ -1389,26 +4141,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 @@ -1804,64 +4554,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 @@ -1947,16 +4703,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 @@ -1967,14 +4724,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 @@ -1990,16 +4749,23 @@ 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) diff --git a/src/test/regress/sql/partition_join.sql b/src/test/regress/sql/partition_join.sql index fb3ba18a26..34ae92135f 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 @@ -95,20 +117,30 @@ SELECT t1.a, ss.t2a, ss.t2c FROM prt1 t1 LEFT JOIN LATERAL -- 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) @@ -172,6 +204,128 @@ 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 default partition and +-- prt2_p4 from prt2 and default partition from prt2 matches default partition +-- and prt1_p0 from prt1 +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; + +-- partition-wise join should be possible when we drop the first and last +-- partitions from both sides +ALTER TABLE prt1 DETACH PARTITION prt1_p0; +ALTER TABLE prt1 DETACH PARTITION prt1_p4; +ANALYZE prt1; +ALTER TABLE prt2 DETACH PARTITION prt2_p0; +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 ATTACH PARTITION prt1_p0 FOR VALUES FROM (MINVALUE) TO (0); +ALTER TABLE prt1 ATTACH PARTITION prt1_p4 FOR VALUES FROM (600) TO (800); +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_p0 FOR VALUES FROM (-250) TO (0); +ALTER TABLE prt2 ATTACH PARTITION prt2_p4 FOR VALUES FROM (600) TO (MAXVALUE); +ALTER TABLE prt2 DETACH PARTITION prt2_p3; +ALTER TABLE prt2 ATTACH PARTITION prt2_p3 FOR VALUES FROM (500) TO (600); +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 -- @@ -196,28 +350,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 @@ -225,6 +430,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; @@ -270,27 +644,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 --