I faced a problem while implementing a recursive query on SQL, including double selection from the database at each iteration.
I have two tables, describing the oriented graph, where each nodes located at grid:
table nodes:
id
grid_x
grid_y
table edges:
source
target
And want to implement selection of nodes, which located at adjacent positions on the grid to the right of the initial node with selection of parents and childs at each iteration.
Selection of right nodes realized by following recursive query:
WITH RECURSIVE
cte(id, grid_x, grid_y) AS (
SELECT id, grid_x, grid_y
FROM nodes WHERE id = 0
UNION ALL
SELECT current.id, current.grid_x, current.grid_y
FROM nodes AS current, cte AS prev
WHERE current.grid_x = prev.grid_x + 1
AND current.grid_y = prev.grid_y
)
SELECT * FROM cte;
let the data in the database have the following representation
i.e.
Table nodes:
id | grid_x | grid_y | |
----+--------+--------+---+--
0 | 0 | 0 | |
13 | 1 | 0 | |
14 | 3 | 0 | |
5 | -1 | 1 | |
1 | 0 | 1 | |
2 | 1 | 1 | |
3 | 2 | 1 | |
4 | 4 | 1 | |
12 | -2 | 2 | |
6 | -1 | 2 | |
7 | 0 | 2 | |
8 | 2 | 2 | |
9 | 3 | 2 | |
10 | 4 | 2 | |
11 | 6 | 2 | |
Table edges:
id | source | target
----+--------+--------
1 | 0 | 5
2 | 0 | 1
3 | 1 | 7
4 | 5 | 6
5 | 5 | 12
6 | 13 | 2
7 | 13 | 3
8 | 2 | 8
9 | 3 | 8
10 | 3 | 9
11 | 14 | 3
12 | 14 | 4
13 | 4 | 10
14 | 4 | 11
query above will select nodes 0 and 13
I want to select at each iteration not only the nodes located on the right, but also the child and parent nodes, if they are not already in the cte.
For example, a query that should also select child nodes should start from node 0. Then at firts iteration select nodes 13, 5 and 1, at second iteration select nodes 2, 3, 12, 6, 7, at third iteration - nodes 8, 9 and at fourth iteration - node 10.
The query, which select at each iteration parents and childs, which not already in cte with neighbor right nodes at grid should select all nodes from picture at finish.
I tried to write the following query for select right nodes with their childs:
WITH RECURSIVE
cte(id, grid_x, grid_y) AS (
SELECT id, grid_x, grid_y
FROM nodes WHERE id = 0
UNION ALL
SELECT * FROM (
SELECT current.id, current.grid_x, current.grid_y
FROM nodes AS current, cte AS prev
WHERE current.grid_x = prev.grid_x + 1
AND current.grid_y = prev.grid_y
UNION
SELECT c.id, c.grid_x, c.grid_y
FROM nodes AS c, cte AS p, edges AS e
WHERE p.id = e.source AND c.id = e.target
AND here should be checking on existing node at cte
)
)
SELECT * FROM cte;
But recursive reference to query "cte" must not appear within a subquery
Query like this
WITH RECURSIVE
cte(id, grid_x, grid_y) AS (
SELECT id, grid_x, grid_y
FROM nodes WHERE id = 0
UNION ALL
SELECT current.id, current.grid_x, current.grid_y
FROM nodes AS current, edges AS e, cte AS prev
WHERE (current.grid_x = prev.grid_x + 1
AND current.grid_y = prev.grid_y)
OR (prev.id = e.source AND e.target = current.id)
)
SELECT * FROM cte;
But this query selects only node 0
Please, help me solve this problem.
Related
I found few topics about it but none fits my expected results.
I have levels of categories stored in the table, just want to display it as tree structure.
All answers are kind of following query:
DB FIDDLE
WITH RECURSIVE cte AS (
SELECT category_id, category_name, parent_category, 1 AS level
FROM category
WHERE level = 1
UNION ALL
SELECT c.category_id, c.category_name, c.parent_category, ct.level + 1
FROM cte ct
JOIN category c ON c.parent_category = ct.category_id
)
SELECT *
FROM cte;
But the results are like
level
1
1
2
2
2
3
3
3
3
3
What I want to achieve is
level
1
2
3
3
2
3
3
1
2
3
3
2
3
3
You would typically keek track of the path to each node and use that for ordering. In Postgres, arrays come handy for this:
with recursive cte as (
select category_id, category_name, parent_category, 1 as level, array[category_id] path
from category
where parent_category is null
union all
select c.category_id, c.category_name, c.parent_category, ct.level + 1, ct.path || c.category_id
from cte ct
join category c on c.parent_category = ct.category_id
)
select *
from cte
order by path
Note that there is no need to store the level in the table; you can compute the information on the fly as you iterate. To identify the root nodes, you can filter on rows whose parent is null.
In your db fiddle, the query returns:
category_id | category_name | parent_category | level | path
----------: | :------------ | --------------: | ----: | :-------
1 | cat1 | null | 1 | {1}
3 | cat3 | 1 | 2 | {1,3}
8 | cat8 | 3 | 3 | {1,3,8}
9 | cat9 | 3 | 3 | {1,3,9}
4 | cat4 | 1 | 2 | {1,4}
6 | cat6 | 4 | 3 | {1,4,6}
7 | cat7 | 4 | 3 | {1,4,7}
5 | cat5 | 1 | 2 | {1,5}
10 | cat10 | 5 | 3 | {1,5,10}
11 | cat11 | 5 | 3 | {1,5,11}
2 | cat2 | null | 1 | {2}
You can keep track of the hierarchy as an array and use that for ordering:
WITH RECURSIVE cte AS (
SELECT category_id, category_name, parent_category, 1 AS level, array[category_id] as categories
FROM category
WHERE level = 1
UNION ALL
SELECT c.category_id, c.category_name, c.parent_category, ct.level + 1, ct.categories || c.category_id
FROM cte ct JOIN
category c
ON c.parent_category = ct.category_id
)
SELECT *
FROM cte
ORDER BY categories;
Here is a db<>fiddle.
I have a table of nodes say
Table Node
Column| type
------|---------------
id | int
x | int
y | int
z | text
for example,
id | x | y | z
---|---|---|---
0 | 0 | 0 |'a'
1 | 0 | 1 |'b'
2 | 1 | 0 |'c'
3 | 1 | 1 |'d'
and
Table Edge
Column| type
------|---------------
source| int references Node.id
target| int references Node.id
for example,
source | target
-------|-------
0 | 1
1 | 3
3 | 2
assuming a node doesn't have an edge to itself, and every source is unique
I want to display the result of the entire edge information
source.x | source.y | target.x | target.y
---------|----------|----------|---------
0 | 0 | 0 | 1
0 | 1 | 1 | 1
1 | 1 | 1 | 0
I have tried many joins(self join of node with inner join with edge), (left join of edge with result of self join of node)
How can I achieve this selection result?
I think you want two joins:
select ns.x, ns.y, nt.x, nt.y
from edge e join
nodes n1
on e.source = ns.id join
nodes nd
on e.target = nt.id
In case of PostgreSQL, you can play with the RECURSIVE WITH clause.
a) build a path:
CREATE LOCAL TEMPORARY TABLE
node(id,x,y,z) AS (
SELECT 0,0,0,'a'
UNION ALL SELECT 1,0,1,'b'
UNION ALL SELECT 2,1,0,'c'
UNION ALL SELECT 3,1,1,'d'
)
;
CREATE LOCAL TEMPORARY TABLE
edge(source,target) AS (
SELECT 0,1
UNION ALL SELECT 1,3
UNION ALL SELECT 3,2
)
;
WITH RECURSIVE backbone AS (
SELECT
source::CHAR(1)||','||target::CHAR(1) AS path
, 1 AS hops
, *
FROM edge
UNION ALL SELECT
p.path||','||c.target::CHAR(1) AS path
, p.hops + 1 AS hops
, c.*
FROM backbone p JOIN edge c ON c.source=p.target
)
SELECT * FROM backbone;
path | hops | source | target
---------+------+--------+--------
0,1 | 1 | 0 | 1
1,3 | 1 | 1 | 3
3,2 | 1 | 3 | 2
0,1,3 | 2 | 1 | 3
1,3,2 | 2 | 3 | 2
0,1,3,2 | 3 | 3 | 2
b) for the paths with 2 and 3 hops, get the first and last node id from the path:
-- as above, but instead of the last line: "SELECT * FROM backbone" ...
,
origin_and_dest AS (
SELECT
*
, SPLIT_PART(path,',',1 )::INT AS origin
, SPLIT_PART(path,',',hops + 1)::INT AS destination
FROM backbone
WHERE hops >=2
)
SELECT * FROM origin_and_dest;
path | hops | source | target | origin | destination
---------+------+--------+--------+--------+-------------
0,1,3 | 2 | 1 | 3 | 0 | 3
1,3,2 | 2 | 3 | 2 | 1 | 2
0,1,3,2 | 3 | 3 | 2 | 0 | 2
With the ultimate source id and the ultimate target id out of the path, join back to nodes:
-- same as above, but instead of the last line: "SELECT * FROM origin_and_dest" ...
SELECT
origin.x AS source_x
, origin.y AS source_y
, dest.x AS target_y
, dest.y AS target_y
FROM origin_and_dest
JOIN node AS origin ON origin=origin.id
JOIN node AS dest ON destination=dest.id
;
source_x | source_y | target_y | target_y
----------+----------+----------+----------
0 | 0 | 1 | 0
0 | 0 | 1 | 1
0 | 1 | 1 | 0
Not quite your result, but then I don't really know what you mean by "entire edge information" ... can you be more precise?
I've got a self referencing table (in SQL Server):
Page
==========
Id: int
RelativeUrl: nvarchar(max)
ParentId: int -> FK to pageId
Example data:
ID | RelativeUrl | ParentId
===============================
1 | /root | null
2 | /test1 | 1
3 | /test2 | 1
4 | /test3 | 1
5 | /test1-1 | 2
6 | /test1-2 | 2
7 | /test1-1-1 | 5
I want to create an sql query to retrieve all pages of the table with FULL relative url.
I thought about using a recursive SQL query, but don't know how to concat the relativeurl to make it a full relative url.
Wanted result:
ID | FullRelativeUrl | ParentId
===================================================
1 | /root | null
2 | /root/test1 | 1
3 | /root/test2 | 1
4 | /root/test3 | 1
5 | /root/test1/test1-1 | 2
6 | /root/test1/test1-2 | 2
7 | /root/test1/test1-1/test1-1-1 | 5
You can use a recursive CTE:
with cte as (
select id, convert(varchar(max), relativeurl) as url, 1 as lev
from page
where parentid is null
union all
select p.id, concat(cte.url, p.relativeurl), lev + 1
from cte join
page p
on p.parentid = cte.id
)
select cte.*
from cte;
Here is a db<>fiddle.
I'm using PostgreSQL 9.4, and I have a table with 13 million rows and with data roughly as follows:
a | b | u | t
-----+---+----+----
foo | 1 | 1 | 10
foo | 1 | 2 | 11
foo | 1 | 2 | 11
foo | 2 | 4 | 1
foo | 3 | 5 | 2
bar | 1 | 6 | 2
bar | 2 | 7 | 2
bar | 2 | 8 | 3
bar | 3 | 9 | 4
bar | 4 | 10 | 5
bar | 5 | 11 | 6
baz | 1 | 12 | 1
baz | 1 | 13 | 2
baz | 1 | 13 | 2
baz | 1 | 13 | 3
There are indices on md5(a), on b, and on (md5(a), b). (In reality, a may contain values longer than 4k chars.) There is also a primary key column of type SERIAL which I have omitted above.
I'm trying to build a query which will return the following results:
a | b | u | t | z
-----+---+----+----+---
foo | 1 | 1 | 10 | 3
foo | 1 | 2 | 11 | 3
foo | 2 | 4 | 1 | 3
foo | 3 | 5 | 2 | 3
bar | 1 | 6 | 2 | 5
bar | 2 | 7 | 2 | 5
bar | 2 | 8 | 3 | 5
bar | 3 | 9 | 4 | 5
bar | 4 | 10 | 5 | 5
bar | 5 | 11 | 6 | 5
In these results, all rows are deduplicated as if GROUP BY a, b, u, t were applied, z is a count of distinct values of b for every partition over a, and only rows with a z value greater than 2 are included.
I can get just the z filter working as follows:
SELECT a, COUNT(b) AS z from (SELECT DISTINCT a, b FROM t) AS foo GROUP BY a
HAVING COUNT(b) > 2;
However, I'm stumped on combining this with the rest of the data in the table.
What's the most efficient way to do this?
Your first step can be simpler already:
SELECT md5(a) AS md5_a, count(DISTINCT b) AS z
FROM t
GROUP BY 1
HAVING count(DISTINCT b) > 2;
Working with md5(a) in place of a, since a can obviously be very long, and you already have an index on md5(a) etc.
Since your table is big, you need an efficient query. This should be among the fastest possible solutions - with adequate index support. Your index on (md5(a), b) is instrumental but - assuming b, u, and t are small columns - an index on (md5(a), b, u, t) would be even better for the second step of the query (the lateral join).
Your desired end result:
SELECT DISTINCT ON (md5(t.a), b, u, t)
t.a, t.b, t.u, t.t, a.z
FROM (
SELECT md5(a) AS md5_a, count(DISTINCT b) AS z
FROM t
GROUP BY 1
HAVING count(DISTINCT b) > 2
) a
JOIN t ON md5(t.a) = md5_a
ORDER BY 1, 2, 3, 4; -- optional
Or probably faster, yet:
SELECT a, b, u, t, z
FROM (
SELECT DISTINCT ON (1, 2, 3, 4)
md5(t.a) AS md5_a, t.b, t.u, t.t, t.a
FROM t
) t
JOIN (
SELECT md5(a) AS md5_a, count(DISTINCT b) AS z
FROM t
GROUP BY 1
HAVING count(DISTINCT b) > 2
) z USING (md5_a)
ORDER BY 1, 2, 3, 4; -- optional
Detailed explanation for DISTINCT ON:
Select first row in each GROUP BY group?
I have a very specific sql problem.
I have a table given with order positions (each position belongs to one order, but this isn't a problem):
| Article ID | Amount |
|--------------|----------|
| 5 | 3 |
| 12 | 4 |
For the customer, I need an export with every physical item that is ordered, e.g.
| Article ID | Position |
|--------------|------------|
| 5 | 1 |
| 5 | 2 |
| 5 | 3 |
| 12 | 1 |
| 12 | 2 |
| 12 | 3 |
| 12 | 4 |
How can I build my select statement to give me this results? I think there are two key tasks:
1) Select a row X times based on the amount
2) Set the position for each physical article
You can do it like this
SELECT ArticleID, n.n Position
FROM table1 t JOIN
(
SELECT a.N + b.N * 10 + 1 n
FROM
(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) a
,(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) b
) n
ON n.n <= t.amount
ORDER BY ArticleID, Position
Note: subquery n generates a sequence of numbers on the fly from 1 to 100. If you do a lot of such queries you may consider to create persisted tally(numbers) table and use it instead.
Here is SQLFiddle demo
or using a recursive CTE
WITH tally AS (
SELECT 1 n
UNION ALL
SELECT n + 1 FROM tally WHERE n < 100
)
SELECT ArticleID, n.n Position
FROM table1 t JOIN tally n
ON n.n <= t.amount
ORDER BY ArticleID, Position
Here is SQLFiddle demo
Output in both cases:
| ARTICLEID | POSITION |
|-----------|----------|
| 5 | 1 |
| 5 | 2 |
| 5 | 3 |
| 12 | 1 |
| 12 | 2 |
| 12 | 3 |
| 12 | 4 |
Query:
SQLFIDDLEExample
SELECT t1.[Article ID],
t2.number
FROM Table1 t1,
master..spt_values t2
WHERE t1.Amount >= t2.number
AND t2.type = 'P'
AND t2.number <= 255
AND t2.number <> 0
Result:
| ARTICLE ID | NUMBER |
|------------|--------|
| 5 | 1 |
| 5 | 2 |
| 5 | 3 |
| 12 | 1 |
| 12 | 2 |
| 12 | 3 |
| 12 | 4 |