postgresql/src/test/regress/expected/with.out

3529 lines
92 KiB
Plaintext

--
-- 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)
-- UNION DISTINCT requires hashable type
WITH RECURSIVE t(n) AS (
VALUES (1::money)
UNION
SELECT n+1::money FROM t WHERE n < 100::money
)
SELECT sum(n) FROM t;
ERROR: could not implement recursive UNION
DETAIL: All column datatypes must be hashable.
-- 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)
-- This is an infinite loop with UNION ALL, but not with UNION
WITH RECURSIVE t(n) AS (
SELECT 1
UNION
SELECT 10-n FROM t)
SELECT * FROM t;
n
---
1
9
(2 rows)
-- This'd be an infinite loop, but outside query reads only as much as needed
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)
-- UNION case should have same property
WITH RECURSIVE t(n) AS (
SELECT 1
UNION
SELECT n+1 FROM t)
SELECT * FROM t LIMIT 10;
n
----
1
2
3
4
5
6
7
8
9
10
(10 rows)
-- Test behavior with an unknown-type literal in the WITH
WITH q AS (SELECT 'foo' AS x)
SELECT x, pg_typeof(x) FROM q;
x | pg_typeof
-----+-----------
foo | text
(1 row)
WITH RECURSIVE t(n) AS (
SELECT 'foo'
UNION ALL
SELECT n || ' bar' FROM t WHERE length(n) < 20
)
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
(6 rows)
-- 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
)
SELECT n, pg_typeof(n) FROM t;
ERROR: operator does not exist: text + integer
LINE 4: SELECT n+1 FROM t WHERE n < 10
^
HINT: No operator matches the given name and argument types. You might need to add explicit type casts.
-- 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)
--
-- 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
SELECT name as root_name, * FROM department WHERE name = 'A'
UNION ALL
-- recursive term
SELECT sd.root_name, d.* FROM department AS d, subdepartment AS sd
WHERE d.parent_department = sd.id
)
SELECT * FROM subdepartment ORDER BY name;
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
(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);
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)+
) +
SELECT id, +
parent_department, +
name +
FROM subdepartment;
(1 row)
SELECT pg_get_viewdef('vsubdepartment'::regclass, true);
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+
) +
SELECT id, +
parent_department, +
name +
FROM subdepartment;
(1 row)
-- 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
View "public.sums_1_100"
Column | Type | Collation | Nullable | Default | Storage | Description
--------+--------+-----------+----------+---------+---------+-------------
sum | bigint | | | | plain |
View definition:
WITH RECURSIVE t(n) AS (
VALUES (1)
UNION ALL
SELECT t_1.n + 1
FROM t t_1
WHERE t_1.n < 100
)
SELECT sum(n) AS sum
FROM t;
-- 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)
-- 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)
-- 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)
-- 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');
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)
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)
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)
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)
-- 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)
-- 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
-- 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
-- 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 +
SELECT f, +
t, +
label +
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)
--
-- 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');
with recursive search_graph(f, t, label, is_cycle, path) as (
select *, false, array[row(g.f, g.t)] from graph g
union all
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;
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)
-- UNION DISTINCT exercises row type hashing support
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;
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)
-- ordering by the path column has same effect as SEARCH DEPTH FIRST
with recursive search_graph(f, t, label, is_cycle, path) as (
select *, false, array[row(g.f, g.t)] from graph g
union all
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 order by path;
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)"}
(25 rows)
-- CYCLE clause
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)
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;
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)
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)
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)
-- 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
) cycle b set c using p
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
cycle f, t set is_cycle using path
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
cycle f, t set is_cycle using path
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
) cycle foo, tar set is_cycle using path
select * from search_graph;
ERROR: cycle column "foo" not in WITH query column list
LINE 7: ) cycle foo, tar set is_cycle 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 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;
ERROR: search sequence column name and cycle path column name are the same
LINE 7: ) search depth first by f, t set foo
^
-- test ruleutils and view expansion
create temp view v_cycle1 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 using path
select f, t, label from search_graph;
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 +
SELECT f, +
t, +
label +
FROM search_graph;
(1 row)
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+
SELECT f, +
t, +
label +
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;
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)
--
-- 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)
--
-- Test WITH attached to a data-modifying statement
--
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;
--
-- error cases
--
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)
^
-- INTERSECT
WITH RECURSIVE x(n) AS (SELECT 1 INTERSECT SELECT n+1 FROM x)
SELECT * FROM x;
ERROR: recursive query "x" does not have the form non-recursive-term UNION [ALL] recursive-term
LINE 1: WITH RECURSIVE x(n) AS (SELECT 1 INTERSECT SELECT n+1 FROM x...
^
WITH RECURSIVE x(n) AS (SELECT 1 INTERSECT ALL SELECT n+1 FROM x)
SELECT * FROM x;
ERROR: recursive query "x" does not have the form non-recursive-term UNION [ALL] recursive-term
LINE 1: WITH RECURSIVE x(n) AS (SELECT 1 INTERSECT ALL SELECT n+1 FR...
^
-- EXCEPT
WITH RECURSIVE x(n) AS (SELECT 1 EXCEPT SELECT n+1 FROM x)
SELECT * FROM x;
ERROR: recursive query "x" does not have the form non-recursive-term UNION [ALL] recursive-term
LINE 1: WITH RECURSIVE x(n) AS (SELECT 1 EXCEPT SELECT n+1 FROM x)
^
WITH RECURSIVE x(n) AS (SELECT 1 EXCEPT ALL SELECT n+1 FROM x)
SELECT * FROM x;
ERROR: recursive query "x" does not have the form non-recursive-term UNION [ALL] recursive-term
LINE 1: WITH RECURSIVE x(n) AS (SELECT 1 EXCEPT ALL SELECT n+1 FROM ...
^
-- no non-recursive term
WITH RECURSIVE x(n) AS (SELECT n FROM x)
SELECT * FROM x;
ERROR: recursive query "x" does not have the form non-recursive-term UNION [ALL] recursive-term
LINE 1: WITH RECURSIVE x(n) AS (SELECT 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;
ERROR: aggregate functions are not allowed in a recursive query's recursive term
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;
ERROR: aggregate functions are not allowed in a recursive query's recursive term
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.
-- 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
--
-- 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;
foo
-------------
0
123456
-123456
2147483647
-2147483647
(5 rows)
select ( with cte(foo) as ( values(f1) )
values((select foo from cte)) )
from int4_tbl;
column1
-------------
0
123456
-123456
2147483647
-2147483647
(5 rows)
--
-- 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)
--
-- 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)
)
SELECT * FROM outermost ORDER BY 1;
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)
)
SELECT * FROM outermost ORDER BY 1;
ERROR: relation "outermost" does not exist
LINE 4: SELECT * FROM outermost -- fail
^
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)
)
SELECT * FROM outermost ORDER BY 1;
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
)
SELECT * FROM outermost ORDER BY 1;
ERROR: recursive reference to query "outermost" must not appear within a subquery
LINE 2: WITH innermost as (SELECT 2 FROM outermost) -- fail
^
--
-- 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)
--
-- 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)
--
-- 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;
-- 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
VALUES(NEW.i);
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)
-- check INSERT ... SELECT rule actions are disallowed on commands
-- 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;
ERROR: INSERT ... SELECT rule actions are not supported for queries having data-modifying statements in WITH
-- 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;
BEGIN; SET LOCAL debug_parallel_query = on;
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)
-- 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)
-- 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)
-- data-modifying WITH containing INSERT...ON CONFLICT DO UPDATE
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);
WITH t AS (
INSERT INTO withz SELECT i, 'insert'
FROM generate_series(0, 16) i
ON CONFLICT (k) DO UPDATE SET v = withz.v || ', now update'
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 (
INSERT INTO withz VALUES(1, 5) ON CONFLICT (k) DO UPDATE SET v = EXCLUDED.v
WHERE withz.k != EXCLUDED.k
RETURNING *
)
SELECT * FROM aa;
k | v
---+---
(0 rows)
-- New query/snapshot demonstrates side-effects of previous query.
SELECT * FROM withz ORDER BY k;
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)
INSERT INTO withz VALUES(1, 'insert')
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)
INSERT INTO withz VALUES(1, 'insert')
ON CONFLICT (k) DO UPDATE SET v = ' update' WHERE withz.k = (SELECT a FROM aa);
WITH aa AS (SELECT 1 a, 2 b)
INSERT INTO withz VALUES(1, 'insert')
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)
INSERT INTO withz VALUES(1, 'insert')
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)
INSERT INTO withz VALUES(1, (SELECT b || ' insert' FROM aa WHERE a = 1 ))
ON CONFLICT (k) DO UPDATE SET v = (SELECT b || ' update' FROM aa WHERE a = 1 LIMIT 1);
-- 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.
WITH simpletup AS (
SELECT 2 k, 'Green' v),
upsert_cte AS (
INSERT INTO withz VALUES(2, 'Blue') ON CONFLICT (k) DO
UPDATE SET (k, v) = (SELECT k, v FROM simpletup WHERE simpletup.k = withz.k)
RETURNING k, v)
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)
RETURNING k, v;
k | v
---+---
(0 rows)
DROP TABLE withz;
-- 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);
QUERY PLAN
----------------------------------------------------------------
Merge on public.m
CTE cte_basic
-> Result
Output: 1, 'cte_basic val'::text
-> Hash Right Join
Output: m.ctid, (0), ('merge source SubPlan'::text)
Hash Cond: (m.k = (0))
-> Seq Scan on public.m
Output: m.ctid, m.k
-> Hash
Output: (0), ('merge source SubPlan'::text)
-> Result
Output: 0, 'merge source SubPlan'::text
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)
(19 rows)
-- 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);
QUERY PLAN
---------------------------------------------------------------
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
Output: m.ctid, (1), ('merge source InitPlan'::text)
Hash Cond: (m.k = (1))
-> Seq Scan on public.m
Output: m.ctid, m.k
-> Hash
Output: (1), ('merge source InitPlan'::text)
-> Result
Output: 1, 'merge source InitPlan'::text
(19 rows)
-- 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
Output: m.ctid, merge_source_cte.a, merge_source_cte.b
Hash Cond: (m.k = merge_source_cte.a)
-> Seq Scan on public.m
Output: m.ctid, m.k
-> Hash
Output: merge_source_cte.a, merge_source_cte.b
-> CTE Scan on merge_source_cte
Output: merge_source_cte.a, merge_source_cte.b
(20 rows)
DROP TABLE m;
-- 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)
-- 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 *
)
SELECT * FROM t LIMIT 1;
NOTICE: y_trigger: a = 31
NOTICE: y_trigger: a = 32
NOTICE: y_trigger: a = 33
a
----
31
(1 row)
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();
-- 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)
-- check EXPLAIN VERBOSE for a wCTE with RETURNING
EXPLAIN (VERBOSE, COSTS OFF)
WITH wcte AS ( INSERT INTO int8_tbl VALUES ( 42, 47 ) RETURNING q2 )
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
CTE wcte
-> Insert on public.int8_tbl
Output: int8_tbl.q2
-> Result
Output: '42'::bigint, '47'::bigint
-> Hash Join
Output: wcte.*, a_star.tableoid, a_star.ctid
Hash Cond: (a_star.aa = wcte.q2)
-> Append
-> 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
-> Hash
Output: wcte.*, wcte.q2
-> CTE Scan on wcte
Output: wcte.*, wcte.q2
(32 rows)
-- 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;
^
-- 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
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
DROP RULE y_rule ON y;
-- 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)
-- check sane response to attempt to modify CTE relation
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 (...
^
-- 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)
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;
i
----
42
(1 row)
drop table with_test;