Improve-partition-matching-for-partitionwise-joins-v23.patch
application/octet-stream
Filename: Improve-partition-matching-for-partitionwise-joins-v23.patch
Type: application/octet-stream
Part: 0
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: unified
Series: patch v23
| File | + | − |
|---|---|---|
| src/backend/optimizer/path/joinrels.c | 80 | 25 |
| src/backend/optimizer/util/relnode.c | 9 | 24 |
| src/backend/partitioning/partbounds.c | 1677 | 0 |
| src/include/partitioning/partbounds.h | 7 | 0 |
| src/test/regress/expected/partition_join.out | 3448 | 682 |
| src/test/regress/sql/partition_join.sql | 396 | 31 |
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
index 43c3b7ea48..4ab218641c 100644
--- a/src/backend/optimizer/path/joinrels.c
+++ b/src/backend/optimizer/path/joinrels.c
@@ -1339,25 +1339,31 @@ 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;
+ PartitionScheme part_scheme;
+ PartitionBoundInfo join_boundinfo;
+ List *parts1;
+ List *parts2;
+ ListCell *lc1;
+ ListCell *lc2;
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)
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. */
@@ -1370,36 +1376,67 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2,
*/
Assert(joinrel->part_scheme == rel1->part_scheme &&
joinrel->part_scheme == rel2->part_scheme);
+ part_scheme = joinrel->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.
+ * Get the list of matching partitions to be joined along with the
+ * partition bounds of the join relation. Because of the restrictions
+ * imposed by partition matching algorithm, not every pair of joining
+ * relations for this join will be able to use partition-wise join. But all
+ * those pairs which can use partition-wise join will produce the same
+ * partition bounds for the join relation.
*/
- 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));
+ join_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 (join_boundinfo == NULL)
+ return;
- nparts = joinrel->nparts;
+ if (joinrel->boundinfo == NULL)
+ {
+ Assert(joinrel->nparts == 0 && joinrel->part_rels == NULL);
+ joinrel->boundinfo = join_boundinfo;
+ joinrel->nparts = list_length(parts1);
+ Assert(joinrel->nparts == list_length(parts2));
+ joinrel->part_rels =
+ (RelOptInfo **) palloc0(sizeof(RelOptInfo *) *
+ joinrel->nparts);
+ }
+ else
+ {
+ Assert(partition_bounds_equal(part_scheme->partnatts,
+ part_scheme->parttyplen,
+ part_scheme->parttypbyval,
+ join_boundinfo, joinrel->boundinfo));
+ /*
+ * Every pair of joining relations should result in the same number
+ * of child-joins.
+ */
+ Assert(joinrel->nparts == list_length(parts1));
+ Assert(joinrel->nparts == list_length(parts2));
+ Assert(joinrel->part_rels);
+ }
/*
* 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++)
+ cnt_parts = -1;
+ forboth(lc1, parts1, lc2, parts2)
{
- 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));
+ int part1 = lfirst_int(lc1);
+ int part2 = lfirst_int(lc2);
+ RelOptInfo *child_rel1;
+ RelOptInfo *child_rel2;
+ bool rel1_empty;
+ bool rel2_empty;
SpecialJoinInfo *child_sjinfo;
List *child_restrictlist;
RelOptInfo *child_joinrel;
@@ -1407,6 +1444,13 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2,
AppendRelInfo **appinfos;
int nappinfos;
+ Assert(part1 >= 0 && part2 >= 0);
+ child_rel1 = rel1->part_rels[part1];
+ child_rel2 = rel2->part_rels[part2];
+ rel1_empty = (child_rel1 == NULL || IS_DUMMY_REL(child_rel1));
+ rel2_empty = (child_rel2 == NULL || IS_DUMMY_REL(child_rel2));
+ cnt_parts++;
+
/*
* 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
@@ -1506,12 +1550,23 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2,
joinrel->part_rels[cnt_parts] = child_joinrel;
}
+ /*
+ * For every pair of joining relations, the set of matching partitions
+ * would change. However, the base relation partitions constituting
+ * the given child should remain same for all the joining pairs. Since
+ * the order in which children are stored in the array of child-joins,
+ * depends upon partition bounds of the join, which are same for all
+ * the joining pairs, every joining pair yields the child-joins in the
+ * same order.
+ */
Assert(bms_equal(child_joinrel->relids, child_joinrelids));
populate_joinrel_with_paths(root, child_rel1, child_rel2,
child_joinrel, child_sjinfo,
child_restrictlist);
}
+
+ Assert(cnt_parts == (joinrel->nparts - 1));
}
/*
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index 6054bd2b53..ead871180f 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -1623,7 +1623,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 ||
@@ -1636,24 +1636,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
@@ -1665,17 +1647,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 13d576c379..1084ec8f69 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,58 @@ 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 PartitionBoundInfo partition_hash_bounds_merge(int partnatts,
+ int16 *parttyplen, bool *parttypbyval,
+ RelOptInfo *outer_rel, RelOptInfo *inner_rel,
+ List **outer_parts, List **inner_parts);
+static void generate_matching_part_pairs(PartitionMap *outer_maps,
+ PartitionMap *inner_maps,
+ int nparts1, int nparts2,
+ JoinType jointype, int nparts,
+ List **parts1, List **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
@@ -2995,3 +3054,1621 @@ 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_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;
+
+ case PARTITION_STRATEGY_HASH:
+ merged_bounds = partition_hash_bounds_merge(partnatts,
+ parttyplen, parttypbyval,
+ outer_rel, inner_rel,
+ outer_parts, inner_parts);
+ break;
+
+ default:
+ elog(ERROR, "unexpected partition strategy: %d", strategy);
+ }
+
+ Assert(merged_bounds || (*outer_parts == NIL && *inner_parts == NIL));
+
+ Assert(list_length(*outer_parts) == list_length(*inner_parts));
+
+ Assert((*outer_parts == NIL || *inner_parts != NIL) &&
+ (*inner_parts == NIL || *outer_parts != NIL));
+
+ 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;
+}
+
+/*
+ * Allocate and initialize partition maps. We maintain four maps, two maps
+ * for each joining relation. pmap[i] gives the partition from the other
+ * relation which would join with ith partition of the given relation.
+ * Partition i from the given relation will join with partition pmap[i]
+ * from the other relation to produce partition mmap[i] of the join (merged
+ * partition).
+ *
+ * pmap[i] = -1 indicates that ith partition of a given relation does not
+ * have a matching partition from the other relation.
+ *
+ * mmap[i] = -1 indicates that ith partition of a given relation does not
+ * contribute to the join result. That can happen only when the given
+ * relation is the inner relation and it doesn't have a matching partition
+ * from the outer relation, hence pmap[i] should be -1.
+ *
+ * In case of an outer join, every partition of the outer join will appear
+ * in the join result, and thus has mmap[i] set for all i. But it's not
+ * necessary that every partition on the outer side will have a matching
+ * partition on the inner side. In such a case, we end up with pmap[i] = -1
+ * and mmap[i] != -1.
+ */
+
+/*
+ * 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_maps, inner_maps,
+ outer_nparts, inner_nparts,
+ jointype, 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_maps, inner_maps,
+ outer_nparts, inner_nparts,
+ jointype, 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;
+}
+
+/*
+ * partition_bounds_merge()'s arm for hash partitioned tables.
+ *
+ * If the given two hash bounds are same, the function returns the first one
+ * without any change, alongwith the lists of matching partitions. Otherwise it
+ * returns NULL.
+ *
+ * We could try merging the bounds when both the bounds have same greatest
+ * modulii. But there seems to be hardly any requirement for the same.
+ */
+static PartitionBoundInfo
+partition_hash_bounds_merge(int partnatts,
+ int16 *parttyplen, bool *parttypbyval,
+ RelOptInfo *outer_rel, RelOptInfo *inner_rel,
+ List **outer_parts, List **inner_parts)
+{
+ PartitionBoundInfo outer_bi = outer_rel->boundinfo,
+ inner_bi = inner_rel->boundinfo;
+ int cnt;
+
+ Assert(*outer_parts == NIL);
+ Assert(*inner_parts == NIL);
+
+ Assert(outer_bi->strategy == inner_bi->strategy &&
+ outer_bi->strategy == PARTITION_STRATEGY_HASH);
+
+ /*
+ * Hash partitioned table does not have explicit NULL accepting partition
+ * and also does not have a default partition.
+ */
+ Assert(!partition_bound_has_default(outer_bi) &&
+ !partition_bound_has_default(inner_bi));
+ Assert(!partition_bound_accepts_nulls(outer_bi) &&
+ !partition_bound_accepts_nulls(inner_bi));
+
+ if (outer_rel->nparts != inner_rel->nparts)
+ return NULL;
+
+ if (!partition_bounds_equal(partnatts, parttyplen, parttypbyval,
+ outer_bi, inner_bi))
+ return NULL;
+
+ /*
+ * Cook up list of matching partitions. Since bounds are exactly same the
+ * partitions at the same position from both the relations match.
+ */
+ for (cnt = 0; cnt < outer_rel->nparts; cnt++)
+ {
+ *outer_parts = lappend_int(*outer_parts, cnt);
+ *inner_parts = lappend_int(*inner_parts, cnt);
+ }
+
+ return outer_bi;
+}
+
+/*
+ * 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(PartitionMap *partmaps1, PartitionMap *partmaps2,
+ int nparts1, int nparts2,
+ JoinType jointype, int nparts,
+ List **matched_parts1, List **matched_parts2)
+{
+ bool merged = true;
+ 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;
+ }
+ }
+ }
+
+ /*
+ * If we have a partition missing on an inner side, we need to add a dummy
+ * relation which joins with the outer partition. If the inner relation
+ * happens to be a base relation, it will require adding a dummy child
+ * base relation during join processing. Right now, we freeze the base
+ * relation arrays like PlannerInfo::simple_rte_array after planning for
+ * base relations. Adding a new (dummy) base relation would require some
+ * changes to that. So, right now, we do not implement partition-wise join
+ * in such cases.
+ */
+ 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);
+
+ switch (jointype)
+ {
+ case JOIN_INNER:
+ case JOIN_SEMI:
+
+ /*
+ * An inner or semi join can not return any row when the
+ * matching partition on either side is missing. We should
+ * have eliminated all such cases while merging the bounds.
+ */
+ Assert(part1 >= 0 && part2 >= 0);
+ break;
+
+ case JOIN_LEFT:
+ case JOIN_ANTI:
+ Assert(part1 >= 0);
+ if (part2 < 0)
+ merged = false;
+ break;
+
+ case JOIN_FULL:
+ if (part1 < 0 || part2 < 0)
+ merged = false;
+ break;
+
+ default:
+ elog(ERROR, "unrecognized join type: %d", (int) jointype);
+ }
+
+ if (!merged)
+ break;
+
+ *matched_parts1 = lappend_int(*matched_parts1, part1);
+ *matched_parts2 = lappend_int(*matched_parts2, part2);
+ }
+
+ pfree(matching1);
+ pfree(matching2);
+
+ if (!merged)
+ {
+ list_free(*matched_parts1);
+ list_free(*matched_parts2);
+ *matched_parts1 = NIL;
+ *matched_parts2 = NIL;
+ }
+}
+
+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/partitioning/partbounds.h b/src/include/partitioning/partbounds.h
index 8585c29c92..636087d83e 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 */
/*
@@ -109,5 +110,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 1296edcdae..d26be918aa 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 table
@@ -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 table 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 db9a6b4a96..7597b069fa 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
--