SQL - Database Hierarchy Structure - sql

I have a table with value like this
id | parent | folder_name
-------------------------
1 | 0 | Root
2 | 1 | NSW
3 | 1 | QLD
4 | 2 | Sydney
5 | 3 | Brisbane
from this table i want to get a folder with all parents until higher level. Example: folder_name = Brisbane
id | parent | folder_name
-------------------------
5 | 3 | Brisbane
3 | 1 | QLD
1 | 0 | Root
i want to use JOIN in sql not CTE
Any help would be great

Recursive CTE is what you are looking for
;WITH cte
AS (SELECT *
FROM Yourtable
WHERE folder_name = 'Brisbane'
UNION ALL
SELECT b.*
FROM cte a
INNER JOIN Yourtable b
ON a.parent = b.id)
SELECT *
FROM cte

Related

How do I query a master-detail result having only the last detail row in MS-Access?

I have the following master table
.------------------.
| id | parent_name |
.------------------.
| 1 | Mike |
| 2 | Sarah |
| 3 | Danial |
| 4 | Alex |
.------------------.
And have the following child-table details:
.------------------------------------------.
| id | parent_id | child_name | birth year |
.------------------------------------------.
| 1 | 1 | mandy | 2000 |
| 2 | 1 | mark | 2003 |
| 3 | 1 | mathew | 2005 |
| 4 | 2 | sandy | 1998 |
| 5 | 2 | sharon | 2006 |
| 6 | 3 | david | 2001 |
.------------------------------------------.
In the example above, I delibretaly choose names of children with the first letter matching their parents' names just to make it easier to understand the relationship, even though each child is connected to his/her parent using the parent_id.
What I would like to have is a list of all parents (4 rows) and to have a matching 4 rows from the children table, selecting only the last born child of each respectful parent.
.-------------------------------.
| id | parent_name | last_child |
.-------------------------------.
| 1 | Mike | mathew |
| 2 | Sarah | sharon |
| 3 | Danial | david |
| 4 | Alex | (null) |
.-------------------------------.
In oracle, this is easy:
SELECT
p.id,
p.parent_name,
c.child_name last_child
FROM
parents_table p,
children_table c
WHERE
p.id = c.parent_id
AND c.birth_year = (SELECT MAX(birth_year) FROM children_table where parent_id = p.id)
But I am struggling to generate the same result in MS Access.. MS Access does not accept sub-queries (for select the child having the maximum birth year for the same parent).
Is there a better way to get the result in MS Access?
Access certainly does support subqueries, but you're using a crossjoin, so you will never get a null there.
Instead, left join and perform a subquery in the FROM clause.
Your query would fail identically in Oracle, by the way. There are no relevant differences between Access and Oracle here.
SELECT
p.id,
p.parent_name,
c.child_name last_child
FROM
parents_table p
LEFT JOIN
(
SELECT *
FROM children_table c
WHERE c.birth_year = (SELECT MAX(c2.birth_year) FROM children_table c2 WHERE c2.parent_id = c.parent_id)
) c
ON p.id = c.parent_id
Access sometimes performs better with an EXISTS, so a rewrite to that would be:
SELECT
p.id,
p.parent_name,
c.child_name last_child
FROM
parents_table p
LEFT JOIN
(
SELECT *
FROM children_table c
WHERE EXISTS(SELECT 1 FROM children_table c2 WHERE c2.parent_id = c.parent_id HAVING c.birth_year = MAX(c2.birth_year))
) c
ON p.id = c.parent_id
If you just want the child's name, you can use a correlated subquery:
select p.*,
(select top 1 child_name
from children_table as ct
where ct.parent_id = p.id
order by ct.birth_year desc, ct.child_name
) as youngest_child_name
from parents_table as p;
This can take advantage of an index on children_table(parent_id, birth_year desc, child_name). With the index, I would expect this to be quite fast.

Postgresql hierarchical (tree) query

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.

SQL recursion + column concatenation

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.

Recursive query for hirarchical data based on adjacency list

Learing SQL, and have a bit of a problem. I have 2 tables level and level_hierarchy
|name | id | |parent_id | child_id|
------------------- ---------------------
| Level1_a | 1 | | NULL | 1 |
| Level2_a | 19 | | 1 | 19 |
| Level2_b | 3 | | 1 | 3 |
| Level3_a | 4 | | 3 | 4 |
| Level3_b | 5 | | 3 | 5 |
| Level4_a | 6 | | 5 | 6 |
| Level4_b | 7 | | 5 | 7 |
Now what I need, is a query that will return all entries from table level from every hirarchy level based on parameter that marks what level hierarchy level I want to get entries from.
Getting Level1 entries is quite easy.
SELECT name FROM level INNER JOIN level_hierarchy ON level.id =
level_hierarchy.child_id WHERE level_hierarchy.parent_id=NULL
Level2 entries:
Level2_a
Level2_b
are just the ones that have a parent and the parent of their parent is NULL and so on. This is where I suspect that recursion comes in.
Is there anyone who can guide thorugh it?
Your query for the first level (here depth to distinguish from the table) should look like this:
select l.name, h.child_id, 1 as depth
from level l
join level_hierarchy h on l.id = h.child_id
where h.parent_id is null;
name | child_id | depth
----------+----------+-------
Level1_a | 1 | 1
(1 row)
Note the proper use of is null (do not use = to compare with null as it always gives null).
You can use the above as an initial query in a recursive cte:
with recursive recursive_query as (
select l.name, h.child_id, 1 as depth
from level l
join level_hierarchy h on l.id = h.child_id
where h.parent_id is null
union all
select l.name, h.child_id, depth + 1
from level l
join level_hierarchy h on l.id = h.child_id
join recursive_query r on h.parent_id = r.child_id
)
select *
from recursive_query
-- where depth = 2
name | child_id | depth
----------+----------+-------
Level1_a | 1 | 1
Level2_b | 3 | 2
Level2_a | 19 | 2
Level3_a | 4 | 3
Level3_b | 5 | 3
Level4_a | 6 | 4
Level4_b | 7 | 4
(7 rows)
Good question, recursion is a difficult topic in SQL and its implementation varies by engine. Thanks for tagging your post with PostgreSQL. PostgreSQL has some excellent documentation on the topic.
WITH RECURSIVE rec_lh(child_id, parent_id) AS (
SELECT child_id, parent_id FROM level_hierarchy
UNION ALL
SELECT lh.child_id, lh.parent_id
FROM rec_lh rlh INNER JOIN level_hierarchy lh
ON lh.parent_id = rlh.child_id
)
SELECT DISTINCT level.name, child_id
FROM rec_lh INNER JOIN level
ON rec_lh.parent_id = level.id
ORDER BY level.name ASC;
See Also:
Recursive query in PostgreSQL. SELECT *

Inner Join all children top(n) parent

I need to do a inner join, where i Get a concrete number of "parent elements" but all it's relations.
If i have the table Agent:
IdAgent | Name
1 | Agent1
2 | Agent2
And the table Econ
IdEcon IdAgent Number
1 | 1 | number1
2 | 2 | number21
3 | 2 | number22
4 | 2 | number23
So if I do a:
SELECT * FROM Agent LEFT JOIN Econ
I Will Get 4 rows
IdAgent | Name | IdEcon IdAgent Number
1 | Name 1 | 1 | 1 | number1
2 | Name 2 | 2 | 2 | number21
2 | Name 2 | 3 | 2 | number22
2 | Name 2 | 4 | 2 | number23
But if i do a:
SELECT top(2) * FROM Agent LEFT JOIN Econ
I Will Get 2 rows and i want 4 rows (I want the top 2 only to affect the table Agent)
How can I accomplish that?
Untested:
SELECT * FROM (Select top(2) * FROM Agent) LEFT JOIN Econ
You could select the top two agents separately and use the result as a derived table to join to Econ, as in jarlh's answer, or you could use TOP (2) WITH TIES together with ORDER BY Agent.IdAgent:
SELECT TOP (2) WITH TIES
...
FROM
dbo.Agent
LEFT JOIN dbo.Econ ON ...
ORDER BY
Agent.IdAgent ASC
;