Recursive way of writing SQL Query - sql

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.

Related

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.

Getting active record based on column value

I have a database table named BusinessAssociate and in that table for the sake of complexity there are 2 columns
BusinessAssociateKey int
AmalgamatedIntoBAKey int
Using the BusinessAssociateKey we can join on other tables, and one of those tables (BACorporateStatus) tells us if that BusinessAssociate is active or amalgamated.
Let's assume that Business Associate key 123456 is amalgamated into BA Key 987654, in the same table there will be a row, with a BusinessAssociateKey of 987654, and this row may well be amalgamated too, for example into BusinessAssociateKey 283746.
Is there a way on a per BusinessAssociateKey to find the active (not amalgamated) Business Associate?
The number of chains is unknown, could be none or could be n.
Edit: Here is a SQL Fiddle, http://sqlfiddle.com/#!9/1e886/1 and in this example BusinessAssociateKey 56781 is not amalgamated, so for BusinessAssociateKey 123 the surviving/active BA Key is 56781.
Do a self join with the table. Here I have added row number to get last records using self join.
Select F.Nbr, F.BusinessAssociateKey, F.AmalgamatedIntoBAKey
From
(Select row_number() Over(order by (select 1)) as Nbr, E.BusinessAssociateKey, E.AmalgamatedIntoBAKey
From BusinessAssociate E
) F
LEFT OUTER JOIN
(Select row_number() Over(order by (select 1)) as Nbr, E.BusinessAssociateKey, E.AmalgamatedIntoBAKey
From BusinessAssociate E
) K
ON F.AmalgamatedIntoBAKey = K.BusinessAssociateKey
where K.Nbr IS NULL
http://sqlfiddle.com/#!6/88b53/26
Recursion:
;with rec_cte as(
select b1.BusinessAssociateKey, b1.AmalgamatedIntoBAKey, 1 as rn
from BusinessAssociate b1 left outer join BusinessAssociate b2 on b1.BusinessAssociateKey = b2.AmalgamatedIntoBAKey
where b2.BusinessAssociateKey is null
union all
select c.BusinessAssociateKey, b.AmalgamatedIntoBAKey, c.rn + 1
from rec_cte c inner join BusinessAssociate b on c.AmalgamatedIntoBAKey = b.BusinessAssociateKey
where b.AmalgamatedIntoBAKey is not null),
cte as(
select BusinessAssociateKey, max(rn) as rn
from rec_cte
group by BusinessAssociateKey)
select r.BusinessAssociateKey, r.AmalgamatedIntoBAKey
from rec_cte r inner join cte c on r.BusinessAssociateKey = c.BusinessAssociateKey and r.rn = c.rn
option (maxdop 0)

return column name of the maximum value in sql server 2012

My table looks like this (Totally different names)
ID Column1--Column2---Column3--------------Column30
X 0 2 6 0101 31
I want to find the second maximum value of Column1 to Column30 and Put the column_Name in a seperate column.
First row would look like :
ID Column1--Column2---Column3--------------Column30------SecondMax
X 0 2 6 0101 31 Column3
Query :
Update Table
Set SecondMax= (select Column_Name from table where ...)
with unpvt as (
select id, c, m
from T
unpivot (c for m in (c1, c2, c3, ..., c30)) as u /* <-- your list of columns */
)
update T
set SecondMax = (
select top 1 m
from unpvt as u1
where
u1.id = T.id
and u1.c < (
select max(c) from unpvt as u2 where u2.id = u1.id
)
order by c desc, m
)
I really don't like relying on top but this isn't a standard sql question anyway. And it doesn't do anything about ties other than returning the first column name by order of alphabetical sort.
You could use a modification via the condition below to get the "third maximum". (Obviously the constant 2 comes from 3 - 1.) Your version of SQL Server lets you use a variable there as well. I think SQL 2012 also supports the limit syntax if that's preferable to top. And since it should work for top 0 and top 1 as well, you might just be able to run this query in a loop to populate all of your "maximums" from first to thirty.
Once you start having ties you'll eventually get a "thirtieth maximum" that's null. Make sure you cover those cases though.
and u1.c < all (
select top 2 distinct c from unpvt as u2 where u2.id = u1.id
)
And after I think about it. If you're going to rank and update so many columns it would probably make even more sense to use a proper ranking function and do the update all at once. You'll also handle the ties a lot better even if the alphabetic sorting is still arbitrary.
with unpvt as (
select id, c, m, row_number() over (partition by id order by c desc, m) as nthmax
from T
unpivot (c for m in (c1, c2, c3, ..., c30)) as u /* <-- your list of columns */
)
update T set
FirstMax = (select c from unpvt as u where u.id = T.id and nth_max = 1),
SecondMax = (select c from unpvt as u where u.id = T.id and nth_max = 2),
...
NthMax = (select c from unpvt as u where u.id = T.id and nth_max = N)

Execute query for each pair of values from a list

I have a list of value pairs over which I iterate and run a query, the skeleton of which could be thought of like this.
list of pairs - ((x1,y1), (x2,y2), ... (xn,yn)) xi, yi are not all distinct.
q is an oracle query which returns a single value for any (xi,yi)
global_table is a single row table with
id col deleted
1 Y NULL
A few rows from 'table':
id col deleted pid did
1 NULL Y 25 1
81 N NULL NULL 149
101 Y NULL 22 149
61 Y NULL NULL NULL
Also, there is a UNIQUE constraint on (pid, did, deleted) in table.
The query q goes like this.
select w.finalcol from
(select coalesce(a.col,b.col,c.col,d.col) as finalcol from
(select * from global_table where deleted is null) a
left outer join
(select * from table where deleted is null) b
on b.pid is null and b.did is null
left outer join
(select * from table where deleted is null) c
on c.pid is null and c.did = xi
left outer join
(select * from table where deleted is null) d
on d.pid = yi and d.did = xi
) w
n = 60
n is determined by another query which returns the list of value pairs.
for element in (list of pairs)
q(xi,yi) (xi and yi might be used any number of times in the query)
I am trying to reduce the number of times I run this query. (from n times to 1)
I can try passing the individual lists and to the query after isolating them from the list of pairs but the catch is that not all pairs are present in the table(s) being queried from. But, you do get a value from the table for pairs that dont exist in the table(s) since there is a default case at play here (not important).
The default case is when
select * from table where deleted is null
and c.pid is null and c.did = xi
select * from table where deleted is null
and c.pid = yi and c.did = xi
dont return any rows.
I want my result to be of the form
x1 y1 q(x1,y1)
x2 y2 q(x2,y2)
.
.
.
xn yn q(xn,yn)
(no pair must be left out, given that a few pairs might actually not be in the table)
How do I achieve this using just a single query?
Thanks

SQL: Flatten hierarchy with missing levels

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