mirror of
https://github.com/postgres/postgres.git
synced 2025-06-26 12:21:12 +03:00
Extend CTE patch to support recursive UNION (ie, without ALL). The
implementation uses an in-memory hash table, so it will poop out for very large recursive results ... but the performance characteristics of a sort-based implementation would be pretty unpleasant too.
This commit is contained in:
@ -1,4 +1,4 @@
|
|||||||
<!-- $PostgreSQL: pgsql/doc/src/sgml/queries.sgml,v 1.46 2008/10/04 21:56:52 tgl Exp $ -->
|
<!-- $PostgreSQL: pgsql/doc/src/sgml/queries.sgml,v 1.47 2008/10/07 19:27:03 tgl Exp $ -->
|
||||||
|
|
||||||
<chapter id="queries">
|
<chapter id="queries">
|
||||||
<title>Queries</title>
|
<title>Queries</title>
|
||||||
@ -1519,7 +1519,8 @@ SELECT sum(n) FROM t;
|
|||||||
</programlisting>
|
</programlisting>
|
||||||
|
|
||||||
The general form of a recursive <literal>WITH</> query is always a
|
The general form of a recursive <literal>WITH</> query is always a
|
||||||
<firstterm>non-recursive term</>, then <literal>UNION ALL</>, then a
|
<firstterm>non-recursive term</>, then <literal>UNION</> (or
|
||||||
|
<literal>UNION ALL</>), then a
|
||||||
<firstterm>recursive term</>, where only the recursive term can contain
|
<firstterm>recursive term</>, where only the recursive term can contain
|
||||||
a reference to the query's own output. Such a query is executed as
|
a reference to the query's own output. Such a query is executed as
|
||||||
follows:
|
follows:
|
||||||
@ -1530,9 +1531,10 @@ SELECT sum(n) FROM t;
|
|||||||
|
|
||||||
<step performance="required">
|
<step performance="required">
|
||||||
<para>
|
<para>
|
||||||
Evaluate the non-recursive term. Include all its output rows in the
|
Evaluate the non-recursive term. For <literal>UNION</> (but not
|
||||||
result of the recursive query, and also place them in a temporary
|
<literal>UNION ALL</>), discard duplicate rows. Include all remaining
|
||||||
<firstterm>working table</>.
|
rows in the result of the recursive query, and also place them in a
|
||||||
|
temporary <firstterm>working table</>.
|
||||||
</para>
|
</para>
|
||||||
</step>
|
</step>
|
||||||
|
|
||||||
@ -1544,9 +1546,11 @@ SELECT sum(n) FROM t;
|
|||||||
<step performance="required">
|
<step performance="required">
|
||||||
<para>
|
<para>
|
||||||
Evaluate the recursive term, substituting the current contents of
|
Evaluate the recursive term, substituting the current contents of
|
||||||
the working table for the recursive self-reference. Include all its
|
the working table for the recursive self-reference.
|
||||||
output rows in the result of the recursive query, and also place them
|
For <literal>UNION</> (but not <literal>UNION ALL</>), discard
|
||||||
in a temporary <firstterm>intermediate table</>.
|
duplicate rows and rows that duplicate any previous result row.
|
||||||
|
Include all remaining rows in the result of the recursive query, and
|
||||||
|
also place them in a temporary <firstterm>intermediate table</>.
|
||||||
</para>
|
</para>
|
||||||
</step>
|
</step>
|
||||||
|
|
||||||
@ -1598,10 +1602,13 @@ GROUP BY sub_part
|
|||||||
<para>
|
<para>
|
||||||
When working with recursive queries it is important to be sure that
|
When working with recursive queries it is important to be sure that
|
||||||
the recursive part of the query will eventually return no tuples,
|
the recursive part of the query will eventually return no tuples,
|
||||||
or else the query will loop indefinitely. A useful trick for
|
or else the query will loop indefinitely. Sometimes, using
|
||||||
development purposes is to place a <literal>LIMIT</> in the parent
|
<literal>UNION</> instead of <literal>UNION ALL</> can accomplish this
|
||||||
query. For example, this query would loop forever without the
|
by discarding rows that duplicate previous output rows; this catches
|
||||||
<literal>LIMIT</>:
|
cycles that would otherwise repeat. A useful trick for testing queries
|
||||||
|
when you are not certain if they might loop is to place a <literal>LIMIT</>
|
||||||
|
in the parent query. For example, this query would loop forever without
|
||||||
|
the <literal>LIMIT</>:
|
||||||
|
|
||||||
<programlisting>
|
<programlisting>
|
||||||
WITH RECURSIVE t(n) AS (
|
WITH RECURSIVE t(n) AS (
|
||||||
@ -1614,7 +1621,7 @@ SELECT n FROM t LIMIT 100;
|
|||||||
|
|
||||||
This works because <productname>PostgreSQL</productname>'s implementation
|
This works because <productname>PostgreSQL</productname>'s implementation
|
||||||
evaluates only as many rows of a <literal>WITH</> query as are actually
|
evaluates only as many rows of a <literal>WITH</> query as are actually
|
||||||
demanded by the parent query. Using this trick in production is not
|
fetched by the parent query. Using this trick in production is not
|
||||||
recommended, because other systems might work differently.
|
recommended, because other systems might work differently.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<!--
|
<!--
|
||||||
$PostgreSQL: pgsql/doc/src/sgml/ref/select.sgml,v 1.105 2008/10/04 21:56:52 tgl Exp $
|
$PostgreSQL: pgsql/doc/src/sgml/ref/select.sgml,v 1.106 2008/10/07 19:27:04 tgl Exp $
|
||||||
PostgreSQL documentation
|
PostgreSQL documentation
|
||||||
-->
|
-->
|
||||||
|
|
||||||
@ -202,10 +202,10 @@ and <replaceable class="parameter">with_query</replaceable> is:
|
|||||||
subquery to reference itself by name. Such a subquery must have
|
subquery to reference itself by name. Such a subquery must have
|
||||||
the form
|
the form
|
||||||
<synopsis>
|
<synopsis>
|
||||||
<replaceable class="parameter">non_recursive_term</replaceable> UNION ALL <replaceable class="parameter">recursive_term</replaceable>
|
<replaceable class="parameter">non_recursive_term</replaceable> UNION [ ALL ] <replaceable class="parameter">recursive_term</replaceable>
|
||||||
</synopsis>
|
</synopsis>
|
||||||
where the recursive self-reference must appear on the right-hand
|
where the recursive self-reference must appear on the right-hand
|
||||||
side of <literal>UNION ALL</>. Only one recursive self-reference
|
side of the <literal>UNION</>. Only one recursive self-reference
|
||||||
is permitted per query.
|
is permitted per query.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
@ -1234,7 +1234,7 @@ SELECT distance, employee_name FROM employee_recursive;
|
|||||||
</programlisting>
|
</programlisting>
|
||||||
|
|
||||||
Notice the typical form of recursive queries:
|
Notice the typical form of recursive queries:
|
||||||
an initial condition, followed by <literal>UNION ALL</literal>,
|
an initial condition, followed by <literal>UNION</literal>,
|
||||||
followed by the recursive part of the query. Be sure that the
|
followed by the recursive part of the query. Be sure that the
|
||||||
recursive part of the query will eventually return no tuples, or
|
recursive part of the query will eventually return no tuples, or
|
||||||
else the query will loop indefinitely. (See <xref linkend="queries-with">
|
else the query will loop indefinitely. (See <xref linkend="queries-with">
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* IDENTIFICATION
|
||||||
* $PostgreSQL: pgsql/src/backend/executor/nodeRecursiveunion.c,v 1.1 2008/10/04 21:56:53 tgl Exp $
|
* $PostgreSQL: pgsql/src/backend/executor/nodeRecursiveunion.c,v 1.2 2008/10/07 19:27:04 tgl Exp $
|
||||||
*
|
*
|
||||||
*-------------------------------------------------------------------------
|
*-------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
@ -17,6 +17,41 @@
|
|||||||
#include "executor/execdebug.h"
|
#include "executor/execdebug.h"
|
||||||
#include "executor/nodeRecursiveunion.h"
|
#include "executor/nodeRecursiveunion.h"
|
||||||
#include "miscadmin.h"
|
#include "miscadmin.h"
|
||||||
|
#include "utils/memutils.h"
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* To implement UNION (without ALL), we need a hashtable that stores tuples
|
||||||
|
* already seen. The hash key is computed from the grouping columns.
|
||||||
|
*/
|
||||||
|
typedef struct RUHashEntryData *RUHashEntry;
|
||||||
|
|
||||||
|
typedef struct RUHashEntryData
|
||||||
|
{
|
||||||
|
TupleHashEntryData shared; /* common header for hash table entries */
|
||||||
|
} RUHashEntryData;
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Initialize the hash table to empty.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
build_hash_table(RecursiveUnionState *rustate)
|
||||||
|
{
|
||||||
|
RecursiveUnion *node = (RecursiveUnion *) rustate->ps.plan;
|
||||||
|
|
||||||
|
Assert(node->numCols > 0);
|
||||||
|
Assert(node->numGroups > 0);
|
||||||
|
|
||||||
|
rustate->hashtable = BuildTupleHashTable(node->numCols,
|
||||||
|
node->dupColIdx,
|
||||||
|
rustate->eqfunctions,
|
||||||
|
rustate->hashfunctions,
|
||||||
|
node->numGroups,
|
||||||
|
sizeof(RUHashEntryData),
|
||||||
|
rustate->tableContext,
|
||||||
|
rustate->tempContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* ----------------------------------------------------------------
|
/* ----------------------------------------------------------------
|
||||||
@ -44,49 +79,85 @@ ExecRecursiveUnion(RecursiveUnionState *node)
|
|||||||
PlanState *innerPlan = innerPlanState(node);
|
PlanState *innerPlan = innerPlanState(node);
|
||||||
RecursiveUnion *plan = (RecursiveUnion *) node->ps.plan;
|
RecursiveUnion *plan = (RecursiveUnion *) node->ps.plan;
|
||||||
TupleTableSlot *slot;
|
TupleTableSlot *slot;
|
||||||
|
RUHashEntry entry;
|
||||||
|
bool isnew;
|
||||||
|
|
||||||
/* 1. Evaluate non-recursive term */
|
/* 1. Evaluate non-recursive term */
|
||||||
if (!node->recursing)
|
if (!node->recursing)
|
||||||
{
|
{
|
||||||
slot = ExecProcNode(outerPlan);
|
for (;;)
|
||||||
if (!TupIsNull(slot))
|
|
||||||
{
|
{
|
||||||
|
slot = ExecProcNode(outerPlan);
|
||||||
|
if (TupIsNull(slot))
|
||||||
|
break;
|
||||||
|
if (plan->numCols > 0)
|
||||||
|
{
|
||||||
|
/* Find or build hashtable entry for this tuple's group */
|
||||||
|
entry = (RUHashEntry)
|
||||||
|
LookupTupleHashEntry(node->hashtable, slot, &isnew);
|
||||||
|
/* Must reset temp context after each hashtable lookup */
|
||||||
|
MemoryContextReset(node->tempContext);
|
||||||
|
/* Ignore tuple if already seen */
|
||||||
|
if (!isnew)
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
/* Each non-duplicate tuple goes to the working table ... */
|
||||||
tuplestore_puttupleslot(node->working_table, slot);
|
tuplestore_puttupleslot(node->working_table, slot);
|
||||||
|
/* ... and to the caller */
|
||||||
return slot;
|
return slot;
|
||||||
}
|
}
|
||||||
node->recursing = true;
|
node->recursing = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
retry:
|
|
||||||
/* 2. Execute recursive term */
|
/* 2. Execute recursive term */
|
||||||
slot = ExecProcNode(innerPlan);
|
for (;;)
|
||||||
if (TupIsNull(slot))
|
|
||||||
{
|
{
|
||||||
if (node->intermediate_empty)
|
slot = ExecProcNode(innerPlan);
|
||||||
return NULL;
|
if (TupIsNull(slot))
|
||||||
|
{
|
||||||
|
/* Done if there's nothing in the intermediate table */
|
||||||
|
if (node->intermediate_empty)
|
||||||
|
break;
|
||||||
|
|
||||||
/* done with old working table ... */
|
/* done with old working table ... */
|
||||||
tuplestore_end(node->working_table);
|
tuplestore_end(node->working_table);
|
||||||
|
|
||||||
/* intermediate table becomes working table */
|
/* intermediate table becomes working table */
|
||||||
node->working_table = node->intermediate_table;
|
node->working_table = node->intermediate_table;
|
||||||
|
|
||||||
/* create new empty intermediate table */
|
/* create new empty intermediate table */
|
||||||
node->intermediate_table = tuplestore_begin_heap(false, false, work_mem);
|
node->intermediate_table = tuplestore_begin_heap(false, false,
|
||||||
node->intermediate_empty = true;
|
work_mem);
|
||||||
|
node->intermediate_empty = true;
|
||||||
|
|
||||||
/* and reset the inner plan */
|
/* reset the recursive term */
|
||||||
innerPlan->chgParam = bms_add_member(innerPlan->chgParam,
|
innerPlan->chgParam = bms_add_member(innerPlan->chgParam,
|
||||||
plan->wtParam);
|
plan->wtParam);
|
||||||
goto retry;
|
|
||||||
}
|
/* and continue fetching from recursive term */
|
||||||
else
|
continue;
|
||||||
{
|
}
|
||||||
|
|
||||||
|
if (plan->numCols > 0)
|
||||||
|
{
|
||||||
|
/* Find or build hashtable entry for this tuple's group */
|
||||||
|
entry = (RUHashEntry)
|
||||||
|
LookupTupleHashEntry(node->hashtable, slot, &isnew);
|
||||||
|
/* Must reset temp context after each hashtable lookup */
|
||||||
|
MemoryContextReset(node->tempContext);
|
||||||
|
/* Ignore tuple if already seen */
|
||||||
|
if (!isnew)
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Else, tuple is good; stash it in intermediate table ... */
|
||||||
node->intermediate_empty = false;
|
node->intermediate_empty = false;
|
||||||
tuplestore_puttupleslot(node->intermediate_table, slot);
|
tuplestore_puttupleslot(node->intermediate_table, slot);
|
||||||
}
|
/* ... and return it */
|
||||||
|
return slot;
|
||||||
|
}
|
||||||
|
|
||||||
return slot;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ----------------------------------------------------------------
|
/* ----------------------------------------------------------------
|
||||||
@ -109,12 +180,40 @@ ExecInitRecursiveUnion(RecursiveUnion *node, EState *estate, int eflags)
|
|||||||
rustate->ps.plan = (Plan *) node;
|
rustate->ps.plan = (Plan *) node;
|
||||||
rustate->ps.state = estate;
|
rustate->ps.state = estate;
|
||||||
|
|
||||||
|
rustate->eqfunctions = NULL;
|
||||||
|
rustate->hashfunctions = NULL;
|
||||||
|
rustate->hashtable = NULL;
|
||||||
|
rustate->tempContext = NULL;
|
||||||
|
rustate->tableContext = NULL;
|
||||||
|
|
||||||
/* initialize processing state */
|
/* initialize processing state */
|
||||||
rustate->recursing = false;
|
rustate->recursing = false;
|
||||||
rustate->intermediate_empty = true;
|
rustate->intermediate_empty = true;
|
||||||
rustate->working_table = tuplestore_begin_heap(false, false, work_mem);
|
rustate->working_table = tuplestore_begin_heap(false, false, work_mem);
|
||||||
rustate->intermediate_table = tuplestore_begin_heap(false, false, work_mem);
|
rustate->intermediate_table = tuplestore_begin_heap(false, false, work_mem);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If hashing, we need a per-tuple memory context for comparisons, and a
|
||||||
|
* longer-lived context to store the hash table. The table can't just be
|
||||||
|
* kept in the per-query context because we want to be able to throw it
|
||||||
|
* away when rescanning.
|
||||||
|
*/
|
||||||
|
if (node->numCols > 0)
|
||||||
|
{
|
||||||
|
rustate->tempContext =
|
||||||
|
AllocSetContextCreate(CurrentMemoryContext,
|
||||||
|
"RecursiveUnion",
|
||||||
|
ALLOCSET_DEFAULT_MINSIZE,
|
||||||
|
ALLOCSET_DEFAULT_INITSIZE,
|
||||||
|
ALLOCSET_DEFAULT_MAXSIZE);
|
||||||
|
rustate->tableContext =
|
||||||
|
AllocSetContextCreate(CurrentMemoryContext,
|
||||||
|
"RecursiveUnion hash table",
|
||||||
|
ALLOCSET_DEFAULT_MINSIZE,
|
||||||
|
ALLOCSET_DEFAULT_INITSIZE,
|
||||||
|
ALLOCSET_DEFAULT_MAXSIZE);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Make the state structure available to descendant WorkTableScan nodes
|
* Make the state structure available to descendant WorkTableScan nodes
|
||||||
* via the Param slot reserved for it.
|
* via the Param slot reserved for it.
|
||||||
@ -154,6 +253,19 @@ ExecInitRecursiveUnion(RecursiveUnion *node, EState *estate, int eflags)
|
|||||||
outerPlanState(rustate) = ExecInitNode(outerPlan(node), estate, eflags);
|
outerPlanState(rustate) = ExecInitNode(outerPlan(node), estate, eflags);
|
||||||
innerPlanState(rustate) = ExecInitNode(innerPlan(node), estate, eflags);
|
innerPlanState(rustate) = ExecInitNode(innerPlan(node), estate, eflags);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If hashing, precompute fmgr lookup data for inner loop, and create
|
||||||
|
* the hash table.
|
||||||
|
*/
|
||||||
|
if (node->numCols > 0)
|
||||||
|
{
|
||||||
|
execTuplesHashPrepare(node->numCols,
|
||||||
|
node->dupOperators,
|
||||||
|
&rustate->eqfunctions,
|
||||||
|
&rustate->hashfunctions);
|
||||||
|
build_hash_table(rustate);
|
||||||
|
}
|
||||||
|
|
||||||
return rustate;
|
return rustate;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -178,6 +290,12 @@ ExecEndRecursiveUnion(RecursiveUnionState *node)
|
|||||||
tuplestore_end(node->working_table);
|
tuplestore_end(node->working_table);
|
||||||
tuplestore_end(node->intermediate_table);
|
tuplestore_end(node->intermediate_table);
|
||||||
|
|
||||||
|
/* free subsidiary stuff including hashtable */
|
||||||
|
if (node->tempContext)
|
||||||
|
MemoryContextDelete(node->tempContext);
|
||||||
|
if (node->tableContext)
|
||||||
|
MemoryContextDelete(node->tableContext);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* clean out the upper tuple table
|
* clean out the upper tuple table
|
||||||
*/
|
*/
|
||||||
@ -217,6 +335,14 @@ ExecRecursiveUnionReScan(RecursiveUnionState *node, ExprContext *exprCtxt)
|
|||||||
if (outerPlan->chgParam == NULL)
|
if (outerPlan->chgParam == NULL)
|
||||||
ExecReScan(outerPlan, exprCtxt);
|
ExecReScan(outerPlan, exprCtxt);
|
||||||
|
|
||||||
|
/* Release any hashtable storage */
|
||||||
|
if (node->tableContext)
|
||||||
|
MemoryContextResetAndDeleteChildren(node->tableContext);
|
||||||
|
|
||||||
|
/* And rebuild empty hashtable if needed */
|
||||||
|
if (plan->numCols > 0)
|
||||||
|
build_hash_table(node);
|
||||||
|
|
||||||
/* reset processing state */
|
/* reset processing state */
|
||||||
node->recursing = false;
|
node->recursing = false;
|
||||||
node->intermediate_empty = true;
|
node->intermediate_empty = true;
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
* Portions Copyright (c) 1994, Regents of the University of California
|
* Portions Copyright (c) 1994, Regents of the University of California
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* IDENTIFICATION
|
||||||
* $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.407 2008/10/06 17:39:25 tgl Exp $
|
* $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.408 2008/10/07 19:27:04 tgl Exp $
|
||||||
*
|
*
|
||||||
*-------------------------------------------------------------------------
|
*-------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
@ -193,6 +193,13 @@ _copyRecursiveUnion(RecursiveUnion *from)
|
|||||||
* copy remainder of node
|
* copy remainder of node
|
||||||
*/
|
*/
|
||||||
COPY_SCALAR_FIELD(wtParam);
|
COPY_SCALAR_FIELD(wtParam);
|
||||||
|
COPY_SCALAR_FIELD(numCols);
|
||||||
|
if (from->numCols > 0)
|
||||||
|
{
|
||||||
|
COPY_POINTER_FIELD(dupColIdx, from->numCols * sizeof(AttrNumber));
|
||||||
|
COPY_POINTER_FIELD(dupOperators, from->numCols * sizeof(Oid));
|
||||||
|
}
|
||||||
|
COPY_SCALAR_FIELD(numGroups);
|
||||||
|
|
||||||
return newnode;
|
return newnode;
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* IDENTIFICATION
|
||||||
* $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.341 2008/10/06 17:39:26 tgl Exp $
|
* $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.342 2008/10/07 19:27:04 tgl Exp $
|
||||||
*
|
*
|
||||||
* NOTES
|
* NOTES
|
||||||
* Every node type that can appear in stored rules' parsetrees *must*
|
* Every node type that can appear in stored rules' parsetrees *must*
|
||||||
@ -334,11 +334,24 @@ _outAppend(StringInfo str, Append *node)
|
|||||||
static void
|
static void
|
||||||
_outRecursiveUnion(StringInfo str, RecursiveUnion *node)
|
_outRecursiveUnion(StringInfo str, RecursiveUnion *node)
|
||||||
{
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
WRITE_NODE_TYPE("RECURSIVEUNION");
|
WRITE_NODE_TYPE("RECURSIVEUNION");
|
||||||
|
|
||||||
_outPlanInfo(str, (Plan *) node);
|
_outPlanInfo(str, (Plan *) node);
|
||||||
|
|
||||||
WRITE_INT_FIELD(wtParam);
|
WRITE_INT_FIELD(wtParam);
|
||||||
|
WRITE_INT_FIELD(numCols);
|
||||||
|
|
||||||
|
appendStringInfo(str, " :dupColIdx");
|
||||||
|
for (i = 0; i < node->numCols; i++)
|
||||||
|
appendStringInfo(str, " %d", node->dupColIdx[i]);
|
||||||
|
|
||||||
|
appendStringInfo(str, " :dupOperators");
|
||||||
|
for (i = 0; i < node->numCols; i++)
|
||||||
|
appendStringInfo(str, " %u", node->dupOperators[i]);
|
||||||
|
|
||||||
|
WRITE_LONG_FIELD(numGroups);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* IDENTIFICATION
|
||||||
* $PostgreSQL: pgsql/src/backend/optimizer/plan/createplan.c,v 1.249 2008/10/04 21:56:53 tgl Exp $
|
* $PostgreSQL: pgsql/src/backend/optimizer/plan/createplan.c,v 1.250 2008/10/07 19:27:04 tgl Exp $
|
||||||
*
|
*
|
||||||
*-------------------------------------------------------------------------
|
*-------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
@ -2545,10 +2545,13 @@ RecursiveUnion *
|
|||||||
make_recursive_union(List *tlist,
|
make_recursive_union(List *tlist,
|
||||||
Plan *lefttree,
|
Plan *lefttree,
|
||||||
Plan *righttree,
|
Plan *righttree,
|
||||||
int wtParam)
|
int wtParam,
|
||||||
|
List *distinctList,
|
||||||
|
long numGroups)
|
||||||
{
|
{
|
||||||
RecursiveUnion *node = makeNode(RecursiveUnion);
|
RecursiveUnion *node = makeNode(RecursiveUnion);
|
||||||
Plan *plan = &node->plan;
|
Plan *plan = &node->plan;
|
||||||
|
int numCols = list_length(distinctList);
|
||||||
|
|
||||||
cost_recursive_union(plan, lefttree, righttree);
|
cost_recursive_union(plan, lefttree, righttree);
|
||||||
|
|
||||||
@ -2558,6 +2561,37 @@ make_recursive_union(List *tlist,
|
|||||||
plan->righttree = righttree;
|
plan->righttree = righttree;
|
||||||
node->wtParam = wtParam;
|
node->wtParam = wtParam;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* convert SortGroupClause list into arrays of attr indexes and equality
|
||||||
|
* operators, as wanted by executor
|
||||||
|
*/
|
||||||
|
node->numCols = numCols;
|
||||||
|
if (numCols > 0)
|
||||||
|
{
|
||||||
|
int keyno = 0;
|
||||||
|
AttrNumber *dupColIdx;
|
||||||
|
Oid *dupOperators;
|
||||||
|
ListCell *slitem;
|
||||||
|
|
||||||
|
dupColIdx = (AttrNumber *) palloc(sizeof(AttrNumber) * numCols);
|
||||||
|
dupOperators = (Oid *) palloc(sizeof(Oid) * numCols);
|
||||||
|
|
||||||
|
foreach(slitem, distinctList)
|
||||||
|
{
|
||||||
|
SortGroupClause *sortcl = (SortGroupClause *) lfirst(slitem);
|
||||||
|
TargetEntry *tle = get_sortgroupclause_tle(sortcl,
|
||||||
|
plan->targetlist);
|
||||||
|
|
||||||
|
dupColIdx[keyno] = tle->resno;
|
||||||
|
dupOperators[keyno] = sortcl->eqop;
|
||||||
|
Assert(OidIsValid(dupOperators[keyno]));
|
||||||
|
keyno++;
|
||||||
|
}
|
||||||
|
node->dupColIdx = dupColIdx;
|
||||||
|
node->dupOperators = dupOperators;
|
||||||
|
}
|
||||||
|
node->numGroups = numGroups;
|
||||||
|
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* IDENTIFICATION
|
||||||
* $PostgreSQL: pgsql/src/backend/optimizer/prep/prepunion.c,v 1.157 2008/10/06 17:39:26 tgl Exp $
|
* $PostgreSQL: pgsql/src/backend/optimizer/prep/prepunion.c,v 1.158 2008/10/07 19:27:04 tgl Exp $
|
||||||
*
|
*
|
||||||
*-------------------------------------------------------------------------
|
*-------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
@ -318,10 +318,12 @@ generate_recursion_plan(SetOperationStmt *setOp, PlannerInfo *root,
|
|||||||
Plan *lplan;
|
Plan *lplan;
|
||||||
Plan *rplan;
|
Plan *rplan;
|
||||||
List *tlist;
|
List *tlist;
|
||||||
|
List *groupList;
|
||||||
|
long numGroups;
|
||||||
|
|
||||||
/* Parser should have rejected other cases */
|
/* Parser should have rejected other cases */
|
||||||
if (setOp->op != SETOP_UNION || !setOp->all)
|
if (setOp->op != SETOP_UNION)
|
||||||
elog(ERROR, "only UNION ALL queries can be recursive");
|
elog(ERROR, "only UNION queries can be recursive");
|
||||||
/* Worktable ID should be assigned */
|
/* Worktable ID should be assigned */
|
||||||
Assert(root->wt_param_id >= 0);
|
Assert(root->wt_param_id >= 0);
|
||||||
|
|
||||||
@ -346,13 +348,46 @@ generate_recursion_plan(SetOperationStmt *setOp, PlannerInfo *root,
|
|||||||
list_make2(lplan, rplan),
|
list_make2(lplan, rplan),
|
||||||
refnames_tlist);
|
refnames_tlist);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If UNION, identify the grouping operators
|
||||||
|
*/
|
||||||
|
if (setOp->all)
|
||||||
|
{
|
||||||
|
groupList = NIL;
|
||||||
|
numGroups = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
double dNumGroups;
|
||||||
|
|
||||||
|
/* Identify the grouping semantics */
|
||||||
|
groupList = generate_setop_grouplist(setOp, tlist);
|
||||||
|
|
||||||
|
/* We only support hashing here */
|
||||||
|
if (!grouping_is_hashable(groupList))
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||||
|
errmsg("could not implement recursive UNION"),
|
||||||
|
errdetail("All column datatypes must be hashable.")));
|
||||||
|
|
||||||
|
/*
|
||||||
|
* For the moment, take the number of distinct groups as equal to
|
||||||
|
* the total input size, ie, the worst case.
|
||||||
|
*/
|
||||||
|
dNumGroups = lplan->plan_rows + rplan->plan_rows * 10;
|
||||||
|
|
||||||
|
/* Also convert to long int --- but 'ware overflow! */
|
||||||
|
numGroups = (long) Min(dNumGroups, (double) LONG_MAX);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* And make the plan node.
|
* And make the plan node.
|
||||||
*/
|
*/
|
||||||
plan = (Plan *) make_recursive_union(tlist, lplan, rplan,
|
plan = (Plan *) make_recursive_union(tlist, lplan, rplan,
|
||||||
root->wt_param_id);
|
root->wt_param_id,
|
||||||
|
groupList, numGroups);
|
||||||
|
|
||||||
*sortClauses = NIL; /* result of UNION ALL is always unsorted */
|
*sortClauses = NIL; /* RecursiveUnion result is always unsorted */
|
||||||
|
|
||||||
return plan;
|
return plan;
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* IDENTIFICATION
|
||||||
* $PostgreSQL: pgsql/src/backend/parser/parse_cte.c,v 2.2 2008/10/05 22:50:55 tgl Exp $
|
* $PostgreSQL: pgsql/src/backend/parser/parse_cte.c,v 2.3 2008/10/07 19:27:04 tgl Exp $
|
||||||
*
|
*
|
||||||
*-------------------------------------------------------------------------
|
*-------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
@ -601,11 +601,11 @@ checkWellFormedRecursion(CteState *cstate)
|
|||||||
if (!cte->cterecursive)
|
if (!cte->cterecursive)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
/* Must have top-level UNION ALL */
|
/* Must have top-level UNION */
|
||||||
if (stmt->op != SETOP_UNION || !stmt->all)
|
if (stmt->op != SETOP_UNION)
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
(errcode(ERRCODE_INVALID_RECURSION),
|
(errcode(ERRCODE_INVALID_RECURSION),
|
||||||
errmsg("recursive query \"%s\" does not have the form non-recursive-term UNION ALL recursive-term",
|
errmsg("recursive query \"%s\" does not have the form non-recursive-term UNION [ALL] recursive-term",
|
||||||
cte->ctename),
|
cte->ctename),
|
||||||
parser_errposition(cstate->pstate, cte->location)));
|
parser_errposition(cstate->pstate, cte->location)));
|
||||||
|
|
||||||
@ -628,7 +628,7 @@ checkWellFormedRecursion(CteState *cstate)
|
|||||||
elog(ERROR, "missing recursive reference");
|
elog(ERROR, "missing recursive reference");
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Disallow ORDER BY and similar decoration atop the UNION ALL.
|
* Disallow ORDER BY and similar decoration atop the UNION.
|
||||||
* These don't make sense because it's impossible to figure out what
|
* These don't make sense because it's impossible to figure out what
|
||||||
* they mean when we have only part of the recursive query's results.
|
* they mean when we have only part of the recursive query's results.
|
||||||
* (If we did allow them, we'd have to check for recursive references
|
* (If we did allow them, we'd have to check for recursive references
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
|
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
|
||||||
* Portions Copyright (c) 1994, Regents of the University of California
|
* Portions Copyright (c) 1994, Regents of the University of California
|
||||||
*
|
*
|
||||||
* $PostgreSQL: pgsql/src/include/nodes/execnodes.h,v 1.189 2008/10/04 21:56:55 tgl Exp $
|
* $PostgreSQL: pgsql/src/include/nodes/execnodes.h,v 1.190 2008/10/07 19:27:04 tgl Exp $
|
||||||
*
|
*
|
||||||
*-------------------------------------------------------------------------
|
*-------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
@ -964,6 +964,12 @@ typedef struct RecursiveUnionState
|
|||||||
bool intermediate_empty;
|
bool intermediate_empty;
|
||||||
Tuplestorestate *working_table;
|
Tuplestorestate *working_table;
|
||||||
Tuplestorestate *intermediate_table;
|
Tuplestorestate *intermediate_table;
|
||||||
|
/* Remaining fields are unused in UNION ALL case */
|
||||||
|
FmgrInfo *eqfunctions; /* per-grouping-field equality fns */
|
||||||
|
FmgrInfo *hashfunctions; /* per-grouping-field hash fns */
|
||||||
|
MemoryContext tempContext; /* short-term context for comparisons */
|
||||||
|
TupleHashTable hashtable; /* hash table for tuples already seen */
|
||||||
|
MemoryContext tableContext; /* memory context containing hash table */
|
||||||
} RecursiveUnionState;
|
} RecursiveUnionState;
|
||||||
|
|
||||||
/* ----------------
|
/* ----------------
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
|
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
|
||||||
* Portions Copyright (c) 1994, Regents of the University of California
|
* Portions Copyright (c) 1994, Regents of the University of California
|
||||||
*
|
*
|
||||||
* $PostgreSQL: pgsql/src/include/nodes/plannodes.h,v 1.104 2008/10/04 21:56:55 tgl Exp $
|
* $PostgreSQL: pgsql/src/include/nodes/plannodes.h,v 1.105 2008/10/07 19:27:04 tgl Exp $
|
||||||
*
|
*
|
||||||
*-------------------------------------------------------------------------
|
*-------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
@ -194,6 +194,12 @@ typedef struct RecursiveUnion
|
|||||||
{
|
{
|
||||||
Plan plan;
|
Plan plan;
|
||||||
int wtParam; /* ID of Param representing work table */
|
int wtParam; /* ID of Param representing work table */
|
||||||
|
/* Remaining fields are zero/null in UNION ALL case */
|
||||||
|
int numCols; /* number of columns to check for
|
||||||
|
* duplicate-ness */
|
||||||
|
AttrNumber *dupColIdx; /* their indexes in the target list */
|
||||||
|
Oid *dupOperators; /* equality operators to compare with */
|
||||||
|
long numGroups; /* estimated number of groups in input */
|
||||||
} RecursiveUnion;
|
} RecursiveUnion;
|
||||||
|
|
||||||
/* ----------------
|
/* ----------------
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
|
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
|
||||||
* Portions Copyright (c) 1994, Regents of the University of California
|
* Portions Copyright (c) 1994, Regents of the University of California
|
||||||
*
|
*
|
||||||
* $PostgreSQL: pgsql/src/include/optimizer/planmain.h,v 1.113 2008/10/04 21:56:55 tgl Exp $
|
* $PostgreSQL: pgsql/src/include/optimizer/planmain.h,v 1.114 2008/10/07 19:27:04 tgl Exp $
|
||||||
*
|
*
|
||||||
*-------------------------------------------------------------------------
|
*-------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
@ -43,7 +43,8 @@ extern SubqueryScan *make_subqueryscan(List *qptlist, List *qpqual,
|
|||||||
Index scanrelid, Plan *subplan, List *subrtable);
|
Index scanrelid, Plan *subplan, List *subrtable);
|
||||||
extern Append *make_append(List *appendplans, bool isTarget, List *tlist);
|
extern Append *make_append(List *appendplans, bool isTarget, List *tlist);
|
||||||
extern RecursiveUnion *make_recursive_union(List *tlist,
|
extern RecursiveUnion *make_recursive_union(List *tlist,
|
||||||
Plan *lefttree, Plan *righttree, int wtParam);
|
Plan *lefttree, Plan *righttree, int wtParam,
|
||||||
|
List *distinctList, long numGroups);
|
||||||
extern Sort *make_sort_from_pathkeys(PlannerInfo *root, Plan *lefttree,
|
extern Sort *make_sort_from_pathkeys(PlannerInfo *root, Plan *lefttree,
|
||||||
List *pathkeys, double limit_tuples);
|
List *pathkeys, double limit_tuples);
|
||||||
extern Sort *make_sort_from_sortclauses(PlannerInfo *root, List *sortcls,
|
extern Sort *make_sort_from_sortclauses(PlannerInfo *root, List *sortcls,
|
||||||
|
@ -49,6 +49,18 @@ SELECT * FROM t;
|
|||||||
5
|
5
|
||||||
(5 rows)
|
(5 rows)
|
||||||
|
|
||||||
|
-- This is an infinite loop with UNION ALL, but not with UNION
|
||||||
|
WITH RECURSIVE t(n) AS (
|
||||||
|
SELECT 1
|
||||||
|
UNION
|
||||||
|
SELECT 10-n FROM t)
|
||||||
|
SELECT * FROM t;
|
||||||
|
n
|
||||||
|
---
|
||||||
|
1
|
||||||
|
9
|
||||||
|
(2 rows)
|
||||||
|
|
||||||
-- This'd be an infinite loop, but outside query reads only as much as needed
|
-- This'd be an infinite loop, but outside query reads only as much as needed
|
||||||
WITH RECURSIVE t(n) AS (
|
WITH RECURSIVE t(n) AS (
|
||||||
VALUES (1)
|
VALUES (1)
|
||||||
@ -69,6 +81,26 @@ SELECT * FROM t LIMIT 10;
|
|||||||
10
|
10
|
||||||
(10 rows)
|
(10 rows)
|
||||||
|
|
||||||
|
-- UNION case should have same property
|
||||||
|
WITH RECURSIVE t(n) AS (
|
||||||
|
SELECT 1
|
||||||
|
UNION
|
||||||
|
SELECT n+1 FROM t)
|
||||||
|
SELECT * FROM t LIMIT 10;
|
||||||
|
n
|
||||||
|
----
|
||||||
|
1
|
||||||
|
2
|
||||||
|
3
|
||||||
|
4
|
||||||
|
5
|
||||||
|
6
|
||||||
|
7
|
||||||
|
8
|
||||||
|
9
|
||||||
|
10
|
||||||
|
(10 rows)
|
||||||
|
|
||||||
-- Test behavior with an unknown-type literal in the WITH
|
-- Test behavior with an unknown-type literal in the WITH
|
||||||
WITH q AS (SELECT 'foo' AS x)
|
WITH q AS (SELECT 'foo' AS x)
|
||||||
SELECT x, x IS OF (unknown) as is_unknown FROM q;
|
SELECT x, x IS OF (unknown) as is_unknown FROM q;
|
||||||
@ -510,38 +542,32 @@ WITH RECURSIVE
|
|||||||
--
|
--
|
||||||
-- error cases
|
-- error cases
|
||||||
--
|
--
|
||||||
-- UNION (should be supported someday)
|
|
||||||
WITH RECURSIVE x(n) AS (SELECT 1 UNION SELECT n+1 FROM x)
|
|
||||||
SELECT * FROM x;
|
|
||||||
ERROR: recursive query "x" does not have the form non-recursive-term UNION ALL recursive-term
|
|
||||||
LINE 1: WITH RECURSIVE x(n) AS (SELECT 1 UNION SELECT n+1 FROM x)
|
|
||||||
^
|
|
||||||
-- INTERSECT
|
-- INTERSECT
|
||||||
WITH RECURSIVE x(n) AS (SELECT 1 INTERSECT SELECT n+1 FROM x)
|
WITH RECURSIVE x(n) AS (SELECT 1 INTERSECT SELECT n+1 FROM x)
|
||||||
SELECT * FROM x;
|
SELECT * FROM x;
|
||||||
ERROR: recursive query "x" does not have the form non-recursive-term UNION ALL recursive-term
|
ERROR: recursive query "x" does not have the form non-recursive-term UNION [ALL] recursive-term
|
||||||
LINE 1: WITH RECURSIVE x(n) AS (SELECT 1 INTERSECT SELECT n+1 FROM x...
|
LINE 1: WITH RECURSIVE x(n) AS (SELECT 1 INTERSECT SELECT n+1 FROM x...
|
||||||
^
|
^
|
||||||
WITH RECURSIVE x(n) AS (SELECT 1 INTERSECT ALL SELECT n+1 FROM x)
|
WITH RECURSIVE x(n) AS (SELECT 1 INTERSECT ALL SELECT n+1 FROM x)
|
||||||
SELECT * FROM x;
|
SELECT * FROM x;
|
||||||
ERROR: recursive query "x" does not have the form non-recursive-term UNION ALL recursive-term
|
ERROR: recursive query "x" does not have the form non-recursive-term UNION [ALL] recursive-term
|
||||||
LINE 1: WITH RECURSIVE x(n) AS (SELECT 1 INTERSECT ALL SELECT n+1 FR...
|
LINE 1: WITH RECURSIVE x(n) AS (SELECT 1 INTERSECT ALL SELECT n+1 FR...
|
||||||
^
|
^
|
||||||
-- EXCEPT
|
-- EXCEPT
|
||||||
WITH RECURSIVE x(n) AS (SELECT 1 EXCEPT SELECT n+1 FROM x)
|
WITH RECURSIVE x(n) AS (SELECT 1 EXCEPT SELECT n+1 FROM x)
|
||||||
SELECT * FROM x;
|
SELECT * FROM x;
|
||||||
ERROR: recursive query "x" does not have the form non-recursive-term UNION ALL recursive-term
|
ERROR: recursive query "x" does not have the form non-recursive-term UNION [ALL] recursive-term
|
||||||
LINE 1: WITH RECURSIVE x(n) AS (SELECT 1 EXCEPT SELECT n+1 FROM x)
|
LINE 1: WITH RECURSIVE x(n) AS (SELECT 1 EXCEPT SELECT n+1 FROM x)
|
||||||
^
|
^
|
||||||
WITH RECURSIVE x(n) AS (SELECT 1 EXCEPT ALL SELECT n+1 FROM x)
|
WITH RECURSIVE x(n) AS (SELECT 1 EXCEPT ALL SELECT n+1 FROM x)
|
||||||
SELECT * FROM x;
|
SELECT * FROM x;
|
||||||
ERROR: recursive query "x" does not have the form non-recursive-term UNION ALL recursive-term
|
ERROR: recursive query "x" does not have the form non-recursive-term UNION [ALL] recursive-term
|
||||||
LINE 1: WITH RECURSIVE x(n) AS (SELECT 1 EXCEPT ALL SELECT n+1 FROM ...
|
LINE 1: WITH RECURSIVE x(n) AS (SELECT 1 EXCEPT ALL SELECT n+1 FROM ...
|
||||||
^
|
^
|
||||||
-- no non-recursive term
|
-- no non-recursive term
|
||||||
WITH RECURSIVE x(n) AS (SELECT n FROM x)
|
WITH RECURSIVE x(n) AS (SELECT n FROM x)
|
||||||
SELECT * FROM x;
|
SELECT * FROM x;
|
||||||
ERROR: recursive query "x" does not have the form non-recursive-term UNION ALL recursive-term
|
ERROR: recursive query "x" does not have the form non-recursive-term UNION [ALL] recursive-term
|
||||||
LINE 1: WITH RECURSIVE x(n) AS (SELECT n FROM x)
|
LINE 1: WITH RECURSIVE x(n) AS (SELECT n FROM x)
|
||||||
^
|
^
|
||||||
-- recursive term in the left hand side (strictly speaking, should allow this)
|
-- recursive term in the left hand side (strictly speaking, should allow this)
|
||||||
|
@ -31,6 +31,13 @@ UNION ALL
|
|||||||
)
|
)
|
||||||
SELECT * FROM t;
|
SELECT * FROM t;
|
||||||
|
|
||||||
|
-- This is an infinite loop with UNION ALL, but not with UNION
|
||||||
|
WITH RECURSIVE t(n) AS (
|
||||||
|
SELECT 1
|
||||||
|
UNION
|
||||||
|
SELECT 10-n FROM t)
|
||||||
|
SELECT * FROM t;
|
||||||
|
|
||||||
-- This'd be an infinite loop, but outside query reads only as much as needed
|
-- This'd be an infinite loop, but outside query reads only as much as needed
|
||||||
WITH RECURSIVE t(n) AS (
|
WITH RECURSIVE t(n) AS (
|
||||||
VALUES (1)
|
VALUES (1)
|
||||||
@ -38,6 +45,13 @@ UNION ALL
|
|||||||
SELECT n+1 FROM t)
|
SELECT n+1 FROM t)
|
||||||
SELECT * FROM t LIMIT 10;
|
SELECT * FROM t LIMIT 10;
|
||||||
|
|
||||||
|
-- UNION case should have same property
|
||||||
|
WITH RECURSIVE t(n) AS (
|
||||||
|
SELECT 1
|
||||||
|
UNION
|
||||||
|
SELECT n+1 FROM t)
|
||||||
|
SELECT * FROM t LIMIT 10;
|
||||||
|
|
||||||
-- Test behavior with an unknown-type literal in the WITH
|
-- Test behavior with an unknown-type literal in the WITH
|
||||||
WITH q AS (SELECT 'foo' AS x)
|
WITH q AS (SELECT 'foo' AS x)
|
||||||
SELECT x, x IS OF (unknown) as is_unknown FROM q;
|
SELECT x, x IS OF (unknown) as is_unknown FROM q;
|
||||||
@ -265,10 +279,6 @@ WITH RECURSIVE
|
|||||||
-- error cases
|
-- error cases
|
||||||
--
|
--
|
||||||
|
|
||||||
-- UNION (should be supported someday)
|
|
||||||
WITH RECURSIVE x(n) AS (SELECT 1 UNION SELECT n+1 FROM x)
|
|
||||||
SELECT * FROM x;
|
|
||||||
|
|
||||||
-- INTERSECT
|
-- INTERSECT
|
||||||
WITH RECURSIVE x(n) AS (SELECT 1 INTERSECT SELECT n+1 FROM x)
|
WITH RECURSIVE x(n) AS (SELECT 1 INTERSECT SELECT n+1 FROM x)
|
||||||
SELECT * FROM x;
|
SELECT * FROM x;
|
||||||
|
Reference in New Issue
Block a user