SQL SELECT Parent-Child with SORT (...again) - tsql

UPDATED:
I have a simple, one level parent child relation table, with following columns:
ID_Asset| Parent_ID_Asset | ProductTitle
I need output grouped by Parent followed by children, and also sorted by Parent and Children Name. My attempts in the fiddle.
See here for details: https://rextester.com/PPCHG20007
Desired order:
9 8 NULL ADONIS Server
7 16 8 ADONIS Designer
8 20 8 ADONIS Portal Module “Control & Release” Package XS
Parent first, than children, while ProductTitle ordered alphabetically.
Thanx all for hints so far.

I would do conditional ordering instead :
select t.*
from table t
order by (case when parent_id is null then id else parent_id end), ProductTitle;
I am assuming you need to sort the data based on parent-child relation.

As far as I understand, you need to sort on the name of the root products, and in between them to show the sub-products, ordered by name, and in between them to show their sub-products, etc.
I guess you are using a recursive cte. You can define a "hierarchy sorting" helper which is a padded number in the current level, and for each level deep, add a suffix with the padded number in the current level, etc.
Something like that:
declare #Products table(ID int, Parent_ID int, ProductTitle varchar(100))
insert into #Products values
(1, NULL, 'ADONIS'),
(2, NULL, 'BACARAT'),
(3, 1, 'Portal Module'),
(4, 1, 'Alhambra'),
(5, NULL, 'ZULU'),
(6, 2, 'Omega')
; with cte as (
select ID, Parent_ID, ProductTitle, FORMAT(ROW_NUMBER() over(order by ProductTitle), '0000') as SortingHelper
from #Products
where Parent_ID is null
union all
select p.ID, p.Parent_ID, p.ProductTitle, cte.SortingHelper + '.' + FORMAT(ROW_NUMBER() over(order by p.ProductTitle), '0000') as SortingHelper
from #Products p
inner join cte on cte.ID = p.Parent_ID
)
select ID, Parent_ID, ProductTitle
from cte
order by SortingHelper

I think this is the ordering you are looking for. This joins each row to its parent (if it exists). Then if there is a TP (parent) record then that is the parent title, ID etc, otherwise the current record must be the parent. I've shown the parent name in the query results for clarity. Then it sorts by
Parent name (so parents and children are together, but in parent name order)
Parent ID (in case two or more parents have the same name/title, this will keep children with the correct parent)
A flag which is 0 for parents, 1 for children, so the parent comes first
The current record name, which will sort children by name/title order
Code is
Select T.*,
isnull(TP.ProductTitle, T.ProductTitle) as ParentName -- This is the parent name, shown for reference
from test T
left outer join Test TP on TP.ID_Asset = T.Parent_ID_Asset --This is the parent record, if it exists
ORDER BY isnull(TP.ProductTitle, T.ProductTitle), --ParentName sort
isnull(TP.ID_Asset, T.ID_Asset), --if two parents have the same title, this makes sure they group with their correct children
Case when T.Parent_ID_Asset is null then 0 else 1 end, --this makes sure the parent comes before the child
T.ProductTitle --Child Sort

Related

Updating a parent and its children without link between them

I'm facing an issue and hope you will be able to guide me in the right direction.
A facility can have 1..n sub-facilities. Sub-facilities can have 1..n sub-facilities as well. And this can be a never ending relationship.
In the database, we only have keys to connect the parent and first children.
Here's a short schema:
--Facility has : facilityid|parentid
---child1 has : facilityid|parentid to facility
----child2 has : facilityid|parentid to child 1 / no key to main parent
-----child2 has : facilityid|parentid to child 2 / no key to main parent
What I am trying to do is update a column named value and set it to true for a parent and all of its children and sub-children included. Right now, since I am only able to see the first parent and its children but not the sub-children, I can only update the value of those. But that leaves all my sub-children not updated.
Knowing that there is no link between sub-children and main parent, how would I accomplish that?
Here's a query that gives me a parent and all of its children.
WITH attributes AS (
select fa.facilityid, a.id
from caip_attribute a
join caip_facility_attribute fa on a.id = fa.attributeid)
select a.facilityid as parentfacilityid, a.id as parentattributeid, f.id as facilitychildid, fa.attributeid as childattributeid
from attributes a
join caip_facility f on f.parentid = a.facilityid
join caip_facility_attribute fa on fa.facilityid = f.id
join caip_attribute at on at.id = fa.attributeid
where at.definitionTypeKey = 'AUTO_CREATE_PROCESS'
order by f.id asc
The update statement is missing here but this is how I get the values that will later need to be updated.
Thank you!
To update all descendants of a parent you can use a recursive CTE.
Suppose we have a data table with relations set using self-referencing foreign key column parent_id:
CREATE TABLE t (
id SERIAL PRIMARY KEY,
value BOOL DEFAULT FALSE,
parent_id INT,
FOREIGN KEY (parent_id) REFERENCES t(id)
);
First, a query that returns a parent with id = 1 and all it's descendants would be like this:
WITH RECURSIVE children(id, value, parent_id) AS (
SELECT id, value, parent_id FROM t WHERE id = 1
UNION
SELECT t.id, t.value, t.parent_id FROM t
INNER JOIN children ON children.id = t.parent_id
)
SELECT * FROM children
Second, to update a value of parent and all it's descendants we can set value for all rows with ids in our select query:
UPDATE t SET value = true
WHERE id IN (
WITH RECURSIVE children(id, value, parent_id) AS (
SELECT id, value, parent_id FROM t WHERE id = 1
UNION
SELECT t.id, t.value, t.parent_id FROM t
INNER JOIN children ON children.id = t.parent_id
)
SELECT id FROM children
)
Check a demo

Postgresql recursive CTE results ordering

I'm working on a query to pull data out of a hierarchy
e.g.
CREATE table org (
id INT PRIMARY KEY,
name TEXT NOT NULL,
parent_id INT);
INSERT INTO org (id, name) VALUES (0, 'top');
INSERT INTO org (id, name, parent_id) VALUES (1, 'middle1', 0);
INSERT INTO org (id, name, parent_id) VALUES (2, 'middle2', 0);
INSERT INTO org (id, name, parent_id) VALUES (3, 'bottom3', 1);
WITH RECURSIVE parent_org (id, parent_id, name) AS (
SELECT id, parent_id, name
FROM org
WHERE id = 3
UNION ALL
SELECT o.id, o.parent_id, o.name
FROM org o, parent_org po
WHERE po.parent_id = o.id)
SELECT id, parent_id, name
FROM parent_org;
It works as expected.
3 1 "bottom3"
1 0 "middle1"
0 "top"
It's also returning the data in the order that I expect, and it makes sense to me that it would do this because of the way that the results would be discovered.
The question is, can I count on the order being like this?
Yes, there is a defined order. In the Postgres WITH doc, they give the following example:
WITH RECURSIVE search_graph(id, link, data, depth, path, cycle) AS (
SELECT g.id, g.link, g.data, 1,
ARRAY[ROW(g.f1, g.f2)],
false
FROM graph g
UNION ALL
SELECT g.id, g.link, g.data, sg.depth + 1,
path || ROW(g.f1, g.f2),
ROW(g.f1, g.f2) = ANY(path)
FROM graph g, search_graph sg
WHERE g.id = sg.link AND NOT cycle
)
SELECT * FROM search_graph;
About which they say in a Tip box (formatting mine):
The recursive query evaluation algorithm produces its output in
breadth-first search order. You can display the results in depth-first
search order by making the outer query ORDER BY a "path" column
constructed in this way.
You do appear to be getting breadth-first output in your case above based on the INSERT statements, so I would say you could, if you wanted, modify your outer SELECT to order it in another fashion.
I believe the analog for depth-first in your case would probably be this:
WITH RECURSIVE parent_org (id, parent_id, name) AS (
SELECT id, parent_id, name
FROM org
WHERE id = 3
UNION ALL
SELECT o.id, o.parent_id, o.name
FROM org o, parent_org po
WHERE po.parent_id = o.id)
SELECT id, parent_id, name
FROM parent_org
ORDER BY id;
As I would expect (running things through in my head) that to yield this:
0 "top"
1 0 "middle1"
3 1 "bottom3"

How to merge JSONB field in a tree structure?

I have a table in Postgres which stores a tree structure. Each node has a jsonb field: params_diff:
CREATE TABLE tree (id INT, parent_id INT, params_diff JSONB);
INSERT INTO tree VALUES
(1, NULL, '{ "some_key": "some value" }'::jsonb)
, (2, 1, '{ "some_key": "other value", "other_key": "smth" }'::jsonb)
, (3, 2, '{ "other_key": "smth else" }'::jsonb);
The thing I need is to select a node by id with additional generated params field which contains the result of merging all params_diff from the whole parents chain:
SELECT tree.*, /* some magic here */ AS params FROM tree WHERE id = 3;
id | parent_id | params_diff | params
----+-----------+----------------------------+-------------------------------------------------------
3 | 2 | {"other_key": "smth else"} | {"some_key": "other value", "other_key": "smth else"}
Generally, a recursive CTE can do the job. Example:
Use table alias in another query to traverse a tree
We just need a more magic to decompose, process and re-assemble the JSON result. I am assuming from your example, that you want each key once only, with the first value in the search path (bottom-up):
WITH RECURSIVE cte AS (
SELECT id, parent_id, params_diff, 1 AS lvl
FROM tree
WHERE id = 3
UNION ALL
SELECT t.id, t.parent_id, t.params_diff, c.lvl + 1
FROM cte c
JOIN tree t ON t.id = c.parent_id
)
SELECT id, parent_id, params_diff
, (SELECT json_object(array_agg(key ORDER BY lvl)
, array_agg(value ORDER BY lvl))::jsonb
FROM (
SELECT key, value
FROM (
SELECT DISTINCT ON (key)
p.key, p.value, c.lvl
FROM cte c, jsonb_each_text(c.params_diff) p
ORDER BY p.key, c.lvl
) sub1
ORDER BY lvl
) sub2
) AS params
FROM cte
WHERE id = 3;
How?
Walk the tree with a classic recursive CTE.
Create a derived table with all keys and values with jsonb_each_text() in a LATERAL JOIN, remember the level in the search path (lvl).
Use DISTINCT ON to get the "first" (lowest lvl) value for each key. Details:
Select first row in each GROUP BY group?
Sort and aggregate resulting keys and values and feed the arrays to json_object() to build the final params value.
SQL Fiddle (only as far as pg 9.3 can go with json instead of jsonb).

Order rows alphabetically after insert (not order by)

I need some advise for making a trigger that orders alphabetically after every insert.
Example:
Table COUNTRIES
ID 1, name 'Germany'
ID 2, name 'Bahamas'
ID 3, name 'Algeria'
Afterwards:
ID 1, name 'Algeria'
ID 2, name 'Bahamas'
ID 3, name 'Germany'
Any thoughts on how achieve this? Thank you.
PD: ORDER BY used on a SELECT statement will only order the rows for "aesthetical" purposes, the real rows would have no order whatsoever.
Finally, I have found an answer for this issue at http://sqlteam.com.
Maybe it will help some other users.
CREATE TRIGGER ORDER_COUNTRIES
AFTER INSERT
ON COUNTRIES
AS
BEGIN
UPDATE COUNTRIES
SET name = Table3.name
FROM Prueba Table1
JOIN (SELECT ID, seq = ROW_NUMBER() OVER (ORDER BY ID) FROM COUNTRIES) Table2
ON Table1.ID = Table2.ID
JOIN (SELECT name, seq = ROW_NUMBER() OVER (ORDER BY name) FROM COUNTRIES) Table3
ON Table2.seq = Table3.seq
END
Result:
ID 1, name 'Algeria'
ID 2, name 'Bahamas'
ID 3, name 'Germany'
Now the table gets properly ordered after every insert. So, if you SELECT either by using ORDER BY or not, your table will always be alphabetically ordered.
Answer found at http://www.sqlteam.com/forums/topic.asp?TOPIC_ID=160974 from the user
"nigelrivett". All credit goes to him.

CTE query to root element postgres

This is a very general question. I found some questions and discussions on more specific problems on SO, but I am quite sure, that many of you have already solved this one:
input:
A table that has a tree structure in one field.
An arbitrary id of a database record x.
question:
How can I get the root of the tree of x?
I found out that there should be a way to implement this recursively, but I couldn't achieve it yet.
The root element can be found in the same way as child elements of a given root,
but the query must search in the opposite direction.
Take a look at simple demo: --> http://www.sqlfiddle.com/#!17/fdc8a/1
This query retrieves all childrens of a given root:
WITH RECURSIVE childs( id, parent_id )
AS (
-- get parent
SELECT id, parent_id
FROM tab
WHERE id = 10
UNION ALL
-- get all children
SELECT t.id, t.parent_id
FROM childs c
JOIN tab t
ON t.parent_id = c.id
)
SELECT * from childs;
and this query retrieves all parents of a given child node:
WITH RECURSIVE parents( id, parent_id )
AS (
-- get leaf children
SELECT id, parent_id
FROM tab
WHERE id = 14
UNION ALL
-- get all parents
SELECT t.id, t.parent_id
FROM parents p
JOIN tab t
ON p.parent_id = t.id
)
SELECT * from parents
-- WHERE parent_id is null;
if only the root node is needed, a clause WHERE parent_id IS NULL filters out all except the root.