2008-10-04 23:56:55 +02:00
|
|
|
--
|
|
|
|
-- Tests for common table expressions (WITH query, ... SELECT ...)
|
|
|
|
--
|
|
|
|
-- Basic WITH
|
|
|
|
WITH q1(x,y) AS (SELECT 1,2)
|
|
|
|
SELECT * FROM q1, q1 AS q2;
|
|
|
|
x | y | x | y
|
|
|
|
---+---+---+---
|
|
|
|
1 | 2 | 1 | 2
|
|
|
|
(1 row)
|
|
|
|
|
|
|
|
-- Multiple uses are evaluated only once
|
|
|
|
SELECT count(*) FROM (
|
|
|
|
WITH q1(x) AS (SELECT random() FROM generate_series(1, 5))
|
|
|
|
SELECT * FROM q1
|
|
|
|
UNION
|
|
|
|
SELECT * FROM q1
|
|
|
|
) ss;
|
|
|
|
count
|
|
|
|
-------
|
|
|
|
5
|
|
|
|
(1 row)
|
|
|
|
|
|
|
|
-- WITH RECURSIVE
|
|
|
|
-- sum of 1..100
|
|
|
|
WITH RECURSIVE t(n) AS (
|
|
|
|
VALUES (1)
|
|
|
|
UNION ALL
|
|
|
|
SELECT n+1 FROM t WHERE n < 100
|
|
|
|
)
|
|
|
|
SELECT sum(n) FROM t;
|
|
|
|
sum
|
|
|
|
------
|
|
|
|
5050
|
|
|
|
(1 row)
|
|
|
|
|
|
|
|
WITH RECURSIVE t(n) AS (
|
|
|
|
SELECT (VALUES(1))
|
|
|
|
UNION ALL
|
|
|
|
SELECT n+1 FROM t WHERE n < 5
|
|
|
|
)
|
|
|
|
SELECT * FROM t;
|
|
|
|
n
|
|
|
|
---
|
|
|
|
1
|
|
|
|
2
|
|
|
|
3
|
|
|
|
4
|
|
|
|
5
|
|
|
|
(5 rows)
|
|
|
|
|
2020-11-18 07:58:37 +01:00
|
|
|
-- UNION DISTINCT requires hashable type
|
|
|
|
WITH RECURSIVE t(n) AS (
|
2024-01-15 01:30:16 +01:00
|
|
|
VALUES ('01'::varbit)
|
2020-11-18 07:58:37 +01:00
|
|
|
UNION
|
2024-01-15 01:30:16 +01:00
|
|
|
SELECT n || '10'::varbit FROM t WHERE n < '100'::varbit
|
2020-11-18 07:58:37 +01:00
|
|
|
)
|
2024-01-15 01:30:16 +01:00
|
|
|
SELECT n FROM t;
|
2020-11-18 07:58:37 +01:00
|
|
|
ERROR: could not implement recursive UNION
|
|
|
|
DETAIL: All column datatypes must be hashable.
|
2013-02-01 04:31:58 +01:00
|
|
|
-- recursive view
|
|
|
|
CREATE RECURSIVE VIEW nums (n) AS
|
|
|
|
VALUES (1)
|
|
|
|
UNION ALL
|
|
|
|
SELECT n+1 FROM nums WHERE n < 5;
|
|
|
|
SELECT * FROM nums;
|
|
|
|
n
|
|
|
|
---
|
|
|
|
1
|
|
|
|
2
|
|
|
|
3
|
|
|
|
4
|
|
|
|
5
|
|
|
|
(5 rows)
|
|
|
|
|
|
|
|
CREATE OR REPLACE RECURSIVE VIEW nums (n) AS
|
|
|
|
VALUES (1)
|
|
|
|
UNION ALL
|
|
|
|
SELECT n+1 FROM nums WHERE n < 6;
|
|
|
|
SELECT * FROM nums;
|
|
|
|
n
|
|
|
|
---
|
|
|
|
1
|
|
|
|
2
|
|
|
|
3
|
|
|
|
4
|
|
|
|
5
|
|
|
|
6
|
|
|
|
(6 rows)
|
|
|
|
|
2008-10-07 21:27:04 +02:00
|
|
|
-- 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)
|
|
|
|
|
2008-10-04 23:56:55 +02:00
|
|
|
-- This'd be an infinite loop, but outside query reads only as much as needed
|
|
|
|
WITH RECURSIVE t(n) AS (
|
|
|
|
VALUES (1)
|
|
|
|
UNION ALL
|
|
|
|
SELECT n+1 FROM t)
|
|
|
|
SELECT * FROM t LIMIT 10;
|
|
|
|
n
|
|
|
|
----
|
|
|
|
1
|
|
|
|
2
|
|
|
|
3
|
|
|
|
4
|
|
|
|
5
|
|
|
|
6
|
|
|
|
7
|
|
|
|
8
|
|
|
|
9
|
|
|
|
10
|
|
|
|
(10 rows)
|
|
|
|
|
2008-10-07 21:27:04 +02:00
|
|
|
-- 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)
|
|
|
|
|
2008-10-06 00:50:55 +02:00
|
|
|
-- Test behavior with an unknown-type literal in the WITH
|
|
|
|
WITH q AS (SELECT 'foo' AS x)
|
Remove undocumented IS [NOT] OF syntax.
This feature was added a long time ago, in 7c1e67bd5 and eb121ba2c,
but never documented in any user-facing way. (Documentation added
in 6126d3e70 was commented out almost immediately, in 8272fc3f7.)
That's because, while this syntax is defined by SQL:99, our
implementation is only vaguely related to the standard's semantics.
The standard appears to intend a run-time not parse-time test, and
it definitely intends that the test should understand subtype
relationships.
No one has stepped up to fix that in the intervening years, but
people keep coming across the code and asking why it's not documented.
Let's just get rid of it: if anyone ever wants to make it work per
spec, they can easily recover whatever parts of this code are still
of value from our git history.
If there's anyone out there who's actually using this despite its
undocumented status, they can switch to using pg_typeof() instead,
eg. "pg_typeof(something) = 'mytype'::regtype". That gives
essentially the same semantics as what our IS OF code did.
(We didn't have that function last time this was discussed, or
we would have ripped out IS OF then.)
Discussion: https://postgr.es/m/CAKFQuwZ2pTc-DSkOiTfjauqLYkNREeNZvWmeg12Q-_69D+sYZA@mail.gmail.com
Discussion: https://postgr.es/m/BAY20-F23E9F2B4DAB3E4E88D3623F99B0@phx.gbl
Discussion: https://postgr.es/m/3E7CF81D.1000203@joeconway.com
2020-11-19 23:39:39 +01:00
|
|
|
SELECT x, pg_typeof(x) FROM q;
|
|
|
|
x | pg_typeof
|
|
|
|
-----+-----------
|
|
|
|
foo | text
|
2008-10-06 00:50:55 +02:00
|
|
|
(1 row)
|
|
|
|
|
|
|
|
WITH RECURSIVE t(n) AS (
|
|
|
|
SELECT 'foo'
|
|
|
|
UNION ALL
|
|
|
|
SELECT n || ' bar' FROM t WHERE length(n) < 20
|
|
|
|
)
|
Remove undocumented IS [NOT] OF syntax.
This feature was added a long time ago, in 7c1e67bd5 and eb121ba2c,
but never documented in any user-facing way. (Documentation added
in 6126d3e70 was commented out almost immediately, in 8272fc3f7.)
That's because, while this syntax is defined by SQL:99, our
implementation is only vaguely related to the standard's semantics.
The standard appears to intend a run-time not parse-time test, and
it definitely intends that the test should understand subtype
relationships.
No one has stepped up to fix that in the intervening years, but
people keep coming across the code and asking why it's not documented.
Let's just get rid of it: if anyone ever wants to make it work per
spec, they can easily recover whatever parts of this code are still
of value from our git history.
If there's anyone out there who's actually using this despite its
undocumented status, they can switch to using pg_typeof() instead,
eg. "pg_typeof(something) = 'mytype'::regtype". That gives
essentially the same semantics as what our IS OF code did.
(We didn't have that function last time this was discussed, or
we would have ripped out IS OF then.)
Discussion: https://postgr.es/m/CAKFQuwZ2pTc-DSkOiTfjauqLYkNREeNZvWmeg12Q-_69D+sYZA@mail.gmail.com
Discussion: https://postgr.es/m/BAY20-F23E9F2B4DAB3E4E88D3623F99B0@phx.gbl
Discussion: https://postgr.es/m/3E7CF81D.1000203@joeconway.com
2020-11-19 23:39:39 +01:00
|
|
|
SELECT n, pg_typeof(n) FROM t;
|
|
|
|
n | pg_typeof
|
|
|
|
-------------------------+-----------
|
|
|
|
foo | text
|
|
|
|
foo bar | text
|
|
|
|
foo bar bar | text
|
|
|
|
foo bar bar bar | text
|
|
|
|
foo bar bar bar bar | text
|
|
|
|
foo bar bar bar bar bar | text
|
2008-10-06 00:50:55 +02:00
|
|
|
(6 rows)
|
|
|
|
|
Change unknown-type literals to type text in SELECT and RETURNING lists.
Previously, we left such literals alone if the query or subquery had
no properties forcing a type decision to be made (such as an ORDER BY or
DISTINCT clause using that output column). This meant that "unknown" could
be an exposed output column type, which has never been a great idea because
it could result in strange failures later on. For example, an outer query
that tried to do any operations on an unknown-type subquery output would
generally fail with some weird error like "failed to find conversion
function from unknown to text" or "could not determine which collation to
use for string comparison". Also, if the case occurred in a CREATE VIEW's
query then the view would have an unknown-type column, causing similar
failures in queries trying to use the view.
To fix, at the tail end of parse analysis of a query, forcibly convert any
remaining "unknown" literals in its SELECT or RETURNING list to type text.
However, provide a switch to suppress that, and use it in the cases of
SELECT inside a set operation or INSERT command. In those cases we already
had type resolution rules that make use of context information from outside
the subquery proper, and we don't want to change that behavior.
Also, change creation of an unknown-type column in a relation from a
warning to a hard error. The error should be unreachable now in CREATE
VIEW or CREATE MATVIEW, but it's still possible to explicitly say "unknown"
in CREATE TABLE or CREATE (composite) TYPE. We want to forbid that because
it's nothing but a foot-gun.
This change creates a pg_upgrade failure case: a matview that contains an
unknown-type column can't be pg_upgraded, because reparsing the matview's
defining query will now decide that the column is of type text, which
doesn't match the cstring-like storage that the old materialized column
would actually have. Add a checking pass to detect that. While at it,
we can detect tables or composite types that would fail, essentially
for free. Those would fail safely anyway later on, but we might as
well fail earlier.
This patch is by me, but it owes something to previous investigations
by Rahila Syed. Also thanks to Ashutosh Bapat and Michael Paquier for
review.
Discussion: https://postgr.es/m/CAH2L28uwwbL9HUM-WR=hromW1Cvamkn7O-g8fPY2m=_7muJ0oA@mail.gmail.com
2017-01-25 15:17:18 +01:00
|
|
|
-- In a perfect world, this would work and resolve the literal as int ...
|
|
|
|
-- but for now, we have to be content with resolving to text too soon.
|
|
|
|
WITH RECURSIVE t(n) AS (
|
|
|
|
SELECT '7'
|
|
|
|
UNION ALL
|
|
|
|
SELECT n+1 FROM t WHERE n < 10
|
|
|
|
)
|
Remove undocumented IS [NOT] OF syntax.
This feature was added a long time ago, in 7c1e67bd5 and eb121ba2c,
but never documented in any user-facing way. (Documentation added
in 6126d3e70 was commented out almost immediately, in 8272fc3f7.)
That's because, while this syntax is defined by SQL:99, our
implementation is only vaguely related to the standard's semantics.
The standard appears to intend a run-time not parse-time test, and
it definitely intends that the test should understand subtype
relationships.
No one has stepped up to fix that in the intervening years, but
people keep coming across the code and asking why it's not documented.
Let's just get rid of it: if anyone ever wants to make it work per
spec, they can easily recover whatever parts of this code are still
of value from our git history.
If there's anyone out there who's actually using this despite its
undocumented status, they can switch to using pg_typeof() instead,
eg. "pg_typeof(something) = 'mytype'::regtype". That gives
essentially the same semantics as what our IS OF code did.
(We didn't have that function last time this was discussed, or
we would have ripped out IS OF then.)
Discussion: https://postgr.es/m/CAKFQuwZ2pTc-DSkOiTfjauqLYkNREeNZvWmeg12Q-_69D+sYZA@mail.gmail.com
Discussion: https://postgr.es/m/BAY20-F23E9F2B4DAB3E4E88D3623F99B0@phx.gbl
Discussion: https://postgr.es/m/3E7CF81D.1000203@joeconway.com
2020-11-19 23:39:39 +01:00
|
|
|
SELECT n, pg_typeof(n) FROM t;
|
Change unknown-type literals to type text in SELECT and RETURNING lists.
Previously, we left such literals alone if the query or subquery had
no properties forcing a type decision to be made (such as an ORDER BY or
DISTINCT clause using that output column). This meant that "unknown" could
be an exposed output column type, which has never been a great idea because
it could result in strange failures later on. For example, an outer query
that tried to do any operations on an unknown-type subquery output would
generally fail with some weird error like "failed to find conversion
function from unknown to text" or "could not determine which collation to
use for string comparison". Also, if the case occurred in a CREATE VIEW's
query then the view would have an unknown-type column, causing similar
failures in queries trying to use the view.
To fix, at the tail end of parse analysis of a query, forcibly convert any
remaining "unknown" literals in its SELECT or RETURNING list to type text.
However, provide a switch to suppress that, and use it in the cases of
SELECT inside a set operation or INSERT command. In those cases we already
had type resolution rules that make use of context information from outside
the subquery proper, and we don't want to change that behavior.
Also, change creation of an unknown-type column in a relation from a
warning to a hard error. The error should be unreachable now in CREATE
VIEW or CREATE MATVIEW, but it's still possible to explicitly say "unknown"
in CREATE TABLE or CREATE (composite) TYPE. We want to forbid that because
it's nothing but a foot-gun.
This change creates a pg_upgrade failure case: a matview that contains an
unknown-type column can't be pg_upgraded, because reparsing the matview's
defining query will now decide that the column is of type text, which
doesn't match the cstring-like storage that the old materialized column
would actually have. Add a checking pass to detect that. While at it,
we can detect tables or composite types that would fail, essentially
for free. Those would fail safely anyway later on, but we might as
well fail earlier.
This patch is by me, but it owes something to previous investigations
by Rahila Syed. Also thanks to Ashutosh Bapat and Michael Paquier for
review.
Discussion: https://postgr.es/m/CAH2L28uwwbL9HUM-WR=hromW1Cvamkn7O-g8fPY2m=_7muJ0oA@mail.gmail.com
2017-01-25 15:17:18 +01:00
|
|
|
ERROR: operator does not exist: text + integer
|
|
|
|
LINE 4: SELECT n+1 FROM t WHERE n < 10
|
|
|
|
^
|
2017-08-23 19:56:59 +02:00
|
|
|
HINT: No operator matches the given name and argument types. You might need to add explicit type casts.
|
2021-02-26 02:47:32 +01:00
|
|
|
-- Deeply nested WITH caused a list-munging problem in v13
|
|
|
|
-- Detection of cross-references and self-references
|
|
|
|
WITH RECURSIVE w1(c1) AS
|
|
|
|
(WITH w2(c2) AS
|
|
|
|
(WITH w3(c3) AS
|
|
|
|
(WITH w4(c4) AS
|
|
|
|
(WITH w5(c5) AS
|
|
|
|
(WITH RECURSIVE w6(c6) AS
|
|
|
|
(WITH w6(c6) AS
|
|
|
|
(WITH w8(c8) AS
|
|
|
|
(SELECT 1)
|
|
|
|
SELECT * FROM w8)
|
|
|
|
SELECT * FROM w6)
|
|
|
|
SELECT * FROM w6)
|
|
|
|
SELECT * FROM w5)
|
|
|
|
SELECT * FROM w4)
|
|
|
|
SELECT * FROM w3)
|
|
|
|
SELECT * FROM w2)
|
|
|
|
SELECT * FROM w1;
|
|
|
|
c1
|
|
|
|
----
|
|
|
|
1
|
|
|
|
(1 row)
|
|
|
|
|
|
|
|
-- Detection of invalid self-references
|
|
|
|
WITH RECURSIVE outermost(x) AS (
|
|
|
|
SELECT 1
|
|
|
|
UNION (WITH innermost1 AS (
|
|
|
|
SELECT 2
|
|
|
|
UNION (WITH innermost2 AS (
|
|
|
|
SELECT 3
|
|
|
|
UNION (WITH innermost3 AS (
|
|
|
|
SELECT 4
|
|
|
|
UNION (WITH innermost4 AS (
|
|
|
|
SELECT 5
|
|
|
|
UNION (WITH innermost5 AS (
|
|
|
|
SELECT 6
|
|
|
|
UNION (WITH innermost6 AS
|
|
|
|
(SELECT 7)
|
|
|
|
SELECT * FROM innermost6))
|
|
|
|
SELECT * FROM innermost5))
|
|
|
|
SELECT * FROM innermost4))
|
|
|
|
SELECT * FROM innermost3))
|
|
|
|
SELECT * FROM innermost2))
|
|
|
|
SELECT * FROM outermost
|
|
|
|
UNION SELECT * FROM innermost1)
|
|
|
|
)
|
|
|
|
SELECT * FROM outermost ORDER BY 1;
|
|
|
|
x
|
|
|
|
---
|
|
|
|
1
|
|
|
|
2
|
|
|
|
3
|
|
|
|
4
|
|
|
|
5
|
|
|
|
6
|
|
|
|
7
|
|
|
|
(7 rows)
|
|
|
|
|
2008-10-04 23:56:55 +02:00
|
|
|
--
|
|
|
|
-- Some examples with a tree
|
|
|
|
--
|
|
|
|
-- department structure represented here is as follows:
|
|
|
|
--
|
|
|
|
-- ROOT-+->A-+->B-+->C
|
|
|
|
-- | |
|
|
|
|
-- | +->D-+->F
|
|
|
|
-- +->E-+->G
|
|
|
|
CREATE TEMP TABLE department (
|
|
|
|
id INTEGER PRIMARY KEY, -- department ID
|
|
|
|
parent_department INTEGER REFERENCES department, -- upper department ID
|
|
|
|
name TEXT -- department name
|
|
|
|
);
|
|
|
|
INSERT INTO department VALUES (0, NULL, 'ROOT');
|
|
|
|
INSERT INTO department VALUES (1, 0, 'A');
|
|
|
|
INSERT INTO department VALUES (2, 1, 'B');
|
|
|
|
INSERT INTO department VALUES (3, 2, 'C');
|
|
|
|
INSERT INTO department VALUES (4, 2, 'D');
|
|
|
|
INSERT INTO department VALUES (5, 0, 'E');
|
|
|
|
INSERT INTO department VALUES (6, 4, 'F');
|
|
|
|
INSERT INTO department VALUES (7, 5, 'G');
|
|
|
|
-- extract all departments under 'A'. Result should be A, B, C, D and F
|
|
|
|
WITH RECURSIVE subdepartment AS
|
|
|
|
(
|
|
|
|
-- non recursive term
|
2008-10-06 00:20:17 +02:00
|
|
|
SELECT name as root_name, * FROM department WHERE name = 'A'
|
2008-10-04 23:56:55 +02:00
|
|
|
UNION ALL
|
|
|
|
-- recursive term
|
2008-10-06 00:20:17 +02:00
|
|
|
SELECT sd.root_name, d.* FROM department AS d, subdepartment AS sd
|
2008-10-04 23:56:55 +02:00
|
|
|
WHERE d.parent_department = sd.id
|
|
|
|
)
|
|
|
|
SELECT * FROM subdepartment ORDER BY name;
|
2008-10-06 00:20:17 +02:00
|
|
|
root_name | id | parent_department | name
|
|
|
|
-----------+----+-------------------+------
|
|
|
|
A | 1 | 0 | A
|
|
|
|
A | 2 | 1 | B
|
|
|
|
A | 3 | 2 | C
|
|
|
|
A | 4 | 2 | D
|
|
|
|
A | 6 | 4 | F
|
2008-10-04 23:56:55 +02:00
|
|
|
(5 rows)
|
|
|
|
|
|
|
|
-- extract all departments under 'A' with "level" number
|
|
|
|
WITH RECURSIVE subdepartment(level, id, parent_department, name) AS
|
|
|
|
(
|
|
|
|
-- non recursive term
|
|
|
|
SELECT 1, * FROM department WHERE name = 'A'
|
|
|
|
UNION ALL
|
|
|
|
-- recursive term
|
|
|
|
SELECT sd.level + 1, d.* FROM department AS d, subdepartment AS sd
|
|
|
|
WHERE d.parent_department = sd.id
|
|
|
|
)
|
|
|
|
SELECT * FROM subdepartment ORDER BY name;
|
|
|
|
level | id | parent_department | name
|
|
|
|
-------+----+-------------------+------
|
|
|
|
1 | 1 | 0 | A
|
|
|
|
2 | 2 | 1 | B
|
|
|
|
3 | 3 | 2 | C
|
|
|
|
3 | 4 | 2 | D
|
|
|
|
4 | 6 | 4 | F
|
|
|
|
(5 rows)
|
|
|
|
|
|
|
|
-- extract all departments under 'A' with "level" number.
|
|
|
|
-- Only shows level 2 or more
|
|
|
|
WITH RECURSIVE subdepartment(level, id, parent_department, name) AS
|
|
|
|
(
|
|
|
|
-- non recursive term
|
|
|
|
SELECT 1, * FROM department WHERE name = 'A'
|
|
|
|
UNION ALL
|
|
|
|
-- recursive term
|
|
|
|
SELECT sd.level + 1, d.* FROM department AS d, subdepartment AS sd
|
|
|
|
WHERE d.parent_department = sd.id
|
|
|
|
)
|
|
|
|
SELECT * FROM subdepartment WHERE level >= 2 ORDER BY name;
|
|
|
|
level | id | parent_department | name
|
|
|
|
-------+----+-------------------+------
|
|
|
|
2 | 2 | 1 | B
|
|
|
|
3 | 3 | 2 | C
|
|
|
|
3 | 4 | 2 | D
|
|
|
|
4 | 6 | 4 | F
|
|
|
|
(4 rows)
|
|
|
|
|
|
|
|
-- "RECURSIVE" is ignored if the query has no self-reference
|
|
|
|
WITH RECURSIVE subdepartment AS
|
|
|
|
(
|
|
|
|
-- note lack of recursive UNION structure
|
|
|
|
SELECT * FROM department WHERE name = 'A'
|
|
|
|
)
|
|
|
|
SELECT * FROM subdepartment ORDER BY name;
|
|
|
|
id | parent_department | name
|
|
|
|
----+-------------------+------
|
|
|
|
1 | 0 | A
|
|
|
|
(1 row)
|
|
|
|
|
|
|
|
-- inside subqueries
|
|
|
|
SELECT count(*) FROM (
|
|
|
|
WITH RECURSIVE t(n) AS (
|
|
|
|
SELECT 1 UNION ALL SELECT n + 1 FROM t WHERE n < 500
|
|
|
|
)
|
|
|
|
SELECT * FROM t) AS t WHERE n < (
|
|
|
|
SELECT count(*) FROM (
|
|
|
|
WITH RECURSIVE t(n) AS (
|
|
|
|
SELECT 1 UNION ALL SELECT n + 1 FROM t WHERE n < 100
|
|
|
|
)
|
|
|
|
SELECT * FROM t WHERE n < 50000
|
|
|
|
) AS t WHERE n < 100);
|
|
|
|
count
|
|
|
|
-------
|
|
|
|
98
|
|
|
|
(1 row)
|
|
|
|
|
|
|
|
-- use same CTE twice at different subquery levels
|
|
|
|
WITH q1(x,y) AS (
|
|
|
|
SELECT hundred, sum(ten) FROM tenk1 GROUP BY hundred
|
|
|
|
)
|
|
|
|
SELECT count(*) FROM q1 WHERE y > (SELECT sum(y)/100 FROM q1 qsub);
|
|
|
|
count
|
|
|
|
-------
|
|
|
|
50
|
|
|
|
(1 row)
|
|
|
|
|
|
|
|
-- via a VIEW
|
|
|
|
CREATE TEMPORARY VIEW vsubdepartment AS
|
|
|
|
WITH RECURSIVE subdepartment AS
|
|
|
|
(
|
|
|
|
-- non recursive term
|
|
|
|
SELECT * FROM department WHERE name = 'A'
|
|
|
|
UNION ALL
|
|
|
|
-- recursive term
|
|
|
|
SELECT d.* FROM department AS d, subdepartment AS sd
|
|
|
|
WHERE d.parent_department = sd.id
|
|
|
|
)
|
|
|
|
SELECT * FROM subdepartment;
|
|
|
|
SELECT * FROM vsubdepartment ORDER BY name;
|
|
|
|
id | parent_department | name
|
|
|
|
----+-------------------+------
|
|
|
|
1 | 0 | A
|
|
|
|
2 | 1 | B
|
|
|
|
3 | 2 | C
|
|
|
|
4 | 2 | D
|
|
|
|
6 | 4 | F
|
|
|
|
(5 rows)
|
|
|
|
|
|
|
|
-- Check reverse listing
|
|
|
|
SELECT pg_get_viewdef('vsubdepartment'::regclass);
|
2014-04-30 19:26:26 +02:00
|
|
|
pg_get_viewdef
|
|
|
|
-----------------------------------------------
|
|
|
|
WITH RECURSIVE subdepartment AS ( +
|
|
|
|
SELECT department.id, +
|
|
|
|
department.parent_department, +
|
|
|
|
department.name +
|
|
|
|
FROM department +
|
|
|
|
WHERE (department.name = 'A'::text)+
|
|
|
|
UNION ALL +
|
|
|
|
SELECT d.id, +
|
|
|
|
d.parent_department, +
|
|
|
|
d.name +
|
|
|
|
FROM department d, +
|
|
|
|
subdepartment sd +
|
|
|
|
WHERE (d.parent_department = sd.id)+
|
|
|
|
) +
|
2023-01-18 19:23:57 +01:00
|
|
|
SELECT id, +
|
|
|
|
parent_department, +
|
|
|
|
name +
|
2013-02-03 21:56:45 +01:00
|
|
|
FROM subdepartment;
|
2008-10-04 23:56:55 +02:00
|
|
|
(1 row)
|
|
|
|
|
|
|
|
SELECT pg_get_viewdef('vsubdepartment'::regclass, true);
|
2014-04-30 19:26:26 +02:00
|
|
|
pg_get_viewdef
|
|
|
|
---------------------------------------------
|
|
|
|
WITH RECURSIVE subdepartment AS ( +
|
|
|
|
SELECT department.id, +
|
|
|
|
department.parent_department, +
|
|
|
|
department.name +
|
|
|
|
FROM department +
|
|
|
|
WHERE department.name = 'A'::text+
|
|
|
|
UNION ALL +
|
|
|
|
SELECT d.id, +
|
|
|
|
d.parent_department, +
|
|
|
|
d.name +
|
|
|
|
FROM department d, +
|
|
|
|
subdepartment sd +
|
|
|
|
WHERE d.parent_department = sd.id+
|
|
|
|
) +
|
2023-01-18 19:23:57 +01:00
|
|
|
SELECT id, +
|
|
|
|
parent_department, +
|
|
|
|
name +
|
2008-10-04 23:56:55 +02:00
|
|
|
FROM subdepartment;
|
|
|
|
(1 row)
|
|
|
|
|
2012-10-12 22:14:11 +02:00
|
|
|
-- Another reverse-listing example
|
|
|
|
CREATE VIEW sums_1_100 AS
|
|
|
|
WITH RECURSIVE t(n) AS (
|
|
|
|
VALUES (1)
|
|
|
|
UNION ALL
|
|
|
|
SELECT n+1 FROM t WHERE n < 100
|
|
|
|
)
|
|
|
|
SELECT sum(n) FROM t;
|
|
|
|
\d+ sums_1_100
|
2016-11-03 17:00:00 +01:00
|
|
|
View "public.sums_1_100"
|
|
|
|
Column | Type | Collation | Nullable | Default | Storage | Description
|
|
|
|
--------+--------+-----------+----------+---------+---------+-------------
|
|
|
|
sum | bigint | | | | plain |
|
2012-10-12 22:14:11 +02:00
|
|
|
View definition:
|
|
|
|
WITH RECURSIVE t(n) AS (
|
2014-04-30 19:26:26 +02:00
|
|
|
VALUES (1)
|
2013-11-11 19:36:38 +01:00
|
|
|
UNION ALL
|
2014-04-30 19:26:26 +02:00
|
|
|
SELECT t_1.n + 1
|
|
|
|
FROM t t_1
|
|
|
|
WHERE t_1.n < 100
|
2012-10-12 22:14:11 +02:00
|
|
|
)
|
2023-01-18 19:23:57 +01:00
|
|
|
SELECT sum(n) AS sum
|
2012-10-12 22:14:11 +02:00
|
|
|
FROM t;
|
|
|
|
|
2008-10-13 02:41:41 +02:00
|
|
|
-- corner case in which sub-WITH gets initialized first
|
|
|
|
with recursive q as (
|
|
|
|
select * from department
|
|
|
|
union all
|
|
|
|
(with x as (select * from q)
|
|
|
|
select * from x)
|
|
|
|
)
|
|
|
|
select * from q limit 24;
|
|
|
|
id | parent_department | name
|
|
|
|
----+-------------------+------
|
|
|
|
0 | | ROOT
|
|
|
|
1 | 0 | A
|
|
|
|
2 | 1 | B
|
|
|
|
3 | 2 | C
|
|
|
|
4 | 2 | D
|
|
|
|
5 | 0 | E
|
|
|
|
6 | 4 | F
|
|
|
|
7 | 5 | G
|
|
|
|
0 | | ROOT
|
|
|
|
1 | 0 | A
|
|
|
|
2 | 1 | B
|
|
|
|
3 | 2 | C
|
|
|
|
4 | 2 | D
|
|
|
|
5 | 0 | E
|
|
|
|
6 | 4 | F
|
|
|
|
7 | 5 | G
|
|
|
|
0 | | ROOT
|
|
|
|
1 | 0 | A
|
|
|
|
2 | 1 | B
|
|
|
|
3 | 2 | C
|
|
|
|
4 | 2 | D
|
|
|
|
5 | 0 | E
|
|
|
|
6 | 4 | F
|
|
|
|
7 | 5 | G
|
|
|
|
(24 rows)
|
|
|
|
|
|
|
|
with recursive q as (
|
|
|
|
select * from department
|
|
|
|
union all
|
|
|
|
(with recursive x as (
|
|
|
|
select * from department
|
|
|
|
union all
|
|
|
|
(select * from q union all select * from x)
|
|
|
|
)
|
|
|
|
select * from x)
|
|
|
|
)
|
|
|
|
select * from q limit 32;
|
|
|
|
id | parent_department | name
|
|
|
|
----+-------------------+------
|
|
|
|
0 | | ROOT
|
|
|
|
1 | 0 | A
|
|
|
|
2 | 1 | B
|
|
|
|
3 | 2 | C
|
|
|
|
4 | 2 | D
|
|
|
|
5 | 0 | E
|
|
|
|
6 | 4 | F
|
|
|
|
7 | 5 | G
|
|
|
|
0 | | ROOT
|
|
|
|
1 | 0 | A
|
|
|
|
2 | 1 | B
|
|
|
|
3 | 2 | C
|
|
|
|
4 | 2 | D
|
|
|
|
5 | 0 | E
|
|
|
|
6 | 4 | F
|
|
|
|
7 | 5 | G
|
|
|
|
0 | | ROOT
|
|
|
|
1 | 0 | A
|
|
|
|
2 | 1 | B
|
|
|
|
3 | 2 | C
|
|
|
|
4 | 2 | D
|
|
|
|
5 | 0 | E
|
|
|
|
6 | 4 | F
|
|
|
|
7 | 5 | G
|
|
|
|
0 | | ROOT
|
|
|
|
1 | 0 | A
|
|
|
|
2 | 1 | B
|
|
|
|
3 | 2 | C
|
|
|
|
4 | 2 | D
|
|
|
|
5 | 0 | E
|
|
|
|
6 | 4 | F
|
|
|
|
7 | 5 | G
|
|
|
|
(32 rows)
|
|
|
|
|
2008-10-04 23:56:55 +02:00
|
|
|
-- recursive term has sub-UNION
|
|
|
|
WITH RECURSIVE t(i,j) AS (
|
|
|
|
VALUES (1,2)
|
|
|
|
UNION ALL
|
|
|
|
SELECT t2.i, t.j+1 FROM
|
|
|
|
(SELECT 2 AS i UNION ALL SELECT 3 AS i) AS t2
|
|
|
|
JOIN t ON (t2.i = t.i+1))
|
|
|
|
SELECT * FROM t;
|
|
|
|
i | j
|
|
|
|
---+---
|
|
|
|
1 | 2
|
|
|
|
2 | 3
|
|
|
|
3 | 4
|
|
|
|
(3 rows)
|
|
|
|
|
|
|
|
--
|
|
|
|
-- different tree example
|
|
|
|
--
|
|
|
|
CREATE TEMPORARY TABLE tree(
|
|
|
|
id INTEGER PRIMARY KEY,
|
|
|
|
parent_id INTEGER REFERENCES tree(id)
|
|
|
|
);
|
|
|
|
INSERT INTO tree
|
|
|
|
VALUES (1, NULL), (2, 1), (3,1), (4,2), (5,2), (6,2), (7,3), (8,3),
|
|
|
|
(9,4), (10,4), (11,7), (12,7), (13,7), (14, 9), (15,11), (16,11);
|
|
|
|
--
|
|
|
|
-- get all paths from "second level" nodes to leaf nodes
|
|
|
|
--
|
|
|
|
WITH RECURSIVE t(id, path) AS (
|
|
|
|
VALUES(1,ARRAY[]::integer[])
|
|
|
|
UNION ALL
|
|
|
|
SELECT tree.id, t.path || tree.id
|
|
|
|
FROM tree JOIN t ON (tree.parent_id = t.id)
|
|
|
|
)
|
|
|
|
SELECT t1.*, t2.* FROM t AS t1 JOIN t AS t2 ON
|
|
|
|
(t1.path[1] = t2.path[1] AND
|
|
|
|
array_upper(t1.path,1) = 1 AND
|
|
|
|
array_upper(t2.path,1) > 1)
|
|
|
|
ORDER BY t1.id, t2.id;
|
|
|
|
id | path | id | path
|
|
|
|
----+------+----+-------------
|
|
|
|
2 | {2} | 4 | {2,4}
|
|
|
|
2 | {2} | 5 | {2,5}
|
|
|
|
2 | {2} | 6 | {2,6}
|
|
|
|
2 | {2} | 9 | {2,4,9}
|
|
|
|
2 | {2} | 10 | {2,4,10}
|
|
|
|
2 | {2} | 14 | {2,4,9,14}
|
|
|
|
3 | {3} | 7 | {3,7}
|
|
|
|
3 | {3} | 8 | {3,8}
|
|
|
|
3 | {3} | 11 | {3,7,11}
|
|
|
|
3 | {3} | 12 | {3,7,12}
|
|
|
|
3 | {3} | 13 | {3,7,13}
|
|
|
|
3 | {3} | 15 | {3,7,11,15}
|
|
|
|
3 | {3} | 16 | {3,7,11,16}
|
|
|
|
(13 rows)
|
|
|
|
|
|
|
|
-- just count 'em
|
|
|
|
WITH RECURSIVE t(id, path) AS (
|
|
|
|
VALUES(1,ARRAY[]::integer[])
|
|
|
|
UNION ALL
|
|
|
|
SELECT tree.id, t.path || tree.id
|
|
|
|
FROM tree JOIN t ON (tree.parent_id = t.id)
|
|
|
|
)
|
|
|
|
SELECT t1.id, count(t2.*) FROM t AS t1 JOIN t AS t2 ON
|
|
|
|
(t1.path[1] = t2.path[1] AND
|
|
|
|
array_upper(t1.path,1) = 1 AND
|
|
|
|
array_upper(t2.path,1) > 1)
|
|
|
|
GROUP BY t1.id
|
|
|
|
ORDER BY t1.id;
|
|
|
|
id | count
|
|
|
|
----+-------
|
|
|
|
2 | 6
|
|
|
|
3 | 7
|
|
|
|
(2 rows)
|
|
|
|
|
2009-03-30 06:08:43 +02:00
|
|
|
-- this variant tickled a whole-row-variable bug in 8.4devel
|
|
|
|
WITH RECURSIVE t(id, path) AS (
|
|
|
|
VALUES(1,ARRAY[]::integer[])
|
|
|
|
UNION ALL
|
|
|
|
SELECT tree.id, t.path || tree.id
|
|
|
|
FROM tree JOIN t ON (tree.parent_id = t.id)
|
|
|
|
)
|
|
|
|
SELECT t1.id, t2.path, t2 FROM t AS t1 JOIN t AS t2 ON
|
|
|
|
(t1.id=t2.id);
|
|
|
|
id | path | t2
|
|
|
|
----+-------------+--------------------
|
|
|
|
1 | {} | (1,{})
|
|
|
|
2 | {2} | (2,{2})
|
|
|
|
3 | {3} | (3,{3})
|
|
|
|
4 | {2,4} | (4,"{2,4}")
|
|
|
|
5 | {2,5} | (5,"{2,5}")
|
|
|
|
6 | {2,6} | (6,"{2,6}")
|
|
|
|
7 | {3,7} | (7,"{3,7}")
|
|
|
|
8 | {3,8} | (8,"{3,8}")
|
|
|
|
9 | {2,4,9} | (9,"{2,4,9}")
|
|
|
|
10 | {2,4,10} | (10,"{2,4,10}")
|
|
|
|
11 | {3,7,11} | (11,"{3,7,11}")
|
|
|
|
12 | {3,7,12} | (12,"{3,7,12}")
|
|
|
|
13 | {3,7,13} | (13,"{3,7,13}")
|
|
|
|
14 | {2,4,9,14} | (14,"{2,4,9,14}")
|
|
|
|
15 | {3,7,11,15} | (15,"{3,7,11,15}")
|
|
|
|
16 | {3,7,11,16} | (16,"{3,7,11,16}")
|
|
|
|
(16 rows)
|
|
|
|
|
2023-11-17 20:36:23 +01:00
|
|
|
-- test that column statistics from a materialized CTE are available
|
|
|
|
-- to upper planner (otherwise, we'd get a stupider plan)
|
|
|
|
explain (costs off)
|
|
|
|
with x as materialized (select unique1 from tenk1 b)
|
|
|
|
select count(*) from tenk1 a
|
|
|
|
where unique1 in (select * from x);
|
|
|
|
QUERY PLAN
|
|
|
|
------------------------------------------------------------
|
|
|
|
Aggregate
|
|
|
|
CTE x
|
|
|
|
-> Index Only Scan using tenk1_unique1 on tenk1 b
|
|
|
|
-> Hash Semi Join
|
|
|
|
Hash Cond: (a.unique1 = x.unique1)
|
|
|
|
-> Index Only Scan using tenk1_unique1 on tenk1 a
|
|
|
|
-> Hash
|
|
|
|
-> CTE Scan on x
|
|
|
|
(8 rows)
|
|
|
|
|
2024-01-08 17:48:44 +01:00
|
|
|
explain (costs off)
|
|
|
|
with x as materialized (insert into tenk1 default values returning unique1)
|
|
|
|
select count(*) from tenk1 a
|
|
|
|
where unique1 in (select * from x);
|
|
|
|
QUERY PLAN
|
|
|
|
------------------------------------------------------------
|
|
|
|
Aggregate
|
|
|
|
CTE x
|
|
|
|
-> Insert on tenk1
|
|
|
|
-> Result
|
|
|
|
-> Nested Loop
|
|
|
|
-> HashAggregate
|
|
|
|
Group Key: x.unique1
|
|
|
|
-> CTE Scan on x
|
|
|
|
-> Index Only Scan using tenk1_unique1 on tenk1 a
|
|
|
|
Index Cond: (unique1 = x.unique1)
|
|
|
|
(10 rows)
|
|
|
|
|
2021-02-01 13:54:59 +01:00
|
|
|
-- SEARCH clause
|
|
|
|
create temp table graph0( f int, t int, label text );
|
|
|
|
insert into graph0 values
|
|
|
|
(1, 2, 'arc 1 -> 2'),
|
|
|
|
(1, 3, 'arc 1 -> 3'),
|
|
|
|
(2, 3, 'arc 2 -> 3'),
|
|
|
|
(1, 4, 'arc 1 -> 4'),
|
|
|
|
(4, 5, 'arc 4 -> 5');
|
2021-09-16 16:45:42 +02:00
|
|
|
explain (verbose, costs off)
|
|
|
|
with recursive search_graph(f, t, label) as (
|
|
|
|
select * from graph0 g
|
|
|
|
union all
|
|
|
|
select g.*
|
|
|
|
from graph0 g, search_graph sg
|
|
|
|
where g.f = sg.t
|
|
|
|
) search depth first by f, t set seq
|
|
|
|
select * from search_graph order by seq;
|
|
|
|
QUERY PLAN
|
|
|
|
----------------------------------------------------------------------------------------------
|
|
|
|
Sort
|
|
|
|
Output: search_graph.f, search_graph.t, search_graph.label, search_graph.seq
|
|
|
|
Sort Key: search_graph.seq
|
|
|
|
CTE search_graph
|
|
|
|
-> Recursive Union
|
|
|
|
-> Seq Scan on pg_temp.graph0 g
|
|
|
|
Output: g.f, g.t, g.label, ARRAY[ROW(g.f, g.t)]
|
|
|
|
-> Merge Join
|
|
|
|
Output: g_1.f, g_1.t, g_1.label, array_cat(sg.seq, ARRAY[ROW(g_1.f, g_1.t)])
|
|
|
|
Merge Cond: (g_1.f = sg.t)
|
|
|
|
-> Sort
|
|
|
|
Output: g_1.f, g_1.t, g_1.label
|
|
|
|
Sort Key: g_1.f
|
|
|
|
-> Seq Scan on pg_temp.graph0 g_1
|
|
|
|
Output: g_1.f, g_1.t, g_1.label
|
|
|
|
-> Sort
|
|
|
|
Output: sg.seq, sg.t
|
|
|
|
Sort Key: sg.t
|
|
|
|
-> WorkTable Scan on search_graph sg
|
|
|
|
Output: sg.seq, sg.t
|
|
|
|
-> CTE Scan on search_graph
|
|
|
|
Output: search_graph.f, search_graph.t, search_graph.label, search_graph.seq
|
|
|
|
(22 rows)
|
|
|
|
|
2021-02-01 13:54:59 +01:00
|
|
|
with recursive search_graph(f, t, label) as (
|
|
|
|
select * from graph0 g
|
|
|
|
union all
|
|
|
|
select g.*
|
|
|
|
from graph0 g, search_graph sg
|
|
|
|
where g.f = sg.t
|
|
|
|
) search depth first by f, t set seq
|
|
|
|
select * from search_graph order by seq;
|
|
|
|
f | t | label | seq
|
|
|
|
---+---+------------+-------------------
|
|
|
|
1 | 2 | arc 1 -> 2 | {"(1,2)"}
|
|
|
|
2 | 3 | arc 2 -> 3 | {"(1,2)","(2,3)"}
|
|
|
|
1 | 3 | arc 1 -> 3 | {"(1,3)"}
|
|
|
|
1 | 4 | arc 1 -> 4 | {"(1,4)"}
|
|
|
|
4 | 5 | arc 4 -> 5 | {"(1,4)","(4,5)"}
|
|
|
|
2 | 3 | arc 2 -> 3 | {"(2,3)"}
|
|
|
|
4 | 5 | arc 4 -> 5 | {"(4,5)"}
|
|
|
|
(7 rows)
|
|
|
|
|
|
|
|
with recursive search_graph(f, t, label) as (
|
|
|
|
select * from graph0 g
|
|
|
|
union distinct
|
|
|
|
select g.*
|
|
|
|
from graph0 g, search_graph sg
|
|
|
|
where g.f = sg.t
|
|
|
|
) search depth first by f, t set seq
|
|
|
|
select * from search_graph order by seq;
|
|
|
|
f | t | label | seq
|
|
|
|
---+---+------------+-------------------
|
|
|
|
1 | 2 | arc 1 -> 2 | {"(1,2)"}
|
|
|
|
2 | 3 | arc 2 -> 3 | {"(1,2)","(2,3)"}
|
|
|
|
1 | 3 | arc 1 -> 3 | {"(1,3)"}
|
|
|
|
1 | 4 | arc 1 -> 4 | {"(1,4)"}
|
|
|
|
4 | 5 | arc 4 -> 5 | {"(1,4)","(4,5)"}
|
|
|
|
2 | 3 | arc 2 -> 3 | {"(2,3)"}
|
|
|
|
4 | 5 | arc 4 -> 5 | {"(4,5)"}
|
|
|
|
(7 rows)
|
|
|
|
|
2021-09-16 16:45:42 +02:00
|
|
|
explain (verbose, costs off)
|
|
|
|
with recursive search_graph(f, t, label) as (
|
|
|
|
select * from graph0 g
|
|
|
|
union all
|
|
|
|
select g.*
|
|
|
|
from graph0 g, search_graph sg
|
|
|
|
where g.f = sg.t
|
|
|
|
) search breadth first by f, t set seq
|
|
|
|
select * from search_graph order by seq;
|
|
|
|
QUERY PLAN
|
|
|
|
-------------------------------------------------------------------------------------------------
|
|
|
|
Sort
|
|
|
|
Output: search_graph.f, search_graph.t, search_graph.label, search_graph.seq
|
|
|
|
Sort Key: search_graph.seq
|
|
|
|
CTE search_graph
|
|
|
|
-> Recursive Union
|
|
|
|
-> Seq Scan on pg_temp.graph0 g
|
|
|
|
Output: g.f, g.t, g.label, ROW('0'::bigint, g.f, g.t)
|
|
|
|
-> Merge Join
|
|
|
|
Output: g_1.f, g_1.t, g_1.label, ROW(int8inc((sg.seq)."*DEPTH*"), g_1.f, g_1.t)
|
|
|
|
Merge Cond: (g_1.f = sg.t)
|
|
|
|
-> Sort
|
|
|
|
Output: g_1.f, g_1.t, g_1.label
|
|
|
|
Sort Key: g_1.f
|
|
|
|
-> Seq Scan on pg_temp.graph0 g_1
|
|
|
|
Output: g_1.f, g_1.t, g_1.label
|
|
|
|
-> Sort
|
|
|
|
Output: sg.seq, sg.t
|
|
|
|
Sort Key: sg.t
|
|
|
|
-> WorkTable Scan on search_graph sg
|
|
|
|
Output: sg.seq, sg.t
|
|
|
|
-> CTE Scan on search_graph
|
|
|
|
Output: search_graph.f, search_graph.t, search_graph.label, search_graph.seq
|
|
|
|
(22 rows)
|
|
|
|
|
2021-02-01 13:54:59 +01:00
|
|
|
with recursive search_graph(f, t, label) as (
|
|
|
|
select * from graph0 g
|
|
|
|
union all
|
|
|
|
select g.*
|
|
|
|
from graph0 g, search_graph sg
|
|
|
|
where g.f = sg.t
|
|
|
|
) search breadth first by f, t set seq
|
|
|
|
select * from search_graph order by seq;
|
|
|
|
f | t | label | seq
|
|
|
|
---+---+------------+---------
|
|
|
|
1 | 2 | arc 1 -> 2 | (0,1,2)
|
|
|
|
1 | 3 | arc 1 -> 3 | (0,1,3)
|
|
|
|
1 | 4 | arc 1 -> 4 | (0,1,4)
|
|
|
|
2 | 3 | arc 2 -> 3 | (0,2,3)
|
|
|
|
4 | 5 | arc 4 -> 5 | (0,4,5)
|
|
|
|
2 | 3 | arc 2 -> 3 | (1,2,3)
|
|
|
|
4 | 5 | arc 4 -> 5 | (1,4,5)
|
|
|
|
(7 rows)
|
|
|
|
|
|
|
|
with recursive search_graph(f, t, label) as (
|
|
|
|
select * from graph0 g
|
|
|
|
union distinct
|
|
|
|
select g.*
|
|
|
|
from graph0 g, search_graph sg
|
|
|
|
where g.f = sg.t
|
|
|
|
) search breadth first by f, t set seq
|
|
|
|
select * from search_graph order by seq;
|
|
|
|
f | t | label | seq
|
|
|
|
---+---+------------+---------
|
|
|
|
1 | 2 | arc 1 -> 2 | (0,1,2)
|
|
|
|
1 | 3 | arc 1 -> 3 | (0,1,3)
|
|
|
|
1 | 4 | arc 1 -> 4 | (0,1,4)
|
|
|
|
2 | 3 | arc 2 -> 3 | (0,2,3)
|
|
|
|
4 | 5 | arc 4 -> 5 | (0,4,5)
|
|
|
|
2 | 3 | arc 2 -> 3 | (1,2,3)
|
|
|
|
4 | 5 | arc 4 -> 5 | (1,4,5)
|
|
|
|
(7 rows)
|
|
|
|
|
2022-10-17 01:18:08 +02:00
|
|
|
-- a constant initial value causes issues for EXPLAIN
|
|
|
|
explain (verbose, costs off)
|
|
|
|
with recursive test as (
|
|
|
|
select 1 as x
|
|
|
|
union all
|
|
|
|
select x + 1
|
|
|
|
from test
|
|
|
|
) search depth first by x set y
|
|
|
|
select * from test limit 5;
|
|
|
|
QUERY PLAN
|
|
|
|
-----------------------------------------------------------------------------------------
|
|
|
|
Limit
|
|
|
|
Output: test.x, test.y
|
|
|
|
CTE test
|
|
|
|
-> Recursive Union
|
|
|
|
-> Result
|
|
|
|
Output: 1, '{(1)}'::record[]
|
|
|
|
-> WorkTable Scan on test test_1
|
|
|
|
Output: (test_1.x + 1), array_cat(test_1.y, ARRAY[ROW((test_1.x + 1))])
|
|
|
|
-> CTE Scan on test
|
|
|
|
Output: test.x, test.y
|
|
|
|
(10 rows)
|
|
|
|
|
|
|
|
with recursive test as (
|
|
|
|
select 1 as x
|
|
|
|
union all
|
|
|
|
select x + 1
|
|
|
|
from test
|
|
|
|
) search depth first by x set y
|
|
|
|
select * from test limit 5;
|
|
|
|
x | y
|
|
|
|
---+-----------------------
|
|
|
|
1 | {(1)}
|
|
|
|
2 | {(1),(2)}
|
|
|
|
3 | {(1),(2),(3)}
|
|
|
|
4 | {(1),(2),(3),(4)}
|
|
|
|
5 | {(1),(2),(3),(4),(5)}
|
|
|
|
(5 rows)
|
|
|
|
|
|
|
|
explain (verbose, costs off)
|
|
|
|
with recursive test as (
|
|
|
|
select 1 as x
|
|
|
|
union all
|
|
|
|
select x + 1
|
|
|
|
from test
|
|
|
|
) search breadth first by x set y
|
|
|
|
select * from test limit 5;
|
|
|
|
QUERY PLAN
|
|
|
|
--------------------------------------------------------------------------------------------
|
|
|
|
Limit
|
|
|
|
Output: test.x, test.y
|
|
|
|
CTE test
|
|
|
|
-> Recursive Union
|
|
|
|
-> Result
|
|
|
|
Output: 1, '(0,1)'::record
|
|
|
|
-> WorkTable Scan on test test_1
|
|
|
|
Output: (test_1.x + 1), ROW(int8inc((test_1.y)."*DEPTH*"), (test_1.x + 1))
|
|
|
|
-> CTE Scan on test
|
|
|
|
Output: test.x, test.y
|
|
|
|
(10 rows)
|
|
|
|
|
|
|
|
with recursive test as (
|
|
|
|
select 1 as x
|
|
|
|
union all
|
|
|
|
select x + 1
|
|
|
|
from test
|
|
|
|
) search breadth first by x set y
|
|
|
|
select * from test limit 5;
|
|
|
|
x | y
|
|
|
|
---+-------
|
|
|
|
1 | (0,1)
|
|
|
|
2 | (1,2)
|
|
|
|
3 | (2,3)
|
|
|
|
4 | (3,4)
|
|
|
|
5 | (4,5)
|
|
|
|
(5 rows)
|
|
|
|
|
2021-02-01 13:54:59 +01:00
|
|
|
-- various syntax errors
|
|
|
|
with recursive search_graph(f, t, label) as (
|
|
|
|
select * from graph0 g
|
|
|
|
union all
|
|
|
|
select g.*
|
|
|
|
from graph0 g, search_graph sg
|
|
|
|
where g.f = sg.t
|
|
|
|
) search depth first by foo, tar set seq
|
|
|
|
select * from search_graph;
|
|
|
|
ERROR: search column "foo" not in WITH query column list
|
|
|
|
LINE 7: ) search depth first by foo, tar set seq
|
|
|
|
^
|
|
|
|
with recursive search_graph(f, t, label) as (
|
|
|
|
select * from graph0 g
|
|
|
|
union all
|
|
|
|
select g.*
|
|
|
|
from graph0 g, search_graph sg
|
|
|
|
where g.f = sg.t
|
|
|
|
) search depth first by f, t set label
|
|
|
|
select * from search_graph;
|
|
|
|
ERROR: search sequence column name "label" already used in WITH query column list
|
|
|
|
LINE 7: ) search depth first by f, t set label
|
|
|
|
^
|
|
|
|
with recursive search_graph(f, t, label) as (
|
|
|
|
select * from graph0 g
|
|
|
|
union all
|
|
|
|
select g.*
|
|
|
|
from graph0 g, search_graph sg
|
|
|
|
where g.f = sg.t
|
|
|
|
) search depth first by f, t, f set seq
|
|
|
|
select * from search_graph;
|
|
|
|
ERROR: search column "f" specified more than once
|
|
|
|
LINE 7: ) search depth first by f, t, f set seq
|
|
|
|
^
|
|
|
|
with recursive search_graph(f, t, label) as (
|
|
|
|
select * from graph0 g
|
|
|
|
union all
|
|
|
|
select * from graph0 g
|
|
|
|
union all
|
|
|
|
select g.*
|
|
|
|
from graph0 g, search_graph sg
|
|
|
|
where g.f = sg.t
|
|
|
|
) search depth first by f, t set seq
|
|
|
|
select * from search_graph order by seq;
|
|
|
|
ERROR: with a SEARCH or CYCLE clause, the left side of the UNION must be a SELECT
|
|
|
|
with recursive search_graph(f, t, label) as (
|
|
|
|
select * from graph0 g
|
|
|
|
union all
|
|
|
|
(select * from graph0 g
|
|
|
|
union all
|
|
|
|
select g.*
|
|
|
|
from graph0 g, search_graph sg
|
|
|
|
where g.f = sg.t)
|
|
|
|
) search depth first by f, t set seq
|
|
|
|
select * from search_graph order by seq;
|
|
|
|
ERROR: with a SEARCH or CYCLE clause, the right side of the UNION must be a SELECT
|
2022-04-23 18:16:12 +02:00
|
|
|
-- check that we distinguish same CTE name used at different levels
|
|
|
|
-- (this case could be supported, perhaps, but it isn't today)
|
|
|
|
with recursive x(col) as (
|
|
|
|
select 1
|
|
|
|
union
|
|
|
|
(with x as (select * from x)
|
|
|
|
select * from x)
|
|
|
|
) search depth first by col set seq
|
|
|
|
select * from x;
|
|
|
|
ERROR: with a SEARCH or CYCLE clause, the recursive reference to WITH query "x" must be at the top level of its right-hand SELECT
|
2021-02-01 13:54:59 +01:00
|
|
|
-- test ruleutils and view expansion
|
|
|
|
create temp view v_search as
|
|
|
|
with recursive search_graph(f, t, label) as (
|
|
|
|
select * from graph0 g
|
|
|
|
union all
|
|
|
|
select g.*
|
|
|
|
from graph0 g, search_graph sg
|
|
|
|
where g.f = sg.t
|
|
|
|
) search depth first by f, t set seq
|
|
|
|
select f, t, label from search_graph;
|
|
|
|
select pg_get_viewdef('v_search');
|
|
|
|
pg_get_viewdef
|
|
|
|
------------------------------------------------
|
|
|
|
WITH RECURSIVE search_graph(f, t, label) AS (+
|
|
|
|
SELECT g.f, +
|
|
|
|
g.t, +
|
|
|
|
g.label +
|
|
|
|
FROM graph0 g +
|
|
|
|
UNION ALL +
|
|
|
|
SELECT g.f, +
|
|
|
|
g.t, +
|
|
|
|
g.label +
|
|
|
|
FROM graph0 g, +
|
|
|
|
search_graph sg +
|
|
|
|
WHERE (g.f = sg.t) +
|
|
|
|
) SEARCH DEPTH FIRST BY f, t SET seq +
|
2023-01-18 19:23:57 +01:00
|
|
|
SELECT f, +
|
|
|
|
t, +
|
|
|
|
label +
|
2021-02-01 13:54:59 +01:00
|
|
|
FROM search_graph;
|
|
|
|
(1 row)
|
|
|
|
|
|
|
|
select * from v_search;
|
|
|
|
f | t | label
|
|
|
|
---+---+------------
|
|
|
|
1 | 2 | arc 1 -> 2
|
|
|
|
1 | 3 | arc 1 -> 3
|
|
|
|
2 | 3 | arc 2 -> 3
|
|
|
|
1 | 4 | arc 1 -> 4
|
|
|
|
4 | 5 | arc 4 -> 5
|
|
|
|
2 | 3 | arc 2 -> 3
|
|
|
|
4 | 5 | arc 4 -> 5
|
|
|
|
(7 rows)
|
|
|
|
|
2008-10-13 18:25:20 +02:00
|
|
|
--
|
|
|
|
-- test cycle detection
|
|
|
|
--
|
|
|
|
create temp table graph( f int, t int, label text );
|
|
|
|
insert into graph values
|
|
|
|
(1, 2, 'arc 1 -> 2'),
|
|
|
|
(1, 3, 'arc 1 -> 3'),
|
|
|
|
(2, 3, 'arc 2 -> 3'),
|
|
|
|
(1, 4, 'arc 1 -> 4'),
|
|
|
|
(4, 5, 'arc 4 -> 5'),
|
|
|
|
(5, 1, 'arc 5 -> 1');
|
2020-10-12 07:46:20 +02:00
|
|
|
with recursive search_graph(f, t, label, is_cycle, path) as (
|
|
|
|
select *, false, array[row(g.f, g.t)] from graph g
|
2008-10-13 18:25:20 +02:00
|
|
|
union all
|
2020-10-12 07:46:20 +02:00
|
|
|
select g.*, row(g.f, g.t) = any(path), path || row(g.f, g.t)
|
2008-10-13 18:25:20 +02:00
|
|
|
from graph g, search_graph sg
|
2020-10-12 07:46:20 +02:00
|
|
|
where g.f = sg.t and not is_cycle
|
2008-10-13 18:25:20 +02:00
|
|
|
)
|
|
|
|
select * from search_graph;
|
2020-10-12 07:46:20 +02:00
|
|
|
f | t | label | is_cycle | path
|
|
|
|
---+---+------------+----------+-------------------------------------------
|
|
|
|
1 | 2 | arc 1 -> 2 | f | {"(1,2)"}
|
|
|
|
1 | 3 | arc 1 -> 3 | f | {"(1,3)"}
|
|
|
|
2 | 3 | arc 2 -> 3 | f | {"(2,3)"}
|
|
|
|
1 | 4 | arc 1 -> 4 | f | {"(1,4)"}
|
|
|
|
4 | 5 | arc 4 -> 5 | f | {"(4,5)"}
|
|
|
|
5 | 1 | arc 5 -> 1 | f | {"(5,1)"}
|
|
|
|
1 | 2 | arc 1 -> 2 | f | {"(5,1)","(1,2)"}
|
|
|
|
1 | 3 | arc 1 -> 3 | f | {"(5,1)","(1,3)"}
|
|
|
|
1 | 4 | arc 1 -> 4 | f | {"(5,1)","(1,4)"}
|
|
|
|
2 | 3 | arc 2 -> 3 | f | {"(1,2)","(2,3)"}
|
|
|
|
4 | 5 | arc 4 -> 5 | f | {"(1,4)","(4,5)"}
|
|
|
|
5 | 1 | arc 5 -> 1 | f | {"(4,5)","(5,1)"}
|
|
|
|
1 | 2 | arc 1 -> 2 | f | {"(4,5)","(5,1)","(1,2)"}
|
|
|
|
1 | 3 | arc 1 -> 3 | f | {"(4,5)","(5,1)","(1,3)"}
|
|
|
|
1 | 4 | arc 1 -> 4 | f | {"(4,5)","(5,1)","(1,4)"}
|
|
|
|
2 | 3 | arc 2 -> 3 | f | {"(5,1)","(1,2)","(2,3)"}
|
|
|
|
4 | 5 | arc 4 -> 5 | f | {"(5,1)","(1,4)","(4,5)"}
|
|
|
|
5 | 1 | arc 5 -> 1 | f | {"(1,4)","(4,5)","(5,1)"}
|
|
|
|
1 | 2 | arc 1 -> 2 | f | {"(1,4)","(4,5)","(5,1)","(1,2)"}
|
|
|
|
1 | 3 | arc 1 -> 3 | f | {"(1,4)","(4,5)","(5,1)","(1,3)"}
|
|
|
|
1 | 4 | arc 1 -> 4 | t | {"(1,4)","(4,5)","(5,1)","(1,4)"}
|
|
|
|
2 | 3 | arc 2 -> 3 | f | {"(4,5)","(5,1)","(1,2)","(2,3)"}
|
|
|
|
4 | 5 | arc 4 -> 5 | t | {"(4,5)","(5,1)","(1,4)","(4,5)"}
|
|
|
|
5 | 1 | arc 5 -> 1 | t | {"(5,1)","(1,4)","(4,5)","(5,1)"}
|
|
|
|
2 | 3 | arc 2 -> 3 | f | {"(1,4)","(4,5)","(5,1)","(1,2)","(2,3)"}
|
2008-10-13 18:25:20 +02:00
|
|
|
(25 rows)
|
|
|
|
|
2020-11-19 09:24:37 +01:00
|
|
|
-- UNION DISTINCT exercises row type hashing support
|
2020-11-18 07:58:37 +01:00
|
|
|
with recursive search_graph(f, t, label, is_cycle, path) as (
|
|
|
|
select *, false, array[row(g.f, g.t)] from graph g
|
|
|
|
union distinct
|
|
|
|
select g.*, row(g.f, g.t) = any(path), path || row(g.f, g.t)
|
|
|
|
from graph g, search_graph sg
|
|
|
|
where g.f = sg.t and not is_cycle
|
|
|
|
)
|
|
|
|
select * from search_graph;
|
2020-11-19 09:24:37 +01:00
|
|
|
f | t | label | is_cycle | path
|
|
|
|
---+---+------------+----------+-------------------------------------------
|
|
|
|
1 | 2 | arc 1 -> 2 | f | {"(1,2)"}
|
|
|
|
1 | 3 | arc 1 -> 3 | f | {"(1,3)"}
|
|
|
|
2 | 3 | arc 2 -> 3 | f | {"(2,3)"}
|
|
|
|
1 | 4 | arc 1 -> 4 | f | {"(1,4)"}
|
|
|
|
4 | 5 | arc 4 -> 5 | f | {"(4,5)"}
|
|
|
|
5 | 1 | arc 5 -> 1 | f | {"(5,1)"}
|
|
|
|
1 | 2 | arc 1 -> 2 | f | {"(5,1)","(1,2)"}
|
|
|
|
1 | 3 | arc 1 -> 3 | f | {"(5,1)","(1,3)"}
|
|
|
|
1 | 4 | arc 1 -> 4 | f | {"(5,1)","(1,4)"}
|
|
|
|
2 | 3 | arc 2 -> 3 | f | {"(1,2)","(2,3)"}
|
|
|
|
4 | 5 | arc 4 -> 5 | f | {"(1,4)","(4,5)"}
|
|
|
|
5 | 1 | arc 5 -> 1 | f | {"(4,5)","(5,1)"}
|
|
|
|
1 | 2 | arc 1 -> 2 | f | {"(4,5)","(5,1)","(1,2)"}
|
|
|
|
1 | 3 | arc 1 -> 3 | f | {"(4,5)","(5,1)","(1,3)"}
|
|
|
|
1 | 4 | arc 1 -> 4 | f | {"(4,5)","(5,1)","(1,4)"}
|
|
|
|
2 | 3 | arc 2 -> 3 | f | {"(5,1)","(1,2)","(2,3)"}
|
|
|
|
4 | 5 | arc 4 -> 5 | f | {"(5,1)","(1,4)","(4,5)"}
|
|
|
|
5 | 1 | arc 5 -> 1 | f | {"(1,4)","(4,5)","(5,1)"}
|
|
|
|
1 | 2 | arc 1 -> 2 | f | {"(1,4)","(4,5)","(5,1)","(1,2)"}
|
|
|
|
1 | 3 | arc 1 -> 3 | f | {"(1,4)","(4,5)","(5,1)","(1,3)"}
|
|
|
|
1 | 4 | arc 1 -> 4 | t | {"(1,4)","(4,5)","(5,1)","(1,4)"}
|
|
|
|
2 | 3 | arc 2 -> 3 | f | {"(4,5)","(5,1)","(1,2)","(2,3)"}
|
|
|
|
4 | 5 | arc 4 -> 5 | t | {"(4,5)","(5,1)","(1,4)","(4,5)"}
|
|
|
|
5 | 1 | arc 5 -> 1 | t | {"(5,1)","(1,4)","(4,5)","(5,1)"}
|
|
|
|
2 | 3 | arc 2 -> 3 | f | {"(1,4)","(4,5)","(5,1)","(1,2)","(2,3)"}
|
|
|
|
(25 rows)
|
|
|
|
|
2008-10-14 02:41:35 +02:00
|
|
|
-- ordering by the path column has same effect as SEARCH DEPTH FIRST
|
2020-10-12 07:46:20 +02:00
|
|
|
with recursive search_graph(f, t, label, is_cycle, path) as (
|
|
|
|
select *, false, array[row(g.f, g.t)] from graph g
|
2008-10-14 02:41:35 +02:00
|
|
|
union all
|
2020-10-12 07:46:20 +02:00
|
|
|
select g.*, row(g.f, g.t) = any(path), path || row(g.f, g.t)
|
2008-10-14 02:41:35 +02:00
|
|
|
from graph g, search_graph sg
|
2020-10-12 07:46:20 +02:00
|
|
|
where g.f = sg.t and not is_cycle
|
2008-10-14 02:41:35 +02:00
|
|
|
)
|
|
|
|
select * from search_graph order by path;
|
2020-10-12 07:46:20 +02:00
|
|
|
f | t | label | is_cycle | path
|
|
|
|
---+---+------------+----------+-------------------------------------------
|
|
|
|
1 | 2 | arc 1 -> 2 | f | {"(1,2)"}
|
|
|
|
2 | 3 | arc 2 -> 3 | f | {"(1,2)","(2,3)"}
|
|
|
|
1 | 3 | arc 1 -> 3 | f | {"(1,3)"}
|
|
|
|
1 | 4 | arc 1 -> 4 | f | {"(1,4)"}
|
|
|
|
4 | 5 | arc 4 -> 5 | f | {"(1,4)","(4,5)"}
|
|
|
|
5 | 1 | arc 5 -> 1 | f | {"(1,4)","(4,5)","(5,1)"}
|
|
|
|
1 | 2 | arc 1 -> 2 | f | {"(1,4)","(4,5)","(5,1)","(1,2)"}
|
|
|
|
2 | 3 | arc 2 -> 3 | f | {"(1,4)","(4,5)","(5,1)","(1,2)","(2,3)"}
|
|
|
|
1 | 3 | arc 1 -> 3 | f | {"(1,4)","(4,5)","(5,1)","(1,3)"}
|
|
|
|
1 | 4 | arc 1 -> 4 | t | {"(1,4)","(4,5)","(5,1)","(1,4)"}
|
|
|
|
2 | 3 | arc 2 -> 3 | f | {"(2,3)"}
|
|
|
|
4 | 5 | arc 4 -> 5 | f | {"(4,5)"}
|
|
|
|
5 | 1 | arc 5 -> 1 | f | {"(4,5)","(5,1)"}
|
|
|
|
1 | 2 | arc 1 -> 2 | f | {"(4,5)","(5,1)","(1,2)"}
|
|
|
|
2 | 3 | arc 2 -> 3 | f | {"(4,5)","(5,1)","(1,2)","(2,3)"}
|
|
|
|
1 | 3 | arc 1 -> 3 | f | {"(4,5)","(5,1)","(1,3)"}
|
|
|
|
1 | 4 | arc 1 -> 4 | f | {"(4,5)","(5,1)","(1,4)"}
|
|
|
|
4 | 5 | arc 4 -> 5 | t | {"(4,5)","(5,1)","(1,4)","(4,5)"}
|
|
|
|
5 | 1 | arc 5 -> 1 | f | {"(5,1)"}
|
|
|
|
1 | 2 | arc 1 -> 2 | f | {"(5,1)","(1,2)"}
|
|
|
|
2 | 3 | arc 2 -> 3 | f | {"(5,1)","(1,2)","(2,3)"}
|
|
|
|
1 | 3 | arc 1 -> 3 | f | {"(5,1)","(1,3)"}
|
|
|
|
1 | 4 | arc 1 -> 4 | f | {"(5,1)","(1,4)"}
|
|
|
|
4 | 5 | arc 4 -> 5 | f | {"(5,1)","(1,4)","(4,5)"}
|
|
|
|
5 | 1 | arc 5 -> 1 | t | {"(5,1)","(1,4)","(4,5)","(5,1)"}
|
2008-10-14 02:41:35 +02:00
|
|
|
(25 rows)
|
|
|
|
|
2021-02-01 13:54:59 +01:00
|
|
|
-- CYCLE clause
|
2021-09-16 16:45:42 +02:00
|
|
|
explain (verbose, costs off)
|
|
|
|
with recursive search_graph(f, t, label) as (
|
|
|
|
select * from graph g
|
|
|
|
union all
|
|
|
|
select g.*
|
|
|
|
from graph g, search_graph sg
|
|
|
|
where g.f = sg.t
|
|
|
|
) cycle f, t set is_cycle using path
|
|
|
|
select * from search_graph;
|
|
|
|
QUERY PLAN
|
|
|
|
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
|
|
CTE Scan on search_graph
|
|
|
|
Output: search_graph.f, search_graph.t, search_graph.label, search_graph.is_cycle, search_graph.path
|
|
|
|
CTE search_graph
|
|
|
|
-> Recursive Union
|
|
|
|
-> Seq Scan on pg_temp.graph g
|
|
|
|
Output: g.f, g.t, g.label, false, ARRAY[ROW(g.f, g.t)]
|
|
|
|
-> Merge Join
|
|
|
|
Output: g_1.f, g_1.t, g_1.label, CASE WHEN (ROW(g_1.f, g_1.t) = ANY (sg.path)) THEN true ELSE false END, array_cat(sg.path, ARRAY[ROW(g_1.f, g_1.t)])
|
|
|
|
Merge Cond: (g_1.f = sg.t)
|
|
|
|
-> Sort
|
|
|
|
Output: g_1.f, g_1.t, g_1.label
|
|
|
|
Sort Key: g_1.f
|
|
|
|
-> Seq Scan on pg_temp.graph g_1
|
|
|
|
Output: g_1.f, g_1.t, g_1.label
|
|
|
|
-> Sort
|
|
|
|
Output: sg.path, sg.t
|
|
|
|
Sort Key: sg.t
|
|
|
|
-> WorkTable Scan on search_graph sg
|
|
|
|
Output: sg.path, sg.t
|
|
|
|
Filter: (NOT sg.is_cycle)
|
|
|
|
(20 rows)
|
|
|
|
|
2021-02-01 13:54:59 +01:00
|
|
|
with recursive search_graph(f, t, label) as (
|
|
|
|
select * from graph g
|
|
|
|
union all
|
|
|
|
select g.*
|
|
|
|
from graph g, search_graph sg
|
|
|
|
where g.f = sg.t
|
2021-02-27 08:11:14 +01:00
|
|
|
) cycle f, t set is_cycle using path
|
2021-02-01 13:54:59 +01:00
|
|
|
select * from search_graph;
|
|
|
|
f | t | label | is_cycle | path
|
|
|
|
---+---+------------+----------+-------------------------------------------
|
|
|
|
1 | 2 | arc 1 -> 2 | f | {"(1,2)"}
|
|
|
|
1 | 3 | arc 1 -> 3 | f | {"(1,3)"}
|
|
|
|
2 | 3 | arc 2 -> 3 | f | {"(2,3)"}
|
|
|
|
1 | 4 | arc 1 -> 4 | f | {"(1,4)"}
|
|
|
|
4 | 5 | arc 4 -> 5 | f | {"(4,5)"}
|
|
|
|
5 | 1 | arc 5 -> 1 | f | {"(5,1)"}
|
|
|
|
1 | 2 | arc 1 -> 2 | f | {"(5,1)","(1,2)"}
|
|
|
|
1 | 3 | arc 1 -> 3 | f | {"(5,1)","(1,3)"}
|
|
|
|
1 | 4 | arc 1 -> 4 | f | {"(5,1)","(1,4)"}
|
|
|
|
2 | 3 | arc 2 -> 3 | f | {"(1,2)","(2,3)"}
|
|
|
|
4 | 5 | arc 4 -> 5 | f | {"(1,4)","(4,5)"}
|
|
|
|
5 | 1 | arc 5 -> 1 | f | {"(4,5)","(5,1)"}
|
|
|
|
1 | 2 | arc 1 -> 2 | f | {"(4,5)","(5,1)","(1,2)"}
|
|
|
|
1 | 3 | arc 1 -> 3 | f | {"(4,5)","(5,1)","(1,3)"}
|
|
|
|
1 | 4 | arc 1 -> 4 | f | {"(4,5)","(5,1)","(1,4)"}
|
|
|
|
2 | 3 | arc 2 -> 3 | f | {"(5,1)","(1,2)","(2,3)"}
|
|
|
|
4 | 5 | arc 4 -> 5 | f | {"(5,1)","(1,4)","(4,5)"}
|
|
|
|
5 | 1 | arc 5 -> 1 | f | {"(1,4)","(4,5)","(5,1)"}
|
|
|
|
1 | 2 | arc 1 -> 2 | f | {"(1,4)","(4,5)","(5,1)","(1,2)"}
|
|
|
|
1 | 3 | arc 1 -> 3 | f | {"(1,4)","(4,5)","(5,1)","(1,3)"}
|
|
|
|
1 | 4 | arc 1 -> 4 | t | {"(1,4)","(4,5)","(5,1)","(1,4)"}
|
|
|
|
2 | 3 | arc 2 -> 3 | f | {"(4,5)","(5,1)","(1,2)","(2,3)"}
|
|
|
|
4 | 5 | arc 4 -> 5 | t | {"(4,5)","(5,1)","(1,4)","(4,5)"}
|
|
|
|
5 | 1 | arc 5 -> 1 | t | {"(5,1)","(1,4)","(4,5)","(5,1)"}
|
|
|
|
2 | 3 | arc 2 -> 3 | f | {"(1,4)","(4,5)","(5,1)","(1,2)","(2,3)"}
|
|
|
|
(25 rows)
|
|
|
|
|
|
|
|
with recursive search_graph(f, t, label) as (
|
|
|
|
select * from graph g
|
|
|
|
union distinct
|
|
|
|
select g.*
|
|
|
|
from graph g, search_graph sg
|
|
|
|
where g.f = sg.t
|
|
|
|
) cycle f, t set is_cycle to 'Y' default 'N' using path
|
|
|
|
select * from search_graph;
|
|
|
|
f | t | label | is_cycle | path
|
|
|
|
---+---+------------+----------+-------------------------------------------
|
|
|
|
1 | 2 | arc 1 -> 2 | N | {"(1,2)"}
|
|
|
|
1 | 3 | arc 1 -> 3 | N | {"(1,3)"}
|
|
|
|
2 | 3 | arc 2 -> 3 | N | {"(2,3)"}
|
|
|
|
1 | 4 | arc 1 -> 4 | N | {"(1,4)"}
|
|
|
|
4 | 5 | arc 4 -> 5 | N | {"(4,5)"}
|
|
|
|
5 | 1 | arc 5 -> 1 | N | {"(5,1)"}
|
|
|
|
1 | 2 | arc 1 -> 2 | N | {"(5,1)","(1,2)"}
|
|
|
|
1 | 3 | arc 1 -> 3 | N | {"(5,1)","(1,3)"}
|
|
|
|
1 | 4 | arc 1 -> 4 | N | {"(5,1)","(1,4)"}
|
|
|
|
2 | 3 | arc 2 -> 3 | N | {"(1,2)","(2,3)"}
|
|
|
|
4 | 5 | arc 4 -> 5 | N | {"(1,4)","(4,5)"}
|
|
|
|
5 | 1 | arc 5 -> 1 | N | {"(4,5)","(5,1)"}
|
|
|
|
1 | 2 | arc 1 -> 2 | N | {"(4,5)","(5,1)","(1,2)"}
|
|
|
|
1 | 3 | arc 1 -> 3 | N | {"(4,5)","(5,1)","(1,3)"}
|
|
|
|
1 | 4 | arc 1 -> 4 | N | {"(4,5)","(5,1)","(1,4)"}
|
|
|
|
2 | 3 | arc 2 -> 3 | N | {"(5,1)","(1,2)","(2,3)"}
|
|
|
|
4 | 5 | arc 4 -> 5 | N | {"(5,1)","(1,4)","(4,5)"}
|
|
|
|
5 | 1 | arc 5 -> 1 | N | {"(1,4)","(4,5)","(5,1)"}
|
|
|
|
1 | 2 | arc 1 -> 2 | N | {"(1,4)","(4,5)","(5,1)","(1,2)"}
|
|
|
|
1 | 3 | arc 1 -> 3 | N | {"(1,4)","(4,5)","(5,1)","(1,3)"}
|
|
|
|
1 | 4 | arc 1 -> 4 | Y | {"(1,4)","(4,5)","(5,1)","(1,4)"}
|
|
|
|
2 | 3 | arc 2 -> 3 | N | {"(4,5)","(5,1)","(1,2)","(2,3)"}
|
|
|
|
4 | 5 | arc 4 -> 5 | Y | {"(4,5)","(5,1)","(1,4)","(4,5)"}
|
|
|
|
5 | 1 | arc 5 -> 1 | Y | {"(5,1)","(1,4)","(4,5)","(5,1)"}
|
|
|
|
2 | 3 | arc 2 -> 3 | N | {"(1,4)","(4,5)","(5,1)","(1,2)","(2,3)"}
|
|
|
|
(25 rows)
|
|
|
|
|
2022-10-17 01:18:08 +02:00
|
|
|
explain (verbose, costs off)
|
|
|
|
with recursive test as (
|
|
|
|
select 0 as x
|
|
|
|
union all
|
|
|
|
select (x + 1) % 10
|
|
|
|
from test
|
|
|
|
) cycle x set is_cycle using path
|
|
|
|
select * from test;
|
|
|
|
QUERY PLAN
|
|
|
|
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
|
|
CTE Scan on test
|
|
|
|
Output: test.x, test.is_cycle, test.path
|
|
|
|
CTE test
|
|
|
|
-> Recursive Union
|
|
|
|
-> Result
|
|
|
|
Output: 0, false, '{(0)}'::record[]
|
|
|
|
-> WorkTable Scan on test test_1
|
|
|
|
Output: ((test_1.x + 1) % 10), CASE WHEN (ROW(((test_1.x + 1) % 10)) = ANY (test_1.path)) THEN true ELSE false END, array_cat(test_1.path, ARRAY[ROW(((test_1.x + 1) % 10))])
|
|
|
|
Filter: (NOT test_1.is_cycle)
|
|
|
|
(9 rows)
|
|
|
|
|
|
|
|
with recursive test as (
|
|
|
|
select 0 as x
|
|
|
|
union all
|
|
|
|
select (x + 1) % 10
|
|
|
|
from test
|
|
|
|
) cycle x set is_cycle using path
|
|
|
|
select * from test;
|
|
|
|
x | is_cycle | path
|
|
|
|
---+----------+-----------------------------------------------
|
|
|
|
0 | f | {(0)}
|
|
|
|
1 | f | {(0),(1)}
|
|
|
|
2 | f | {(0),(1),(2)}
|
|
|
|
3 | f | {(0),(1),(2),(3)}
|
|
|
|
4 | f | {(0),(1),(2),(3),(4)}
|
|
|
|
5 | f | {(0),(1),(2),(3),(4),(5)}
|
|
|
|
6 | f | {(0),(1),(2),(3),(4),(5),(6)}
|
|
|
|
7 | f | {(0),(1),(2),(3),(4),(5),(6),(7)}
|
|
|
|
8 | f | {(0),(1),(2),(3),(4),(5),(6),(7),(8)}
|
|
|
|
9 | f | {(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)}
|
|
|
|
0 | t | {(0),(1),(2),(3),(4),(5),(6),(7),(8),(9),(0)}
|
|
|
|
(11 rows)
|
|
|
|
|
2022-12-16 19:07:42 +01:00
|
|
|
with recursive test as (
|
|
|
|
select 0 as x
|
|
|
|
union all
|
|
|
|
select (x + 1) % 10
|
|
|
|
from test
|
|
|
|
where not is_cycle -- redundant, but legal
|
|
|
|
) cycle x set is_cycle using path
|
|
|
|
select * from test;
|
|
|
|
x | is_cycle | path
|
|
|
|
---+----------+-----------------------------------------------
|
|
|
|
0 | f | {(0)}
|
|
|
|
1 | f | {(0),(1)}
|
|
|
|
2 | f | {(0),(1),(2)}
|
|
|
|
3 | f | {(0),(1),(2),(3)}
|
|
|
|
4 | f | {(0),(1),(2),(3),(4)}
|
|
|
|
5 | f | {(0),(1),(2),(3),(4),(5)}
|
|
|
|
6 | f | {(0),(1),(2),(3),(4),(5),(6)}
|
|
|
|
7 | f | {(0),(1),(2),(3),(4),(5),(6),(7)}
|
|
|
|
8 | f | {(0),(1),(2),(3),(4),(5),(6),(7),(8)}
|
|
|
|
9 | f | {(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)}
|
|
|
|
0 | t | {(0),(1),(2),(3),(4),(5),(6),(7),(8),(9),(0)}
|
|
|
|
(11 rows)
|
|
|
|
|
2021-02-01 13:54:59 +01:00
|
|
|
-- multiple CTEs
|
|
|
|
with recursive
|
|
|
|
graph(f, t, label) as (
|
|
|
|
values (1, 2, 'arc 1 -> 2'),
|
|
|
|
(1, 3, 'arc 1 -> 3'),
|
|
|
|
(2, 3, 'arc 2 -> 3'),
|
|
|
|
(1, 4, 'arc 1 -> 4'),
|
|
|
|
(4, 5, 'arc 4 -> 5'),
|
|
|
|
(5, 1, 'arc 5 -> 1')
|
|
|
|
),
|
|
|
|
search_graph(f, t, label) as (
|
|
|
|
select * from graph g
|
|
|
|
union all
|
|
|
|
select g.*
|
|
|
|
from graph g, search_graph sg
|
|
|
|
where g.f = sg.t
|
|
|
|
) cycle f, t set is_cycle to true default false using path
|
|
|
|
select f, t, label from search_graph;
|
|
|
|
f | t | label
|
|
|
|
---+---+------------
|
|
|
|
1 | 2 | arc 1 -> 2
|
|
|
|
1 | 3 | arc 1 -> 3
|
|
|
|
2 | 3 | arc 2 -> 3
|
|
|
|
1 | 4 | arc 1 -> 4
|
|
|
|
4 | 5 | arc 4 -> 5
|
|
|
|
5 | 1 | arc 5 -> 1
|
|
|
|
2 | 3 | arc 2 -> 3
|
|
|
|
4 | 5 | arc 4 -> 5
|
|
|
|
5 | 1 | arc 5 -> 1
|
|
|
|
1 | 4 | arc 1 -> 4
|
|
|
|
1 | 3 | arc 1 -> 3
|
|
|
|
1 | 2 | arc 1 -> 2
|
|
|
|
5 | 1 | arc 5 -> 1
|
|
|
|
1 | 4 | arc 1 -> 4
|
|
|
|
1 | 3 | arc 1 -> 3
|
|
|
|
1 | 2 | arc 1 -> 2
|
|
|
|
4 | 5 | arc 4 -> 5
|
|
|
|
2 | 3 | arc 2 -> 3
|
|
|
|
1 | 4 | arc 1 -> 4
|
|
|
|
1 | 3 | arc 1 -> 3
|
|
|
|
1 | 2 | arc 1 -> 2
|
|
|
|
4 | 5 | arc 4 -> 5
|
|
|
|
2 | 3 | arc 2 -> 3
|
|
|
|
5 | 1 | arc 5 -> 1
|
|
|
|
2 | 3 | arc 2 -> 3
|
|
|
|
(25 rows)
|
|
|
|
|
|
|
|
-- star expansion
|
|
|
|
with recursive a as (
|
|
|
|
select 1 as b
|
|
|
|
union all
|
|
|
|
select * from a
|
2021-02-27 08:11:14 +01:00
|
|
|
) cycle b set c using p
|
2021-02-01 13:54:59 +01:00
|
|
|
select * from a;
|
|
|
|
b | c | p
|
|
|
|
---+---+-----------
|
|
|
|
1 | f | {(1)}
|
|
|
|
1 | t | {(1),(1)}
|
|
|
|
(2 rows)
|
|
|
|
|
|
|
|
-- search+cycle
|
|
|
|
with recursive search_graph(f, t, label) as (
|
|
|
|
select * from graph g
|
|
|
|
union all
|
|
|
|
select g.*
|
|
|
|
from graph g, search_graph sg
|
|
|
|
where g.f = sg.t
|
|
|
|
) search depth first by f, t set seq
|
2021-02-27 08:11:14 +01:00
|
|
|
cycle f, t set is_cycle using path
|
2021-02-01 13:54:59 +01:00
|
|
|
select * from search_graph;
|
|
|
|
f | t | label | seq | is_cycle | path
|
|
|
|
---+---+------------+-------------------------------------------+----------+-------------------------------------------
|
|
|
|
1 | 2 | arc 1 -> 2 | {"(1,2)"} | f | {"(1,2)"}
|
|
|
|
1 | 3 | arc 1 -> 3 | {"(1,3)"} | f | {"(1,3)"}
|
|
|
|
2 | 3 | arc 2 -> 3 | {"(2,3)"} | f | {"(2,3)"}
|
|
|
|
1 | 4 | arc 1 -> 4 | {"(1,4)"} | f | {"(1,4)"}
|
|
|
|
4 | 5 | arc 4 -> 5 | {"(4,5)"} | f | {"(4,5)"}
|
|
|
|
5 | 1 | arc 5 -> 1 | {"(5,1)"} | f | {"(5,1)"}
|
|
|
|
1 | 2 | arc 1 -> 2 | {"(5,1)","(1,2)"} | f | {"(5,1)","(1,2)"}
|
|
|
|
1 | 3 | arc 1 -> 3 | {"(5,1)","(1,3)"} | f | {"(5,1)","(1,3)"}
|
|
|
|
1 | 4 | arc 1 -> 4 | {"(5,1)","(1,4)"} | f | {"(5,1)","(1,4)"}
|
|
|
|
2 | 3 | arc 2 -> 3 | {"(1,2)","(2,3)"} | f | {"(1,2)","(2,3)"}
|
|
|
|
4 | 5 | arc 4 -> 5 | {"(1,4)","(4,5)"} | f | {"(1,4)","(4,5)"}
|
|
|
|
5 | 1 | arc 5 -> 1 | {"(4,5)","(5,1)"} | f | {"(4,5)","(5,1)"}
|
|
|
|
1 | 2 | arc 1 -> 2 | {"(4,5)","(5,1)","(1,2)"} | f | {"(4,5)","(5,1)","(1,2)"}
|
|
|
|
1 | 3 | arc 1 -> 3 | {"(4,5)","(5,1)","(1,3)"} | f | {"(4,5)","(5,1)","(1,3)"}
|
|
|
|
1 | 4 | arc 1 -> 4 | {"(4,5)","(5,1)","(1,4)"} | f | {"(4,5)","(5,1)","(1,4)"}
|
|
|
|
2 | 3 | arc 2 -> 3 | {"(5,1)","(1,2)","(2,3)"} | f | {"(5,1)","(1,2)","(2,3)"}
|
|
|
|
4 | 5 | arc 4 -> 5 | {"(5,1)","(1,4)","(4,5)"} | f | {"(5,1)","(1,4)","(4,5)"}
|
|
|
|
5 | 1 | arc 5 -> 1 | {"(1,4)","(4,5)","(5,1)"} | f | {"(1,4)","(4,5)","(5,1)"}
|
|
|
|
1 | 2 | arc 1 -> 2 | {"(1,4)","(4,5)","(5,1)","(1,2)"} | f | {"(1,4)","(4,5)","(5,1)","(1,2)"}
|
|
|
|
1 | 3 | arc 1 -> 3 | {"(1,4)","(4,5)","(5,1)","(1,3)"} | f | {"(1,4)","(4,5)","(5,1)","(1,3)"}
|
|
|
|
1 | 4 | arc 1 -> 4 | {"(1,4)","(4,5)","(5,1)","(1,4)"} | t | {"(1,4)","(4,5)","(5,1)","(1,4)"}
|
|
|
|
2 | 3 | arc 2 -> 3 | {"(4,5)","(5,1)","(1,2)","(2,3)"} | f | {"(4,5)","(5,1)","(1,2)","(2,3)"}
|
|
|
|
4 | 5 | arc 4 -> 5 | {"(4,5)","(5,1)","(1,4)","(4,5)"} | t | {"(4,5)","(5,1)","(1,4)","(4,5)"}
|
|
|
|
5 | 1 | arc 5 -> 1 | {"(5,1)","(1,4)","(4,5)","(5,1)"} | t | {"(5,1)","(1,4)","(4,5)","(5,1)"}
|
|
|
|
2 | 3 | arc 2 -> 3 | {"(1,4)","(4,5)","(5,1)","(1,2)","(2,3)"} | f | {"(1,4)","(4,5)","(5,1)","(1,2)","(2,3)"}
|
|
|
|
(25 rows)
|
|
|
|
|
|
|
|
with recursive search_graph(f, t, label) as (
|
|
|
|
select * from graph g
|
|
|
|
union all
|
|
|
|
select g.*
|
|
|
|
from graph g, search_graph sg
|
|
|
|
where g.f = sg.t
|
|
|
|
) search breadth first by f, t set seq
|
2021-02-27 08:11:14 +01:00
|
|
|
cycle f, t set is_cycle using path
|
2021-02-01 13:54:59 +01:00
|
|
|
select * from search_graph;
|
|
|
|
f | t | label | seq | is_cycle | path
|
|
|
|
---+---+------------+---------+----------+-------------------------------------------
|
|
|
|
1 | 2 | arc 1 -> 2 | (0,1,2) | f | {"(1,2)"}
|
|
|
|
1 | 3 | arc 1 -> 3 | (0,1,3) | f | {"(1,3)"}
|
|
|
|
2 | 3 | arc 2 -> 3 | (0,2,3) | f | {"(2,3)"}
|
|
|
|
1 | 4 | arc 1 -> 4 | (0,1,4) | f | {"(1,4)"}
|
|
|
|
4 | 5 | arc 4 -> 5 | (0,4,5) | f | {"(4,5)"}
|
|
|
|
5 | 1 | arc 5 -> 1 | (0,5,1) | f | {"(5,1)"}
|
|
|
|
1 | 2 | arc 1 -> 2 | (1,1,2) | f | {"(5,1)","(1,2)"}
|
|
|
|
1 | 3 | arc 1 -> 3 | (1,1,3) | f | {"(5,1)","(1,3)"}
|
|
|
|
1 | 4 | arc 1 -> 4 | (1,1,4) | f | {"(5,1)","(1,4)"}
|
|
|
|
2 | 3 | arc 2 -> 3 | (1,2,3) | f | {"(1,2)","(2,3)"}
|
|
|
|
4 | 5 | arc 4 -> 5 | (1,4,5) | f | {"(1,4)","(4,5)"}
|
|
|
|
5 | 1 | arc 5 -> 1 | (1,5,1) | f | {"(4,5)","(5,1)"}
|
|
|
|
1 | 2 | arc 1 -> 2 | (2,1,2) | f | {"(4,5)","(5,1)","(1,2)"}
|
|
|
|
1 | 3 | arc 1 -> 3 | (2,1,3) | f | {"(4,5)","(5,1)","(1,3)"}
|
|
|
|
1 | 4 | arc 1 -> 4 | (2,1,4) | f | {"(4,5)","(5,1)","(1,4)"}
|
|
|
|
2 | 3 | arc 2 -> 3 | (2,2,3) | f | {"(5,1)","(1,2)","(2,3)"}
|
|
|
|
4 | 5 | arc 4 -> 5 | (2,4,5) | f | {"(5,1)","(1,4)","(4,5)"}
|
|
|
|
5 | 1 | arc 5 -> 1 | (2,5,1) | f | {"(1,4)","(4,5)","(5,1)"}
|
|
|
|
1 | 2 | arc 1 -> 2 | (3,1,2) | f | {"(1,4)","(4,5)","(5,1)","(1,2)"}
|
|
|
|
1 | 3 | arc 1 -> 3 | (3,1,3) | f | {"(1,4)","(4,5)","(5,1)","(1,3)"}
|
|
|
|
1 | 4 | arc 1 -> 4 | (3,1,4) | t | {"(1,4)","(4,5)","(5,1)","(1,4)"}
|
|
|
|
2 | 3 | arc 2 -> 3 | (3,2,3) | f | {"(4,5)","(5,1)","(1,2)","(2,3)"}
|
|
|
|
4 | 5 | arc 4 -> 5 | (3,4,5) | t | {"(4,5)","(5,1)","(1,4)","(4,5)"}
|
|
|
|
5 | 1 | arc 5 -> 1 | (3,5,1) | t | {"(5,1)","(1,4)","(4,5)","(5,1)"}
|
|
|
|
2 | 3 | arc 2 -> 3 | (4,2,3) | f | {"(1,4)","(4,5)","(5,1)","(1,2)","(2,3)"}
|
|
|
|
(25 rows)
|
|
|
|
|
|
|
|
-- various syntax errors
|
|
|
|
with recursive search_graph(f, t, label) as (
|
|
|
|
select * from graph g
|
|
|
|
union all
|
|
|
|
select g.*
|
|
|
|
from graph g, search_graph sg
|
|
|
|
where g.f = sg.t
|
2021-02-27 08:11:14 +01:00
|
|
|
) cycle foo, tar set is_cycle using path
|
2021-02-01 13:54:59 +01:00
|
|
|
select * from search_graph;
|
|
|
|
ERROR: cycle column "foo" not in WITH query column list
|
2021-02-27 08:11:14 +01:00
|
|
|
LINE 7: ) cycle foo, tar set is_cycle using path
|
2021-02-01 13:54:59 +01:00
|
|
|
^
|
|
|
|
with recursive search_graph(f, t, label) as (
|
|
|
|
select * from graph g
|
|
|
|
union all
|
|
|
|
select g.*
|
|
|
|
from graph g, search_graph sg
|
|
|
|
where g.f = sg.t
|
|
|
|
) cycle f, t set is_cycle to true default 55 using path
|
|
|
|
select * from search_graph;
|
|
|
|
ERROR: CYCLE types boolean and integer cannot be matched
|
|
|
|
LINE 7: ) cycle f, t set is_cycle to true default 55 using path
|
|
|
|
^
|
|
|
|
with recursive search_graph(f, t, label) as (
|
|
|
|
select * from graph g
|
|
|
|
union all
|
|
|
|
select g.*
|
|
|
|
from graph g, search_graph sg
|
|
|
|
where g.f = sg.t
|
|
|
|
) cycle f, t set is_cycle to point '(1,1)' default point '(0,0)' using path
|
|
|
|
select * from search_graph;
|
|
|
|
ERROR: could not identify an equality operator for type point
|
|
|
|
with recursive search_graph(f, t, label) as (
|
|
|
|
select * from graph g
|
|
|
|
union all
|
|
|
|
select g.*
|
|
|
|
from graph g, search_graph sg
|
|
|
|
where g.f = sg.t
|
|
|
|
) cycle f, t set label to true default false using path
|
|
|
|
select * from search_graph;
|
|
|
|
ERROR: cycle mark column name "label" already used in WITH query column list
|
|
|
|
LINE 7: ) cycle f, t set label to true default false using path
|
|
|
|
^
|
|
|
|
with recursive search_graph(f, t, label) as (
|
|
|
|
select * from graph g
|
|
|
|
union all
|
|
|
|
select g.*
|
|
|
|
from graph g, search_graph sg
|
|
|
|
where g.f = sg.t
|
|
|
|
) cycle f, t set is_cycle to true default false using label
|
|
|
|
select * from search_graph;
|
|
|
|
ERROR: cycle path column name "label" already used in WITH query column list
|
|
|
|
LINE 7: ) cycle f, t set is_cycle to true default false using label
|
|
|
|
^
|
|
|
|
with recursive search_graph(f, t, label) as (
|
|
|
|
select * from graph g
|
|
|
|
union all
|
|
|
|
select g.*
|
|
|
|
from graph g, search_graph sg
|
|
|
|
where g.f = sg.t
|
|
|
|
) cycle f, t set foo to true default false using foo
|
|
|
|
select * from search_graph;
|
|
|
|
ERROR: cycle mark column name and cycle path column name are the same
|
|
|
|
LINE 7: ) cycle f, t set foo to true default false using foo
|
|
|
|
^
|
|
|
|
with recursive search_graph(f, t, label) as (
|
|
|
|
select * from graph g
|
|
|
|
union all
|
|
|
|
select g.*
|
|
|
|
from graph g, search_graph sg
|
|
|
|
where g.f = sg.t
|
|
|
|
) cycle f, t, f set is_cycle to true default false using path
|
|
|
|
select * from search_graph;
|
|
|
|
ERROR: cycle column "f" specified more than once
|
|
|
|
LINE 7: ) cycle f, t, f set is_cycle to true default false using pat...
|
|
|
|
^
|
|
|
|
with recursive search_graph(f, t, label) as (
|
|
|
|
select * from graph g
|
|
|
|
union all
|
|
|
|
select g.*
|
|
|
|
from graph g, search_graph sg
|
|
|
|
where g.f = sg.t
|
|
|
|
) search depth first by f, t set foo
|
|
|
|
cycle f, t set foo to true default false using path
|
|
|
|
select * from search_graph;
|
|
|
|
ERROR: search sequence column name and cycle mark column name are the same
|
|
|
|
LINE 7: ) search depth first by f, t set foo
|
|
|
|
^
|
|
|
|
with recursive search_graph(f, t, label) as (
|
|
|
|
select * from graph g
|
|
|
|
union all
|
|
|
|
select g.*
|
|
|
|
from graph g, search_graph sg
|
|
|
|
where g.f = sg.t
|
|
|
|
) search depth first by f, t set foo
|
|
|
|
cycle f, t set is_cycle to true default false using foo
|
|
|
|
select * from search_graph;
|
2021-05-11 09:06:49 +02:00
|
|
|
ERROR: search sequence column name and cycle path column name are the same
|
2021-02-01 13:54:59 +01:00
|
|
|
LINE 7: ) search depth first by f, t set foo
|
|
|
|
^
|
|
|
|
-- test ruleutils and view expansion
|
2021-02-27 08:11:14 +01:00
|
|
|
create temp view v_cycle1 as
|
2021-02-01 13:54:59 +01:00
|
|
|
with recursive search_graph(f, t, label) as (
|
|
|
|
select * from graph g
|
|
|
|
union all
|
|
|
|
select g.*
|
|
|
|
from graph g, search_graph sg
|
|
|
|
where g.f = sg.t
|
2021-02-27 08:11:14 +01:00
|
|
|
) cycle f, t set is_cycle using path
|
2021-02-01 13:54:59 +01:00
|
|
|
select f, t, label from search_graph;
|
2021-02-27 08:11:14 +01:00
|
|
|
create temp view v_cycle2 as
|
|
|
|
with recursive search_graph(f, t, label) as (
|
|
|
|
select * from graph g
|
|
|
|
union all
|
|
|
|
select g.*
|
|
|
|
from graph g, search_graph sg
|
|
|
|
where g.f = sg.t
|
|
|
|
) cycle f, t set is_cycle to 'Y' default 'N' using path
|
|
|
|
select f, t, label from search_graph;
|
|
|
|
select pg_get_viewdef('v_cycle1');
|
|
|
|
pg_get_viewdef
|
|
|
|
------------------------------------------------
|
|
|
|
WITH RECURSIVE search_graph(f, t, label) AS (+
|
|
|
|
SELECT g.f, +
|
|
|
|
g.t, +
|
|
|
|
g.label +
|
|
|
|
FROM graph g +
|
|
|
|
UNION ALL +
|
|
|
|
SELECT g.f, +
|
|
|
|
g.t, +
|
|
|
|
g.label +
|
|
|
|
FROM graph g, +
|
|
|
|
search_graph sg +
|
|
|
|
WHERE (g.f = sg.t) +
|
|
|
|
) CYCLE f, t SET is_cycle USING path +
|
2023-01-18 19:23:57 +01:00
|
|
|
SELECT f, +
|
|
|
|
t, +
|
|
|
|
label +
|
2021-02-01 13:54:59 +01:00
|
|
|
FROM search_graph;
|
|
|
|
(1 row)
|
|
|
|
|
2021-02-27 08:11:14 +01:00
|
|
|
select pg_get_viewdef('v_cycle2');
|
|
|
|
pg_get_viewdef
|
|
|
|
-----------------------------------------------------------------------------
|
|
|
|
WITH RECURSIVE search_graph(f, t, label) AS ( +
|
|
|
|
SELECT g.f, +
|
|
|
|
g.t, +
|
|
|
|
g.label +
|
|
|
|
FROM graph g +
|
|
|
|
UNION ALL +
|
|
|
|
SELECT g.f, +
|
|
|
|
g.t, +
|
|
|
|
g.label +
|
|
|
|
FROM graph g, +
|
|
|
|
search_graph sg +
|
|
|
|
WHERE (g.f = sg.t) +
|
|
|
|
) CYCLE f, t SET is_cycle TO 'Y'::text DEFAULT 'N'::text USING path+
|
2023-01-18 19:23:57 +01:00
|
|
|
SELECT f, +
|
|
|
|
t, +
|
|
|
|
label +
|
2021-02-27 08:11:14 +01:00
|
|
|
FROM search_graph;
|
|
|
|
(1 row)
|
|
|
|
|
|
|
|
select * from v_cycle1;
|
|
|
|
f | t | label
|
|
|
|
---+---+------------
|
|
|
|
1 | 2 | arc 1 -> 2
|
|
|
|
1 | 3 | arc 1 -> 3
|
|
|
|
2 | 3 | arc 2 -> 3
|
|
|
|
1 | 4 | arc 1 -> 4
|
|
|
|
4 | 5 | arc 4 -> 5
|
|
|
|
5 | 1 | arc 5 -> 1
|
|
|
|
1 | 2 | arc 1 -> 2
|
|
|
|
1 | 3 | arc 1 -> 3
|
|
|
|
1 | 4 | arc 1 -> 4
|
|
|
|
2 | 3 | arc 2 -> 3
|
|
|
|
4 | 5 | arc 4 -> 5
|
|
|
|
5 | 1 | arc 5 -> 1
|
|
|
|
1 | 2 | arc 1 -> 2
|
|
|
|
1 | 3 | arc 1 -> 3
|
|
|
|
1 | 4 | arc 1 -> 4
|
|
|
|
2 | 3 | arc 2 -> 3
|
|
|
|
4 | 5 | arc 4 -> 5
|
|
|
|
5 | 1 | arc 5 -> 1
|
|
|
|
1 | 2 | arc 1 -> 2
|
|
|
|
1 | 3 | arc 1 -> 3
|
|
|
|
1 | 4 | arc 1 -> 4
|
|
|
|
2 | 3 | arc 2 -> 3
|
|
|
|
4 | 5 | arc 4 -> 5
|
|
|
|
5 | 1 | arc 5 -> 1
|
|
|
|
2 | 3 | arc 2 -> 3
|
|
|
|
(25 rows)
|
|
|
|
|
|
|
|
select * from v_cycle2;
|
2021-02-01 13:54:59 +01:00
|
|
|
f | t | label
|
|
|
|
---+---+------------
|
|
|
|
1 | 2 | arc 1 -> 2
|
|
|
|
1 | 3 | arc 1 -> 3
|
|
|
|
2 | 3 | arc 2 -> 3
|
|
|
|
1 | 4 | arc 1 -> 4
|
|
|
|
4 | 5 | arc 4 -> 5
|
|
|
|
5 | 1 | arc 5 -> 1
|
|
|
|
1 | 2 | arc 1 -> 2
|
|
|
|
1 | 3 | arc 1 -> 3
|
|
|
|
1 | 4 | arc 1 -> 4
|
|
|
|
2 | 3 | arc 2 -> 3
|
|
|
|
4 | 5 | arc 4 -> 5
|
|
|
|
5 | 1 | arc 5 -> 1
|
|
|
|
1 | 2 | arc 1 -> 2
|
|
|
|
1 | 3 | arc 1 -> 3
|
|
|
|
1 | 4 | arc 1 -> 4
|
|
|
|
2 | 3 | arc 2 -> 3
|
|
|
|
4 | 5 | arc 4 -> 5
|
|
|
|
5 | 1 | arc 5 -> 1
|
|
|
|
1 | 2 | arc 1 -> 2
|
|
|
|
1 | 3 | arc 1 -> 3
|
|
|
|
1 | 4 | arc 1 -> 4
|
|
|
|
2 | 3 | arc 2 -> 3
|
|
|
|
4 | 5 | arc 4 -> 5
|
|
|
|
5 | 1 | arc 5 -> 1
|
|
|
|
2 | 3 | arc 2 -> 3
|
|
|
|
(25 rows)
|
|
|
|
|
2008-10-04 23:56:55 +02:00
|
|
|
--
|
|
|
|
-- test multiple WITH queries
|
|
|
|
--
|
|
|
|
WITH RECURSIVE
|
|
|
|
y (id) AS (VALUES (1)),
|
|
|
|
x (id) AS (SELECT * FROM y UNION ALL SELECT id+1 FROM x WHERE id < 5)
|
|
|
|
SELECT * FROM x;
|
|
|
|
id
|
|
|
|
----
|
|
|
|
1
|
|
|
|
2
|
|
|
|
3
|
|
|
|
4
|
|
|
|
5
|
|
|
|
(5 rows)
|
|
|
|
|
|
|
|
-- forward reference OK
|
|
|
|
WITH RECURSIVE
|
|
|
|
x(id) AS (SELECT * FROM y UNION ALL SELECT id+1 FROM x WHERE id < 5),
|
|
|
|
y(id) AS (values (1))
|
|
|
|
SELECT * FROM x;
|
|
|
|
id
|
|
|
|
----
|
|
|
|
1
|
|
|
|
2
|
|
|
|
3
|
|
|
|
4
|
|
|
|
5
|
|
|
|
(5 rows)
|
|
|
|
|
|
|
|
WITH RECURSIVE
|
|
|
|
x(id) AS
|
|
|
|
(VALUES (1) UNION ALL SELECT id+1 FROM x WHERE id < 5),
|
|
|
|
y(id) AS
|
|
|
|
(VALUES (1) UNION ALL SELECT id+1 FROM y WHERE id < 10)
|
|
|
|
SELECT y.*, x.* FROM y LEFT JOIN x USING (id);
|
|
|
|
id | id
|
|
|
|
----+----
|
|
|
|
1 | 1
|
|
|
|
2 | 2
|
|
|
|
3 | 3
|
|
|
|
4 | 4
|
|
|
|
5 | 5
|
|
|
|
6 |
|
|
|
|
7 |
|
|
|
|
8 |
|
|
|
|
9 |
|
|
|
|
10 |
|
|
|
|
(10 rows)
|
|
|
|
|
|
|
|
WITH RECURSIVE
|
|
|
|
x(id) AS
|
|
|
|
(VALUES (1) UNION ALL SELECT id+1 FROM x WHERE id < 5),
|
|
|
|
y(id) AS
|
|
|
|
(VALUES (1) UNION ALL SELECT id+1 FROM x WHERE id < 10)
|
|
|
|
SELECT y.*, x.* FROM y LEFT JOIN x USING (id);
|
|
|
|
id | id
|
|
|
|
----+----
|
|
|
|
1 | 1
|
|
|
|
2 | 2
|
|
|
|
3 | 3
|
|
|
|
4 | 4
|
|
|
|
5 | 5
|
|
|
|
6 |
|
|
|
|
(6 rows)
|
|
|
|
|
|
|
|
WITH RECURSIVE
|
|
|
|
x(id) AS
|
|
|
|
(SELECT 1 UNION ALL SELECT id+1 FROM x WHERE id < 3 ),
|
|
|
|
y(id) AS
|
|
|
|
(SELECT * FROM x UNION ALL SELECT * FROM x),
|
|
|
|
z(id) AS
|
|
|
|
(SELECT * FROM x UNION ALL SELECT id+1 FROM z WHERE id < 10)
|
|
|
|
SELECT * FROM z;
|
|
|
|
id
|
|
|
|
----
|
|
|
|
1
|
|
|
|
2
|
|
|
|
3
|
|
|
|
2
|
|
|
|
3
|
|
|
|
4
|
|
|
|
3
|
|
|
|
4
|
|
|
|
5
|
|
|
|
4
|
|
|
|
5
|
|
|
|
6
|
|
|
|
5
|
|
|
|
6
|
|
|
|
7
|
|
|
|
6
|
|
|
|
7
|
|
|
|
8
|
|
|
|
7
|
|
|
|
8
|
|
|
|
9
|
|
|
|
8
|
|
|
|
9
|
|
|
|
10
|
|
|
|
9
|
|
|
|
10
|
|
|
|
10
|
|
|
|
(27 rows)
|
|
|
|
|
|
|
|
WITH RECURSIVE
|
|
|
|
x(id) AS
|
|
|
|
(SELECT 1 UNION ALL SELECT id+1 FROM x WHERE id < 3 ),
|
|
|
|
y(id) AS
|
|
|
|
(SELECT * FROM x UNION ALL SELECT * FROM x),
|
|
|
|
z(id) AS
|
|
|
|
(SELECT * FROM y UNION ALL SELECT id+1 FROM z WHERE id < 10)
|
|
|
|
SELECT * FROM z;
|
|
|
|
id
|
|
|
|
----
|
|
|
|
1
|
|
|
|
2
|
|
|
|
3
|
|
|
|
1
|
|
|
|
2
|
|
|
|
3
|
|
|
|
2
|
|
|
|
3
|
|
|
|
4
|
|
|
|
2
|
|
|
|
3
|
|
|
|
4
|
|
|
|
3
|
|
|
|
4
|
|
|
|
5
|
|
|
|
3
|
|
|
|
4
|
|
|
|
5
|
|
|
|
4
|
|
|
|
5
|
|
|
|
6
|
|
|
|
4
|
|
|
|
5
|
|
|
|
6
|
|
|
|
5
|
|
|
|
6
|
|
|
|
7
|
|
|
|
5
|
|
|
|
6
|
|
|
|
7
|
|
|
|
6
|
|
|
|
7
|
|
|
|
8
|
|
|
|
6
|
|
|
|
7
|
|
|
|
8
|
|
|
|
7
|
|
|
|
8
|
|
|
|
9
|
|
|
|
7
|
|
|
|
8
|
|
|
|
9
|
|
|
|
8
|
|
|
|
9
|
|
|
|
10
|
|
|
|
8
|
|
|
|
9
|
|
|
|
10
|
|
|
|
9
|
|
|
|
10
|
|
|
|
9
|
|
|
|
10
|
|
|
|
10
|
|
|
|
10
|
|
|
|
(54 rows)
|
|
|
|
|
2010-10-16 01:53:59 +02:00
|
|
|
--
|
2011-02-26 00:56:23 +01:00
|
|
|
-- Test WITH attached to a data-modifying statement
|
2010-10-16 01:53:59 +02:00
|
|
|
--
|
|
|
|
CREATE TEMPORARY TABLE y (a INTEGER);
|
|
|
|
INSERT INTO y SELECT generate_series(1, 10);
|
|
|
|
WITH t AS (
|
|
|
|
SELECT a FROM y
|
|
|
|
)
|
|
|
|
INSERT INTO y
|
|
|
|
SELECT a+20 FROM t RETURNING *;
|
|
|
|
a
|
|
|
|
----
|
|
|
|
21
|
|
|
|
22
|
|
|
|
23
|
|
|
|
24
|
|
|
|
25
|
|
|
|
26
|
|
|
|
27
|
|
|
|
28
|
|
|
|
29
|
|
|
|
30
|
|
|
|
(10 rows)
|
|
|
|
|
|
|
|
SELECT * FROM y;
|
|
|
|
a
|
|
|
|
----
|
|
|
|
1
|
|
|
|
2
|
|
|
|
3
|
|
|
|
4
|
|
|
|
5
|
|
|
|
6
|
|
|
|
7
|
|
|
|
8
|
|
|
|
9
|
|
|
|
10
|
|
|
|
21
|
|
|
|
22
|
|
|
|
23
|
|
|
|
24
|
|
|
|
25
|
|
|
|
26
|
|
|
|
27
|
|
|
|
28
|
|
|
|
29
|
|
|
|
30
|
|
|
|
(20 rows)
|
|
|
|
|
|
|
|
WITH t AS (
|
|
|
|
SELECT a FROM y
|
|
|
|
)
|
|
|
|
UPDATE y SET a = y.a-10 FROM t WHERE y.a > 20 AND t.a = y.a RETURNING y.a;
|
|
|
|
a
|
|
|
|
----
|
|
|
|
11
|
|
|
|
12
|
|
|
|
13
|
|
|
|
14
|
|
|
|
15
|
|
|
|
16
|
|
|
|
17
|
|
|
|
18
|
|
|
|
19
|
|
|
|
20
|
|
|
|
(10 rows)
|
|
|
|
|
|
|
|
SELECT * FROM y;
|
|
|
|
a
|
|
|
|
----
|
|
|
|
1
|
|
|
|
2
|
|
|
|
3
|
|
|
|
4
|
|
|
|
5
|
|
|
|
6
|
|
|
|
7
|
|
|
|
8
|
|
|
|
9
|
|
|
|
10
|
|
|
|
11
|
|
|
|
12
|
|
|
|
13
|
|
|
|
14
|
|
|
|
15
|
|
|
|
16
|
|
|
|
17
|
|
|
|
18
|
|
|
|
19
|
|
|
|
20
|
|
|
|
(20 rows)
|
|
|
|
|
|
|
|
WITH RECURSIVE t(a) AS (
|
|
|
|
SELECT 11
|
|
|
|
UNION ALL
|
|
|
|
SELECT a+1 FROM t WHERE a < 50
|
|
|
|
)
|
|
|
|
DELETE FROM y USING t WHERE t.a = y.a RETURNING y.a;
|
|
|
|
a
|
|
|
|
----
|
|
|
|
11
|
|
|
|
12
|
|
|
|
13
|
|
|
|
14
|
|
|
|
15
|
|
|
|
16
|
|
|
|
17
|
|
|
|
18
|
|
|
|
19
|
|
|
|
20
|
|
|
|
(10 rows)
|
|
|
|
|
|
|
|
SELECT * FROM y;
|
|
|
|
a
|
|
|
|
----
|
|
|
|
1
|
|
|
|
2
|
|
|
|
3
|
|
|
|
4
|
|
|
|
5
|
|
|
|
6
|
|
|
|
7
|
|
|
|
8
|
|
|
|
9
|
|
|
|
10
|
|
|
|
(10 rows)
|
|
|
|
|
|
|
|
DROP TABLE y;
|
2008-10-04 23:56:55 +02:00
|
|
|
--
|
|
|
|
-- error cases
|
|
|
|
--
|
2022-05-18 20:28:31 +02:00
|
|
|
WITH x(n, b) AS (SELECT 1)
|
|
|
|
SELECT * FROM x;
|
|
|
|
ERROR: WITH query "x" has 1 columns available but 2 columns specified
|
|
|
|
LINE 1: WITH x(n, b) AS (SELECT 1)
|
|
|
|
^
|
2008-10-04 23:56:55 +02:00
|
|
|
-- INTERSECT
|
|
|
|
WITH RECURSIVE x(n) AS (SELECT 1 INTERSECT SELECT n+1 FROM x)
|
|
|
|
SELECT * FROM x;
|
2008-10-07 21:27:04 +02:00
|
|
|
ERROR: recursive query "x" does not have the form non-recursive-term UNION [ALL] recursive-term
|
2008-10-04 23:56:55 +02:00
|
|
|
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)
|
|
|
|
SELECT * FROM x;
|
2008-10-07 21:27:04 +02:00
|
|
|
ERROR: recursive query "x" does not have the form non-recursive-term UNION [ALL] recursive-term
|
2008-10-04 23:56:55 +02:00
|
|
|
LINE 1: WITH RECURSIVE x(n) AS (SELECT 1 INTERSECT ALL SELECT n+1 FR...
|
|
|
|
^
|
|
|
|
-- EXCEPT
|
|
|
|
WITH RECURSIVE x(n) AS (SELECT 1 EXCEPT SELECT n+1 FROM x)
|
|
|
|
SELECT * FROM x;
|
2008-10-07 21:27:04 +02:00
|
|
|
ERROR: recursive query "x" does not have the form non-recursive-term UNION [ALL] recursive-term
|
2008-10-04 23:56:55 +02:00
|
|
|
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)
|
|
|
|
SELECT * FROM x;
|
2008-10-07 21:27:04 +02:00
|
|
|
ERROR: recursive query "x" does not have the form non-recursive-term UNION [ALL] recursive-term
|
2008-10-04 23:56:55 +02:00
|
|
|
LINE 1: WITH RECURSIVE x(n) AS (SELECT 1 EXCEPT ALL SELECT n+1 FROM ...
|
|
|
|
^
|
|
|
|
-- no non-recursive term
|
|
|
|
WITH RECURSIVE x(n) AS (SELECT n FROM x)
|
|
|
|
SELECT * FROM x;
|
2008-10-07 21:27:04 +02:00
|
|
|
ERROR: recursive query "x" does not have the form non-recursive-term UNION [ALL] recursive-term
|
2008-10-04 23:56:55 +02:00
|
|
|
LINE 1: WITH RECURSIVE x(n) AS (SELECT n FROM x)
|
|
|
|
^
|
|
|
|
-- recursive term in the left hand side (strictly speaking, should allow this)
|
|
|
|
WITH RECURSIVE x(n) AS (SELECT n FROM x UNION ALL SELECT 1)
|
|
|
|
SELECT * FROM x;
|
|
|
|
ERROR: recursive reference to query "x" must not appear within its non-recursive term
|
|
|
|
LINE 1: WITH RECURSIVE x(n) AS (SELECT n FROM x UNION ALL SELECT 1)
|
|
|
|
^
|
|
|
|
CREATE TEMPORARY TABLE y (a INTEGER);
|
|
|
|
INSERT INTO y SELECT generate_series(1, 10);
|
|
|
|
-- LEFT JOIN
|
|
|
|
WITH RECURSIVE x(n) AS (SELECT a FROM y WHERE a = 1
|
|
|
|
UNION ALL
|
|
|
|
SELECT x.n+1 FROM y LEFT JOIN x ON x.n = y.a WHERE n < 10)
|
|
|
|
SELECT * FROM x;
|
|
|
|
ERROR: recursive reference to query "x" must not appear within an outer join
|
|
|
|
LINE 3: SELECT x.n+1 FROM y LEFT JOIN x ON x.n = y.a WHERE n < 10)
|
|
|
|
^
|
|
|
|
-- RIGHT JOIN
|
|
|
|
WITH RECURSIVE x(n) AS (SELECT a FROM y WHERE a = 1
|
|
|
|
UNION ALL
|
|
|
|
SELECT x.n+1 FROM x RIGHT JOIN y ON x.n = y.a WHERE n < 10)
|
|
|
|
SELECT * FROM x;
|
|
|
|
ERROR: recursive reference to query "x" must not appear within an outer join
|
|
|
|
LINE 3: SELECT x.n+1 FROM x RIGHT JOIN y ON x.n = y.a WHERE n < 10)
|
|
|
|
^
|
|
|
|
-- FULL JOIN
|
|
|
|
WITH RECURSIVE x(n) AS (SELECT a FROM y WHERE a = 1
|
|
|
|
UNION ALL
|
|
|
|
SELECT x.n+1 FROM x FULL JOIN y ON x.n = y.a WHERE n < 10)
|
|
|
|
SELECT * FROM x;
|
|
|
|
ERROR: recursive reference to query "x" must not appear within an outer join
|
|
|
|
LINE 3: SELECT x.n+1 FROM x FULL JOIN y ON x.n = y.a WHERE n < 10)
|
|
|
|
^
|
|
|
|
-- subquery
|
|
|
|
WITH RECURSIVE x(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM x
|
|
|
|
WHERE n IN (SELECT * FROM x))
|
|
|
|
SELECT * FROM x;
|
|
|
|
ERROR: recursive reference to query "x" must not appear within a subquery
|
|
|
|
LINE 2: WHERE n IN (SELECT * FROM x))
|
|
|
|
^
|
|
|
|
-- aggregate functions
|
|
|
|
WITH RECURSIVE x(n) AS (SELECT 1 UNION ALL SELECT count(*) FROM x)
|
|
|
|
SELECT * FROM x;
|
Centralize the logic for detecting misplaced aggregates, window funcs, etc.
Formerly we relied on checking after-the-fact to see if an expression
contained aggregates, window functions, or sub-selects when it shouldn't.
This is grotty, easily forgotten (indeed, we had forgotten to teach
DefineIndex about rejecting window functions), and none too efficient
since it requires extra traversals of the parse tree. To improve matters,
define an enum type that classifies all SQL sub-expressions, store it in
ParseState to show what kind of expression we are currently parsing, and
make transformAggregateCall, transformWindowFuncCall, and transformSubLink
check the expression type and throw error if the type indicates the
construct is disallowed. This allows removal of a large number of ad-hoc
checks scattered around the code base. The enum type is sufficiently
fine-grained that we can still produce error messages of at least the
same specificity as before.
Bringing these error checks together revealed that we'd been none too
consistent about phrasing of the error messages, so standardize the wording
a bit.
Also, rewrite checking of aggregate arguments so that it requires only one
traversal of the arguments, rather than up to three as before.
In passing, clean up some more comments left over from add_missing_from
support, and annotate some tests that I think are dead code now that that's
gone. (I didn't risk actually removing said dead code, though.)
2012-08-10 17:35:33 +02:00
|
|
|
ERROR: aggregate functions are not allowed in a recursive query's recursive term
|
2008-10-04 23:56:55 +02:00
|
|
|
LINE 1: WITH RECURSIVE x(n) AS (SELECT 1 UNION ALL SELECT count(*) F...
|
|
|
|
^
|
|
|
|
WITH RECURSIVE x(n) AS (SELECT 1 UNION ALL SELECT sum(n) FROM x)
|
|
|
|
SELECT * FROM x;
|
Centralize the logic for detecting misplaced aggregates, window funcs, etc.
Formerly we relied on checking after-the-fact to see if an expression
contained aggregates, window functions, or sub-selects when it shouldn't.
This is grotty, easily forgotten (indeed, we had forgotten to teach
DefineIndex about rejecting window functions), and none too efficient
since it requires extra traversals of the parse tree. To improve matters,
define an enum type that classifies all SQL sub-expressions, store it in
ParseState to show what kind of expression we are currently parsing, and
make transformAggregateCall, transformWindowFuncCall, and transformSubLink
check the expression type and throw error if the type indicates the
construct is disallowed. This allows removal of a large number of ad-hoc
checks scattered around the code base. The enum type is sufficiently
fine-grained that we can still produce error messages of at least the
same specificity as before.
Bringing these error checks together revealed that we'd been none too
consistent about phrasing of the error messages, so standardize the wording
a bit.
Also, rewrite checking of aggregate arguments so that it requires only one
traversal of the arguments, rather than up to three as before.
In passing, clean up some more comments left over from add_missing_from
support, and annotate some tests that I think are dead code now that that's
gone. (I didn't risk actually removing said dead code, though.)
2012-08-10 17:35:33 +02:00
|
|
|
ERROR: aggregate functions are not allowed in a recursive query's recursive term
|
2008-10-04 23:56:55 +02:00
|
|
|
LINE 1: WITH RECURSIVE x(n) AS (SELECT 1 UNION ALL SELECT sum(n) FRO...
|
|
|
|
^
|
|
|
|
-- ORDER BY
|
|
|
|
WITH RECURSIVE x(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM x ORDER BY 1)
|
|
|
|
SELECT * FROM x;
|
|
|
|
ERROR: ORDER BY in a recursive query is not implemented
|
|
|
|
LINE 1: ...VE x(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM x ORDER BY 1)
|
|
|
|
^
|
|
|
|
-- LIMIT/OFFSET
|
|
|
|
WITH RECURSIVE x(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM x LIMIT 10 OFFSET 1)
|
|
|
|
SELECT * FROM x;
|
|
|
|
ERROR: OFFSET in a recursive query is not implemented
|
|
|
|
LINE 1: ... AS (SELECT 1 UNION ALL SELECT n+1 FROM x LIMIT 10 OFFSET 1)
|
|
|
|
^
|
|
|
|
-- FOR UPDATE
|
|
|
|
WITH RECURSIVE x(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM x FOR UPDATE)
|
|
|
|
SELECT * FROM x;
|
|
|
|
ERROR: FOR UPDATE/SHARE in a recursive query is not implemented
|
|
|
|
-- target list has a recursive query name
|
|
|
|
WITH RECURSIVE x(id) AS (values (1)
|
|
|
|
UNION ALL
|
|
|
|
SELECT (SELECT * FROM x) FROM x WHERE id < 5
|
|
|
|
) SELECT * FROM x;
|
|
|
|
ERROR: recursive reference to query "x" must not appear within a subquery
|
|
|
|
LINE 3: SELECT (SELECT * FROM x) FROM x WHERE id < 5
|
|
|
|
^
|
|
|
|
-- mutual recursive query (not implemented)
|
|
|
|
WITH RECURSIVE
|
|
|
|
x (id) AS (SELECT 1 UNION ALL SELECT id+1 FROM y WHERE id < 5),
|
|
|
|
y (id) AS (SELECT 1 UNION ALL SELECT id+1 FROM x WHERE id < 5)
|
|
|
|
SELECT * FROM x;
|
|
|
|
ERROR: mutual recursion between WITH items is not implemented
|
|
|
|
LINE 2: x (id) AS (SELECT 1 UNION ALL SELECT id+1 FROM y WHERE id ...
|
|
|
|
^
|
|
|
|
-- non-linear recursion is not allowed
|
|
|
|
WITH RECURSIVE foo(i) AS
|
|
|
|
(values (1)
|
|
|
|
UNION ALL
|
|
|
|
(SELECT i+1 FROM foo WHERE i < 10
|
|
|
|
UNION ALL
|
|
|
|
SELECT i+1 FROM foo WHERE i < 5)
|
|
|
|
) SELECT * FROM foo;
|
|
|
|
ERROR: recursive reference to query "foo" must not appear more than once
|
|
|
|
LINE 6: SELECT i+1 FROM foo WHERE i < 5)
|
|
|
|
^
|
|
|
|
WITH RECURSIVE foo(i) AS
|
|
|
|
(values (1)
|
|
|
|
UNION ALL
|
|
|
|
SELECT * FROM
|
|
|
|
(SELECT i+1 FROM foo WHERE i < 10
|
|
|
|
UNION ALL
|
|
|
|
SELECT i+1 FROM foo WHERE i < 5) AS t
|
|
|
|
) SELECT * FROM foo;
|
|
|
|
ERROR: recursive reference to query "foo" must not appear more than once
|
|
|
|
LINE 7: SELECT i+1 FROM foo WHERE i < 5) AS t
|
|
|
|
^
|
|
|
|
WITH RECURSIVE foo(i) AS
|
|
|
|
(values (1)
|
|
|
|
UNION ALL
|
|
|
|
(SELECT i+1 FROM foo WHERE i < 10
|
|
|
|
EXCEPT
|
|
|
|
SELECT i+1 FROM foo WHERE i < 5)
|
|
|
|
) SELECT * FROM foo;
|
|
|
|
ERROR: recursive reference to query "foo" must not appear within EXCEPT
|
|
|
|
LINE 6: SELECT i+1 FROM foo WHERE i < 5)
|
|
|
|
^
|
|
|
|
WITH RECURSIVE foo(i) AS
|
|
|
|
(values (1)
|
|
|
|
UNION ALL
|
|
|
|
(SELECT i+1 FROM foo WHERE i < 10
|
|
|
|
INTERSECT
|
|
|
|
SELECT i+1 FROM foo WHERE i < 5)
|
|
|
|
) SELECT * FROM foo;
|
|
|
|
ERROR: recursive reference to query "foo" must not appear more than once
|
|
|
|
LINE 6: SELECT i+1 FROM foo WHERE i < 5)
|
|
|
|
^
|
|
|
|
-- Wrong type induced from non-recursive term
|
|
|
|
WITH RECURSIVE foo(i) AS
|
|
|
|
(SELECT i FROM (VALUES(1),(2)) t(i)
|
|
|
|
UNION ALL
|
|
|
|
SELECT (i+1)::numeric(10,0) FROM foo WHERE i < 10)
|
|
|
|
SELECT * FROM foo;
|
|
|
|
ERROR: recursive query "foo" column 1 has type integer in non-recursive term but type numeric overall
|
|
|
|
LINE 2: (SELECT i FROM (VALUES(1),(2)) t(i)
|
|
|
|
^
|
|
|
|
HINT: Cast the output of the non-recursive term to the correct type.
|
|
|
|
-- rejects different typmod, too (should we allow this?)
|
|
|
|
WITH RECURSIVE foo(i) AS
|
|
|
|
(SELECT i::numeric(3,0) FROM (VALUES(1),(2)) t(i)
|
|
|
|
UNION ALL
|
|
|
|
SELECT (i+1)::numeric(10,0) FROM foo WHERE i < 10)
|
|
|
|
SELECT * FROM foo;
|
|
|
|
ERROR: recursive query "foo" column 1 has type numeric(3,0) in non-recursive term but type numeric overall
|
|
|
|
LINE 2: (SELECT i::numeric(3,0) FROM (VALUES(1),(2)) t(i)
|
|
|
|
^
|
|
|
|
HINT: Cast the output of the non-recursive term to the correct type.
|
2010-10-16 01:53:59 +02:00
|
|
|
-- disallow OLD/NEW reference in CTE
|
|
|
|
CREATE TEMPORARY TABLE x (n integer);
|
|
|
|
CREATE RULE r2 AS ON UPDATE TO x DO INSTEAD
|
|
|
|
WITH t AS (SELECT OLD.*) UPDATE y SET a = t.n FROM t;
|
|
|
|
ERROR: cannot refer to OLD within WITH query
|
2009-07-06 04:16:03 +02:00
|
|
|
--
|
|
|
|
-- test for bug #4902
|
|
|
|
--
|
|
|
|
with cte(foo) as ( values(42) ) values((select foo from cte));
|
|
|
|
column1
|
|
|
|
---------
|
|
|
|
42
|
|
|
|
(1 row)
|
|
|
|
|
|
|
|
with cte(foo) as ( select 42 ) select * from ((select foo from cte)) q;
|
|
|
|
foo
|
|
|
|
-----
|
|
|
|
42
|
|
|
|
(1 row)
|
|
|
|
|
|
|
|
-- test CTE referencing an outer-level variable (to see that changed-parameter
|
|
|
|
-- signaling still works properly after fixing this bug)
|
|
|
|
select ( with cte(foo) as ( values(f1) )
|
|
|
|
select (select foo from cte) )
|
|
|
|
from int4_tbl;
|
2011-10-01 20:01:46 +02:00
|
|
|
foo
|
2009-07-06 04:16:03 +02:00
|
|
|
-------------
|
|
|
|
0
|
|
|
|
123456
|
|
|
|
-123456
|
|
|
|
2147483647
|
|
|
|
-2147483647
|
|
|
|
(5 rows)
|
|
|
|
|
|
|
|
select ( with cte(foo) as ( values(f1) )
|
|
|
|
values((select foo from cte)) )
|
|
|
|
from int4_tbl;
|
2011-10-01 20:01:46 +02:00
|
|
|
column1
|
2009-07-06 04:16:03 +02:00
|
|
|
-------------
|
|
|
|
0
|
|
|
|
123456
|
|
|
|
-123456
|
|
|
|
2147483647
|
|
|
|
-2147483647
|
|
|
|
(5 rows)
|
|
|
|
|
2009-09-09 05:32:52 +02:00
|
|
|
--
|
|
|
|
-- test for nested-recursive-WITH bug
|
|
|
|
--
|
|
|
|
WITH RECURSIVE t(j) AS (
|
|
|
|
WITH RECURSIVE s(i) AS (
|
|
|
|
VALUES (1)
|
|
|
|
UNION ALL
|
|
|
|
SELECT i+1 FROM s WHERE i < 10
|
|
|
|
)
|
|
|
|
SELECT i FROM s
|
|
|
|
UNION ALL
|
|
|
|
SELECT j+1 FROM t WHERE j < 10
|
|
|
|
)
|
|
|
|
SELECT * FROM t;
|
|
|
|
j
|
|
|
|
----
|
|
|
|
1
|
|
|
|
2
|
|
|
|
3
|
|
|
|
4
|
|
|
|
5
|
|
|
|
6
|
|
|
|
7
|
|
|
|
8
|
|
|
|
9
|
|
|
|
10
|
|
|
|
2
|
|
|
|
3
|
|
|
|
4
|
|
|
|
5
|
|
|
|
6
|
|
|
|
7
|
|
|
|
8
|
|
|
|
9
|
|
|
|
10
|
|
|
|
3
|
|
|
|
4
|
|
|
|
5
|
|
|
|
6
|
|
|
|
7
|
|
|
|
8
|
|
|
|
9
|
|
|
|
10
|
|
|
|
4
|
|
|
|
5
|
|
|
|
6
|
|
|
|
7
|
|
|
|
8
|
|
|
|
9
|
|
|
|
10
|
|
|
|
5
|
|
|
|
6
|
|
|
|
7
|
|
|
|
8
|
|
|
|
9
|
|
|
|
10
|
|
|
|
6
|
|
|
|
7
|
|
|
|
8
|
|
|
|
9
|
|
|
|
10
|
|
|
|
7
|
|
|
|
8
|
|
|
|
9
|
|
|
|
10
|
|
|
|
8
|
|
|
|
9
|
|
|
|
10
|
|
|
|
9
|
|
|
|
10
|
|
|
|
10
|
|
|
|
(55 rows)
|
|
|
|
|
2012-07-31 23:56:21 +02:00
|
|
|
--
|
|
|
|
-- test WITH attached to intermediate-level set operation
|
|
|
|
--
|
|
|
|
WITH outermost(x) AS (
|
|
|
|
SELECT 1
|
|
|
|
UNION (WITH innermost as (SELECT 2)
|
|
|
|
SELECT * FROM innermost
|
|
|
|
UNION SELECT 3)
|
|
|
|
)
|
2016-10-10 22:41:57 +02:00
|
|
|
SELECT * FROM outermost ORDER BY 1;
|
2012-07-31 23:56:21 +02:00
|
|
|
x
|
|
|
|
---
|
|
|
|
1
|
|
|
|
2
|
|
|
|
3
|
|
|
|
(3 rows)
|
|
|
|
|
|
|
|
WITH outermost(x) AS (
|
|
|
|
SELECT 1
|
|
|
|
UNION (WITH innermost as (SELECT 2)
|
|
|
|
SELECT * FROM outermost -- fail
|
|
|
|
UNION SELECT * FROM innermost)
|
|
|
|
)
|
2016-10-10 22:41:57 +02:00
|
|
|
SELECT * FROM outermost ORDER BY 1;
|
2012-07-31 23:56:21 +02:00
|
|
|
ERROR: relation "outermost" does not exist
|
2021-12-01 18:06:31 +01:00
|
|
|
LINE 4: SELECT * FROM outermost -- fail
|
2012-07-31 23:56:21 +02:00
|
|
|
^
|
|
|
|
DETAIL: There is a WITH item named "outermost", but it cannot be referenced from this part of the query.
|
|
|
|
HINT: Use WITH RECURSIVE, or re-order the WITH items to remove forward references.
|
|
|
|
WITH RECURSIVE outermost(x) AS (
|
|
|
|
SELECT 1
|
|
|
|
UNION (WITH innermost as (SELECT 2)
|
|
|
|
SELECT * FROM outermost
|
|
|
|
UNION SELECT * FROM innermost)
|
|
|
|
)
|
2016-10-10 22:41:57 +02:00
|
|
|
SELECT * FROM outermost ORDER BY 1;
|
2012-07-31 23:56:21 +02:00
|
|
|
x
|
|
|
|
---
|
|
|
|
1
|
|
|
|
2
|
|
|
|
(2 rows)
|
|
|
|
|
|
|
|
WITH RECURSIVE outermost(x) AS (
|
|
|
|
WITH innermost as (SELECT 2 FROM outermost) -- fail
|
|
|
|
SELECT * FROM innermost
|
|
|
|
UNION SELECT * from outermost
|
|
|
|
)
|
2016-10-10 22:41:57 +02:00
|
|
|
SELECT * FROM outermost ORDER BY 1;
|
2012-07-31 23:56:21 +02:00
|
|
|
ERROR: recursive reference to query "outermost" must not appear within a subquery
|
2021-12-01 18:06:31 +01:00
|
|
|
LINE 2: WITH innermost as (SELECT 2 FROM outermost) -- fail
|
2012-07-31 23:56:21 +02:00
|
|
|
^
|
Fix PARAM_EXEC assignment mechanism to be safe in the presence of WITH.
The planner previously assumed that parameter Vars having the same absolute
query level, varno, and varattno could safely be assigned the same runtime
PARAM_EXEC slot, even though they might be different Vars appearing in
different subqueries. This was (probably) safe before the introduction of
CTEs, but the lazy-evalution mechanism used for CTEs means that a CTE can
be executed during execution of some other subquery, causing the lifespan
of Params at the same syntactic nesting level as the CTE to overlap with
use of the same slots inside the CTE. In 9.1 we created additional hazards
by using the same parameter-assignment technology for nestloop inner scan
parameters, but it was broken before that, as illustrated by the added
regression test.
To fix, restructure the planner's management of PlannerParamItems so that
items having different semantic lifespans are kept rigorously separated.
This will probably result in complex queries using more runtime PARAM_EXEC
slots than before, but the slots are cheap enough that this hardly matters.
Also, stop generating PlannerParamItems containing Params for subquery
outputs: all we really need to do is reserve the PARAM_EXEC slot number,
and that now only takes incrementing a counter. The planning code is
simpler and probably faster than before, as well as being more correct.
Per report from Vik Reykja.
These changes will mostly also need to be made in the back branches, but
I'm going to hold off on that until after 9.2.0 wraps.
2012-09-05 18:54:03 +02:00
|
|
|
--
|
|
|
|
-- This test will fail with the old implementation of PARAM_EXEC parameter
|
|
|
|
-- assignment, because the "q1" Var passed down to A's targetlist subselect
|
|
|
|
-- looks exactly like the "A.id" Var passed down to C's subselect, causing
|
|
|
|
-- the old code to give them the same runtime PARAM_EXEC slot. But the
|
|
|
|
-- lifespans of the two parameters overlap, thanks to B also reading A.
|
|
|
|
--
|
|
|
|
with
|
|
|
|
A as ( select q2 as id, (select q1) as x from int8_tbl ),
|
|
|
|
B as ( select id, row_number() over (partition by id) as r from A ),
|
|
|
|
C as ( select A.id, array(select B.id from B where B.id = A.id) from A )
|
|
|
|
select * from C;
|
|
|
|
id | array
|
|
|
|
-------------------+-------------------------------------
|
|
|
|
456 | {456}
|
|
|
|
4567890123456789 | {4567890123456789,4567890123456789}
|
|
|
|
123 | {123}
|
|
|
|
4567890123456789 | {4567890123456789,4567890123456789}
|
|
|
|
-4567890123456789 | {-4567890123456789}
|
|
|
|
(5 rows)
|
|
|
|
|
2012-08-16 01:01:16 +02:00
|
|
|
--
|
|
|
|
-- Test CTEs read in non-initialization orders
|
|
|
|
--
|
|
|
|
WITH RECURSIVE
|
|
|
|
tab(id_key,link) AS (VALUES (1,17), (2,17), (3,17), (4,17), (6,17), (5,17)),
|
|
|
|
iter (id_key, row_type, link) AS (
|
|
|
|
SELECT 0, 'base', 17
|
|
|
|
UNION ALL (
|
|
|
|
WITH remaining(id_key, row_type, link, min) AS (
|
|
|
|
SELECT tab.id_key, 'true'::text, iter.link, MIN(tab.id_key) OVER ()
|
|
|
|
FROM tab INNER JOIN iter USING (link)
|
|
|
|
WHERE tab.id_key > iter.id_key
|
|
|
|
),
|
|
|
|
first_remaining AS (
|
|
|
|
SELECT id_key, row_type, link
|
|
|
|
FROM remaining
|
|
|
|
WHERE id_key=min
|
|
|
|
),
|
|
|
|
effect AS (
|
|
|
|
SELECT tab.id_key, 'new'::text, tab.link
|
|
|
|
FROM first_remaining e INNER JOIN tab ON e.id_key=tab.id_key
|
|
|
|
WHERE e.row_type = 'false'
|
|
|
|
)
|
|
|
|
SELECT * FROM first_remaining
|
|
|
|
UNION ALL SELECT * FROM effect
|
|
|
|
)
|
|
|
|
)
|
|
|
|
SELECT * FROM iter;
|
|
|
|
id_key | row_type | link
|
|
|
|
--------+----------+------
|
|
|
|
0 | base | 17
|
|
|
|
1 | true | 17
|
|
|
|
2 | true | 17
|
|
|
|
3 | true | 17
|
|
|
|
4 | true | 17
|
|
|
|
5 | true | 17
|
|
|
|
6 | true | 17
|
|
|
|
(7 rows)
|
|
|
|
|
|
|
|
WITH RECURSIVE
|
|
|
|
tab(id_key,link) AS (VALUES (1,17), (2,17), (3,17), (4,17), (6,17), (5,17)),
|
|
|
|
iter (id_key, row_type, link) AS (
|
|
|
|
SELECT 0, 'base', 17
|
|
|
|
UNION (
|
|
|
|
WITH remaining(id_key, row_type, link, min) AS (
|
|
|
|
SELECT tab.id_key, 'true'::text, iter.link, MIN(tab.id_key) OVER ()
|
|
|
|
FROM tab INNER JOIN iter USING (link)
|
|
|
|
WHERE tab.id_key > iter.id_key
|
|
|
|
),
|
|
|
|
first_remaining AS (
|
|
|
|
SELECT id_key, row_type, link
|
|
|
|
FROM remaining
|
|
|
|
WHERE id_key=min
|
|
|
|
),
|
|
|
|
effect AS (
|
|
|
|
SELECT tab.id_key, 'new'::text, tab.link
|
|
|
|
FROM first_remaining e INNER JOIN tab ON e.id_key=tab.id_key
|
|
|
|
WHERE e.row_type = 'false'
|
|
|
|
)
|
|
|
|
SELECT * FROM first_remaining
|
|
|
|
UNION ALL SELECT * FROM effect
|
|
|
|
)
|
|
|
|
)
|
|
|
|
SELECT * FROM iter;
|
|
|
|
id_key | row_type | link
|
|
|
|
--------+----------+------
|
|
|
|
0 | base | 17
|
|
|
|
1 | true | 17
|
|
|
|
2 | true | 17
|
|
|
|
3 | true | 17
|
|
|
|
4 | true | 17
|
|
|
|
5 | true | 17
|
|
|
|
6 | true | 17
|
|
|
|
(7 rows)
|
|
|
|
|
2011-02-26 00:56:23 +01:00
|
|
|
--
|
|
|
|
-- Data-modifying statements in WITH
|
|
|
|
--
|
|
|
|
-- INSERT ... RETURNING
|
|
|
|
WITH t AS (
|
|
|
|
INSERT INTO y
|
|
|
|
VALUES
|
|
|
|
(11),
|
|
|
|
(12),
|
|
|
|
(13),
|
|
|
|
(14),
|
|
|
|
(15),
|
|
|
|
(16),
|
|
|
|
(17),
|
|
|
|
(18),
|
|
|
|
(19),
|
|
|
|
(20)
|
|
|
|
RETURNING *
|
|
|
|
)
|
|
|
|
SELECT * FROM t;
|
|
|
|
a
|
|
|
|
----
|
|
|
|
11
|
|
|
|
12
|
|
|
|
13
|
|
|
|
14
|
|
|
|
15
|
|
|
|
16
|
|
|
|
17
|
|
|
|
18
|
|
|
|
19
|
|
|
|
20
|
|
|
|
(10 rows)
|
|
|
|
|
|
|
|
SELECT * FROM y;
|
|
|
|
a
|
|
|
|
----
|
|
|
|
1
|
|
|
|
2
|
|
|
|
3
|
|
|
|
4
|
|
|
|
5
|
|
|
|
6
|
|
|
|
7
|
|
|
|
8
|
|
|
|
9
|
|
|
|
10
|
|
|
|
11
|
|
|
|
12
|
|
|
|
13
|
|
|
|
14
|
|
|
|
15
|
|
|
|
16
|
|
|
|
17
|
|
|
|
18
|
|
|
|
19
|
|
|
|
20
|
|
|
|
(20 rows)
|
|
|
|
|
|
|
|
-- UPDATE ... RETURNING
|
|
|
|
WITH t AS (
|
|
|
|
UPDATE y
|
|
|
|
SET a=a+1
|
|
|
|
RETURNING *
|
|
|
|
)
|
|
|
|
SELECT * FROM t;
|
|
|
|
a
|
|
|
|
----
|
|
|
|
2
|
|
|
|
3
|
|
|
|
4
|
|
|
|
5
|
|
|
|
6
|
|
|
|
7
|
|
|
|
8
|
|
|
|
9
|
|
|
|
10
|
|
|
|
11
|
|
|
|
12
|
|
|
|
13
|
|
|
|
14
|
|
|
|
15
|
|
|
|
16
|
|
|
|
17
|
|
|
|
18
|
|
|
|
19
|
|
|
|
20
|
|
|
|
21
|
|
|
|
(20 rows)
|
|
|
|
|
|
|
|
SELECT * FROM y;
|
|
|
|
a
|
|
|
|
----
|
|
|
|
2
|
|
|
|
3
|
|
|
|
4
|
|
|
|
5
|
|
|
|
6
|
|
|
|
7
|
|
|
|
8
|
|
|
|
9
|
|
|
|
10
|
|
|
|
11
|
|
|
|
12
|
|
|
|
13
|
|
|
|
14
|
|
|
|
15
|
|
|
|
16
|
|
|
|
17
|
|
|
|
18
|
|
|
|
19
|
|
|
|
20
|
|
|
|
21
|
|
|
|
(20 rows)
|
|
|
|
|
|
|
|
-- DELETE ... RETURNING
|
|
|
|
WITH t AS (
|
|
|
|
DELETE FROM y
|
|
|
|
WHERE a <= 10
|
|
|
|
RETURNING *
|
|
|
|
)
|
|
|
|
SELECT * FROM t;
|
|
|
|
a
|
|
|
|
----
|
|
|
|
2
|
|
|
|
3
|
|
|
|
4
|
|
|
|
5
|
|
|
|
6
|
|
|
|
7
|
|
|
|
8
|
|
|
|
9
|
|
|
|
10
|
|
|
|
(9 rows)
|
|
|
|
|
|
|
|
SELECT * FROM y;
|
|
|
|
a
|
|
|
|
----
|
|
|
|
11
|
|
|
|
12
|
|
|
|
13
|
|
|
|
14
|
|
|
|
15
|
|
|
|
16
|
|
|
|
17
|
|
|
|
18
|
|
|
|
19
|
|
|
|
20
|
|
|
|
21
|
|
|
|
(11 rows)
|
|
|
|
|
|
|
|
-- forward reference
|
|
|
|
WITH RECURSIVE t AS (
|
|
|
|
INSERT INTO y
|
|
|
|
SELECT a+5 FROM t2 WHERE a > 5
|
|
|
|
RETURNING *
|
|
|
|
), t2 AS (
|
|
|
|
UPDATE y SET a=a-11 RETURNING *
|
|
|
|
)
|
|
|
|
SELECT * FROM t
|
|
|
|
UNION ALL
|
|
|
|
SELECT * FROM t2;
|
|
|
|
a
|
|
|
|
----
|
|
|
|
11
|
|
|
|
12
|
|
|
|
13
|
|
|
|
14
|
|
|
|
15
|
|
|
|
0
|
|
|
|
1
|
|
|
|
2
|
|
|
|
3
|
|
|
|
4
|
|
|
|
5
|
|
|
|
6
|
|
|
|
7
|
|
|
|
8
|
|
|
|
9
|
|
|
|
10
|
|
|
|
(16 rows)
|
|
|
|
|
|
|
|
SELECT * FROM y;
|
|
|
|
a
|
|
|
|
----
|
|
|
|
0
|
|
|
|
1
|
|
|
|
2
|
|
|
|
3
|
|
|
|
4
|
|
|
|
5
|
|
|
|
6
|
|
|
|
11
|
|
|
|
7
|
|
|
|
12
|
|
|
|
8
|
|
|
|
13
|
|
|
|
9
|
|
|
|
14
|
|
|
|
10
|
|
|
|
15
|
|
|
|
(16 rows)
|
|
|
|
|
|
|
|
-- unconditional DO INSTEAD rule
|
|
|
|
CREATE RULE y_rule AS ON DELETE TO y DO INSTEAD
|
|
|
|
INSERT INTO y VALUES(42) RETURNING *;
|
|
|
|
WITH t AS (
|
|
|
|
DELETE FROM y RETURNING *
|
|
|
|
)
|
|
|
|
SELECT * FROM t;
|
|
|
|
a
|
|
|
|
----
|
|
|
|
42
|
|
|
|
(1 row)
|
|
|
|
|
|
|
|
SELECT * FROM y;
|
|
|
|
a
|
|
|
|
----
|
|
|
|
0
|
|
|
|
1
|
|
|
|
2
|
|
|
|
3
|
|
|
|
4
|
|
|
|
5
|
|
|
|
6
|
|
|
|
11
|
|
|
|
7
|
|
|
|
12
|
|
|
|
8
|
|
|
|
13
|
|
|
|
9
|
|
|
|
14
|
|
|
|
10
|
|
|
|
15
|
|
|
|
42
|
|
|
|
(17 rows)
|
|
|
|
|
|
|
|
DROP RULE y_rule ON y;
|
2011-06-07 06:08:31 +02:00
|
|
|
-- check merging of outer CTE with CTE in a rule action
|
|
|
|
CREATE TEMP TABLE bug6051 AS
|
|
|
|
select i from generate_series(1,3) as t(i);
|
|
|
|
SELECT * FROM bug6051;
|
|
|
|
i
|
|
|
|
---
|
|
|
|
1
|
|
|
|
2
|
|
|
|
3
|
|
|
|
(3 rows)
|
|
|
|
|
|
|
|
WITH t1 AS ( DELETE FROM bug6051 RETURNING * )
|
|
|
|
INSERT INTO bug6051 SELECT * FROM t1;
|
|
|
|
SELECT * FROM bug6051;
|
|
|
|
i
|
|
|
|
---
|
|
|
|
1
|
|
|
|
2
|
|
|
|
3
|
|
|
|
(3 rows)
|
|
|
|
|
|
|
|
CREATE TEMP TABLE bug6051_2 (i int);
|
|
|
|
CREATE RULE bug6051_ins AS ON INSERT TO bug6051 DO INSTEAD
|
|
|
|
INSERT INTO bug6051_2
|
2021-09-08 18:05:43 +02:00
|
|
|
VALUES(NEW.i);
|
2011-06-07 06:08:31 +02:00
|
|
|
WITH t1 AS ( DELETE FROM bug6051 RETURNING * )
|
|
|
|
INSERT INTO bug6051 SELECT * FROM t1;
|
|
|
|
SELECT * FROM bug6051;
|
|
|
|
i
|
|
|
|
---
|
|
|
|
(0 rows)
|
|
|
|
|
|
|
|
SELECT * FROM bug6051_2;
|
|
|
|
i
|
|
|
|
---
|
|
|
|
1
|
|
|
|
2
|
|
|
|
3
|
|
|
|
(3 rows)
|
|
|
|
|
2022-09-15 06:40:17 +02:00
|
|
|
-- check INSERT ... SELECT rule actions are disallowed on commands
|
2021-09-08 18:05:43 +02:00
|
|
|
-- that have modifyingCTEs
|
|
|
|
CREATE OR REPLACE RULE bug6051_ins AS ON INSERT TO bug6051 DO INSTEAD
|
|
|
|
INSERT INTO bug6051_2
|
|
|
|
SELECT NEW.i;
|
|
|
|
WITH t1 AS ( DELETE FROM bug6051 RETURNING * )
|
|
|
|
INSERT INTO bug6051 SELECT * FROM t1;
|
2022-09-15 06:40:17 +02:00
|
|
|
ERROR: INSERT ... SELECT rule actions are not supported for queries having data-modifying statements in WITH
|
2021-09-08 18:05:43 +02:00
|
|
|
-- silly example to verify that hasModifyingCTE flag is propagated
|
|
|
|
CREATE TEMP TABLE bug6051_3 AS
|
|
|
|
SELECT a FROM generate_series(11,13) AS a;
|
|
|
|
CREATE RULE bug6051_3_ins AS ON INSERT TO bug6051_3 DO INSTEAD
|
|
|
|
SELECT i FROM bug6051_2;
|
2023-02-15 09:21:59 +01:00
|
|
|
BEGIN; SET LOCAL debug_parallel_query = on;
|
2021-09-08 18:05:43 +02:00
|
|
|
WITH t1 AS ( DELETE FROM bug6051_3 RETURNING * )
|
|
|
|
INSERT INTO bug6051_3 SELECT * FROM t1;
|
|
|
|
i
|
|
|
|
---
|
|
|
|
1
|
|
|
|
2
|
|
|
|
3
|
|
|
|
1
|
|
|
|
2
|
|
|
|
3
|
|
|
|
1
|
|
|
|
2
|
|
|
|
3
|
|
|
|
(9 rows)
|
|
|
|
|
|
|
|
COMMIT;
|
|
|
|
SELECT * FROM bug6051_3;
|
|
|
|
a
|
|
|
|
---
|
|
|
|
(0 rows)
|
|
|
|
|
2022-04-21 23:58:52 +02:00
|
|
|
-- check case where CTE reference is removed due to optimization
|
|
|
|
EXPLAIN (VERBOSE, COSTS OFF)
|
|
|
|
SELECT q1 FROM
|
|
|
|
(
|
|
|
|
WITH t_cte AS (SELECT * FROM int8_tbl t)
|
|
|
|
SELECT q1, (SELECT q2 FROM t_cte WHERE t_cte.q1 = i8.q1) AS t_sub
|
|
|
|
FROM int8_tbl i8
|
|
|
|
) ss;
|
|
|
|
QUERY PLAN
|
|
|
|
--------------------------------------
|
|
|
|
Subquery Scan on ss
|
|
|
|
Output: ss.q1
|
|
|
|
-> Seq Scan on public.int8_tbl i8
|
|
|
|
Output: i8.q1, NULL::bigint
|
|
|
|
(4 rows)
|
|
|
|
|
|
|
|
SELECT q1 FROM
|
|
|
|
(
|
|
|
|
WITH t_cte AS (SELECT * FROM int8_tbl t)
|
|
|
|
SELECT q1, (SELECT q2 FROM t_cte WHERE t_cte.q1 = i8.q1) AS t_sub
|
|
|
|
FROM int8_tbl i8
|
|
|
|
) ss;
|
|
|
|
q1
|
|
|
|
------------------
|
|
|
|
123
|
|
|
|
123
|
|
|
|
4567890123456789
|
|
|
|
4567890123456789
|
|
|
|
4567890123456789
|
|
|
|
(5 rows)
|
|
|
|
|
|
|
|
EXPLAIN (VERBOSE, COSTS OFF)
|
|
|
|
SELECT q1 FROM
|
|
|
|
(
|
|
|
|
WITH t_cte AS MATERIALIZED (SELECT * FROM int8_tbl t)
|
|
|
|
SELECT q1, (SELECT q2 FROM t_cte WHERE t_cte.q1 = i8.q1) AS t_sub
|
|
|
|
FROM int8_tbl i8
|
|
|
|
) ss;
|
|
|
|
QUERY PLAN
|
|
|
|
---------------------------------------------
|
|
|
|
Subquery Scan on ss
|
|
|
|
Output: ss.q1
|
|
|
|
-> Seq Scan on public.int8_tbl i8
|
|
|
|
Output: i8.q1, NULL::bigint
|
|
|
|
CTE t_cte
|
|
|
|
-> Seq Scan on public.int8_tbl t
|
|
|
|
Output: t.q1, t.q2
|
|
|
|
(7 rows)
|
|
|
|
|
|
|
|
SELECT q1 FROM
|
|
|
|
(
|
|
|
|
WITH t_cte AS MATERIALIZED (SELECT * FROM int8_tbl t)
|
|
|
|
SELECT q1, (SELECT q2 FROM t_cte WHERE t_cte.q1 = i8.q1) AS t_sub
|
|
|
|
FROM int8_tbl i8
|
|
|
|
) ss;
|
|
|
|
q1
|
|
|
|
------------------
|
|
|
|
123
|
|
|
|
123
|
|
|
|
4567890123456789
|
|
|
|
4567890123456789
|
|
|
|
4567890123456789
|
|
|
|
(5 rows)
|
|
|
|
|
2011-02-26 00:56:23 +01:00
|
|
|
-- a truly recursive CTE in the same list
|
|
|
|
WITH RECURSIVE t(a) AS (
|
|
|
|
SELECT 0
|
|
|
|
UNION ALL
|
|
|
|
SELECT a+1 FROM t WHERE a+1 < 5
|
|
|
|
), t2 as (
|
|
|
|
INSERT INTO y
|
|
|
|
SELECT * FROM t RETURNING *
|
|
|
|
)
|
|
|
|
SELECT * FROM t2 JOIN y USING (a) ORDER BY a;
|
|
|
|
a
|
|
|
|
---
|
|
|
|
0
|
|
|
|
1
|
|
|
|
2
|
|
|
|
3
|
|
|
|
4
|
|
|
|
(5 rows)
|
|
|
|
|
|
|
|
SELECT * FROM y;
|
|
|
|
a
|
|
|
|
----
|
|
|
|
0
|
|
|
|
1
|
|
|
|
2
|
|
|
|
3
|
|
|
|
4
|
|
|
|
5
|
|
|
|
6
|
|
|
|
11
|
|
|
|
7
|
|
|
|
12
|
|
|
|
8
|
|
|
|
13
|
|
|
|
9
|
|
|
|
14
|
|
|
|
10
|
|
|
|
15
|
|
|
|
42
|
|
|
|
0
|
|
|
|
1
|
|
|
|
2
|
|
|
|
3
|
|
|
|
4
|
|
|
|
(22 rows)
|
|
|
|
|
|
|
|
-- data-modifying WITH in a modifying statement
|
|
|
|
WITH t AS (
|
|
|
|
DELETE FROM y
|
|
|
|
WHERE a <= 10
|
|
|
|
RETURNING *
|
|
|
|
)
|
|
|
|
INSERT INTO y SELECT -a FROM t RETURNING *;
|
|
|
|
a
|
|
|
|
-----
|
|
|
|
0
|
|
|
|
-1
|
|
|
|
-2
|
|
|
|
-3
|
|
|
|
-4
|
|
|
|
-5
|
|
|
|
-6
|
|
|
|
-7
|
|
|
|
-8
|
|
|
|
-9
|
|
|
|
-10
|
|
|
|
0
|
|
|
|
-1
|
|
|
|
-2
|
|
|
|
-3
|
|
|
|
-4
|
|
|
|
(16 rows)
|
|
|
|
|
|
|
|
SELECT * FROM y;
|
|
|
|
a
|
|
|
|
-----
|
|
|
|
11
|
|
|
|
12
|
|
|
|
13
|
|
|
|
14
|
|
|
|
15
|
|
|
|
42
|
|
|
|
0
|
|
|
|
-1
|
|
|
|
-2
|
|
|
|
-3
|
|
|
|
-4
|
|
|
|
-5
|
|
|
|
-6
|
|
|
|
-7
|
|
|
|
-8
|
|
|
|
-9
|
|
|
|
-10
|
|
|
|
0
|
|
|
|
-1
|
|
|
|
-2
|
|
|
|
-3
|
|
|
|
-4
|
|
|
|
(22 rows)
|
|
|
|
|
|
|
|
-- check that WITH query is run to completion even if outer query isn't
|
|
|
|
WITH t AS (
|
|
|
|
UPDATE y SET a = a * 100 RETURNING *
|
|
|
|
)
|
|
|
|
SELECT * FROM t LIMIT 10;
|
|
|
|
a
|
|
|
|
------
|
|
|
|
1100
|
|
|
|
1200
|
|
|
|
1300
|
|
|
|
1400
|
|
|
|
1500
|
|
|
|
4200
|
|
|
|
0
|
|
|
|
-100
|
|
|
|
-200
|
|
|
|
-300
|
|
|
|
(10 rows)
|
|
|
|
|
|
|
|
SELECT * FROM y;
|
|
|
|
a
|
|
|
|
-------
|
|
|
|
1100
|
|
|
|
1200
|
|
|
|
1300
|
|
|
|
1400
|
|
|
|
1500
|
|
|
|
4200
|
|
|
|
0
|
|
|
|
-100
|
|
|
|
-200
|
|
|
|
-300
|
|
|
|
-400
|
|
|
|
-500
|
|
|
|
-600
|
|
|
|
-700
|
|
|
|
-800
|
|
|
|
-900
|
|
|
|
-1000
|
|
|
|
0
|
|
|
|
-100
|
|
|
|
-200
|
|
|
|
-300
|
|
|
|
-400
|
|
|
|
(22 rows)
|
|
|
|
|
Add support for INSERT ... ON CONFLICT DO NOTHING/UPDATE.
The newly added ON CONFLICT clause allows to specify an alternative to
raising a unique or exclusion constraint violation error when inserting.
ON CONFLICT refers to constraints that can either be specified using a
inference clause (by specifying the columns of a unique constraint) or
by naming a unique or exclusion constraint. DO NOTHING avoids the
constraint violation, without touching the pre-existing row. DO UPDATE
SET ... [WHERE ...] updates the pre-existing tuple, and has access to
both the tuple proposed for insertion and the existing tuple; the
optional WHERE clause can be used to prevent an update from being
executed. The UPDATE SET and WHERE clauses have access to the tuple
proposed for insertion using the "magic" EXCLUDED alias, and to the
pre-existing tuple using the table name or its alias.
This feature is often referred to as upsert.
This is implemented using a new infrastructure called "speculative
insertion". It is an optimistic variant of regular insertion that first
does a pre-check for existing tuples and then attempts an insert. If a
violating tuple was inserted concurrently, the speculatively inserted
tuple is deleted and a new attempt is made. If the pre-check finds a
matching tuple the alternative DO NOTHING or DO UPDATE action is taken.
If the insertion succeeds without detecting a conflict, the tuple is
deemed inserted.
To handle the possible ambiguity between the excluded alias and a table
named excluded, and for convenience with long relation names, INSERT
INTO now can alias its target table.
Bumps catversion as stored rules change.
Author: Peter Geoghegan, with significant contributions from Heikki
Linnakangas and Andres Freund. Testing infrastructure by Jeff Janes.
Reviewed-By: Heikki Linnakangas, Andres Freund, Robert Haas, Simon Riggs,
Dean Rasheed, Stephen Frost and many others.
2015-05-08 05:31:36 +02:00
|
|
|
-- data-modifying WITH containing INSERT...ON CONFLICT DO UPDATE
|
Clean up duplicate table and function names in regression tests.
Many of the objects we create during the regression tests are put in the
public schema, so that using the same names in different regression tests
creates a hazard of test failures if any two such scripts run concurrently.
This patch cleans up a bunch of latent hazards of that sort, as well as two
live hazards.
The current situation in this regard is far worse than it was a year or two
back, because practically all of the partitioning-related test cases have
reused table names with enthusiasm. I despaired of cleaning up that mess
within the five most-affected tests (create_table, alter_table, insert,
update, inherit); fortunately those don't run concurrently.
Other than partitioning problems, most of the issues boil down to using
names like "foo", "bar", "tmp", etc, without thought for the fact that
other test scripts might use similar names concurrently. I've made an
effort to make all such names more specific.
One of the live hazards was that commit 7421f4b8 caused with.sql to
create a table named "test", conflicting with a similarly-named table
in alter_table.sql; this was exposed in the buildfarm recently.
The other one was that join.sql and transactions.sql both create tables
named "foo" and "bar"; but join.sql's uses of those names date back
only to December or so.
Since commit 7421f4b8 was back-patched to v10, back-patch a minimal
fix for that problem. The rest of this is just future-proofing.
Discussion: https://postgr.es/m/4627.1521070268@sss.pgh.pa.us
2018-03-15 22:08:51 +01:00
|
|
|
CREATE TABLE withz AS SELECT i AS k, (i || ' v')::text v FROM generate_series(1, 16, 3) i;
|
|
|
|
ALTER TABLE withz ADD UNIQUE (k);
|
Add support for INSERT ... ON CONFLICT DO NOTHING/UPDATE.
The newly added ON CONFLICT clause allows to specify an alternative to
raising a unique or exclusion constraint violation error when inserting.
ON CONFLICT refers to constraints that can either be specified using a
inference clause (by specifying the columns of a unique constraint) or
by naming a unique or exclusion constraint. DO NOTHING avoids the
constraint violation, without touching the pre-existing row. DO UPDATE
SET ... [WHERE ...] updates the pre-existing tuple, and has access to
both the tuple proposed for insertion and the existing tuple; the
optional WHERE clause can be used to prevent an update from being
executed. The UPDATE SET and WHERE clauses have access to the tuple
proposed for insertion using the "magic" EXCLUDED alias, and to the
pre-existing tuple using the table name or its alias.
This feature is often referred to as upsert.
This is implemented using a new infrastructure called "speculative
insertion". It is an optimistic variant of regular insertion that first
does a pre-check for existing tuples and then attempts an insert. If a
violating tuple was inserted concurrently, the speculatively inserted
tuple is deleted and a new attempt is made. If the pre-check finds a
matching tuple the alternative DO NOTHING or DO UPDATE action is taken.
If the insertion succeeds without detecting a conflict, the tuple is
deemed inserted.
To handle the possible ambiguity between the excluded alias and a table
named excluded, and for convenience with long relation names, INSERT
INTO now can alias its target table.
Bumps catversion as stored rules change.
Author: Peter Geoghegan, with significant contributions from Heikki
Linnakangas and Andres Freund. Testing infrastructure by Jeff Janes.
Reviewed-By: Heikki Linnakangas, Andres Freund, Robert Haas, Simon Riggs,
Dean Rasheed, Stephen Frost and many others.
2015-05-08 05:31:36 +02:00
|
|
|
WITH t AS (
|
Clean up duplicate table and function names in regression tests.
Many of the objects we create during the regression tests are put in the
public schema, so that using the same names in different regression tests
creates a hazard of test failures if any two such scripts run concurrently.
This patch cleans up a bunch of latent hazards of that sort, as well as two
live hazards.
The current situation in this regard is far worse than it was a year or two
back, because practically all of the partitioning-related test cases have
reused table names with enthusiasm. I despaired of cleaning up that mess
within the five most-affected tests (create_table, alter_table, insert,
update, inherit); fortunately those don't run concurrently.
Other than partitioning problems, most of the issues boil down to using
names like "foo", "bar", "tmp", etc, without thought for the fact that
other test scripts might use similar names concurrently. I've made an
effort to make all such names more specific.
One of the live hazards was that commit 7421f4b8 caused with.sql to
create a table named "test", conflicting with a similarly-named table
in alter_table.sql; this was exposed in the buildfarm recently.
The other one was that join.sql and transactions.sql both create tables
named "foo" and "bar"; but join.sql's uses of those names date back
only to December or so.
Since commit 7421f4b8 was back-patched to v10, back-patch a minimal
fix for that problem. The rest of this is just future-proofing.
Discussion: https://postgr.es/m/4627.1521070268@sss.pgh.pa.us
2018-03-15 22:08:51 +01:00
|
|
|
INSERT INTO withz SELECT i, 'insert'
|
Add support for INSERT ... ON CONFLICT DO NOTHING/UPDATE.
The newly added ON CONFLICT clause allows to specify an alternative to
raising a unique or exclusion constraint violation error when inserting.
ON CONFLICT refers to constraints that can either be specified using a
inference clause (by specifying the columns of a unique constraint) or
by naming a unique or exclusion constraint. DO NOTHING avoids the
constraint violation, without touching the pre-existing row. DO UPDATE
SET ... [WHERE ...] updates the pre-existing tuple, and has access to
both the tuple proposed for insertion and the existing tuple; the
optional WHERE clause can be used to prevent an update from being
executed. The UPDATE SET and WHERE clauses have access to the tuple
proposed for insertion using the "magic" EXCLUDED alias, and to the
pre-existing tuple using the table name or its alias.
This feature is often referred to as upsert.
This is implemented using a new infrastructure called "speculative
insertion". It is an optimistic variant of regular insertion that first
does a pre-check for existing tuples and then attempts an insert. If a
violating tuple was inserted concurrently, the speculatively inserted
tuple is deleted and a new attempt is made. If the pre-check finds a
matching tuple the alternative DO NOTHING or DO UPDATE action is taken.
If the insertion succeeds without detecting a conflict, the tuple is
deemed inserted.
To handle the possible ambiguity between the excluded alias and a table
named excluded, and for convenience with long relation names, INSERT
INTO now can alias its target table.
Bumps catversion as stored rules change.
Author: Peter Geoghegan, with significant contributions from Heikki
Linnakangas and Andres Freund. Testing infrastructure by Jeff Janes.
Reviewed-By: Heikki Linnakangas, Andres Freund, Robert Haas, Simon Riggs,
Dean Rasheed, Stephen Frost and many others.
2015-05-08 05:31:36 +02:00
|
|
|
FROM generate_series(0, 16) i
|
Clean up duplicate table and function names in regression tests.
Many of the objects we create during the regression tests are put in the
public schema, so that using the same names in different regression tests
creates a hazard of test failures if any two such scripts run concurrently.
This patch cleans up a bunch of latent hazards of that sort, as well as two
live hazards.
The current situation in this regard is far worse than it was a year or two
back, because practically all of the partitioning-related test cases have
reused table names with enthusiasm. I despaired of cleaning up that mess
within the five most-affected tests (create_table, alter_table, insert,
update, inherit); fortunately those don't run concurrently.
Other than partitioning problems, most of the issues boil down to using
names like "foo", "bar", "tmp", etc, without thought for the fact that
other test scripts might use similar names concurrently. I've made an
effort to make all such names more specific.
One of the live hazards was that commit 7421f4b8 caused with.sql to
create a table named "test", conflicting with a similarly-named table
in alter_table.sql; this was exposed in the buildfarm recently.
The other one was that join.sql and transactions.sql both create tables
named "foo" and "bar"; but join.sql's uses of those names date back
only to December or so.
Since commit 7421f4b8 was back-patched to v10, back-patch a minimal
fix for that problem. The rest of this is just future-proofing.
Discussion: https://postgr.es/m/4627.1521070268@sss.pgh.pa.us
2018-03-15 22:08:51 +01:00
|
|
|
ON CONFLICT (k) DO UPDATE SET v = withz.v || ', now update'
|
Add support for INSERT ... ON CONFLICT DO NOTHING/UPDATE.
The newly added ON CONFLICT clause allows to specify an alternative to
raising a unique or exclusion constraint violation error when inserting.
ON CONFLICT refers to constraints that can either be specified using a
inference clause (by specifying the columns of a unique constraint) or
by naming a unique or exclusion constraint. DO NOTHING avoids the
constraint violation, without touching the pre-existing row. DO UPDATE
SET ... [WHERE ...] updates the pre-existing tuple, and has access to
both the tuple proposed for insertion and the existing tuple; the
optional WHERE clause can be used to prevent an update from being
executed. The UPDATE SET and WHERE clauses have access to the tuple
proposed for insertion using the "magic" EXCLUDED alias, and to the
pre-existing tuple using the table name or its alias.
This feature is often referred to as upsert.
This is implemented using a new infrastructure called "speculative
insertion". It is an optimistic variant of regular insertion that first
does a pre-check for existing tuples and then attempts an insert. If a
violating tuple was inserted concurrently, the speculatively inserted
tuple is deleted and a new attempt is made. If the pre-check finds a
matching tuple the alternative DO NOTHING or DO UPDATE action is taken.
If the insertion succeeds without detecting a conflict, the tuple is
deemed inserted.
To handle the possible ambiguity between the excluded alias and a table
named excluded, and for convenience with long relation names, INSERT
INTO now can alias its target table.
Bumps catversion as stored rules change.
Author: Peter Geoghegan, with significant contributions from Heikki
Linnakangas and Andres Freund. Testing infrastructure by Jeff Janes.
Reviewed-By: Heikki Linnakangas, Andres Freund, Robert Haas, Simon Riggs,
Dean Rasheed, Stephen Frost and many others.
2015-05-08 05:31:36 +02:00
|
|
|
RETURNING *
|
|
|
|
)
|
|
|
|
SELECT * FROM t JOIN y ON t.k = y.a ORDER BY a, k;
|
|
|
|
k | v | a
|
|
|
|
---+--------+---
|
|
|
|
0 | insert | 0
|
|
|
|
0 | insert | 0
|
|
|
|
(2 rows)
|
|
|
|
|
|
|
|
-- Test EXCLUDED.* reference within CTE
|
|
|
|
WITH aa AS (
|
Clean up duplicate table and function names in regression tests.
Many of the objects we create during the regression tests are put in the
public schema, so that using the same names in different regression tests
creates a hazard of test failures if any two such scripts run concurrently.
This patch cleans up a bunch of latent hazards of that sort, as well as two
live hazards.
The current situation in this regard is far worse than it was a year or two
back, because practically all of the partitioning-related test cases have
reused table names with enthusiasm. I despaired of cleaning up that mess
within the five most-affected tests (create_table, alter_table, insert,
update, inherit); fortunately those don't run concurrently.
Other than partitioning problems, most of the issues boil down to using
names like "foo", "bar", "tmp", etc, without thought for the fact that
other test scripts might use similar names concurrently. I've made an
effort to make all such names more specific.
One of the live hazards was that commit 7421f4b8 caused with.sql to
create a table named "test", conflicting with a similarly-named table
in alter_table.sql; this was exposed in the buildfarm recently.
The other one was that join.sql and transactions.sql both create tables
named "foo" and "bar"; but join.sql's uses of those names date back
only to December or so.
Since commit 7421f4b8 was back-patched to v10, back-patch a minimal
fix for that problem. The rest of this is just future-proofing.
Discussion: https://postgr.es/m/4627.1521070268@sss.pgh.pa.us
2018-03-15 22:08:51 +01:00
|
|
|
INSERT INTO withz VALUES(1, 5) ON CONFLICT (k) DO UPDATE SET v = EXCLUDED.v
|
|
|
|
WHERE withz.k != EXCLUDED.k
|
Add support for INSERT ... ON CONFLICT DO NOTHING/UPDATE.
The newly added ON CONFLICT clause allows to specify an alternative to
raising a unique or exclusion constraint violation error when inserting.
ON CONFLICT refers to constraints that can either be specified using a
inference clause (by specifying the columns of a unique constraint) or
by naming a unique or exclusion constraint. DO NOTHING avoids the
constraint violation, without touching the pre-existing row. DO UPDATE
SET ... [WHERE ...] updates the pre-existing tuple, and has access to
both the tuple proposed for insertion and the existing tuple; the
optional WHERE clause can be used to prevent an update from being
executed. The UPDATE SET and WHERE clauses have access to the tuple
proposed for insertion using the "magic" EXCLUDED alias, and to the
pre-existing tuple using the table name or its alias.
This feature is often referred to as upsert.
This is implemented using a new infrastructure called "speculative
insertion". It is an optimistic variant of regular insertion that first
does a pre-check for existing tuples and then attempts an insert. If a
violating tuple was inserted concurrently, the speculatively inserted
tuple is deleted and a new attempt is made. If the pre-check finds a
matching tuple the alternative DO NOTHING or DO UPDATE action is taken.
If the insertion succeeds without detecting a conflict, the tuple is
deemed inserted.
To handle the possible ambiguity between the excluded alias and a table
named excluded, and for convenience with long relation names, INSERT
INTO now can alias its target table.
Bumps catversion as stored rules change.
Author: Peter Geoghegan, with significant contributions from Heikki
Linnakangas and Andres Freund. Testing infrastructure by Jeff Janes.
Reviewed-By: Heikki Linnakangas, Andres Freund, Robert Haas, Simon Riggs,
Dean Rasheed, Stephen Frost and many others.
2015-05-08 05:31:36 +02:00
|
|
|
RETURNING *
|
|
|
|
)
|
|
|
|
SELECT * FROM aa;
|
|
|
|
k | v
|
|
|
|
---+---
|
|
|
|
(0 rows)
|
|
|
|
|
|
|
|
-- New query/snapshot demonstrates side-effects of previous query.
|
Clean up duplicate table and function names in regression tests.
Many of the objects we create during the regression tests are put in the
public schema, so that using the same names in different regression tests
creates a hazard of test failures if any two such scripts run concurrently.
This patch cleans up a bunch of latent hazards of that sort, as well as two
live hazards.
The current situation in this regard is far worse than it was a year or two
back, because practically all of the partitioning-related test cases have
reused table names with enthusiasm. I despaired of cleaning up that mess
within the five most-affected tests (create_table, alter_table, insert,
update, inherit); fortunately those don't run concurrently.
Other than partitioning problems, most of the issues boil down to using
names like "foo", "bar", "tmp", etc, without thought for the fact that
other test scripts might use similar names concurrently. I've made an
effort to make all such names more specific.
One of the live hazards was that commit 7421f4b8 caused with.sql to
create a table named "test", conflicting with a similarly-named table
in alter_table.sql; this was exposed in the buildfarm recently.
The other one was that join.sql and transactions.sql both create tables
named "foo" and "bar"; but join.sql's uses of those names date back
only to December or so.
Since commit 7421f4b8 was back-patched to v10, back-patch a minimal
fix for that problem. The rest of this is just future-proofing.
Discussion: https://postgr.es/m/4627.1521070268@sss.pgh.pa.us
2018-03-15 22:08:51 +01:00
|
|
|
SELECT * FROM withz ORDER BY k;
|
Add support for INSERT ... ON CONFLICT DO NOTHING/UPDATE.
The newly added ON CONFLICT clause allows to specify an alternative to
raising a unique or exclusion constraint violation error when inserting.
ON CONFLICT refers to constraints that can either be specified using a
inference clause (by specifying the columns of a unique constraint) or
by naming a unique or exclusion constraint. DO NOTHING avoids the
constraint violation, without touching the pre-existing row. DO UPDATE
SET ... [WHERE ...] updates the pre-existing tuple, and has access to
both the tuple proposed for insertion and the existing tuple; the
optional WHERE clause can be used to prevent an update from being
executed. The UPDATE SET and WHERE clauses have access to the tuple
proposed for insertion using the "magic" EXCLUDED alias, and to the
pre-existing tuple using the table name or its alias.
This feature is often referred to as upsert.
This is implemented using a new infrastructure called "speculative
insertion". It is an optimistic variant of regular insertion that first
does a pre-check for existing tuples and then attempts an insert. If a
violating tuple was inserted concurrently, the speculatively inserted
tuple is deleted and a new attempt is made. If the pre-check finds a
matching tuple the alternative DO NOTHING or DO UPDATE action is taken.
If the insertion succeeds without detecting a conflict, the tuple is
deemed inserted.
To handle the possible ambiguity between the excluded alias and a table
named excluded, and for convenience with long relation names, INSERT
INTO now can alias its target table.
Bumps catversion as stored rules change.
Author: Peter Geoghegan, with significant contributions from Heikki
Linnakangas and Andres Freund. Testing infrastructure by Jeff Janes.
Reviewed-By: Heikki Linnakangas, Andres Freund, Robert Haas, Simon Riggs,
Dean Rasheed, Stephen Frost and many others.
2015-05-08 05:31:36 +02:00
|
|
|
k | v
|
|
|
|
----+------------------
|
|
|
|
0 | insert
|
|
|
|
1 | 1 v, now update
|
|
|
|
2 | insert
|
|
|
|
3 | insert
|
|
|
|
4 | 4 v, now update
|
|
|
|
5 | insert
|
|
|
|
6 | insert
|
|
|
|
7 | 7 v, now update
|
|
|
|
8 | insert
|
|
|
|
9 | insert
|
|
|
|
10 | 10 v, now update
|
|
|
|
11 | insert
|
|
|
|
12 | insert
|
|
|
|
13 | 13 v, now update
|
|
|
|
14 | insert
|
|
|
|
15 | insert
|
|
|
|
16 | 16 v, now update
|
|
|
|
(17 rows)
|
|
|
|
|
|
|
|
--
|
|
|
|
-- Ensure subqueries within the update clause work, even if they
|
|
|
|
-- reference outside values
|
|
|
|
--
|
|
|
|
WITH aa AS (SELECT 1 a, 2 b)
|
Clean up duplicate table and function names in regression tests.
Many of the objects we create during the regression tests are put in the
public schema, so that using the same names in different regression tests
creates a hazard of test failures if any two such scripts run concurrently.
This patch cleans up a bunch of latent hazards of that sort, as well as two
live hazards.
The current situation in this regard is far worse than it was a year or two
back, because practically all of the partitioning-related test cases have
reused table names with enthusiasm. I despaired of cleaning up that mess
within the five most-affected tests (create_table, alter_table, insert,
update, inherit); fortunately those don't run concurrently.
Other than partitioning problems, most of the issues boil down to using
names like "foo", "bar", "tmp", etc, without thought for the fact that
other test scripts might use similar names concurrently. I've made an
effort to make all such names more specific.
One of the live hazards was that commit 7421f4b8 caused with.sql to
create a table named "test", conflicting with a similarly-named table
in alter_table.sql; this was exposed in the buildfarm recently.
The other one was that join.sql and transactions.sql both create tables
named "foo" and "bar"; but join.sql's uses of those names date back
only to December or so.
Since commit 7421f4b8 was back-patched to v10, back-patch a minimal
fix for that problem. The rest of this is just future-proofing.
Discussion: https://postgr.es/m/4627.1521070268@sss.pgh.pa.us
2018-03-15 22:08:51 +01:00
|
|
|
INSERT INTO withz VALUES(1, 'insert')
|
Add support for INSERT ... ON CONFLICT DO NOTHING/UPDATE.
The newly added ON CONFLICT clause allows to specify an alternative to
raising a unique or exclusion constraint violation error when inserting.
ON CONFLICT refers to constraints that can either be specified using a
inference clause (by specifying the columns of a unique constraint) or
by naming a unique or exclusion constraint. DO NOTHING avoids the
constraint violation, without touching the pre-existing row. DO UPDATE
SET ... [WHERE ...] updates the pre-existing tuple, and has access to
both the tuple proposed for insertion and the existing tuple; the
optional WHERE clause can be used to prevent an update from being
executed. The UPDATE SET and WHERE clauses have access to the tuple
proposed for insertion using the "magic" EXCLUDED alias, and to the
pre-existing tuple using the table name or its alias.
This feature is often referred to as upsert.
This is implemented using a new infrastructure called "speculative
insertion". It is an optimistic variant of regular insertion that first
does a pre-check for existing tuples and then attempts an insert. If a
violating tuple was inserted concurrently, the speculatively inserted
tuple is deleted and a new attempt is made. If the pre-check finds a
matching tuple the alternative DO NOTHING or DO UPDATE action is taken.
If the insertion succeeds without detecting a conflict, the tuple is
deemed inserted.
To handle the possible ambiguity between the excluded alias and a table
named excluded, and for convenience with long relation names, INSERT
INTO now can alias its target table.
Bumps catversion as stored rules change.
Author: Peter Geoghegan, with significant contributions from Heikki
Linnakangas and Andres Freund. Testing infrastructure by Jeff Janes.
Reviewed-By: Heikki Linnakangas, Andres Freund, Robert Haas, Simon Riggs,
Dean Rasheed, Stephen Frost and many others.
2015-05-08 05:31:36 +02:00
|
|
|
ON CONFLICT (k) DO UPDATE SET v = (SELECT b || ' update' FROM aa WHERE a = 1 LIMIT 1);
|
|
|
|
WITH aa AS (SELECT 1 a, 2 b)
|
Clean up duplicate table and function names in regression tests.
Many of the objects we create during the regression tests are put in the
public schema, so that using the same names in different regression tests
creates a hazard of test failures if any two such scripts run concurrently.
This patch cleans up a bunch of latent hazards of that sort, as well as two
live hazards.
The current situation in this regard is far worse than it was a year or two
back, because practically all of the partitioning-related test cases have
reused table names with enthusiasm. I despaired of cleaning up that mess
within the five most-affected tests (create_table, alter_table, insert,
update, inherit); fortunately those don't run concurrently.
Other than partitioning problems, most of the issues boil down to using
names like "foo", "bar", "tmp", etc, without thought for the fact that
other test scripts might use similar names concurrently. I've made an
effort to make all such names more specific.
One of the live hazards was that commit 7421f4b8 caused with.sql to
create a table named "test", conflicting with a similarly-named table
in alter_table.sql; this was exposed in the buildfarm recently.
The other one was that join.sql and transactions.sql both create tables
named "foo" and "bar"; but join.sql's uses of those names date back
only to December or so.
Since commit 7421f4b8 was back-patched to v10, back-patch a minimal
fix for that problem. The rest of this is just future-proofing.
Discussion: https://postgr.es/m/4627.1521070268@sss.pgh.pa.us
2018-03-15 22:08:51 +01:00
|
|
|
INSERT INTO withz VALUES(1, 'insert')
|
|
|
|
ON CONFLICT (k) DO UPDATE SET v = ' update' WHERE withz.k = (SELECT a FROM aa);
|
Add support for INSERT ... ON CONFLICT DO NOTHING/UPDATE.
The newly added ON CONFLICT clause allows to specify an alternative to
raising a unique or exclusion constraint violation error when inserting.
ON CONFLICT refers to constraints that can either be specified using a
inference clause (by specifying the columns of a unique constraint) or
by naming a unique or exclusion constraint. DO NOTHING avoids the
constraint violation, without touching the pre-existing row. DO UPDATE
SET ... [WHERE ...] updates the pre-existing tuple, and has access to
both the tuple proposed for insertion and the existing tuple; the
optional WHERE clause can be used to prevent an update from being
executed. The UPDATE SET and WHERE clauses have access to the tuple
proposed for insertion using the "magic" EXCLUDED alias, and to the
pre-existing tuple using the table name or its alias.
This feature is often referred to as upsert.
This is implemented using a new infrastructure called "speculative
insertion". It is an optimistic variant of regular insertion that first
does a pre-check for existing tuples and then attempts an insert. If a
violating tuple was inserted concurrently, the speculatively inserted
tuple is deleted and a new attempt is made. If the pre-check finds a
matching tuple the alternative DO NOTHING or DO UPDATE action is taken.
If the insertion succeeds without detecting a conflict, the tuple is
deemed inserted.
To handle the possible ambiguity between the excluded alias and a table
named excluded, and for convenience with long relation names, INSERT
INTO now can alias its target table.
Bumps catversion as stored rules change.
Author: Peter Geoghegan, with significant contributions from Heikki
Linnakangas and Andres Freund. Testing infrastructure by Jeff Janes.
Reviewed-By: Heikki Linnakangas, Andres Freund, Robert Haas, Simon Riggs,
Dean Rasheed, Stephen Frost and many others.
2015-05-08 05:31:36 +02:00
|
|
|
WITH aa AS (SELECT 1 a, 2 b)
|
Clean up duplicate table and function names in regression tests.
Many of the objects we create during the regression tests are put in the
public schema, so that using the same names in different regression tests
creates a hazard of test failures if any two such scripts run concurrently.
This patch cleans up a bunch of latent hazards of that sort, as well as two
live hazards.
The current situation in this regard is far worse than it was a year or two
back, because practically all of the partitioning-related test cases have
reused table names with enthusiasm. I despaired of cleaning up that mess
within the five most-affected tests (create_table, alter_table, insert,
update, inherit); fortunately those don't run concurrently.
Other than partitioning problems, most of the issues boil down to using
names like "foo", "bar", "tmp", etc, without thought for the fact that
other test scripts might use similar names concurrently. I've made an
effort to make all such names more specific.
One of the live hazards was that commit 7421f4b8 caused with.sql to
create a table named "test", conflicting with a similarly-named table
in alter_table.sql; this was exposed in the buildfarm recently.
The other one was that join.sql and transactions.sql both create tables
named "foo" and "bar"; but join.sql's uses of those names date back
only to December or so.
Since commit 7421f4b8 was back-patched to v10, back-patch a minimal
fix for that problem. The rest of this is just future-proofing.
Discussion: https://postgr.es/m/4627.1521070268@sss.pgh.pa.us
2018-03-15 22:08:51 +01:00
|
|
|
INSERT INTO withz VALUES(1, 'insert')
|
Add support for INSERT ... ON CONFLICT DO NOTHING/UPDATE.
The newly added ON CONFLICT clause allows to specify an alternative to
raising a unique or exclusion constraint violation error when inserting.
ON CONFLICT refers to constraints that can either be specified using a
inference clause (by specifying the columns of a unique constraint) or
by naming a unique or exclusion constraint. DO NOTHING avoids the
constraint violation, without touching the pre-existing row. DO UPDATE
SET ... [WHERE ...] updates the pre-existing tuple, and has access to
both the tuple proposed for insertion and the existing tuple; the
optional WHERE clause can be used to prevent an update from being
executed. The UPDATE SET and WHERE clauses have access to the tuple
proposed for insertion using the "magic" EXCLUDED alias, and to the
pre-existing tuple using the table name or its alias.
This feature is often referred to as upsert.
This is implemented using a new infrastructure called "speculative
insertion". It is an optimistic variant of regular insertion that first
does a pre-check for existing tuples and then attempts an insert. If a
violating tuple was inserted concurrently, the speculatively inserted
tuple is deleted and a new attempt is made. If the pre-check finds a
matching tuple the alternative DO NOTHING or DO UPDATE action is taken.
If the insertion succeeds without detecting a conflict, the tuple is
deemed inserted.
To handle the possible ambiguity between the excluded alias and a table
named excluded, and for convenience with long relation names, INSERT
INTO now can alias its target table.
Bumps catversion as stored rules change.
Author: Peter Geoghegan, with significant contributions from Heikki
Linnakangas and Andres Freund. Testing infrastructure by Jeff Janes.
Reviewed-By: Heikki Linnakangas, Andres Freund, Robert Haas, Simon Riggs,
Dean Rasheed, Stephen Frost and many others.
2015-05-08 05:31:36 +02:00
|
|
|
ON CONFLICT (k) DO UPDATE SET v = (SELECT b || ' update' FROM aa WHERE a = 1 LIMIT 1);
|
|
|
|
WITH aa AS (SELECT 'a' a, 'b' b UNION ALL SELECT 'a' a, 'b' b)
|
Clean up duplicate table and function names in regression tests.
Many of the objects we create during the regression tests are put in the
public schema, so that using the same names in different regression tests
creates a hazard of test failures if any two such scripts run concurrently.
This patch cleans up a bunch of latent hazards of that sort, as well as two
live hazards.
The current situation in this regard is far worse than it was a year or two
back, because practically all of the partitioning-related test cases have
reused table names with enthusiasm. I despaired of cleaning up that mess
within the five most-affected tests (create_table, alter_table, insert,
update, inherit); fortunately those don't run concurrently.
Other than partitioning problems, most of the issues boil down to using
names like "foo", "bar", "tmp", etc, without thought for the fact that
other test scripts might use similar names concurrently. I've made an
effort to make all such names more specific.
One of the live hazards was that commit 7421f4b8 caused with.sql to
create a table named "test", conflicting with a similarly-named table
in alter_table.sql; this was exposed in the buildfarm recently.
The other one was that join.sql and transactions.sql both create tables
named "foo" and "bar"; but join.sql's uses of those names date back
only to December or so.
Since commit 7421f4b8 was back-patched to v10, back-patch a minimal
fix for that problem. The rest of this is just future-proofing.
Discussion: https://postgr.es/m/4627.1521070268@sss.pgh.pa.us
2018-03-15 22:08:51 +01:00
|
|
|
INSERT INTO withz VALUES(1, 'insert')
|
Add support for INSERT ... ON CONFLICT DO NOTHING/UPDATE.
The newly added ON CONFLICT clause allows to specify an alternative to
raising a unique or exclusion constraint violation error when inserting.
ON CONFLICT refers to constraints that can either be specified using a
inference clause (by specifying the columns of a unique constraint) or
by naming a unique or exclusion constraint. DO NOTHING avoids the
constraint violation, without touching the pre-existing row. DO UPDATE
SET ... [WHERE ...] updates the pre-existing tuple, and has access to
both the tuple proposed for insertion and the existing tuple; the
optional WHERE clause can be used to prevent an update from being
executed. The UPDATE SET and WHERE clauses have access to the tuple
proposed for insertion using the "magic" EXCLUDED alias, and to the
pre-existing tuple using the table name or its alias.
This feature is often referred to as upsert.
This is implemented using a new infrastructure called "speculative
insertion". It is an optimistic variant of regular insertion that first
does a pre-check for existing tuples and then attempts an insert. If a
violating tuple was inserted concurrently, the speculatively inserted
tuple is deleted and a new attempt is made. If the pre-check finds a
matching tuple the alternative DO NOTHING or DO UPDATE action is taken.
If the insertion succeeds without detecting a conflict, the tuple is
deemed inserted.
To handle the possible ambiguity between the excluded alias and a table
named excluded, and for convenience with long relation names, INSERT
INTO now can alias its target table.
Bumps catversion as stored rules change.
Author: Peter Geoghegan, with significant contributions from Heikki
Linnakangas and Andres Freund. Testing infrastructure by Jeff Janes.
Reviewed-By: Heikki Linnakangas, Andres Freund, Robert Haas, Simon Riggs,
Dean Rasheed, Stephen Frost and many others.
2015-05-08 05:31:36 +02:00
|
|
|
ON CONFLICT (k) DO UPDATE SET v = (SELECT b || ' update' FROM aa WHERE a = 'a' LIMIT 1);
|
|
|
|
WITH aa AS (SELECT 1 a, 2 b)
|
Clean up duplicate table and function names in regression tests.
Many of the objects we create during the regression tests are put in the
public schema, so that using the same names in different regression tests
creates a hazard of test failures if any two such scripts run concurrently.
This patch cleans up a bunch of latent hazards of that sort, as well as two
live hazards.
The current situation in this regard is far worse than it was a year or two
back, because practically all of the partitioning-related test cases have
reused table names with enthusiasm. I despaired of cleaning up that mess
within the five most-affected tests (create_table, alter_table, insert,
update, inherit); fortunately those don't run concurrently.
Other than partitioning problems, most of the issues boil down to using
names like "foo", "bar", "tmp", etc, without thought for the fact that
other test scripts might use similar names concurrently. I've made an
effort to make all such names more specific.
One of the live hazards was that commit 7421f4b8 caused with.sql to
create a table named "test", conflicting with a similarly-named table
in alter_table.sql; this was exposed in the buildfarm recently.
The other one was that join.sql and transactions.sql both create tables
named "foo" and "bar"; but join.sql's uses of those names date back
only to December or so.
Since commit 7421f4b8 was back-patched to v10, back-patch a minimal
fix for that problem. The rest of this is just future-proofing.
Discussion: https://postgr.es/m/4627.1521070268@sss.pgh.pa.us
2018-03-15 22:08:51 +01:00
|
|
|
INSERT INTO withz VALUES(1, (SELECT b || ' insert' FROM aa WHERE a = 1 ))
|
Add support for INSERT ... ON CONFLICT DO NOTHING/UPDATE.
The newly added ON CONFLICT clause allows to specify an alternative to
raising a unique or exclusion constraint violation error when inserting.
ON CONFLICT refers to constraints that can either be specified using a
inference clause (by specifying the columns of a unique constraint) or
by naming a unique or exclusion constraint. DO NOTHING avoids the
constraint violation, without touching the pre-existing row. DO UPDATE
SET ... [WHERE ...] updates the pre-existing tuple, and has access to
both the tuple proposed for insertion and the existing tuple; the
optional WHERE clause can be used to prevent an update from being
executed. The UPDATE SET and WHERE clauses have access to the tuple
proposed for insertion using the "magic" EXCLUDED alias, and to the
pre-existing tuple using the table name or its alias.
This feature is often referred to as upsert.
This is implemented using a new infrastructure called "speculative
insertion". It is an optimistic variant of regular insertion that first
does a pre-check for existing tuples and then attempts an insert. If a
violating tuple was inserted concurrently, the speculatively inserted
tuple is deleted and a new attempt is made. If the pre-check finds a
matching tuple the alternative DO NOTHING or DO UPDATE action is taken.
If the insertion succeeds without detecting a conflict, the tuple is
deemed inserted.
To handle the possible ambiguity between the excluded alias and a table
named excluded, and for convenience with long relation names, INSERT
INTO now can alias its target table.
Bumps catversion as stored rules change.
Author: Peter Geoghegan, with significant contributions from Heikki
Linnakangas and Andres Freund. Testing infrastructure by Jeff Janes.
Reviewed-By: Heikki Linnakangas, Andres Freund, Robert Haas, Simon Riggs,
Dean Rasheed, Stephen Frost and many others.
2015-05-08 05:31:36 +02:00
|
|
|
ON CONFLICT (k) DO UPDATE SET v = (SELECT b || ' update' FROM aa WHERE a = 1 LIMIT 1);
|
Fix ON CONFLICT UPDATE bug breaking AFTER UPDATE triggers.
ExecOnConflictUpdate() passed t_ctid of the to-be-updated tuple to
ExecUpdate(). That's problematic primarily because of two reason: First
and foremost t_ctid could point to a different tuple. Secondly, and
that's what triggered the complaint by Stanislav, t_ctid is changed by
heap_update() to point to the new tuple version. The behavior of AFTER
UPDATE triggers was therefore broken, with NEW.* and OLD.* tuples
spuriously identical within AFTER UPDATE triggers.
To fix both issues, pass a pointer to t_self of a on-stack HeapTuple
instead.
Fixing this bug lead to one change in regression tests, which previously
failed due to the first issue mentioned above. There's a reasonable
expectation that test fails, as it updates one row repeatedly within one
INSERT ... ON CONFLICT statement. That is only possible if the second
update is triggered via ON CONFLICT ... SET, ON CONFLICT ... WHERE, or
by a WITH CHECK expression, as those are executed after
ExecOnConflictUpdate() does a visibility check. That could easily be
prohibited, but given it's allowed for plain UPDATEs and a rare corner
case, it doesn't seem worthwhile.
Reported-By: Stanislav Grozev
Author: Andres Freund and Peter Geoghegan
Discussion: CAA78GVqy1+LisN-8DygekD_Ldfy=BJLarSpjGhytOsgkpMavfQ@mail.gmail.com
Backpatch: 9.5, where ON CONFLICT was introduced
2015-12-10 16:26:45 +01:00
|
|
|
-- Update a row more than once, in different parts of a wCTE. That is
|
|
|
|
-- an allowed, presumably very rare, edge case, but since it was
|
|
|
|
-- broken in the past, having a test seems worthwhile.
|
Add support for INSERT ... ON CONFLICT DO NOTHING/UPDATE.
The newly added ON CONFLICT clause allows to specify an alternative to
raising a unique or exclusion constraint violation error when inserting.
ON CONFLICT refers to constraints that can either be specified using a
inference clause (by specifying the columns of a unique constraint) or
by naming a unique or exclusion constraint. DO NOTHING avoids the
constraint violation, without touching the pre-existing row. DO UPDATE
SET ... [WHERE ...] updates the pre-existing tuple, and has access to
both the tuple proposed for insertion and the existing tuple; the
optional WHERE clause can be used to prevent an update from being
executed. The UPDATE SET and WHERE clauses have access to the tuple
proposed for insertion using the "magic" EXCLUDED alias, and to the
pre-existing tuple using the table name or its alias.
This feature is often referred to as upsert.
This is implemented using a new infrastructure called "speculative
insertion". It is an optimistic variant of regular insertion that first
does a pre-check for existing tuples and then attempts an insert. If a
violating tuple was inserted concurrently, the speculatively inserted
tuple is deleted and a new attempt is made. If the pre-check finds a
matching tuple the alternative DO NOTHING or DO UPDATE action is taken.
If the insertion succeeds without detecting a conflict, the tuple is
deemed inserted.
To handle the possible ambiguity between the excluded alias and a table
named excluded, and for convenience with long relation names, INSERT
INTO now can alias its target table.
Bumps catversion as stored rules change.
Author: Peter Geoghegan, with significant contributions from Heikki
Linnakangas and Andres Freund. Testing infrastructure by Jeff Janes.
Reviewed-By: Heikki Linnakangas, Andres Freund, Robert Haas, Simon Riggs,
Dean Rasheed, Stephen Frost and many others.
2015-05-08 05:31:36 +02:00
|
|
|
WITH simpletup AS (
|
|
|
|
SELECT 2 k, 'Green' v),
|
|
|
|
upsert_cte AS (
|
Clean up duplicate table and function names in regression tests.
Many of the objects we create during the regression tests are put in the
public schema, so that using the same names in different regression tests
creates a hazard of test failures if any two such scripts run concurrently.
This patch cleans up a bunch of latent hazards of that sort, as well as two
live hazards.
The current situation in this regard is far worse than it was a year or two
back, because practically all of the partitioning-related test cases have
reused table names with enthusiasm. I despaired of cleaning up that mess
within the five most-affected tests (create_table, alter_table, insert,
update, inherit); fortunately those don't run concurrently.
Other than partitioning problems, most of the issues boil down to using
names like "foo", "bar", "tmp", etc, without thought for the fact that
other test scripts might use similar names concurrently. I've made an
effort to make all such names more specific.
One of the live hazards was that commit 7421f4b8 caused with.sql to
create a table named "test", conflicting with a similarly-named table
in alter_table.sql; this was exposed in the buildfarm recently.
The other one was that join.sql and transactions.sql both create tables
named "foo" and "bar"; but join.sql's uses of those names date back
only to December or so.
Since commit 7421f4b8 was back-patched to v10, back-patch a minimal
fix for that problem. The rest of this is just future-proofing.
Discussion: https://postgr.es/m/4627.1521070268@sss.pgh.pa.us
2018-03-15 22:08:51 +01:00
|
|
|
INSERT INTO withz VALUES(2, 'Blue') ON CONFLICT (k) DO
|
|
|
|
UPDATE SET (k, v) = (SELECT k, v FROM simpletup WHERE simpletup.k = withz.k)
|
Add support for INSERT ... ON CONFLICT DO NOTHING/UPDATE.
The newly added ON CONFLICT clause allows to specify an alternative to
raising a unique or exclusion constraint violation error when inserting.
ON CONFLICT refers to constraints that can either be specified using a
inference clause (by specifying the columns of a unique constraint) or
by naming a unique or exclusion constraint. DO NOTHING avoids the
constraint violation, without touching the pre-existing row. DO UPDATE
SET ... [WHERE ...] updates the pre-existing tuple, and has access to
both the tuple proposed for insertion and the existing tuple; the
optional WHERE clause can be used to prevent an update from being
executed. The UPDATE SET and WHERE clauses have access to the tuple
proposed for insertion using the "magic" EXCLUDED alias, and to the
pre-existing tuple using the table name or its alias.
This feature is often referred to as upsert.
This is implemented using a new infrastructure called "speculative
insertion". It is an optimistic variant of regular insertion that first
does a pre-check for existing tuples and then attempts an insert. If a
violating tuple was inserted concurrently, the speculatively inserted
tuple is deleted and a new attempt is made. If the pre-check finds a
matching tuple the alternative DO NOTHING or DO UPDATE action is taken.
If the insertion succeeds without detecting a conflict, the tuple is
deemed inserted.
To handle the possible ambiguity between the excluded alias and a table
named excluded, and for convenience with long relation names, INSERT
INTO now can alias its target table.
Bumps catversion as stored rules change.
Author: Peter Geoghegan, with significant contributions from Heikki
Linnakangas and Andres Freund. Testing infrastructure by Jeff Janes.
Reviewed-By: Heikki Linnakangas, Andres Freund, Robert Haas, Simon Riggs,
Dean Rasheed, Stephen Frost and many others.
2015-05-08 05:31:36 +02:00
|
|
|
RETURNING k, v)
|
Clean up duplicate table and function names in regression tests.
Many of the objects we create during the regression tests are put in the
public schema, so that using the same names in different regression tests
creates a hazard of test failures if any two such scripts run concurrently.
This patch cleans up a bunch of latent hazards of that sort, as well as two
live hazards.
The current situation in this regard is far worse than it was a year or two
back, because practically all of the partitioning-related test cases have
reused table names with enthusiasm. I despaired of cleaning up that mess
within the five most-affected tests (create_table, alter_table, insert,
update, inherit); fortunately those don't run concurrently.
Other than partitioning problems, most of the issues boil down to using
names like "foo", "bar", "tmp", etc, without thought for the fact that
other test scripts might use similar names concurrently. I've made an
effort to make all such names more specific.
One of the live hazards was that commit 7421f4b8 caused with.sql to
create a table named "test", conflicting with a similarly-named table
in alter_table.sql; this was exposed in the buildfarm recently.
The other one was that join.sql and transactions.sql both create tables
named "foo" and "bar"; but join.sql's uses of those names date back
only to December or so.
Since commit 7421f4b8 was back-patched to v10, back-patch a minimal
fix for that problem. The rest of this is just future-proofing.
Discussion: https://postgr.es/m/4627.1521070268@sss.pgh.pa.us
2018-03-15 22:08:51 +01:00
|
|
|
INSERT INTO withz VALUES(2, 'Red') ON CONFLICT (k) DO
|
|
|
|
UPDATE SET (k, v) = (SELECT k, v FROM upsert_cte WHERE upsert_cte.k = withz.k)
|
Add support for INSERT ... ON CONFLICT DO NOTHING/UPDATE.
The newly added ON CONFLICT clause allows to specify an alternative to
raising a unique or exclusion constraint violation error when inserting.
ON CONFLICT refers to constraints that can either be specified using a
inference clause (by specifying the columns of a unique constraint) or
by naming a unique or exclusion constraint. DO NOTHING avoids the
constraint violation, without touching the pre-existing row. DO UPDATE
SET ... [WHERE ...] updates the pre-existing tuple, and has access to
both the tuple proposed for insertion and the existing tuple; the
optional WHERE clause can be used to prevent an update from being
executed. The UPDATE SET and WHERE clauses have access to the tuple
proposed for insertion using the "magic" EXCLUDED alias, and to the
pre-existing tuple using the table name or its alias.
This feature is often referred to as upsert.
This is implemented using a new infrastructure called "speculative
insertion". It is an optimistic variant of regular insertion that first
does a pre-check for existing tuples and then attempts an insert. If a
violating tuple was inserted concurrently, the speculatively inserted
tuple is deleted and a new attempt is made. If the pre-check finds a
matching tuple the alternative DO NOTHING or DO UPDATE action is taken.
If the insertion succeeds without detecting a conflict, the tuple is
deemed inserted.
To handle the possible ambiguity between the excluded alias and a table
named excluded, and for convenience with long relation names, INSERT
INTO now can alias its target table.
Bumps catversion as stored rules change.
Author: Peter Geoghegan, with significant contributions from Heikki
Linnakangas and Andres Freund. Testing infrastructure by Jeff Janes.
Reviewed-By: Heikki Linnakangas, Andres Freund, Robert Haas, Simon Riggs,
Dean Rasheed, Stephen Frost and many others.
2015-05-08 05:31:36 +02:00
|
|
|
RETURNING k, v;
|
Fix ON CONFLICT UPDATE bug breaking AFTER UPDATE triggers.
ExecOnConflictUpdate() passed t_ctid of the to-be-updated tuple to
ExecUpdate(). That's problematic primarily because of two reason: First
and foremost t_ctid could point to a different tuple. Secondly, and
that's what triggered the complaint by Stanislav, t_ctid is changed by
heap_update() to point to the new tuple version. The behavior of AFTER
UPDATE triggers was therefore broken, with NEW.* and OLD.* tuples
spuriously identical within AFTER UPDATE triggers.
To fix both issues, pass a pointer to t_self of a on-stack HeapTuple
instead.
Fixing this bug lead to one change in regression tests, which previously
failed due to the first issue mentioned above. There's a reasonable
expectation that test fails, as it updates one row repeatedly within one
INSERT ... ON CONFLICT statement. That is only possible if the second
update is triggered via ON CONFLICT ... SET, ON CONFLICT ... WHERE, or
by a WITH CHECK expression, as those are executed after
ExecOnConflictUpdate() does a visibility check. That could easily be
prohibited, but given it's allowed for plain UPDATEs and a rare corner
case, it doesn't seem worthwhile.
Reported-By: Stanislav Grozev
Author: Andres Freund and Peter Geoghegan
Discussion: CAA78GVqy1+LisN-8DygekD_Ldfy=BJLarSpjGhytOsgkpMavfQ@mail.gmail.com
Backpatch: 9.5, where ON CONFLICT was introduced
2015-12-10 16:26:45 +01:00
|
|
|
k | v
|
|
|
|
---+---
|
|
|
|
(0 rows)
|
|
|
|
|
Clean up duplicate table and function names in regression tests.
Many of the objects we create during the regression tests are put in the
public schema, so that using the same names in different regression tests
creates a hazard of test failures if any two such scripts run concurrently.
This patch cleans up a bunch of latent hazards of that sort, as well as two
live hazards.
The current situation in this regard is far worse than it was a year or two
back, because practically all of the partitioning-related test cases have
reused table names with enthusiasm. I despaired of cleaning up that mess
within the five most-affected tests (create_table, alter_table, insert,
update, inherit); fortunately those don't run concurrently.
Other than partitioning problems, most of the issues boil down to using
names like "foo", "bar", "tmp", etc, without thought for the fact that
other test scripts might use similar names concurrently. I've made an
effort to make all such names more specific.
One of the live hazards was that commit 7421f4b8 caused with.sql to
create a table named "test", conflicting with a similarly-named table
in alter_table.sql; this was exposed in the buildfarm recently.
The other one was that join.sql and transactions.sql both create tables
named "foo" and "bar"; but join.sql's uses of those names date back
only to December or so.
Since commit 7421f4b8 was back-patched to v10, back-patch a minimal
fix for that problem. The rest of this is just future-proofing.
Discussion: https://postgr.es/m/4627.1521070268@sss.pgh.pa.us
2018-03-15 22:08:51 +01:00
|
|
|
DROP TABLE withz;
|
2022-03-28 16:45:58 +02:00
|
|
|
-- WITH referenced by MERGE statement
|
|
|
|
CREATE TABLE m AS SELECT i AS k, (i || ' v')::text v FROM generate_series(1, 16, 3) i;
|
|
|
|
ALTER TABLE m ADD UNIQUE (k);
|
|
|
|
WITH RECURSIVE cte_basic AS (SELECT 1 a, 'cte_basic val' b)
|
|
|
|
MERGE INTO m USING (select 0 k, 'merge source SubPlan' v) o ON m.k=o.k
|
|
|
|
WHEN MATCHED THEN UPDATE SET v = (SELECT b || ' merge update' FROM cte_basic WHERE cte_basic.a = m.k LIMIT 1)
|
|
|
|
WHEN NOT MATCHED THEN INSERT VALUES(o.k, o.v);
|
|
|
|
ERROR: WITH RECURSIVE is not supported for MERGE statement
|
|
|
|
-- Basic:
|
|
|
|
WITH cte_basic AS MATERIALIZED (SELECT 1 a, 'cte_basic val' b)
|
|
|
|
MERGE INTO m USING (select 0 k, 'merge source SubPlan' v offset 0) o ON m.k=o.k
|
|
|
|
WHEN MATCHED THEN UPDATE SET v = (SELECT b || ' merge update' FROM cte_basic WHERE cte_basic.a = m.k LIMIT 1)
|
|
|
|
WHEN NOT MATCHED THEN INSERT VALUES(o.k, o.v);
|
|
|
|
-- Examine
|
|
|
|
SELECT * FROM m where k = 0;
|
|
|
|
k | v
|
|
|
|
---+----------------------
|
|
|
|
0 | merge source SubPlan
|
|
|
|
(1 row)
|
|
|
|
|
|
|
|
-- See EXPLAIN output for same query:
|
|
|
|
EXPLAIN (VERBOSE, COSTS OFF)
|
|
|
|
WITH cte_basic AS MATERIALIZED (SELECT 1 a, 'cte_basic val' b)
|
|
|
|
MERGE INTO m USING (select 0 k, 'merge source SubPlan' v offset 0) o ON m.k=o.k
|
|
|
|
WHEN MATCHED THEN UPDATE SET v = (SELECT b || ' merge update' FROM cte_basic WHERE cte_basic.a = m.k LIMIT 1)
|
|
|
|
WHEN NOT MATCHED THEN INSERT VALUES(o.k, o.v);
|
2023-09-30 11:52:21 +02:00
|
|
|
QUERY PLAN
|
|
|
|
-------------------------------------------------------------------
|
2022-03-28 16:45:58 +02:00
|
|
|
Merge on public.m
|
|
|
|
CTE cte_basic
|
|
|
|
-> Result
|
|
|
|
Output: 1, 'cte_basic val'::text
|
|
|
|
-> Hash Right Join
|
2023-09-30 11:52:21 +02:00
|
|
|
Output: m.ctid, o.k, o.v, o.*
|
|
|
|
Hash Cond: (m.k = o.k)
|
2022-03-28 16:45:58 +02:00
|
|
|
-> Seq Scan on public.m
|
|
|
|
Output: m.ctid, m.k
|
|
|
|
-> Hash
|
2023-09-30 11:52:21 +02:00
|
|
|
Output: o.k, o.v, o.*
|
|
|
|
-> Subquery Scan on o
|
|
|
|
Output: o.k, o.v, o.*
|
|
|
|
-> Result
|
|
|
|
Output: 0, 'merge source SubPlan'::text
|
2022-03-28 16:45:58 +02:00
|
|
|
SubPlan 2
|
|
|
|
-> Limit
|
|
|
|
Output: ((cte_basic.b || ' merge update'::text))
|
|
|
|
-> CTE Scan on cte_basic
|
|
|
|
Output: (cte_basic.b || ' merge update'::text)
|
|
|
|
Filter: (cte_basic.a = m.k)
|
2023-09-30 11:52:21 +02:00
|
|
|
(21 rows)
|
2022-03-28 16:45:58 +02:00
|
|
|
|
|
|
|
-- InitPlan
|
|
|
|
WITH cte_init AS MATERIALIZED (SELECT 1 a, 'cte_init val' b)
|
|
|
|
MERGE INTO m USING (select 1 k, 'merge source InitPlan' v offset 0) o ON m.k=o.k
|
|
|
|
WHEN MATCHED THEN UPDATE SET v = (SELECT b || ' merge update' FROM cte_init WHERE a = 1 LIMIT 1)
|
|
|
|
WHEN NOT MATCHED THEN INSERT VALUES(o.k, o.v);
|
|
|
|
-- Examine
|
|
|
|
SELECT * FROM m where k = 1;
|
|
|
|
k | v
|
|
|
|
---+---------------------------
|
|
|
|
1 | cte_init val merge update
|
|
|
|
(1 row)
|
|
|
|
|
|
|
|
-- See EXPLAIN output for same query:
|
|
|
|
EXPLAIN (VERBOSE, COSTS OFF)
|
|
|
|
WITH cte_init AS MATERIALIZED (SELECT 1 a, 'cte_init val' b)
|
|
|
|
MERGE INTO m USING (select 1 k, 'merge source InitPlan' v offset 0) o ON m.k=o.k
|
|
|
|
WHEN MATCHED THEN UPDATE SET v = (SELECT b || ' merge update' FROM cte_init WHERE a = 1 LIMIT 1)
|
|
|
|
WHEN NOT MATCHED THEN INSERT VALUES(o.k, o.v);
|
2023-09-30 11:52:21 +02:00
|
|
|
QUERY PLAN
|
|
|
|
--------------------------------------------------------------------
|
2022-03-28 16:45:58 +02:00
|
|
|
Merge on public.m
|
|
|
|
CTE cte_init
|
|
|
|
-> Result
|
|
|
|
Output: 1, 'cte_init val'::text
|
|
|
|
InitPlan 2 (returns $1)
|
|
|
|
-> Limit
|
|
|
|
Output: ((cte_init.b || ' merge update'::text))
|
|
|
|
-> CTE Scan on cte_init
|
|
|
|
Output: (cte_init.b || ' merge update'::text)
|
|
|
|
Filter: (cte_init.a = 1)
|
|
|
|
-> Hash Right Join
|
2023-09-30 11:52:21 +02:00
|
|
|
Output: m.ctid, o.k, o.v, o.*
|
|
|
|
Hash Cond: (m.k = o.k)
|
2022-03-28 16:45:58 +02:00
|
|
|
-> Seq Scan on public.m
|
|
|
|
Output: m.ctid, m.k
|
|
|
|
-> Hash
|
2023-09-30 11:52:21 +02:00
|
|
|
Output: o.k, o.v, o.*
|
|
|
|
-> Subquery Scan on o
|
|
|
|
Output: o.k, o.v, o.*
|
|
|
|
-> Result
|
|
|
|
Output: 1, 'merge source InitPlan'::text
|
|
|
|
(21 rows)
|
2022-03-28 16:45:58 +02:00
|
|
|
|
|
|
|
-- MERGE source comes from CTE:
|
|
|
|
WITH merge_source_cte AS MATERIALIZED (SELECT 15 a, 'merge_source_cte val' b)
|
|
|
|
MERGE INTO m USING (select * from merge_source_cte) o ON m.k=o.a
|
|
|
|
WHEN MATCHED THEN UPDATE SET v = (SELECT b || merge_source_cte.*::text || ' merge update' FROM merge_source_cte WHERE a = 15)
|
|
|
|
WHEN NOT MATCHED THEN INSERT VALUES(o.a, o.b || (SELECT merge_source_cte.*::text || ' merge insert' FROM merge_source_cte));
|
|
|
|
-- Examine
|
|
|
|
SELECT * FROM m where k = 15;
|
|
|
|
k | v
|
|
|
|
----+--------------------------------------------------------------
|
|
|
|
15 | merge_source_cte val(15,"merge_source_cte val") merge insert
|
|
|
|
(1 row)
|
|
|
|
|
|
|
|
-- See EXPLAIN output for same query:
|
|
|
|
EXPLAIN (VERBOSE, COSTS OFF)
|
|
|
|
WITH merge_source_cte AS MATERIALIZED (SELECT 15 a, 'merge_source_cte val' b)
|
|
|
|
MERGE INTO m USING (select * from merge_source_cte) o ON m.k=o.a
|
|
|
|
WHEN MATCHED THEN UPDATE SET v = (SELECT b || merge_source_cte.*::text || ' merge update' FROM merge_source_cte WHERE a = 15)
|
|
|
|
WHEN NOT MATCHED THEN INSERT VALUES(o.a, o.b || (SELECT merge_source_cte.*::text || ' merge insert' FROM merge_source_cte));
|
|
|
|
QUERY PLAN
|
|
|
|
-----------------------------------------------------------------------------------------------------
|
|
|
|
Merge on public.m
|
|
|
|
CTE merge_source_cte
|
|
|
|
-> Result
|
|
|
|
Output: 15, 'merge_source_cte val'::text
|
|
|
|
InitPlan 2 (returns $1)
|
|
|
|
-> CTE Scan on merge_source_cte merge_source_cte_1
|
|
|
|
Output: ((merge_source_cte_1.b || (merge_source_cte_1.*)::text) || ' merge update'::text)
|
|
|
|
Filter: (merge_source_cte_1.a = 15)
|
|
|
|
InitPlan 3 (returns $2)
|
|
|
|
-> CTE Scan on merge_source_cte merge_source_cte_2
|
|
|
|
Output: ((merge_source_cte_2.*)::text || ' merge insert'::text)
|
|
|
|
-> Hash Right Join
|
2023-09-30 11:52:21 +02:00
|
|
|
Output: m.ctid, merge_source_cte.a, merge_source_cte.b, merge_source_cte.*
|
2022-03-28 16:45:58 +02:00
|
|
|
Hash Cond: (m.k = merge_source_cte.a)
|
|
|
|
-> Seq Scan on public.m
|
|
|
|
Output: m.ctid, m.k
|
|
|
|
-> Hash
|
2023-09-30 11:52:21 +02:00
|
|
|
Output: merge_source_cte.a, merge_source_cte.b, merge_source_cte.*
|
2022-03-28 16:45:58 +02:00
|
|
|
-> CTE Scan on merge_source_cte
|
2023-09-30 11:52:21 +02:00
|
|
|
Output: merge_source_cte.a, merge_source_cte.b, merge_source_cte.*
|
2022-03-28 16:45:58 +02:00
|
|
|
(20 rows)
|
|
|
|
|
|
|
|
DROP TABLE m;
|
2011-02-26 05:53:34 +01:00
|
|
|
-- check that run to completion happens in proper ordering
|
|
|
|
TRUNCATE TABLE y;
|
|
|
|
INSERT INTO y SELECT generate_series(1, 3);
|
|
|
|
CREATE TEMPORARY TABLE yy (a INTEGER);
|
|
|
|
WITH RECURSIVE t1 AS (
|
|
|
|
INSERT INTO y SELECT * FROM y RETURNING *
|
|
|
|
), t2 AS (
|
|
|
|
INSERT INTO yy SELECT * FROM t1 RETURNING *
|
|
|
|
)
|
|
|
|
SELECT 1;
|
|
|
|
?column?
|
|
|
|
----------
|
|
|
|
1
|
|
|
|
(1 row)
|
|
|
|
|
|
|
|
SELECT * FROM y;
|
|
|
|
a
|
|
|
|
---
|
|
|
|
1
|
|
|
|
2
|
|
|
|
3
|
|
|
|
1
|
|
|
|
2
|
|
|
|
3
|
|
|
|
(6 rows)
|
|
|
|
|
|
|
|
SELECT * FROM yy;
|
|
|
|
a
|
|
|
|
---
|
|
|
|
1
|
|
|
|
2
|
|
|
|
3
|
|
|
|
(3 rows)
|
|
|
|
|
|
|
|
WITH RECURSIVE t1 AS (
|
|
|
|
INSERT INTO yy SELECT * FROM t2 RETURNING *
|
|
|
|
), t2 AS (
|
|
|
|
INSERT INTO y SELECT * FROM y RETURNING *
|
|
|
|
)
|
|
|
|
SELECT 1;
|
|
|
|
?column?
|
|
|
|
----------
|
|
|
|
1
|
|
|
|
(1 row)
|
|
|
|
|
|
|
|
SELECT * FROM y;
|
|
|
|
a
|
|
|
|
---
|
|
|
|
1
|
|
|
|
2
|
|
|
|
3
|
|
|
|
1
|
|
|
|
2
|
|
|
|
3
|
|
|
|
1
|
|
|
|
2
|
|
|
|
3
|
|
|
|
1
|
|
|
|
2
|
|
|
|
3
|
|
|
|
(12 rows)
|
|
|
|
|
|
|
|
SELECT * FROM yy;
|
|
|
|
a
|
|
|
|
---
|
|
|
|
1
|
|
|
|
2
|
|
|
|
3
|
|
|
|
1
|
|
|
|
2
|
|
|
|
3
|
|
|
|
1
|
|
|
|
2
|
|
|
|
3
|
|
|
|
(9 rows)
|
|
|
|
|
2011-02-26 00:56:23 +01:00
|
|
|
-- triggers
|
|
|
|
TRUNCATE TABLE y;
|
|
|
|
INSERT INTO y SELECT generate_series(1, 10);
|
|
|
|
CREATE FUNCTION y_trigger() RETURNS trigger AS $$
|
|
|
|
begin
|
|
|
|
raise notice 'y_trigger: a = %', new.a;
|
|
|
|
return new;
|
|
|
|
end;
|
|
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
CREATE TRIGGER y_trig BEFORE INSERT ON y FOR EACH ROW
|
|
|
|
EXECUTE PROCEDURE y_trigger();
|
|
|
|
WITH t AS (
|
|
|
|
INSERT INTO y
|
|
|
|
VALUES
|
|
|
|
(21),
|
|
|
|
(22),
|
|
|
|
(23)
|
|
|
|
RETURNING *
|
|
|
|
)
|
|
|
|
SELECT * FROM t;
|
|
|
|
NOTICE: y_trigger: a = 21
|
|
|
|
NOTICE: y_trigger: a = 22
|
|
|
|
NOTICE: y_trigger: a = 23
|
|
|
|
a
|
|
|
|
----
|
|
|
|
21
|
|
|
|
22
|
|
|
|
23
|
|
|
|
(3 rows)
|
|
|
|
|
|
|
|
SELECT * FROM y;
|
|
|
|
a
|
|
|
|
----
|
|
|
|
1
|
|
|
|
2
|
|
|
|
3
|
|
|
|
4
|
|
|
|
5
|
|
|
|
6
|
|
|
|
7
|
|
|
|
8
|
|
|
|
9
|
|
|
|
10
|
|
|
|
21
|
|
|
|
22
|
|
|
|
23
|
|
|
|
(13 rows)
|
|
|
|
|
|
|
|
DROP TRIGGER y_trig ON y;
|
|
|
|
CREATE TRIGGER y_trig AFTER INSERT ON y FOR EACH ROW
|
|
|
|
EXECUTE PROCEDURE y_trigger();
|
|
|
|
WITH t AS (
|
|
|
|
INSERT INTO y
|
|
|
|
VALUES
|
|
|
|
(31),
|
|
|
|
(32),
|
|
|
|
(33)
|
|
|
|
RETURNING *
|
|
|
|
)
|
2011-02-27 19:43:29 +01:00
|
|
|
SELECT * FROM t LIMIT 1;
|
2011-02-26 00:56:23 +01:00
|
|
|
NOTICE: y_trigger: a = 31
|
|
|
|
NOTICE: y_trigger: a = 32
|
|
|
|
NOTICE: y_trigger: a = 33
|
|
|
|
a
|
|
|
|
----
|
|
|
|
31
|
2011-02-27 19:43:29 +01:00
|
|
|
(1 row)
|
2011-02-26 00:56:23 +01:00
|
|
|
|
|
|
|
SELECT * FROM y;
|
|
|
|
a
|
|
|
|
----
|
|
|
|
1
|
|
|
|
2
|
|
|
|
3
|
|
|
|
4
|
|
|
|
5
|
|
|
|
6
|
|
|
|
7
|
|
|
|
8
|
|
|
|
9
|
|
|
|
10
|
|
|
|
21
|
|
|
|
22
|
|
|
|
23
|
|
|
|
31
|
|
|
|
32
|
|
|
|
33
|
|
|
|
(16 rows)
|
|
|
|
|
|
|
|
DROP TRIGGER y_trig ON y;
|
|
|
|
CREATE OR REPLACE FUNCTION y_trigger() RETURNS trigger AS $$
|
|
|
|
begin
|
|
|
|
raise notice 'y_trigger';
|
|
|
|
return null;
|
|
|
|
end;
|
|
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
CREATE TRIGGER y_trig AFTER INSERT ON y FOR EACH STATEMENT
|
|
|
|
EXECUTE PROCEDURE y_trigger();
|
|
|
|
WITH t AS (
|
|
|
|
INSERT INTO y
|
|
|
|
VALUES
|
|
|
|
(41),
|
|
|
|
(42),
|
|
|
|
(43)
|
|
|
|
RETURNING *
|
|
|
|
)
|
|
|
|
SELECT * FROM t;
|
|
|
|
NOTICE: y_trigger
|
|
|
|
a
|
|
|
|
----
|
|
|
|
41
|
|
|
|
42
|
|
|
|
43
|
|
|
|
(3 rows)
|
|
|
|
|
|
|
|
SELECT * FROM y;
|
|
|
|
a
|
|
|
|
----
|
|
|
|
1
|
|
|
|
2
|
|
|
|
3
|
|
|
|
4
|
|
|
|
5
|
|
|
|
6
|
|
|
|
7
|
|
|
|
8
|
|
|
|
9
|
|
|
|
10
|
|
|
|
21
|
|
|
|
22
|
|
|
|
23
|
|
|
|
31
|
|
|
|
32
|
|
|
|
33
|
|
|
|
41
|
|
|
|
42
|
|
|
|
43
|
|
|
|
(19 rows)
|
|
|
|
|
|
|
|
DROP TRIGGER y_trig ON y;
|
|
|
|
DROP FUNCTION y_trigger();
|
2012-01-29 02:24:42 +01:00
|
|
|
-- WITH attached to inherited UPDATE or DELETE
|
|
|
|
CREATE TEMP TABLE parent ( id int, val text );
|
|
|
|
CREATE TEMP TABLE child1 ( ) INHERITS ( parent );
|
|
|
|
CREATE TEMP TABLE child2 ( ) INHERITS ( parent );
|
|
|
|
INSERT INTO parent VALUES ( 1, 'p1' );
|
|
|
|
INSERT INTO child1 VALUES ( 11, 'c11' ),( 12, 'c12' );
|
|
|
|
INSERT INTO child2 VALUES ( 23, 'c21' ),( 24, 'c22' );
|
|
|
|
WITH rcte AS ( SELECT sum(id) AS totalid FROM parent )
|
|
|
|
UPDATE parent SET id = id + totalid FROM rcte;
|
|
|
|
SELECT * FROM parent;
|
|
|
|
id | val
|
|
|
|
----+-----
|
|
|
|
72 | p1
|
|
|
|
82 | c11
|
|
|
|
83 | c12
|
|
|
|
94 | c21
|
|
|
|
95 | c22
|
|
|
|
(5 rows)
|
|
|
|
|
|
|
|
WITH wcte AS ( INSERT INTO child1 VALUES ( 42, 'new' ) RETURNING id AS newid )
|
|
|
|
UPDATE parent SET id = id + newid FROM wcte;
|
|
|
|
SELECT * FROM parent;
|
|
|
|
id | val
|
|
|
|
-----+-----
|
|
|
|
114 | p1
|
|
|
|
42 | new
|
|
|
|
124 | c11
|
|
|
|
125 | c12
|
|
|
|
136 | c21
|
|
|
|
137 | c22
|
|
|
|
(6 rows)
|
|
|
|
|
|
|
|
WITH rcte AS ( SELECT max(id) AS maxid FROM parent )
|
|
|
|
DELETE FROM parent USING rcte WHERE id = maxid;
|
|
|
|
SELECT * FROM parent;
|
|
|
|
id | val
|
|
|
|
-----+-----
|
|
|
|
114 | p1
|
|
|
|
42 | new
|
|
|
|
124 | c11
|
|
|
|
125 | c12
|
|
|
|
136 | c21
|
|
|
|
(5 rows)
|
|
|
|
|
|
|
|
WITH wcte AS ( INSERT INTO child2 VALUES ( 42, 'new2' ) RETURNING id AS newid )
|
|
|
|
DELETE FROM parent USING wcte WHERE id = newid;
|
|
|
|
SELECT * FROM parent;
|
|
|
|
id | val
|
|
|
|
-----+------
|
|
|
|
114 | p1
|
|
|
|
124 | c11
|
|
|
|
125 | c12
|
|
|
|
136 | c21
|
|
|
|
42 | new2
|
|
|
|
(5 rows)
|
|
|
|
|
Fix planner's handling of RETURNING lists in writable CTEs.
setrefs.c failed to do "rtoffset" adjustment of Vars in RETURNING lists,
which meant they were left with the wrong varnos when the RETURNING list
was in a subquery. That was never possible before writable CTEs, of
course, but now it's broken. The executor fails to notice any problem
because ExecEvalVar just references the ecxt_scantuple for any normal
varno; but EXPLAIN breaks when the varno is wrong, as illustrated in a
recent complaint from Bartosz Dmytrak.
Since the eventual rtoffset of the subquery is not known at the time
we are preparing its plan node, the previous scheme of executing
set_returning_clause_references() at that time cannot handle this
adjustment. Fortunately, it turns out that we don't really need to do it
that way, because all the needed information is available during normal
setrefs.c execution; we just have to dig it out of the ModifyTable node.
So, do that, and get rid of the kluge of early setrefs processing of
RETURNING lists. (This is a little bit of a cheat in the case of inherited
UPDATE/DELETE, because we are not passing a "root" struct that corresponds
exactly to what the subplan was built with. But that doesn't matter, and
anyway this is less ugly than early setrefs processing was.)
Back-patch to 9.1, where the problem became possible to hit.
2012-04-26 02:20:33 +02:00
|
|
|
-- check EXPLAIN VERBOSE for a wCTE with RETURNING
|
|
|
|
EXPLAIN (VERBOSE, COSTS OFF)
|
|
|
|
WITH wcte AS ( INSERT INTO int8_tbl VALUES ( 42, 47 ) RETURNING q2 )
|
2022-02-08 21:30:38 +01:00
|
|
|
DELETE FROM a_star USING wcte WHERE aa = q2;
|
|
|
|
QUERY PLAN
|
|
|
|
---------------------------------------------------------------------------
|
|
|
|
Delete on public.a_star
|
|
|
|
Delete on public.a_star a_star_1
|
|
|
|
Delete on public.b_star a_star_2
|
|
|
|
Delete on public.c_star a_star_3
|
|
|
|
Delete on public.d_star a_star_4
|
|
|
|
Delete on public.e_star a_star_5
|
|
|
|
Delete on public.f_star a_star_6
|
Fix planner's handling of RETURNING lists in writable CTEs.
setrefs.c failed to do "rtoffset" adjustment of Vars in RETURNING lists,
which meant they were left with the wrong varnos when the RETURNING list
was in a subquery. That was never possible before writable CTEs, of
course, but now it's broken. The executor fails to notice any problem
because ExecEvalVar just references the ecxt_scantuple for any normal
varno; but EXPLAIN breaks when the varno is wrong, as illustrated in a
recent complaint from Bartosz Dmytrak.
Since the eventual rtoffset of the subquery is not known at the time
we are preparing its plan node, the previous scheme of executing
set_returning_clause_references() at that time cannot handle this
adjustment. Fortunately, it turns out that we don't really need to do it
that way, because all the needed information is available during normal
setrefs.c execution; we just have to dig it out of the ModifyTable node.
So, do that, and get rid of the kluge of early setrefs processing of
RETURNING lists. (This is a little bit of a cheat in the case of inherited
UPDATE/DELETE, because we are not passing a "root" struct that corresponds
exactly to what the subplan was built with. But that doesn't matter, and
anyway this is less ugly than early setrefs processing was.)
Back-patch to 9.1, where the problem became possible to hit.
2012-04-26 02:20:33 +02:00
|
|
|
CTE wcte
|
|
|
|
-> Insert on public.int8_tbl
|
|
|
|
Output: int8_tbl.q2
|
|
|
|
-> Result
|
2015-03-30 20:59:49 +02:00
|
|
|
Output: '42'::bigint, '47'::bigint
|
Rework planning and execution of UPDATE and DELETE.
This patch makes two closely related sets of changes:
1. For UPDATE, the subplan of the ModifyTable node now only delivers
the new values of the changed columns (i.e., the expressions computed
in the query's SET clause) plus row identity information such as CTID.
ModifyTable must re-fetch the original tuple to merge in the old
values of any unchanged columns. The core advantage of this is that
the changed columns are uniform across all tables of an inherited or
partitioned target relation, whereas the other columns might not be.
A secondary advantage, when the UPDATE involves joins, is that less
data needs to pass through the plan tree. The disadvantage of course
is an extra fetch of each tuple to be updated. However, that seems to
be very nearly free in context; even worst-case tests don't show it to
add more than a couple percent to the total query cost. At some point
it might be interesting to combine the re-fetch with the tuple access
that ModifyTable must do anyway to mark the old tuple dead; but that
would require a good deal of refactoring and it seems it wouldn't buy
all that much, so this patch doesn't attempt it.
2. For inherited UPDATE/DELETE, instead of generating a separate
subplan for each target relation, we now generate a single subplan
that is just exactly like a SELECT's plan, then stick ModifyTable
on top of that. To let ModifyTable know which target relation a
given incoming row refers to, a tableoid junk column is added to
the row identity information. This gets rid of the horrid hack
that was inheritance_planner(), eliminating O(N^2) planning cost
and memory consumption in cases where there were many unprunable
target relations.
Point 2 of course requires point 1, so that there is a uniform
definition of the non-junk columns to be returned by the subplan.
We can't insist on uniform definition of the row identity junk
columns however, if we want to keep the ability to have both
plain and foreign tables in a partitioning hierarchy. Since
it wouldn't scale very far to have every child table have its
own row identity column, this patch includes provisions to merge
similar row identity columns into one column of the subplan result.
In particular, we can merge the whole-row Vars typically used as
row identity by FDWs into one column by pretending they are type
RECORD. (It's still okay for the actual composite Datums to be
labeled with the table's rowtype OID, though.)
There is more that can be done to file down residual inefficiencies
in this patch, but it seems to be committable now.
FDW authors should note several API changes:
* The argument list for AddForeignUpdateTargets() has changed, and so
has the method it must use for adding junk columns to the query. Call
add_row_identity_var() instead of manipulating the parse tree directly.
You might want to reconsider exactly what you're adding, too.
* PlanDirectModify() must now work a little harder to find the
ForeignScan plan node; if the foreign table is part of a partitioning
hierarchy then the ForeignScan might not be the direct child of
ModifyTable. See postgres_fdw for sample code.
* To check whether a relation is a target relation, it's no
longer sufficient to compare its relid to root->parse->resultRelation.
Instead, check it against all_result_relids or leaf_result_relids,
as appropriate.
Amit Langote and Tom Lane
Discussion: https://postgr.es/m/CA+HiwqHpHdqdDn48yCEhynnniahH78rwcrv1rEX65-fsZGBOLQ@mail.gmail.com
2021-03-31 17:52:34 +02:00
|
|
|
-> Hash Join
|
2022-02-08 21:30:38 +01:00
|
|
|
Output: wcte.*, a_star.tableoid, a_star.ctid
|
|
|
|
Hash Cond: (a_star.aa = wcte.q2)
|
Rework planning and execution of UPDATE and DELETE.
This patch makes two closely related sets of changes:
1. For UPDATE, the subplan of the ModifyTable node now only delivers
the new values of the changed columns (i.e., the expressions computed
in the query's SET clause) plus row identity information such as CTID.
ModifyTable must re-fetch the original tuple to merge in the old
values of any unchanged columns. The core advantage of this is that
the changed columns are uniform across all tables of an inherited or
partitioned target relation, whereas the other columns might not be.
A secondary advantage, when the UPDATE involves joins, is that less
data needs to pass through the plan tree. The disadvantage of course
is an extra fetch of each tuple to be updated. However, that seems to
be very nearly free in context; even worst-case tests don't show it to
add more than a couple percent to the total query cost. At some point
it might be interesting to combine the re-fetch with the tuple access
that ModifyTable must do anyway to mark the old tuple dead; but that
would require a good deal of refactoring and it seems it wouldn't buy
all that much, so this patch doesn't attempt it.
2. For inherited UPDATE/DELETE, instead of generating a separate
subplan for each target relation, we now generate a single subplan
that is just exactly like a SELECT's plan, then stick ModifyTable
on top of that. To let ModifyTable know which target relation a
given incoming row refers to, a tableoid junk column is added to
the row identity information. This gets rid of the horrid hack
that was inheritance_planner(), eliminating O(N^2) planning cost
and memory consumption in cases where there were many unprunable
target relations.
Point 2 of course requires point 1, so that there is a uniform
definition of the non-junk columns to be returned by the subplan.
We can't insist on uniform definition of the row identity junk
columns however, if we want to keep the ability to have both
plain and foreign tables in a partitioning hierarchy. Since
it wouldn't scale very far to have every child table have its
own row identity column, this patch includes provisions to merge
similar row identity columns into one column of the subplan result.
In particular, we can merge the whole-row Vars typically used as
row identity by FDWs into one column by pretending they are type
RECORD. (It's still okay for the actual composite Datums to be
labeled with the table's rowtype OID, though.)
There is more that can be done to file down residual inefficiencies
in this patch, but it seems to be committable now.
FDW authors should note several API changes:
* The argument list for AddForeignUpdateTargets() has changed, and so
has the method it must use for adding junk columns to the query. Call
add_row_identity_var() instead of manipulating the parse tree directly.
You might want to reconsider exactly what you're adding, too.
* PlanDirectModify() must now work a little harder to find the
ForeignScan plan node; if the foreign table is part of a partitioning
hierarchy then the ForeignScan might not be the direct child of
ModifyTable. See postgres_fdw for sample code.
* To check whether a relation is a target relation, it's no
longer sufficient to compare its relid to root->parse->resultRelation.
Instead, check it against all_result_relids or leaf_result_relids,
as appropriate.
Amit Langote and Tom Lane
Discussion: https://postgr.es/m/CA+HiwqHpHdqdDn48yCEhynnniahH78rwcrv1rEX65-fsZGBOLQ@mail.gmail.com
2021-03-31 17:52:34 +02:00
|
|
|
-> Append
|
2022-02-08 21:30:38 +01:00
|
|
|
-> Seq Scan on public.a_star a_star_1
|
|
|
|
Output: a_star_1.aa, a_star_1.tableoid, a_star_1.ctid
|
|
|
|
-> Seq Scan on public.b_star a_star_2
|
|
|
|
Output: a_star_2.aa, a_star_2.tableoid, a_star_2.ctid
|
|
|
|
-> Seq Scan on public.c_star a_star_3
|
|
|
|
Output: a_star_3.aa, a_star_3.tableoid, a_star_3.ctid
|
|
|
|
-> Seq Scan on public.d_star a_star_4
|
|
|
|
Output: a_star_4.aa, a_star_4.tableoid, a_star_4.ctid
|
|
|
|
-> Seq Scan on public.e_star a_star_5
|
|
|
|
Output: a_star_5.aa, a_star_5.tableoid, a_star_5.ctid
|
|
|
|
-> Seq Scan on public.f_star a_star_6
|
|
|
|
Output: a_star_6.aa, a_star_6.tableoid, a_star_6.ctid
|
Rework planning and execution of UPDATE and DELETE.
This patch makes two closely related sets of changes:
1. For UPDATE, the subplan of the ModifyTable node now only delivers
the new values of the changed columns (i.e., the expressions computed
in the query's SET clause) plus row identity information such as CTID.
ModifyTable must re-fetch the original tuple to merge in the old
values of any unchanged columns. The core advantage of this is that
the changed columns are uniform across all tables of an inherited or
partitioned target relation, whereas the other columns might not be.
A secondary advantage, when the UPDATE involves joins, is that less
data needs to pass through the plan tree. The disadvantage of course
is an extra fetch of each tuple to be updated. However, that seems to
be very nearly free in context; even worst-case tests don't show it to
add more than a couple percent to the total query cost. At some point
it might be interesting to combine the re-fetch with the tuple access
that ModifyTable must do anyway to mark the old tuple dead; but that
would require a good deal of refactoring and it seems it wouldn't buy
all that much, so this patch doesn't attempt it.
2. For inherited UPDATE/DELETE, instead of generating a separate
subplan for each target relation, we now generate a single subplan
that is just exactly like a SELECT's plan, then stick ModifyTable
on top of that. To let ModifyTable know which target relation a
given incoming row refers to, a tableoid junk column is added to
the row identity information. This gets rid of the horrid hack
that was inheritance_planner(), eliminating O(N^2) planning cost
and memory consumption in cases where there were many unprunable
target relations.
Point 2 of course requires point 1, so that there is a uniform
definition of the non-junk columns to be returned by the subplan.
We can't insist on uniform definition of the row identity junk
columns however, if we want to keep the ability to have both
plain and foreign tables in a partitioning hierarchy. Since
it wouldn't scale very far to have every child table have its
own row identity column, this patch includes provisions to merge
similar row identity columns into one column of the subplan result.
In particular, we can merge the whole-row Vars typically used as
row identity by FDWs into one column by pretending they are type
RECORD. (It's still okay for the actual composite Datums to be
labeled with the table's rowtype OID, though.)
There is more that can be done to file down residual inefficiencies
in this patch, but it seems to be committable now.
FDW authors should note several API changes:
* The argument list for AddForeignUpdateTargets() has changed, and so
has the method it must use for adding junk columns to the query. Call
add_row_identity_var() instead of manipulating the parse tree directly.
You might want to reconsider exactly what you're adding, too.
* PlanDirectModify() must now work a little harder to find the
ForeignScan plan node; if the foreign table is part of a partitioning
hierarchy then the ForeignScan might not be the direct child of
ModifyTable. See postgres_fdw for sample code.
* To check whether a relation is a target relation, it's no
longer sufficient to compare its relid to root->parse->resultRelation.
Instead, check it against all_result_relids or leaf_result_relids,
as appropriate.
Amit Langote and Tom Lane
Discussion: https://postgr.es/m/CA+HiwqHpHdqdDn48yCEhynnniahH78rwcrv1rEX65-fsZGBOLQ@mail.gmail.com
2021-03-31 17:52:34 +02:00
|
|
|
-> Hash
|
Fix planner's handling of RETURNING lists in writable CTEs.
setrefs.c failed to do "rtoffset" adjustment of Vars in RETURNING lists,
which meant they were left with the wrong varnos when the RETURNING list
was in a subquery. That was never possible before writable CTEs, of
course, but now it's broken. The executor fails to notice any problem
because ExecEvalVar just references the ecxt_scantuple for any normal
varno; but EXPLAIN breaks when the varno is wrong, as illustrated in a
recent complaint from Bartosz Dmytrak.
Since the eventual rtoffset of the subquery is not known at the time
we are preparing its plan node, the previous scheme of executing
set_returning_clause_references() at that time cannot handle this
adjustment. Fortunately, it turns out that we don't really need to do it
that way, because all the needed information is available during normal
setrefs.c execution; we just have to dig it out of the ModifyTable node.
So, do that, and get rid of the kluge of early setrefs processing of
RETURNING lists. (This is a little bit of a cheat in the case of inherited
UPDATE/DELETE, because we are not passing a "root" struct that corresponds
exactly to what the subplan was built with. But that doesn't matter, and
anyway this is less ugly than early setrefs processing was.)
Back-patch to 9.1, where the problem became possible to hit.
2012-04-26 02:20:33 +02:00
|
|
|
Output: wcte.*, wcte.q2
|
Rework planning and execution of UPDATE and DELETE.
This patch makes two closely related sets of changes:
1. For UPDATE, the subplan of the ModifyTable node now only delivers
the new values of the changed columns (i.e., the expressions computed
in the query's SET clause) plus row identity information such as CTID.
ModifyTable must re-fetch the original tuple to merge in the old
values of any unchanged columns. The core advantage of this is that
the changed columns are uniform across all tables of an inherited or
partitioned target relation, whereas the other columns might not be.
A secondary advantage, when the UPDATE involves joins, is that less
data needs to pass through the plan tree. The disadvantage of course
is an extra fetch of each tuple to be updated. However, that seems to
be very nearly free in context; even worst-case tests don't show it to
add more than a couple percent to the total query cost. At some point
it might be interesting to combine the re-fetch with the tuple access
that ModifyTable must do anyway to mark the old tuple dead; but that
would require a good deal of refactoring and it seems it wouldn't buy
all that much, so this patch doesn't attempt it.
2. For inherited UPDATE/DELETE, instead of generating a separate
subplan for each target relation, we now generate a single subplan
that is just exactly like a SELECT's plan, then stick ModifyTable
on top of that. To let ModifyTable know which target relation a
given incoming row refers to, a tableoid junk column is added to
the row identity information. This gets rid of the horrid hack
that was inheritance_planner(), eliminating O(N^2) planning cost
and memory consumption in cases where there were many unprunable
target relations.
Point 2 of course requires point 1, so that there is a uniform
definition of the non-junk columns to be returned by the subplan.
We can't insist on uniform definition of the row identity junk
columns however, if we want to keep the ability to have both
plain and foreign tables in a partitioning hierarchy. Since
it wouldn't scale very far to have every child table have its
own row identity column, this patch includes provisions to merge
similar row identity columns into one column of the subplan result.
In particular, we can merge the whole-row Vars typically used as
row identity by FDWs into one column by pretending they are type
RECORD. (It's still okay for the actual composite Datums to be
labeled with the table's rowtype OID, though.)
There is more that can be done to file down residual inefficiencies
in this patch, but it seems to be committable now.
FDW authors should note several API changes:
* The argument list for AddForeignUpdateTargets() has changed, and so
has the method it must use for adding junk columns to the query. Call
add_row_identity_var() instead of manipulating the parse tree directly.
You might want to reconsider exactly what you're adding, too.
* PlanDirectModify() must now work a little harder to find the
ForeignScan plan node; if the foreign table is part of a partitioning
hierarchy then the ForeignScan might not be the direct child of
ModifyTable. See postgres_fdw for sample code.
* To check whether a relation is a target relation, it's no
longer sufficient to compare its relid to root->parse->resultRelation.
Instead, check it against all_result_relids or leaf_result_relids,
as appropriate.
Amit Langote and Tom Lane
Discussion: https://postgr.es/m/CA+HiwqHpHdqdDn48yCEhynnniahH78rwcrv1rEX65-fsZGBOLQ@mail.gmail.com
2021-03-31 17:52:34 +02:00
|
|
|
-> CTE Scan on wcte
|
|
|
|
Output: wcte.*, wcte.q2
|
2022-02-08 21:30:38 +01:00
|
|
|
(32 rows)
|
Fix planner's handling of RETURNING lists in writable CTEs.
setrefs.c failed to do "rtoffset" adjustment of Vars in RETURNING lists,
which meant they were left with the wrong varnos when the RETURNING list
was in a subquery. That was never possible before writable CTEs, of
course, but now it's broken. The executor fails to notice any problem
because ExecEvalVar just references the ecxt_scantuple for any normal
varno; but EXPLAIN breaks when the varno is wrong, as illustrated in a
recent complaint from Bartosz Dmytrak.
Since the eventual rtoffset of the subquery is not known at the time
we are preparing its plan node, the previous scheme of executing
set_returning_clause_references() at that time cannot handle this
adjustment. Fortunately, it turns out that we don't really need to do it
that way, because all the needed information is available during normal
setrefs.c execution; we just have to dig it out of the ModifyTable node.
So, do that, and get rid of the kluge of early setrefs processing of
RETURNING lists. (This is a little bit of a cheat in the case of inherited
UPDATE/DELETE, because we are not passing a "root" struct that corresponds
exactly to what the subplan was built with. But that doesn't matter, and
anyway this is less ugly than early setrefs processing was.)
Back-patch to 9.1, where the problem became possible to hit.
2012-04-26 02:20:33 +02:00
|
|
|
|
2011-02-26 00:56:23 +01:00
|
|
|
-- error cases
|
|
|
|
-- data-modifying WITH tries to use its own output
|
|
|
|
WITH RECURSIVE t AS (
|
|
|
|
INSERT INTO y
|
|
|
|
SELECT * FROM t
|
|
|
|
)
|
|
|
|
VALUES(FALSE);
|
|
|
|
ERROR: recursive query "t" must not contain data-modifying statements
|
|
|
|
LINE 1: WITH RECURSIVE t AS (
|
|
|
|
^
|
|
|
|
-- no RETURNING in a referenced data-modifying WITH
|
|
|
|
WITH t AS (
|
|
|
|
INSERT INTO y VALUES(0)
|
|
|
|
)
|
|
|
|
SELECT * FROM t;
|
|
|
|
ERROR: WITH query "t" does not have a RETURNING clause
|
|
|
|
LINE 4: SELECT * FROM t;
|
|
|
|
^
|
Add RETURNING support to MERGE.
This allows a RETURNING clause to be appended to a MERGE query, to
return values based on each row inserted, updated, or deleted. As with
plain INSERT, UPDATE, and DELETE commands, the returned values are
based on the new contents of the target table for INSERT and UPDATE
actions, and on its old contents for DELETE actions. Values from the
source relation may also be returned.
As with INSERT/UPDATE/DELETE, the output of MERGE ... RETURNING may be
used as the source relation for other operations such as WITH queries
and COPY commands.
Additionally, a special function merge_action() is provided, which
returns 'INSERT', 'UPDATE', or 'DELETE', depending on the action
executed for each row. The merge_action() function can be used
anywhere in the RETURNING list, including in arbitrary expressions and
subqueries, but it is an error to use it anywhere outside of a MERGE
query's RETURNING list.
Dean Rasheed, reviewed by Isaac Morland, Vik Fearing, Alvaro Herrera,
Gurjeet Singh, Jian He, Jeff Davis, Merlin Moncure, Peter Eisentraut,
and Wolfgang Walther.
Discussion: http://postgr.es/m/CAEZATCWePEGQR5LBn-vD6SfeLZafzEm2Qy_L_Oky2=qw2w3Pzg@mail.gmail.com
2024-03-17 14:58:59 +01:00
|
|
|
-- RETURNING tries to return its own output
|
|
|
|
WITH RECURSIVE t(action, a) AS (
|
|
|
|
MERGE INTO y USING (VALUES (11)) v(a) ON y.a = v.a
|
|
|
|
WHEN NOT MATCHED THEN INSERT VALUES (v.a)
|
|
|
|
RETURNING merge_action(), (SELECT a FROM t)
|
|
|
|
)
|
|
|
|
SELECT * FROM t;
|
|
|
|
ERROR: recursive query "t" must not contain data-modifying statements
|
|
|
|
LINE 1: WITH RECURSIVE t(action, a) AS (
|
|
|
|
^
|
2011-02-26 00:56:23 +01:00
|
|
|
-- data-modifying WITH allowed only at the top level
|
|
|
|
SELECT * FROM (
|
|
|
|
WITH t AS (UPDATE y SET a=a+1 RETURNING *)
|
|
|
|
SELECT * FROM t
|
|
|
|
) ss;
|
|
|
|
ERROR: WITH clause containing a data-modifying statement must be at the top level
|
|
|
|
LINE 2: WITH t AS (UPDATE y SET a=a+1 RETURNING *)
|
|
|
|
^
|
|
|
|
-- most variants of rules aren't allowed
|
|
|
|
CREATE RULE y_rule AS ON INSERT TO y WHERE a=0 DO INSTEAD DELETE FROM y;
|
|
|
|
WITH t AS (
|
|
|
|
INSERT INTO y VALUES(0)
|
|
|
|
)
|
|
|
|
VALUES(FALSE);
|
|
|
|
ERROR: conditional DO INSTEAD rules are not supported for data-modifying statements in WITH
|
2021-07-09 17:02:26 +02:00
|
|
|
CREATE OR REPLACE RULE y_rule AS ON INSERT TO y DO INSTEAD NOTHING;
|
|
|
|
WITH t AS (
|
|
|
|
INSERT INTO y VALUES(0)
|
|
|
|
)
|
|
|
|
VALUES(FALSE);
|
|
|
|
ERROR: DO INSTEAD NOTHING rules are not supported for data-modifying statements in WITH
|
|
|
|
CREATE OR REPLACE RULE y_rule AS ON INSERT TO y DO INSTEAD NOTIFY foo;
|
|
|
|
WITH t AS (
|
|
|
|
INSERT INTO y VALUES(0)
|
|
|
|
)
|
|
|
|
VALUES(FALSE);
|
|
|
|
ERROR: DO INSTEAD NOTIFY rules are not supported for data-modifying statements in WITH
|
|
|
|
CREATE OR REPLACE RULE y_rule AS ON INSERT TO y DO ALSO NOTIFY foo;
|
|
|
|
WITH t AS (
|
|
|
|
INSERT INTO y VALUES(0)
|
|
|
|
)
|
|
|
|
VALUES(FALSE);
|
|
|
|
ERROR: DO ALSO rules are not supported for data-modifying statements in WITH
|
|
|
|
CREATE OR REPLACE RULE y_rule AS ON INSERT TO y
|
|
|
|
DO INSTEAD (NOTIFY foo; NOTIFY bar);
|
|
|
|
WITH t AS (
|
|
|
|
INSERT INTO y VALUES(0)
|
|
|
|
)
|
|
|
|
VALUES(FALSE);
|
|
|
|
ERROR: multi-statement DO INSTEAD rules are not supported for data-modifying statements in WITH
|
2011-02-26 00:56:23 +01:00
|
|
|
DROP RULE y_rule ON y;
|
Improve parser's one-extra-token lookahead mechanism.
There are a couple of places in our grammar that fail to be strict LALR(1),
by requiring more than a single token of lookahead to decide what to do.
Up to now we've dealt with that by using a filter between the lexer and
parser that merges adjacent tokens into one in the places where two tokens
of lookahead are necessary. But that creates a number of user-visible
anomalies, for instance that you can't name a CTE "ordinality" because
"WITH ordinality AS ..." triggers folding of WITH and ORDINALITY into one
token. I realized that there's a better way.
In this patch, we still do the lookahead basically as before, but we never
merge the second token into the first; we replace just the first token by
a special lookahead symbol when one of the lookahead pairs is seen.
This requires a couple extra productions in the grammar, but it involves
fewer special tokens, so that the grammar tables come out a bit smaller
than before. The filter logic is no slower than before, perhaps a bit
faster.
I also fixed the filter logic so that when backing up after a lookahead,
the current token's terminator is correctly restored; this eliminates some
weird behavior in error message issuance, as is shown by the one change in
existing regression test outputs.
I believe that this patch entirely eliminates odd behaviors caused by
lookahead for WITH. It doesn't really improve the situation for NULLS
followed by FIRST/LAST unfortunately: those sequences still act like a
reserved word, even though there are cases where they should be seen as two
ordinary identifiers, eg "SELECT nulls first FROM ...". I experimented
with additional grammar hacks but couldn't find any simple solution for
that. Still, this is better than before, and it seems much more likely
that we *could* somehow solve the NULLS case on the basis of this filter
behavior than the previous one.
2015-02-24 23:53:42 +01:00
|
|
|
-- check that parser lookahead for WITH doesn't cause any odd behavior
|
|
|
|
create table foo (with baz); -- fail, WITH is a reserved word
|
|
|
|
ERROR: syntax error at or near "with"
|
|
|
|
LINE 1: create table foo (with baz);
|
|
|
|
^
|
|
|
|
create table foo (with ordinality); -- fail, WITH is a reserved word
|
|
|
|
ERROR: syntax error at or near "with"
|
|
|
|
LINE 1: create table foo (with ordinality);
|
|
|
|
^
|
|
|
|
with ordinality as (select 1 as x) select * from ordinality;
|
|
|
|
x
|
|
|
|
---
|
|
|
|
1
|
|
|
|
(1 row)
|
|
|
|
|
2017-04-01 06:17:18 +02:00
|
|
|
-- check sane response to attempt to modify CTE relation
|
2021-12-30 04:09:53 +01:00
|
|
|
WITH with_test AS (SELECT 42) INSERT INTO with_test VALUES (1);
|
|
|
|
ERROR: relation "with_test" does not exist
|
|
|
|
LINE 1: WITH with_test AS (SELECT 42) INSERT INTO with_test VALUES (...
|
|
|
|
^
|
Fix incorrect handling of CTEs and ENRs as DML target relations.
setTargetTable threw an error if the proposed target RangeVar's relname
matched any visible CTE or ENR. This breaks backwards compatibility in
the CTE case, since pre-v10 we never looked for a CTE here at all, so that
CTE names did not mask regular tables. It does seem like a good idea to
throw an error for the ENR case, though, thus causing ENRs to mask tables
for this purpose; ENRs are new in v10 so we're not breaking existing code,
and we may someday want to allow them to be the targets of DML.
To fix that, replace use of getRTEForSpecialRelationTypes, which was
overkill anyway, with use of scanNameSpaceForENR.
A second problem was that the check neglected to verify null schemaname,
so that a CTE or ENR could incorrectly be thought to match a qualified
RangeVar. That happened because getRTEForSpecialRelationTypes relied
on its caller to have checked for null schemaname. Even though the one
remaining caller got it right, this is obviously bug-prone, so move
the check inside getRTEForSpecialRelationTypes.
Also, revert commit 18ce3a4ab's extremely poorly thought out decision to
add a NULL return case to parserOpenTable --- without either documenting
that or adjusting any of the callers to check for it. The current bug
seems to have arisen in part due to working around that bad idea.
In passing, remove the one-line shim functions transformCTEReference and
transformENRReference --- they don't seem to be adding any clarity or
functionality.
Per report from Hugo Mercier (via Julien Rouhaud). Back-patch to v10
where the bug was introduced.
Thomas Munro, with minor editing by me
Discussion: https://postgr.es/m/CAOBaU_YdPVH+PTtiKSSLOiiW3mVDYsnNUekK+XPbHXiP=wrFLA@mail.gmail.com
2017-10-16 23:56:42 +02:00
|
|
|
-- check response to attempt to modify table with same name as a CTE (perhaps
|
|
|
|
-- surprisingly it works, because CTEs don't hide tables from data-modifying
|
|
|
|
-- statements)
|
2021-12-30 04:09:53 +01:00
|
|
|
create temp table with_test (i int);
|
|
|
|
with with_test as (select 42) insert into with_test select * from with_test;
|
|
|
|
select * from with_test;
|
Fix incorrect handling of CTEs and ENRs as DML target relations.
setTargetTable threw an error if the proposed target RangeVar's relname
matched any visible CTE or ENR. This breaks backwards compatibility in
the CTE case, since pre-v10 we never looked for a CTE here at all, so that
CTE names did not mask regular tables. It does seem like a good idea to
throw an error for the ENR case, though, thus causing ENRs to mask tables
for this purpose; ENRs are new in v10 so we're not breaking existing code,
and we may someday want to allow them to be the targets of DML.
To fix that, replace use of getRTEForSpecialRelationTypes, which was
overkill anyway, with use of scanNameSpaceForENR.
A second problem was that the check neglected to verify null schemaname,
so that a CTE or ENR could incorrectly be thought to match a qualified
RangeVar. That happened because getRTEForSpecialRelationTypes relied
on its caller to have checked for null schemaname. Even though the one
remaining caller got it right, this is obviously bug-prone, so move
the check inside getRTEForSpecialRelationTypes.
Also, revert commit 18ce3a4ab's extremely poorly thought out decision to
add a NULL return case to parserOpenTable --- without either documenting
that or adjusting any of the callers to check for it. The current bug
seems to have arisen in part due to working around that bad idea.
In passing, remove the one-line shim functions transformCTEReference and
transformENRReference --- they don't seem to be adding any clarity or
functionality.
Per report from Hugo Mercier (via Julien Rouhaud). Back-patch to v10
where the bug was introduced.
Thomas Munro, with minor editing by me
Discussion: https://postgr.es/m/CAOBaU_YdPVH+PTtiKSSLOiiW3mVDYsnNUekK+XPbHXiP=wrFLA@mail.gmail.com
2017-10-16 23:56:42 +02:00
|
|
|
i
|
|
|
|
----
|
|
|
|
42
|
|
|
|
(1 row)
|
|
|
|
|
2021-12-30 04:09:53 +01:00
|
|
|
drop table with_test;
|