Linked list concept in postgresql - sql

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.

Related

SQL SELECT Question of a current problem that I have to solve

Just a simple question of SQL that I was not able to figure out the solution.
table 2 AUditLog
Machine SettingCode User changeSTR timeChanged Order
A C1 U1 0->10 12/5/2020 10h00 1
A C1 U2 10->3 12/5/2020 10h07 1
A C1 U1 0->3 12/5/2020 11h00 3
I want to do a select of this table AuditLog and have. (in the cases of the same machine, settingCode, Order) I want to have a result with the users of the most recente change.
Machine SettingCode LastUserChange Order
A C1 U2 1
A C1 U1 3
A simple and portable solution is to filter with a correlated subquery:
select a.*
from auditLog a
where a.timeChanged = (
select max(a1.timeChanged)
from auditLog a1
where
a1.machine = a.machine
and a1.settingCode = a.settingCode
and a1.order = a.order
)
You can also use row_number(), if your database, which you did not tell, supports window functions:
select *
from (
select
a.*,
row_number() over(
partition by machine, settingCode, a.order
order by timeChanged desc) rn
from auditLog a
) a
where rn = 1
Note that order is a SQL keyword, hence not a good choice for a column name.

Iterating over the table with recursive query

I have the following data:
cte1
=================
gp_id | m_ids
------|----------
1 | {123}
2 | {432,222}
3 | {123,222}
And a function foobar(m_ids integer[]). The function contains the following cte:
with RECURSIVE foo as (
select id, p_id, name from bar where id = any(m_ids)
union all
select b.id, b.p_id, b.name from bar b join foo f on f.p_id = b.id
)
The function is being used kind of like this:
select foobar(m_ids) from cte1;
Now, as a part of a process of improving performance, I was told to get rid of the function. My plan was to use cte foo in my cte chain, but I stuck trying to adjust usage of any(m_ids).
EDITED: To be clear, the problem is that m_ids that are used in the where id = any(m_ids) statement is the parameter of the function, so I got to transform cte in order to make it work outside of the function.
I thought of the following:
with RECURSIVE foo as (
select (select id, p_id, name from bar where id = any(cte1.m_ids)
union all
select b.id, b.p_id, b.name from bar b join foo f on f.p_id = b.id)
from cte1
)
But that would not work because
1) recursive query "foo" does not have the form non-recursive-term UNION [ALL] recursive-term
2) subquery must return only one column
In the end, I would like to get my data in the form like this:
m_ids |foobar_result
---------|-------------
{123} | 125
{432,222}| 215
Maybe JOIN that table holding parameter?
with RECURSIVE foo as (
select m_ids, id, p_id, name from bar
JOIN cte1 ON id = ANY(m_ids)
union all
select m_ids, b.id, b.p_id, b.name from bar b join foo f on f.p_id = b.id
)

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)

Grouping data without an aggregate function in SQL

I was wondering if anyone could lend some insight into this problem. I'm managing a number of stock portfolios which must contain the same stocks. I need to design a query that will compare all portfolios against all portfolios and return the stocks that exist in one, but not the other.
For simplicity's sake, let's say I have a table that looks like this:
stock_symbol portfolio
AAPL A
IBM A
MCD A
NFLX A
AAPL B
IBM B
MCD B
FB B
AAPL C
IBM C
MCD C
Ideally, I want the query to return something like this:
p1 p2 stock_symbol
A B NFLX
A C NFLX
B A FB
B C FB
So comparing A to B will return NFLX, while comparing B to A will return FB.
Currently, I've got a query that works with a small number of portfolios, but I'm going to be managing >20 portfolios soon. That's hundreds of comparisons. I want to use GROUP BY, but I don't have an aggregate function.
Any ideas as to what I can do? Thanks!
This type of query doesn't need group by. It needs left join. Here is an example query that should do what you want:
select p1.portfolio, p2.portfolio, p1.stock_symbol
from table p1 left join
table p2
on p1.stock_symbol = p2.stock_symbol and
p1.portfolio <> p2.portfolio
where p2.stock_symbol is null;
EDIT:
That is such a good point that p2.portfolio will be NULL. Here is a better solution:
select p1.portfolio, p2.portfolio, p1.stock_symbol
from (select distinct portfolio from table) p1 cross join
(select distinct portfolio from table) p2 left join
table sp1
on sp1.portfolio = p1.portfolio left join
table sp2
on sp1.stock_symbol = sp2.stock_symbol
where sp2.stock_symbol is null;
Give this a try...
Setup:
select * into #tbla from (
SELECT 'AAPL' stock_symbol, 'A' portfolio union all
SELECT 'IBM ', 'A' union all
SELECT 'MCD ', 'A'union all
SELECT 'NFLX', 'A'union all
SELECT 'AAPL', 'B'union all
SELECT 'IBM ', 'B'union all
SELECT 'MCD ', 'B'union all
SELECT 'FB ', 'B'union all
SELECT 'AAPL', 'C'union all
SELECT 'IBM ', 'C'union all
SELECT 'MCD ', 'C'
)a
Query:
WITH symbols
AS (
SELECT DISTINCT stock_symbol
FROM #tbla
)
,portfolios
AS (
SELECT DISTINCT portfolio
FROM #tbla
)
,mstr
AS (
SELECT *
FROM portfolios A
CROSS JOIN symbols B
)
,interim
AS (
SELECT *
FROM mstr m
WHERE NOT EXISTS (
SELECT 1
FROM #tbla A
WHERE m.portfolio = A.portfolio
AND m.stock_symbol = A.stock_symbol
)
)
SELECT A.portfolio
,b.portfolio
,b.stock_symbol
FROM interim A
CROSS JOIN interim B
WHERE A.portfolio <> B.portfolio
AND A.stock_symbol <> B.stock_symbol
Result:
portfolio portfolio stock_symbol
A B NFLX
A C NFLX
B A FB
B C FB
C B NFLX
C A FB