Select rows from table using tree order - sql

I have table witch contains fields: id, parent_id, name (etc.)
i want to order this table in "tree travel order" ie.
id parent_id
1, 0
3, 1
5, 1
2, 0
8, 2
4, 0
9, 4
(...)
in short describe: take root node, append all children, take next root node append children etc.

By your description I assume you mean breadth-first order, which could be easly done using a WITH RECURSIVE query (PostgreSQL 8.4+):
WITH RECURSIVE tree
AS
(
SELECT
node_name, id, parent_id, NULL::varchar AS parent_name
FROM foo
WHERE parent_id IS NULL
UNION
SELECT
node_name, f1.id, f1.parent_id, tree.node_name AS parent_name
FROM
tree
JOIN foo f1 ON f1.parent_id = tree.id
)
SELECT node_name, empno, parent_id, node_name FROM tree;
You could also use depth-first order using the following SQL:
WITH RECURSIVE tree
AS
(
SELECT
node_name, id, parent_id, NULL::varchar AS parent_name, id::text AS path
FROM foo WHERE parent_id IS NULL
UNION
SELECT
node_name, f1.id, f1.parent_id, tree.node_name AS parent_name, tree.path || '-' || f1.id::text AS path
FROM
tree
JOIN foo f1 ON f1.parent_id = tree.id
)
SELECT node_name, empno, parent_id, node_name, path FROM tree ORDER BY path;

As noticed by synergetic, the solution for depth-first order provided by Diogo Biazus won't work for id's with different number of digits.
But you can use this solution instead, that uses arrays of integer :
WITH RECURSIVE tree
AS
(
SELECT
node_name, id, parent_id, NULL::varchar AS parent_name, array[id] AS path
FROM foo WHERE parent_id IS NULL
UNION
SELECT
node_name, f1.id, f1.parent_id, tree.node_name AS parent_name, tree.path || f1.id AS path
FROM
tree
JOIN foo f1 ON f1.parent_id = tree.id
)
SELECT node_name, empno, parent_id, node_name, path FROM tree ORDER BY path;

You can also use the excellent LTree module, but you need to reorganise your data a bit.

SELECT * FROM table ORDER BY id,parent_id
That should order my columns in the order there placed within the query.
Unless you mean GROUP the items, witch I think you do, then use
SELECT * FROM table ORDER BY id GROUP BY parent_id
And i also advise you to read this article: http://mikehillyer.com/articles/managing-hierarchical-data-in-mysql/

Related

How to tune the multiple self join query in SQL Server

I have table like as below
I need to fetch summary based on issue id and parent id, I need to fetch actual parent summary as parent desc and preceding summary as succeed desc. There will be 5 level hierarchy (max).
So I self-joined the table for 5 times, the below code is the example code. But the query is taking almost 250 seconds to execute. How to optimize the code in SQL Server?
Expected output
WITH cte_a AS
(
SELECT
'AON property' AS summary, 1001 AS issue_id, 2001 AS parent_id
UNION ALL
SELECT 'AON property L1', 2001, 3001
UNION ALL
SELECT 'AON Property L2', 3001, 4001
UNION ALL
SELECT 'AON Property L3', 4001, NULL
UNION ALL
SELECT 'LONG CHAIN CLUBS', 1002, 2222
UNION ALL
SELECT 'LONG CHAIN L1', 2222, 3003
UNION ALL
SELECT 'LONG CHAIN L2', 3003, NULL
)
SELECT
a.*,
CASE
WHEN f.summary IS NOT NULL THEN e.summary
WHEN e.summary IS NOT NULL THEN d.summary
WHEN d.summary IS NOT NULL THEN c.summary
WHEN c.summary IS NOT NULL THEN b.summary
WHEN b.summary IS NOT NULL THEN then a.summary
END AS succeed_desc,
COALESCE (f.summary, e.summary, d.summary, c.summary, b.summary, a.summary) AS parent_desc
FROM
cte_a a
LEFT JOIN
cte_a b ON a.parent_id = b.issue_id --Level1
LEFT JOIN
cte_a c ON b.parent_id = c.issue_id --Level2
LEFT JOIN
cte_a d ON c.parent_id = d.issue_id --Level3
LEFT JOIN
cte_a e ON d.parent_id = e.issue_id --Level4
LEFT JOIN
cte_a f ON e.parent_id = f.issue_id --Level5
I agree with Martin Smith saying in the comments that you need to define an INDEX.
With that said, you also need to look into recursive CTEs. summary, issue_id, parent_id all come from cte_a; parent_desc is going to be the result of the recursive CTE and succeed_desc is the result of a simple join.
I will work my way through in 3 steps.
Step 1: Building up the recursive CTE
A recursive CTE is in the form:
WITH Recursive_CTE AS (
SELECT ...
FROM SomeTable
UNION ALL
SELECT ...
FROM Recursive_CTE
JOIN SomeTable ON ...
)
In your case, you want to browse into records from a parent_id to the next issue_id. Importantly, you want to remember the original issue_id and parent_id to build the final result AND the joined parent_id to keep your recursion going.
For clarity's sake, I will add a column so you can keep track of the recursion (I will drop it later).
WITH cte_a(summary, issue_id, parent_id) AS
(
...
), cte_b(summary, issue_id, parent_id, ancestor_id, loop_number) AS (
SELECT summary, issue_id, parent_id, parent_id, 0 from cte_a
UNION ALL
SELECT cte_b.summary, cte_b.issue_id, cte_b.parent_id, cte_a.parent_id, 1+loop_number
FROM cte_a
JOIN cte_b ON cte_a.issue_id = cte_b.ancestor_id
)
SELECT *
FROM cte_b
Note that parent_id appears twice in the initialisation of the recursive CTE. You should not be surprised by that: 1 is to keep the original value, the other is for the loop. The difference shows after the UNION ALL (cte_b.parent_id vs cte_a.parent_id).
Step 2: Preparing the output for the final query
We now need to tweak the above query in order to:
Add the parent_desc column (that will be in the recursive CTE)
Allow the additiona of the succeed_desc column (that will also be in the CTE)
Keep only the records we want to keep in the final result (that will be outside the CTE)
For that, we add 2 columns in the CTE and the records we want to keep are the ones where the recursion stopped (ancestor_id IS NULL):
WITH cte_a(summary, issue_id, parent_id) AS
(
...
), cte_b(summary, issue_id, parent_id, penultimate_ancestor_id, ancestor_id, ancestor_summary) AS (
SELECT summary, issue_id, parent_id, issue_id, parent_id, summary from cte_a
UNION ALL
SELECT cte_b.summary, cte_b.issue_id, cte_b.parent_id,
cte_a.issue_id, cte_a.parent_id, cte_a.summary
FROM cte_a
JOIN cte_b ON cte_a.issue_id = cte_b.ancestor_id
)
SELECT *
FROM cte_b
WHERE ancestor_id IS NULL
Step 3: Finalizing the query
In the previous step, we added the penultimate_ancestor_id column to keep track of the last issue_id encountered before reaching NULL. succeed_desc can easily be obtained using it.
WITH cte_a(summary, issue_id, parent_id) AS
(
...
), cte_b(summary, issue_id, parent_id, penultimate_ancestor_id, ancestor_id, ancestor_summary) AS (
SELECT summary, issue_id, parent_id, issue_id, parent_id, summary from cte_a
UNION ALL
SELECT cte_b.summary, cte_b.issue_id, cte_b.parent_id,
cte_a.issue_id, cte_a.parent_id, cte_a.summary
FROM cte_a
JOIN cte_b ON cte_a.issue_id = cte_b.ancestor_id
)
SELECT subquery.summary,
subquery.issue_id,
subquery.parent_id,
cte_a.summary as succeed_desc,
subquery.ancestor_summary as parent_desc
FROM (SELECT * FROM cte_b WHERE ancestor_id IS NULL) subquery
LEFT OUTER JOIN cte_a ON subquery.parent_id IS NOT NULL
AND subquery.penultimate_ancestor_id = cte_a.parent_id
ORDER BY parent_desc, issue_id

How to generate Tree path using traverse CTE

Suppose my data in table like in attached picture without having path column.
i want to generate a column, like "Path" in picture using traverse CTE in sql
Picture example:
Use a recursive CTE to solve this:
WITH recCTE
AS (
SELECT id,
parentid,
id AS original_id,
parentid AS original_parentid,
name as original_name,
1 AS depth,
CAST(name AS VARCHAR(5000)) AS path
FROM yourtable
UNION ALL
SELECT yourtable.id,
yourtable.parentid,
recCTE.original_id,
recCTE.original_parentID,
recCTE.original_name,
recCTE.depth + 1,
CAST(recCTE.path + '-' + yourtable.name as VARCHAR(5000))
FROM recCTE
INNER JOIN yourtable
ON recCTE.parentid = yourtable.id
WHERE depth < 20 /*prevent cycling*/
)
SELECT original_id as id, original_parentid as parentid, original_name as name, depth, path
FROM recCTE t1
WHERE depth = (SELECT max(depth) FROM recCTE WHERE t1.original_id = recCTE.original_id)
sqlfiddle example
That CTE has two parts:
The "Anchor Member" which is the first selection from the table. This defines the output (which columns and column type are in the output).
The "Recursive Member" which selects from the CTE in which it's contained at is performed iteratively until the join fails.
In this example we capture the path by concatenating the path to the name over and over again in the recursive member. We also track Depth (how many recursions have been performed) and track the current id and parentid as well as the original id and original parentid so they can be selected in the final SELECT statement.
try this,
;with cte(id,parentId,name,path,cnt)
AS
(
select id,parentid,name,cast(name as VARCHAR(1024)) as path, 1 as cnt from test_cte
union all
select a.id,a.parentid,a.name,CAST((a.name + '-' +path ) as VARCHAR(1024)), case when a.parentid is null then 0 else cnt + 1 end as cnt from test_cte a join cte c on c.id = a.parentid where c.cnt is not null
)
select id,parentid,name,path from (select id,parentid,name,path, row_number() over(partition by id order by cnt desc) as rank from cte) a where a.rank = 1 order by 1 asc ;

how to make a sql loop?

here is the simplified table
filesystem (id, name, parentId);
and some entries
(1, 'root', NULL)
(2, 'folder', 1)
(3, 'subfolder', 2)
(4, 'subsubfolder', 3)
is there a way using native SQL to print the absolute path of one entry ?
for instance, the last entry would print 'root/folder/subfolder/subsubfolder'. the entry 2 would print 'root/folder' and so on.
You can do something like this
with tree(id, Level, Hierarchy) as
(
select id, 0, cast(Name as varchar(max))
from filesystem
union all
select a.id, b.Level+1,
b.Hierarchy+'/'+a.Name
from filesystem a
inner join tree b on a.parentid=b.id
)
select top(1) id, Hierarchy
from tree
where id=4
order by Level desc
It will give you id with full file path.
TO read in details you can check this
You didn't state your DBMS, the following is standard (ANSI) SQL:
with recursive folder_tree as (
select id, name, parentid, name as fullpath
from filesystem
where parentid is null
union all
select c.id, c.name, c.parentid, p.fullpath||'/'||c.name
from filesystem c
join folder_tree p on c.parentid = p.id
)
select *
from folder_tree
SQLFiddle: http://sqlfiddle.com/#!15/91332/7
Recursive CTE solution for SQL Server:
WITH FileSystem(id,name,parentID)
AS
(
SELECT 1,'root',NULL
UNION ALL
SELECT 2,'folder',1
UNION ALL
SELECT 3,'subFolder',2
UNION ALL
SELECT 4,'subSubFolder',3
),
CTE_Recursion
AS
(
SELECT ROW_NUMBER() OVER (ORDER BY ID) filePath_id,ID,CAST(name AS VARCHAR(100)) name,parentID
FROM FileSystem
WHERE parentID IS NULL
UNION ALL
SELECT A.filePath_id,B.id,CAST(A.name + '\' + B.name AS VARCHAR(100)),B.parentID
FROM CTE_Recursion A
INNER JOIN FileSystem B
ON A.ID = B.parentID
)
SELECT filePath_id,MAX(name) filePath
FROM CTE_Recursion
GROUP BY filepath_id
Results:
filePath_id filePath
-------------------- -----------------------------------
1 root\folder\subFolder\subSubFolder

SQL: How to create view from a recursive query?

Question: I have a view which I want to derive from a recursive query.
The query is of the same structure as this one here:
http://forums.asp.net/t/1207101.aspx
And represents a treeview as an ordered dataset.
How can I create a view which does this:
;WITH Tree (ID, [NAME], PARENT_ID, Depth, Sort) AS
(
SELECT ID, [NAME], PARENT_ID, 0 AS Depth, CONVERT(varchar(255), [Name]) AS Sort FROM Category
WHERE PARENT_ID = 0
UNION ALL
SELECT CT.ID, CT.[NAME], CT.PARENT_ID, Parent.Depth + 1 AS Depth,
CONVERT(varchar(255), Parent.Sort + ' | ' + CT.[NAME]) AS Sort
FROM Category CT
INNER JOIN Tree as Parent ON Parent.ID = CT.PARENT_ID
)
-- HERE IS YOUR TREE, Depths gives you the level starting with 0 and Sort is the Name based path
SELECT ID, [NAME], PARENT_ID, Depth, Sort FROM Tree
ORDER BY Sort
It should be as simple as:
CREATE VIEW YourViewName
AS
WITH Tree (ID, [NAME], PARENT_ID, Depth, Sort) AS
(
SELECT ID, [NAME], PARENT_ID, 0 AS Depth, CONVERT(varchar(255), [Name]) AS Sort
FROM Category
WHERE PARENT_ID = 0
UNION ALL
SELECT CT.ID, CT.[NAME], CT.PARENT_ID, Parent.Depth + 1 AS Depth,
CONVERT(varchar(255), Parent.Sort + ' | ' + CT.[NAME]) AS Sort
FROM Category CT
INNER JOIN Tree as Parent ON Parent.ID = CT.PARENT_ID
)
-- HERE IS YOUR TREE, Depths gives you the level starting with 0 and Sort is the Name based path
SELECT ID, [NAME], PARENT_ID, Depth, Sort FROM Tree
GO

PostgreSQL recursive with

I need help with a recursive query. Assuming the following table:
CREATE TEMPORARY TABLE tree (
id integer PRIMARY KEY,
parent_id integer NOT NULL,
name varchar(50)
);
INSERT INTO tree (id, parent_id, name) VALUES (3, 0, 'Peter'), (2,0, 'Thomas'), (5,2, 'David'), (1, 0, 'Rob'), (8, 0, 'Brian');
I can retrieve a list of all people and their children with the following query:
WITH RECURSIVE recursetree(id, parent_id) AS (
SELECT id, parent_id FROM tree WHERE parent_id = 0
UNION
SELECT t.id, t.parent_id
FROM tree t
JOIN recursetree rt ON rt.id = t.parent_id
)
SELECT * FROM recursetree;
How can I list them in order, and also sort the first level items by name? For example, the desired output would be:
id, parent_id, name
8, 0, "Brian"
3, 0, "Peter"
1, 0; "Rob"
2, 0, "Thomas"
5, 2, " David"
Thanks,
**EDIT. Please note that adding an ORDER BY won't work: **
WITH RECURSIVE recursetree(id, parent_id, path, name) AS (
SELECT
id,
parent_id,
array[id] AS path,
name
FROM tree WHERE parent_id = 0
UNION ALL
SELECT t.id, t.parent_id, rt.path || t.id, t.name
FROM tree t
JOIN recursetree rt ON rt.id = t.parent_id
)
SELECT * FROM recursetree ORDER BY path;
The above will retain the parent child relationship (children follow their parents), but applying any other ORDER BY clause (ie: name - like some have suggested) will cause the result to lose it's parent-child relationships.
See also this (translated) article about CTE's in PostgreSQL: wiki.phpfreakz.nl
Edit: Try this one, using an array:
WITH RECURSIVE recursetree(id, parent_ids, firstname) AS (
SELECT id, NULL::int[] || parent_id, name FROM tree WHERE parent_id = 0
UNION ALL
SELECT
t.id,
rt.parent_ids || t.parent_id,
name
FROM tree t
JOIN recursetree rt ON rt.id = t.parent_id
)
SELECT * FROM recursetree ORDER BY parent_ids;
You can add a path to your query and order by it at the end:
WITH RECURSIVE recursetree(id, parent_id,path) AS (
SELECT id, parent_id,id||'' as path FROM tree WHERE parent_id = 0
UNION
SELECT t.id, t.parent_id,concat(rt.path,'_',t.id)
FROM tree t
JOIN recursetree rt ON rt.id = t.parent_id
)
SELECT * FROM recursetree
ORDER BY rt.path;