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