SQL: Flatten hierarchy with missing levels - sql

I have a table with a parent-child hierarchy together with a column that tells on which hierarchy level the current id is.
An example:
id pid level
A H1
B A H2
C B H3
D C H4
E H1
F E H3
G F H4
I want this to be transposed or flatten creating two rows one for each id on the lowest level.
Like this:
id H1 H2 H3 H4
D A B C D
G E F G
Can you do it in SQL using pivot? I was thinking that maybe the value of the "level" column could be used as name for the columns in the result table? Value "H1" maps to column name "H1" and so on.
Stored procedure would also be a possible solution that I could think of. Anyone who have done something like this?
Thanks for your help!
/Andreas

I created a table ttt that contains your data. This query will pivot five levels for you:
select id
, [H1], [H2], [H3], [H4], [H5]
from
(
select distinct
coalesce(c6.id,c5.id,c4.id,c3.id,c2.id,c1.id) as id
, c1.id as lid
, c1.level
from ttt c1
left join ttt c2
on c1.id = c2.pid
left join ttt c3
on c2.id = c3.pid
left join ttt c4
on c3.id = c4.pid
left join ttt c5
on c4.id = c5.pid
left join ttt c6
on c5.id = c6.pid ) as sourcetable
pivot ( max(lid)
for level in ([H1], [H2], [H3], [H4], [H5])
) as pivottable;
you can check it out at sqlfiddle: example

Related

Recursive way of writing SQL Query

I have table which stores something like a parent-child pair values.
The point to note is that a child can act as a parent of another child.
In the below query, the namekey is the child and the namekeyow (namekey owner) is the parent.
As can be seen from the query, the purpose is to start from a child and find 6 of its ancestors.
SELECT a1.namekey n1,
a1.namekeyow n2,
a2.namekeyow n3,
a3.namekeyow n4,
a4.namekeyow n5,
a5.namekeyow n6,
a6.namekeyow n7
FROM iacira a1
LEFT JOIN iacira a2
ON a2.namekey = a1.namekeyow
LEFT JOIN iacira a3
ON a3.namekey = a2.namekeyow
LEFT JOIN iacira a4
ON a4.namekey = a3.namekeyow
LEFT JOIN iacira a5
ON a5.namekey = a4.namekeyow
LEFT JOIN iacira a6
ON a6.namekey = a5.namekeyow
Question is, is there some way to do this recursively without these multiple self joins ?
A recursive CTE takes the form:
with
n (namekey, namekeyow, lvl) as (
select a.*, 1 from iacira a where namekey = 1234
union all
select a.*, n.lvl + 1
from n, iacira a
where a.namekey = n.namekeyow and n.lvl <= 6
)
select * from n
It can walk the graph in an unlimited number of levels. The query above limits the levels to 6 using where n.lvl <= 6.

Linked list concept in postgresql

I am new to postgresql, can you please guide me about my query listed below?
I have a table in postgres (database) named "app" having two columns "aid" and "cid".
Table Name: app
aid | cid
a1 | a3
a2 | null
a3 | a5
a4 | a6
a5 | null
a6 | null
What I want to display(using sql query in server), when I select "a1" or "a3" or "a5" from aid using sql query, I want to list all values associated with "a1" and its child cid (in this case I want an output = a1 a3 a5), its like a linked list a1 is connected to a3 and a3 is connected to a5.
If I select "a4" using sql query, I need an output like this("a4 a6")
You need to use recursion to accomplish this:
with recursive walk as (
select aid, cid, array[aid] as path
from app
union all
select w.aid, a.cid, w.path||a.aid
from walk w
join app a
on a.aid = w.cid
)
select *, array_to_string(path, ' ') as text_path
from walk
where cid is null;
Working fiddle here.
If your table is large, then to limit the cost of recursion, use a where clause in the top half of the walk CTE to restrict your starting point.
with recursive walk as (
select aid, cid, array[aid] as path
from app
where aid = 'a1'
union all
. . .
You can get the reverse path without having to recurse again like this:
with recursive walk as (
select aid, cid, array[aid] as path
from app
union all
select w.aid, a.cid, w.path||a.aid
from walk w
join app a
on a.aid = w.cid
), forward as (
select *, array_to_string(path, ' ') as text_path
from walk
where cid is null
), reverse as (
select distinct on (a.aid) a.aid, f.path, f.text_path, r.path as rpath
from app a
join forward f
on f.aid = a.aid
join forward r
on r.path #> array[a.aid]
order by a.aid, array_length(r.path, 1) desc
)
select r.aid, r.path, r.text_path,
array_agg(u.rid order by u.rn desc) as up_path,
string_agg(u.rid, ' ' order by u.rn desc) as text_up_path
from reverse r
join lateral unnest(rpath)
with ordinality as u(rid, rn)
on u.rn <= array_position(r.rpath, r.aid)
group by r.aid, r.path, r.text_path;
Updated fiddle.

Update a table by using a join [duplicate]

This question already has answers here:
Update a table using JOIN in SQL Server?
(13 answers)
Closed 8 years ago.
I have two tables ta, tb. ta columns - cId, c1, c2. c1 and c2 contain nulls and need to be filled with data. tb columns - cId, c3, c4. The data for c1 and c2 will come from c3 and c4 respectively.
So, I tried to do a simple inner join first. Both tables were aliased as al_ta and al_tb respectively. Then, I put an update statement -
UPDATE ta SET
al_ta.c1 = al_tb.c3,
al_ta.c2 = al_tb.c4
FROM ta AS al_ta
INNER JOIN tb AS al_tb
ON al_tb.cId = al_tb.cId
This does not work and I get an error - The multi-part identifier al_ta.c1 could not be bound. How do I make this work ?
Sample tables -
ta
cId c1 c2
1 NULL NULL
2 NULL NULL
3 NULL NULL
tb
cId c3 c4
1 11 111
2 22 222
3 33 333
4 44 444
When referencing the columns, you need to use the alias, not the base table name, if you've abstracted the table names away in the JOIN. Guessing at what your join might look like, you probably meant to write it this way:
UPDATE ta SET
ta.c1 = tb.c3,
ta.c2 = tb.c4
FROM dbo.some_long_table_name_a AS ta
INNER JOIN dbo.some_long_table_name_b AS tb
ON ta.cId = tb.cId
WHERE ta.c1 IS NULL OR ta.c2 IS NULL;
I don't understand the purposes of saying:
FROM ta AS al_ta
Why would you bother using an alias here that is actually harder to write than the original table name?
Please Try it
update ta set
ta.c1 = b.c3,
ta.c2 = b.c4
from ta a join tb b on a.cid = b.cid

SQL using same codes table for two different columns

I have a table (Foo) that has two columns that store a code value from a codes table:
id - code1 - code2
1 - CC - DD
The Codes table:
Name - Code - Grouping
Call Center - CC - 22
County - DD - 54
I need a SQL that will pull 'Call Center' and 'County' based on the first table. It is assumed that I know Foo.code1 necessarily uses Codes.Grouping=22 and Foo.code2 uses Codes.Grouping=54.
I'm trying to write one SQL that will return both values.
Try this query:
select name from codes c inner join foo f on c.code = f.code1 or c.code = f.code2
Here is sqlfiddle
I am not really sure if this is the answer to your question, because I don't know exactly what you mean. I guess however, that you want to get both codes and groupings for an id value of your Foo table. For this I would
SELECT Foo.id,
C1.Name AS code1_name, C1.Code AS code1_code,
C1.Grouping AS code1_grouping,
C2.Name AS code2_name, C2.Code AS code2_code,
C2.Grouping AS code2_grouping
FROM Foo
INNER JOIN Codes AS C1 ON C1.Code = Foo.code1
INNER JOIN Codes AS C2 ON C2.Code = Foo.code2
WHERE Foo.id = 1;
expanding tuffkid sqlfiddlesqlfiddle

Need help with a complex SQL query - I think I need a two-stage inner join or something like that?

Okay, here's what I'm trying to do. I have a drupal table (term_data) in mysql that lists tags and their ID numbers and a second table, (term_node) that shows the relationship between tags and the data the tags refer to. For example, if node 1 had 3 tags, "A", "B" and "C". term_data might look like this:
name tid
A 1
B 2
C 3
and term_node might look like this:
nid tid
1 1
1 2
2 2
3 3
3 2
In this example, node 1 has been tagged with "A" and "B", node 2 has been tagged with "A" and node 3 has been tagged with "B", and "C".
I need to write a query that, given a tag name, list for me all the OTHER tags that are ever used with that tag. In the above example, searching on "A" should return "A" and "B" because node 1 uses both, searching on "C" should return "B" and "C", and searching on "B" should return "A", "B" and "C".
Any ideas? I got this far:
select distinct n.nid from term_node n INNER join term_data t where n.tid = t.tid and t.name='A';
Which gives me a list of every node that has been tagged with "A" - but I can't figure out the next step.
Can anyone help me out?
Try:
select distinct d2.name
from term_data d1
join term_node n1 on d1.tid = n1.tid
join term_node n2 on n1.nid = n2.nid
join term_data d2 on n2.tid = d2.tid
where d1.name = 'A'
Updated: Mark pointed out that the query wasn't correct.
SELECT DISTINCT t.name, t2.name Other
FROM
term_data t
INNER JOIN term_node n ON t.tid = n.tid
INNER JOIN term_node n2 ON n2.nid = n.nid
INNER JOIN term_data t2 ON n2.tid = t2.tid
WHERE
t.name = 'A'
Marks answer should be accepted since he got it right first. Here is a demonstration of a similar query
https://data.stackexchange.com/stackoverflow/query/13283/demo-for-need-help-with-a-complex-sql-query
Your description of term_node data and the example do not seem to match but using the example data provided I believe the following query will do what you need.
select distinct td.name, td2.name as tagged_name
from term_data td
inner join term_node tn
on tn.tid = td.tid
inner join term_node tn2
on tn2.nid = tn.nid
inner join term_data td2
on td2.tid = tn2.tid
The first join looks up the term_node records that match the name, term_node is then joined to itself to find all other tid's for that node, finally the second term_node is joined to term_data to retrieve the names of the tag.
You need to tack on the appropriate where clause to select just the tag you want.
Result set follows for above:-
name tagged_name
A A
A B
B A
B B
B C
C B
C C
Hope this helps
Ray
I created the schema in my workbench, and here's the query I came up with:
SELECT * FROM `term_data` WHERE `term_data`.`tid` IN (
SELECT `term_node`.`tid` from `term_node` WHERE `nid` IN (
SELECT `nid` FROM `term_node` JOIN `term_data` ON `term_data`.`tid` = `term_node`.`tid` WHERE `term_data`.`name` = 'A'
)
);
Sorry for the structure ;) Here's SHOW CREATE TABLE for both tables:
CREATE TABLE `term_data` (
`tid` int(11) NOT NULL,
`name` varchar(45) DEFAULT NULL,
PRIMARY KEY (`tid`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
CREATE TABLE `term_node` (
`term_node_id` int(11) NOT NULL,
`nid` int(11) NOT NULL,
`tid` varchar(45) DEFAULT NULL,
PRIMARY KEY (`term_node_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
This seemed to work as expected, if I understood your question correctly. So one more time, we have some nodes which are tagged. We'd like to select a tag (A), and then select other tags that were used to tag same nodes as tag A.
Cheers.
P.S. Output is the following:
tid name
/* For tag A */
1 A
2 B
/* For tag B */
1 A
2 B
3 C
/* For tag C */
2 B
3 C