summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/backend/nodes/copyfuncs.c4
-rw-r--r--src/backend/nodes/equalfuncs.c4
-rw-r--r--src/backend/nodes/outfuncs.c4
-rw-r--r--src/backend/optimizer/plan/initsplan.c149
-rw-r--r--src/include/nodes/relation.h9
-rw-r--r--src/test/regress/expected/join.out24
-rw-r--r--src/test/regress/expected/join_1.out24
-rw-r--r--src/test/regress/sql/join.sql22
8 files changed, 189 insertions, 51 deletions
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index a9cb0416c34..6aa7e559087 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -15,7 +15,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.353.2.1 2007/05/22 23:24:08 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.353.2.2 2007/08/31 01:44:14 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -1316,6 +1316,8 @@ _copyOuterJoinInfo(OuterJoinInfo *from)
COPY_BITMAPSET_FIELD(min_lefthand);
COPY_BITMAPSET_FIELD(min_righthand);
+ COPY_BITMAPSET_FIELD(syn_lefthand);
+ COPY_BITMAPSET_FIELD(syn_righthand);
COPY_SCALAR_FIELD(is_full_join);
COPY_SCALAR_FIELD(lhs_strict);
COPY_SCALAR_FIELD(delay_upper_joins);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 13be0e0f9c8..0979e2b8716 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -18,7 +18,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.287.2.1 2007/05/22 23:24:08 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.287.2.2 2007/08/31 01:44:14 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -612,6 +612,8 @@ _equalOuterJoinInfo(OuterJoinInfo *a, OuterJoinInfo *b)
{
COMPARE_BITMAPSET_FIELD(min_lefthand);
COMPARE_BITMAPSET_FIELD(min_righthand);
+ COMPARE_BITMAPSET_FIELD(syn_lefthand);
+ COMPARE_BITMAPSET_FIELD(syn_righthand);
COMPARE_SCALAR_FIELD(is_full_join);
COMPARE_SCALAR_FIELD(lhs_strict);
COMPARE_SCALAR_FIELD(delay_upper_joins);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 3d02e3fada7..ac6787688d7 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.285.2.3 2007/07/17 01:21:55 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.285.2.4 2007/08/31 01:44:14 tgl Exp $
*
* NOTES
* Every node type that can appear in stored rules' parsetrees *must*
@@ -1291,6 +1291,8 @@ _outOuterJoinInfo(StringInfo str, OuterJoinInfo *node)
WRITE_BITMAPSET_FIELD(min_lefthand);
WRITE_BITMAPSET_FIELD(min_righthand);
+ WRITE_BITMAPSET_FIELD(syn_lefthand);
+ WRITE_BITMAPSET_FIELD(syn_righthand);
WRITE_BOOL_FIELD(is_full_join);
WRITE_BOOL_FIELD(lhs_strict);
WRITE_BOOL_FIELD(delay_upper_joins);
diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
index 0b315f2cd7d..f991f30e130 100644
--- a/src/backend/optimizer/plan/initsplan.c
+++ b/src/backend/optimizer/plan/initsplan.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/optimizer/plan/initsplan.c,v 1.123.2.5 2007/05/22 23:24:08 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/optimizer/plan/initsplan.c,v 1.123.2.6 2007/08/31 01:44:14 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -40,9 +40,11 @@ int join_collapse_limit;
static void add_vars_to_targetlist(PlannerInfo *root, List *vars,
Relids where_needed);
static List *deconstruct_recurse(PlannerInfo *root, Node *jtnode,
- bool below_outer_join, Relids *qualscope);
+ bool below_outer_join,
+ Relids *qualscope, Relids *inner_join_rels);
static OuterJoinInfo *make_outerjoininfo(PlannerInfo *root,
Relids left_rels, Relids right_rels,
+ Relids inner_join_rels,
bool is_full_join, Node *clause);
static void distribute_qual_to_rels(PlannerInfo *root, Node *clause,
bool is_deduced,
@@ -206,13 +208,14 @@ List *
deconstruct_jointree(PlannerInfo *root)
{
Relids qualscope;
+ Relids inner_join_rels;
/* Start recursion at top of jointree */
Assert(root->parse->jointree != NULL &&
IsA(root->parse->jointree, FromExpr));
return deconstruct_recurse(root, (Node *) root->parse->jointree, false,
- &qualscope);
+ &qualscope, &inner_join_rels);
}
/*
@@ -226,20 +229,24 @@ deconstruct_jointree(PlannerInfo *root)
* Outputs:
* *qualscope gets the set of base Relids syntactically included in this
* jointree node (do not modify or free this, as it may also be pointed
- * to by RestrictInfo nodes)
+ * to by RestrictInfo and OuterJoinInfo nodes)
+ * *inner_join_rels gets the set of base Relids syntactically included in
+ * inner joins appearing at or below this jointree node (do not modify
+ * or free this, either)
* Return value is the appropriate joinlist for this jointree node
*
* In addition, entries will be added to root->oj_info_list for outer joins.
*/
static List *
deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
- Relids *qualscope)
+ Relids *qualscope, Relids *inner_join_rels)
{
List *joinlist;
if (jtnode == NULL)
{
*qualscope = NULL;
+ *inner_join_rels = NULL;
return NIL;
}
if (IsA(jtnode, RangeTblRef))
@@ -248,6 +255,8 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
/* No quals to deal with, just return correct result */
*qualscope = bms_make_singleton(varno);
+ /* A single baserel does not create an inner join */
+ *inner_join_rels = NULL;
joinlist = list_make1(jtnode);
}
else if (IsA(jtnode, FromExpr))
@@ -263,6 +272,7 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
* subproblems, since that won't lengthen the joinlist anyway.
*/
*qualscope = NULL;
+ *inner_join_rels = NULL;
joinlist = NIL;
remaining = list_length(f->fromlist);
foreach(l, f->fromlist)
@@ -273,7 +283,8 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
sub_joinlist = deconstruct_recurse(root, lfirst(l),
below_outer_join,
- &sub_qualscope);
+ &sub_qualscope,
+ inner_join_rels);
*qualscope = bms_add_members(*qualscope, sub_qualscope);
sub_members = list_length(sub_joinlist);
remaining--;
@@ -285,6 +296,16 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
}
/*
+ * A FROM with more than one list element is an inner join subsuming
+ * all below it, so we should report inner_join_rels = qualscope.
+ * If there was exactly one element, we should (and already did) report
+ * whatever its inner_join_rels were. If there were no elements
+ * (is that possible?) the initialization before the loop fixed it.
+ */
+ if (list_length(f->fromlist) > 1)
+ *inner_join_rels = *qualscope;
+
+ /*
* Now process the top-level quals.
*/
foreach(l, (List *) f->quals)
@@ -297,6 +318,8 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
JoinExpr *j = (JoinExpr *) jtnode;
Relids leftids,
rightids,
+ left_inners,
+ right_inners,
nonnullable_rels,
ojscope;
List *leftjoinlist,
@@ -321,32 +344,35 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
case JOIN_INNER:
leftjoinlist = deconstruct_recurse(root, j->larg,
below_outer_join,
- &leftids);
+ &leftids, &left_inners);
rightjoinlist = deconstruct_recurse(root, j->rarg,
below_outer_join,
- &rightids);
+ &rightids, &right_inners);
*qualscope = bms_union(leftids, rightids);
+ *inner_join_rels = *qualscope;
/* Inner join adds no restrictions for quals */
nonnullable_rels = NULL;
break;
case JOIN_LEFT:
leftjoinlist = deconstruct_recurse(root, j->larg,
below_outer_join,
- &leftids);
+ &leftids, &left_inners);
rightjoinlist = deconstruct_recurse(root, j->rarg,
true,
- &rightids);
+ &rightids, &right_inners);
*qualscope = bms_union(leftids, rightids);
+ *inner_join_rels = bms_union(left_inners, right_inners);
nonnullable_rels = leftids;
break;
case JOIN_FULL:
leftjoinlist = deconstruct_recurse(root, j->larg,
true,
- &leftids);
+ &leftids, &left_inners);
rightjoinlist = deconstruct_recurse(root, j->rarg,
true,
- &rightids);
+ &rightids, &right_inners);
*qualscope = bms_union(leftids, rightids);
+ *inner_join_rels = bms_union(left_inners, right_inners);
/* each side is both outer and inner */
nonnullable_rels = *qualscope;
break;
@@ -354,11 +380,12 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
/* notice we switch leftids and rightids */
leftjoinlist = deconstruct_recurse(root, j->larg,
true,
- &rightids);
+ &rightids, &right_inners);
rightjoinlist = deconstruct_recurse(root, j->rarg,
below_outer_join,
- &leftids);
+ &leftids, &left_inners);
*qualscope = bms_union(leftids, rightids);
+ *inner_join_rels = bms_union(left_inners, right_inners);
nonnullable_rels = leftids;
break;
default:
@@ -377,8 +404,11 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
*/
if (j->jointype != JOIN_INNER)
{
- ojinfo = make_outerjoininfo(root, leftids, rightids,
- (j->jointype == JOIN_FULL), j->quals);
+ ojinfo = make_outerjoininfo(root,
+ leftids, rightids,
+ *inner_join_rels,
+ (j->jointype == JOIN_FULL),
+ j->quals);
ojscope = bms_union(ojinfo->min_lefthand, ojinfo->min_righthand);
}
else
@@ -447,6 +477,7 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
* Inputs:
* left_rels: the base Relids syntactically on outer side of join
* right_rels: the base Relids syntactically on inner side of join
+ * inner_join_rels: base Relids participating in inner joins below this one
* is_full_join: what it says
* clause: the outer join's join condition
*
@@ -459,17 +490,19 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
*
* Note: we assume that this function is invoked bottom-up, so that
* root->oj_info_list already contains entries for all outer joins that are
- * syntactically below this one; and indeed that oj_info_list is ordered
- * with syntactically lower joins listed first.
+ * syntactically below this one.
*/
static OuterJoinInfo *
make_outerjoininfo(PlannerInfo *root,
Relids left_rels, Relids right_rels,
+ Relids inner_join_rels,
bool is_full_join, Node *clause)
{
OuterJoinInfo *ojinfo = makeNode(OuterJoinInfo);
Relids clause_relids;
Relids strict_relids;
+ Relids min_lefthand;
+ Relids min_righthand;
ListCell *l;
/*
@@ -499,6 +532,8 @@ make_outerjoininfo(PlannerInfo *root,
ojinfo->delay_upper_joins = false;
/* If it's a full join, no need to be very smart */
+ ojinfo->syn_lefthand = left_rels;
+ ojinfo->syn_righthand = right_rels;
ojinfo->is_full_join = is_full_join;
if (is_full_join)
{
@@ -523,21 +558,17 @@ make_outerjoininfo(PlannerInfo *root,
ojinfo->lhs_strict = bms_overlap(strict_relids, left_rels);
/*
- * Required LHS is basically the LHS rels mentioned in the clause... but
- * if there aren't any, punt and make it the full LHS, to avoid having an
- * empty min_lefthand which will confuse later processing. (We don't try
- * to be smart about such cases, just correct.) We may have to add more
- * rels based on lower outer joins; see below.
+ * Required LHS always includes the LHS rels mentioned in the clause.
+ * We may have to add more rels based on lower outer joins; see below.
*/
- ojinfo->min_lefthand = bms_intersect(clause_relids, left_rels);
- if (bms_is_empty(ojinfo->min_lefthand))
- ojinfo->min_lefthand = bms_copy(left_rels);
+ min_lefthand = bms_intersect(clause_relids, left_rels);
/*
- * Required RHS is normally the full set of RHS rels. Sometimes we can
- * exclude some, see below.
+ * Similarly for required RHS. But here, we must also include any lower
+ * inner joins, to ensure we don't try to commute with any of them.
*/
- ojinfo->min_righthand = bms_copy(right_rels);
+ min_righthand = bms_int_members(bms_union(clause_relids, inner_join_rels),
+ right_rels);
foreach(l, root->oj_info_list)
{
@@ -550,23 +581,29 @@ make_outerjoininfo(PlannerInfo *root,
/*
* For a lower OJ in our LHS, if our join condition uses the lower
* join's RHS and is not strict for that rel, we must preserve the
- * ordering of the two OJs, so add lower OJ's full required relset to
- * min_lefthand.
+ * ordering of the two OJs, so add lower OJ's full syntactic relset to
+ * min_lefthand. (We must use its full syntactic relset, not just
+ * its min_lefthand + min_righthand. This is because there might
+ * be other OJs below this one that this one can commute with,
+ * but we cannot commute with them if we don't with this one.)
+ *
+ * Note: I believe we have to insist on being strict for at least one
+ * rel in the lower OJ's min_righthand, not its whole syn_righthand.
*/
- if (bms_overlap(ojinfo->min_lefthand, otherinfo->min_righthand) &&
+ if (bms_overlap(left_rels, otherinfo->syn_righthand) &&
!bms_overlap(strict_relids, otherinfo->min_righthand))
{
- ojinfo->min_lefthand = bms_add_members(ojinfo->min_lefthand,
- otherinfo->min_lefthand);
- ojinfo->min_lefthand = bms_add_members(ojinfo->min_lefthand,
- otherinfo->min_righthand);
+ min_lefthand = bms_add_members(min_lefthand,
+ otherinfo->syn_lefthand);
+ min_lefthand = bms_add_members(min_lefthand,
+ otherinfo->syn_righthand);
}
/*
* For a lower OJ in our RHS, if our join condition does not use the
* lower join's RHS and the lower OJ's join condition is strict, we
- * can interchange the ordering of the two OJs, so exclude the lower
- * RHS from our min_righthand.
+ * can interchange the ordering of the two OJs; otherwise we must
+ * add lower OJ's full syntactic relset to min_righthand.
*
* Here, we have to consider that "our join condition" includes
* any clauses that syntactically appeared above the lower OJ and
@@ -579,20 +616,38 @@ make_outerjoininfo(PlannerInfo *root,
* effect is therefore sufficiently represented by the
* delay_upper_joins flag saved for us by distribute_qual_to_rels.
*/
- if (bms_overlap(ojinfo->min_righthand, otherinfo->min_righthand) &&
- !bms_overlap(clause_relids, otherinfo->min_righthand) &&
- otherinfo->lhs_strict && !otherinfo->delay_upper_joins)
+ if (bms_overlap(right_rels, otherinfo->syn_righthand))
{
- ojinfo->min_righthand = bms_del_members(ojinfo->min_righthand,
- otherinfo->min_righthand);
+ if (bms_overlap(clause_relids, otherinfo->syn_righthand) ||
+ !otherinfo->lhs_strict || otherinfo->delay_upper_joins)
+ {
+ min_righthand = bms_add_members(min_righthand,
+ otherinfo->syn_lefthand);
+ min_righthand = bms_add_members(min_righthand,
+ otherinfo->syn_righthand);
+ }
}
}
- /* Neither set should be empty, else we might get confused later */
- Assert(!bms_is_empty(ojinfo->min_lefthand));
- Assert(!bms_is_empty(ojinfo->min_righthand));
+ /*
+ * If we found nothing to put in min_lefthand, punt and make it the full
+ * LHS, to avoid having an empty min_lefthand which will confuse later
+ * processing. (We don't try to be smart about such cases, just correct.)
+ * Likewise for min_righthand.
+ */
+ if (bms_is_empty(min_lefthand))
+ min_lefthand = bms_copy(left_rels);
+ if (bms_is_empty(min_righthand))
+ min_righthand = bms_copy(right_rels);
+
+ /* Now they'd better be nonempty */
+ Assert(!bms_is_empty(min_lefthand));
+ Assert(!bms_is_empty(min_righthand));
/* Shouldn't overlap either */
- Assert(!bms_overlap(ojinfo->min_lefthand, ojinfo->min_righthand));
+ Assert(!bms_overlap(min_lefthand, min_righthand));
+
+ ojinfo->min_lefthand = min_lefthand;
+ ojinfo->min_righthand = min_righthand;
return ojinfo;
}
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index b1cedcc850a..34727773e06 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/nodes/relation.h,v 1.128.2.3 2007/05/22 23:24:09 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/relation.h,v 1.128.2.4 2007/08/31 01:44:14 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -852,6 +852,11 @@ typedef struct InnerIndexscanInfo
* It is not valid for either min_lefthand or min_righthand to be empty sets;
* if they were, this would break the logic that enforces join order.
*
+ * syn_lefthand and syn_righthand are the sets of base relids that are
+ * syntactically below this outer join. (These are needed to help compute
+ * min_lefthand and min_righthand for higher joins, but are not used
+ * thereafter.)
+ *
* delay_upper_joins is set TRUE if we detect a pushed-down clause that has
* to be evaluated after this join is formed (because it references the RHS).
* Any outer joins that have such a clause and this join in their RHS cannot
@@ -869,6 +874,8 @@ typedef struct OuterJoinInfo
NodeTag type;
Relids min_lefthand; /* base relids in minimum LHS for join */
Relids min_righthand; /* base relids in minimum RHS for join */
+ Relids syn_lefthand; /* base relids syntactically within LHS */
+ Relids syn_righthand; /* base relids syntactically within RHS */
bool is_full_join; /* it's a FULL OUTER JOIN */
bool lhs_strict; /* joinclause is strict for some LHS rel */
bool delay_upper_joins; /* can't commute with upper RHS */
diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out
index 3b7bdd9cbb3..3dd4f5ed7d0 100644
--- a/src/test/regress/expected/join.out
+++ b/src/test/regress/expected/join.out
@@ -2266,3 +2266,27 @@ select * from tt5,tt6 where tt5.f1 = tt6.f1 and tt5.f1 = tt5.f2 - tt6.f2;
1 | 10 | 1 | 9
(1 row)
+--
+-- regression test for problems of the sort depicted in bug #3588
+--
+create temp table xx (pkxx int);
+create temp table yy (pkyy int, pkxx int);
+insert into xx values (1);
+insert into xx values (2);
+insert into xx values (3);
+insert into yy values (101, 1);
+insert into yy values (201, 2);
+insert into yy values (301, NULL);
+select yy.pkyy as yy_pkyy, yy.pkxx as yy_pkxx, yya.pkyy as yya_pkyy,
+ xxa.pkxx as xxa_pkxx, xxb.pkxx as xxb_pkxx
+from yy
+ left join (SELECT * FROM yy where pkyy = 101) as yya ON yy.pkyy = yya.pkyy
+ left join xx xxa on yya.pkxx = xxa.pkxx
+ left join xx xxb on coalesce (xxa.pkxx, 1) = xxb.pkxx;
+ yy_pkyy | yy_pkxx | yya_pkyy | xxa_pkxx | xxb_pkxx
+---------+---------+----------+----------+----------
+ 101 | 1 | 101 | 1 | 1
+ 201 | 2 | | | 1
+ 301 | | | | 1
+(3 rows)
+
diff --git a/src/test/regress/expected/join_1.out b/src/test/regress/expected/join_1.out
index f1bf4b9af26..9e10c73acb7 100644
--- a/src/test/regress/expected/join_1.out
+++ b/src/test/regress/expected/join_1.out
@@ -2266,3 +2266,27 @@ select * from tt5,tt6 where tt5.f1 = tt6.f1 and tt5.f1 = tt5.f2 - tt6.f2;
1 | 10 | 1 | 9
(1 row)
+--
+-- regression test for problems of the sort depicted in bug #3588
+--
+create temp table xx (pkxx int);
+create temp table yy (pkyy int, pkxx int);
+insert into xx values (1);
+insert into xx values (2);
+insert into xx values (3);
+insert into yy values (101, 1);
+insert into yy values (201, 2);
+insert into yy values (301, NULL);
+select yy.pkyy as yy_pkyy, yy.pkxx as yy_pkxx, yya.pkyy as yya_pkyy,
+ xxa.pkxx as xxa_pkxx, xxb.pkxx as xxb_pkxx
+from yy
+ left join (SELECT * FROM yy where pkyy = 101) as yya ON yy.pkyy = yya.pkyy
+ left join xx xxa on yya.pkxx = xxa.pkxx
+ left join xx xxb on coalesce (xxa.pkxx, 1) = xxb.pkxx;
+ yy_pkyy | yy_pkxx | yya_pkyy | xxa_pkxx | xxb_pkxx
+---------+---------+----------+----------+----------
+ 101 | 1 | 101 | 1 | 1
+ 201 | 2 | | | 1
+ 301 | | | | 1
+(3 rows)
+
diff --git a/src/test/regress/sql/join.sql b/src/test/regress/sql/join.sql
index ad8957cd6e3..4efb3c72836 100644
--- a/src/test/regress/sql/join.sql
+++ b/src/test/regress/sql/join.sql
@@ -440,3 +440,25 @@ insert into tt6 values(1, 2);
insert into tt6 values(2, 9);
select * from tt5,tt6 where tt5.f1 = tt6.f1 and tt5.f1 = tt5.f2 - tt6.f2;
+
+--
+-- regression test for problems of the sort depicted in bug #3588
+--
+
+create temp table xx (pkxx int);
+create temp table yy (pkyy int, pkxx int);
+
+insert into xx values (1);
+insert into xx values (2);
+insert into xx values (3);
+
+insert into yy values (101, 1);
+insert into yy values (201, 2);
+insert into yy values (301, NULL);
+
+select yy.pkyy as yy_pkyy, yy.pkxx as yy_pkxx, yya.pkyy as yya_pkyy,
+ xxa.pkxx as xxa_pkxx, xxb.pkxx as xxb_pkxx
+from yy
+ left join (SELECT * FROM yy where pkyy = 101) as yya ON yy.pkyy = yya.pkyy
+ left join xx xxa on yya.pkxx = xxa.pkxx
+ left join xx xxb on coalesce (xxa.pkxx, 1) = xxb.pkxx;