How to do recursive select in PostgreSQL with array as an argument - arrays

I am trying to implement easy recursive function in PostgreSQL but I cannot finish it...
I have got table MyTable which includes columns Col1 and Col2. Data inside is like this:
Col1 | Col2
1 | 2
2 | 5
2 | 6
3 | 7
4 | 5
4 | 2
5 | 3
I would like to write a function which takes as a parameter array of Col1 f.e. (1,2) and gives me back values from Col2 like this :
1 | 2
2 | 5
2 | 6
and after that, does it again with results : (2, 5, 6)
so:
1 | 2
2 | 5
2 | 6
5 | 3
(2 is already in, and key '6' does not exist)
and again (3):
1 | 2
2 | 5
2 | 6
5 | 3
3 | 7
and for (7) nothing because value '7' does not exist in Col1.
It is an easy recursion but I have no idea how to implement it. I have got so far something like this:
with recursive aaa(params) as (
select Col1, Col2
from MyTable
where Col1 = params -- I need an array here
union all
select Col1, Col2
from aaa
)
select * from aaa;
But it of course does not work
Thanks in advance

The basic pattern for recursion is to have your base case as the first part of the union and in the second part join the recursion result to what you need to produce the next level of results. In your case it would look like this:
WITH RECURSIVE aaa(col1, col2) AS (
SELECT col1, col2 FROM mytable
WHERE col1 = ANY (ARRAY[1,2]) -- initial case based on an array
UNION -- regular union because we only want new values
SELECT child.col1, child.col2
FROM aaa, mytable AS child -- join the source table to the result
WHERE aaa.col2 = child.col1 -- the recursion condition
)
SELECT * FROM aaa;

Related

SQL Get Union Result into different cols

What is the best to way, to get a Union result into different cols
For example
SELECT 1 AS Col1
UNION
SELECT 2 AS Col2
Result:
| Col1 | Col2 |
| 1 | 2 |
The only way I see it, is to create a auxiliary table with two cols and insert each value in the respective col. However, I would like a cleaner and better way.
(There is no unique key between both selects, to make a JOIN)

SQL child parent hierarchy - Using a where on child to hide parent

Let's say I have table below:
ID | Name | Active | ParentID
1 | Foo1 | 1 | 0
2 | Foo2 | 1 | 1
3 | Foo3 | 1 | 2
4 | Foo4 | 1 | 3
5 | Foo5 | 1 | 3
6 | Foo6 | 0 | 5
7 | Foo7 | 1 | 2
7 | Foo7 | 1 | 6
8 | Foo8 | 1 | 7
9 | Foo9 | 1 | 5
(I have indeed duplicate ID's, on which I expressed my thoughts but to no result)
As you can see, once child can have multiple parents. ID's with ParentID 0 have no parent. I need to select all ID's that are active and do not have an inactive parent above them, however high in the tree that might be.
So with the data set above, my result would be:
ID | Name |
1 | Foo1 |
2 | Foo2 |
3 | Foo3 |
4 | Foo4 |
5 | Foo5 |
9 | Foo9 |
ID 6 got removed because it was Inactive
ID 7 got removed because one of its parents (6) is inactive
ID 8 got removed because a parent (6) of its parent (7) is inactive
ID 9 is fine because its parent (5) is active and so are 5 his parents etc
I attempted this with a subquery in the where
SELECT *
FROM table
WHERE ID not in (SELECT ID FROM table where Active = 0)
But that only solves it for the current record.
I've also tried a typical self-join as used for employee/manager, but that only goes one layer deep, while here I also need to check for the parent of the parent etc
Any suggestions/ideas?
One method would be to use an rCTE to work through the hierachy, with a column that retains the initial ID. Then you can use an EXISTS to ensure there are no rows with a value of 0 for Active:
WITH rCTE AS(
SELECT ID,
Name,
Active,
ParentID,
ID AS InitialID
FROM dbo.YourTable YT
UNION ALL
SELECT YT.ID,
YT.Name,
YT.Active,
YT.ParentID,
r.InitialID
FROM rCTE r
JOIN dbo.YourTable YT ON r.ParentID = YT.ID)
SELECT *
FROM dbo.YourTable YT
WHERE NOT EXISTS (SELECT 1
FROM rCTE r
WHERE r.InitialID = YT.ID
AND r.Active = 0);
I would use a recursive CTE to identify IDs where the chain is continuous, using both conditional and unconditional increment by 1 as follows:
With A As
(Select ID, [Name], Active, ParentID, 0 As NUM_1, 0 As NUM_2
From Tbl Where ParentID=0
Union All
Select Tbl.ID, Tbl.[Name], Tbl.Active, Tbl.ParentID,
NUM_1 + 1 As NUM_1,
NUM_2 + IIF(Tbl.Active=1,1,0) As NUM_2
From Tbl Inner Join A On (Tbl.ParentID=A.ID)
)
Select ID, [Name]
From A
Where ID Not In (Select ID From A Where NUM_1<>NUM_2)
Order by ID
Result:
ID
Name
1
Foo1
2
Foo2
3
Foo3
4
Foo4
5
Foo5
9
Foo9
db<>fiddle

Using Group By And Over clause on Different columns

I have a table like this;
col 1|col 2|col 3
a | 2 | 10
b | -1 | 10
a | 10 | 10
The goal is to get output as;
col 1| col 2| col 3
a | 12 | 30
b | -1 |30
I tried the following query;
select col 1,
sum(col 2),
sum(col 3) OVER()
From table t1
group by col 1
But I got the error; col 3 is not a valid group by expression.
Kindly suggest an alternate solution.thanks in advance.
You may try taking the sum of SUM(col3) over the entire table:
SELECT
col1,
SUM(col2),
SUM(SUM(col3)) OVER()
FROM table t1
GROUP BY col1;
If you want to use SUM as a window function, then you need to sum something which is available after GROUP BY has evaluated. SUM(col3) is available, while col3 is not.

T-SQL: UPDATE table according to a column

TLDNR: how do I update a table depending on a column?
Problem situation: the current column SortingNumber is full of bad data.
Solution: reassign new values to SortingNumber based on their Parent. The SortingNumber shall be 1 for the lowest current SortingNumber (by Parent) and be incremented by 1 for every subsequent dataset.
Current data: Desired result:
ID | Parent | SortingNumber >> ID | Parent | SortingNumber
1 | 1 | 3 >> 1 | 1 | 1
2 | 1 | 4 >> 2 | 1 | 2
3 | 1 | 5 >> 3 | 1 | 3
4 | 2 | 8 >> 4 | 2 | 1
5 | 2 | 10 >> 5 | 2 | 2
6 | 2 | 13 >> 6 | 2 | 3
Actual problem: I'm having trouble figuring out how to update the datasets corresponding to their parents.
My script currently updates all the values incrementally and doesn't group it by Parent.
My current solution:
DECLARE #lastSN INTEGER = 0;
WITH toUpdate AS
(
SELECT
T1.*,
-- "calculate" the sorting number from the row above
LAG(T1.SortingNumber + 1, 1, 1) OVER (ORDER BY T1.SortingNumber) AS [newSortNumber]
FROM
T AS T1
INNER JOIN
T AS T2 ON T1.Parent = T2.ID
)
UPDATE toUpdate
SET
#lastSN = CASE WHEN [newSortNumber] = 1 AND #lastSN = 0 THEN 1 ELSE #lastSN + 1 END,
toUpdate.SortingNumber = #lastSN
;
Result is:
ID | Parent | SortingNumber
1 | 1 | 1
2 | 1 | 2
3 | 1 | 3
4 | 2 | 4
5 | 2 | 5
6 | 2 | 6
I guess my question could be phrased as: how do I update datasets depending on the Parent column?
PS: here is the CREATE statement if you wish to try it out yourself
CREATE TABLE T
(
ID INT IDENTITY(1,1) PRIMARY KEY,
Parent INT FOREIGN KEY REFERENCES T(ID),
SortingNumber INT
);
GO
INSERT INTO T (Parent, SortingNumber)
VALUES (1, 3), (1, 4), (1, 5), (2, 8), (2, 10), (2, 13);
You can employ row_number to achieve this using partitioning by Parent and ordering by SortingNumber.
WITH cte AS (
SELECT
* ,
ROW_NUMBER() OVER (PARTITION BY Parent ORDER BY SortingNumber) AS NewSortingNumber
FROM T
)
UPDATE cte
SET SortingNumber = NewSortingNumber
A window function creates small tables within the table using Parent, so we have two subsets, one for Parent = 1 and the another for Parent = 2. Then it uses ORDER BY to know from which row it should start count (starting from 1). The first row is for Parent = 1 and ID =1 so it gets 1, the next row gets 2 etc. Please look here for more details.
As an alternative you can just rank, ordering by patient then ID:
UPDATE tt
SET sortingnumber = drank from (select *, DENSE_RANK() OVER (order by Parent, ID) as drank from tt ) a where tt.ID=a.id and tt.parent=a.parent
select * from tt

How to shift the value to the next row in SQL Server?

I have a table input like
column1 | column2
--------+--------
1,2,3 | A,B,C
4,5,6 | D,E,F
I need output like this:
column1 |column2
--------+-------
1 | A
2 | B
3 | C
4 | D
5 | E
6 | F
You question is quite unclear, but my magic crystal ball tells me, that you might be looking for something like this:
DECLARE #tbl TABLE(ID INT IDENTITY,column1 VARCHAR(100),column2 VARCHAR(100))
INSERT INTO #tbl VALUES
('1,2,3','A,B,C')
,('4,5,6','D,E,F');
WITH Casted AS
(
SELECT ID
,CAST('<x>' + REPLACE(column1,',','</x><x>')+'</x>' AS XML) AS col1XML
,CAST('<x>' + REPLACE(column2,',','</x><x>')+'</x>' AS XML) AS col2XML
FROM #tbl
)
SELECT ID,col1XML.value('/x[1]','int') AS Column1,col2XML.value('/x[1]','nvarchar(max)') AS Column2
FROM Casted
UNION ALL
SELECT ID,col1XML.value('/x[2]','int') AS Column1,col2XML.value('/x[2]','nvarchar(max)') AS Column2
FROM Casted
UNION ALL
SELECT ID,col1XML.value('/x[3]','int') AS Column1,col2XML.value('/x[3]','nvarchar(max)') AS Column2
FROM Casted
ORDER BY ID,Column1
The result
ID c1 c2
1 1 A
1 2 B
1 3 C
2 4 D
2 5 E
2 6 F

Resources