SQL Rows to Separate Columns - sql

I realise this maybe similar to other questions, but I am stuck!
I am having trouble organising some data into an appropriate format to export to another tool. Basically I have an ID column and then 2 response columns. I would like to separate the ID and then list the responses under each. See the example below for clarification.
I have played around with Pivot and UnPivot but can't get it quite right.
Here is how the data looks now.
ID X1 X2
1 2 Y
1 5 Y
1 3 N
1 7 N
1 6 Y
2 5 N
2 4 Y
2 8 Y
2 3 N
3 5 Y
3 1 N
3 9 N
Here is how I would like the data to look
ID1_X1 ID1_X2 ID2_X1 ID2_X2 ID3_X1 ID3_X2
2 Y 5 N 5 Y
5 Y 4 Y 1 N
3 N 8 Y 9 N
7 N 3 N null null
6 Y null null null null
Here is the code to create/populate the table.
create table #test (ID int, X1 int, X2 varchar(1))
insert into #test values
('1','2','Y'),('1','5','Y'),('1','3','N'),('1','7','N'),
('1','6','Y'),('2','5','N'),('2','4','Y'),('2','8','Y'),
('2','3','N'),('3','5','Y'),('3','1','N'),('3','9','N')

You can do this using aggregation and row_number() . . . assuming you know the ids in advance:
select max(case when id = 1 then x1 end) as x1_1,
max(case when id = 1 then x2 end) as x2_1,
max(case when id = 2 then x1 end) as x1_2,
max(case when id = 2 then x2 end) as x2_2,
max(case when id = 3 then x1 end) as x1_3,
max(case when id = 3 then x2 end) as x2_3
from (select t.*,
row_number() over (partition by id order by (select null)) a seqnum
from #test t
) t
group by seqnum;
I should note that SQL tables represent unordered sets. Your original data doesn't have an indication of the ordering, so this is not guaranteed to put the values in the same order as the original data (actually, there is no such order that that statement is a tautology). If you have another column with the ordering, then you can use that.

Here is a alternative approach to Gordan's good answer using OUTER JOIN's
Considering that there is a Identity column in your table to define the order of X1 in each ID and fixed number of ID's
;WITH FST
AS (SELECT ROW_NUMBER()OVER(ORDER BY IDENTITY_COL) RN,X1 AS ID1_X1,X2 AS ID1_X2
FROM #TEST A
WHERE ID = 1),
SCD
AS (SELECT ROW_NUMBER()OVER(ORDER BY IDENTITY_COL) RN,X1 AS ID2_X1,X2 AS ID2_X2
FROM #TEST A
WHERE ID = 2),
TRD
AS (SELECT ROW_NUMBER()OVER(ORDER BY IDENTITY_COL) RN,X1 AS ID3_X1,X2 AS ID3_X2
FROM #TEST A
WHERE ID = 3)
SELECT ID1_X1,ID1_X2,ID2_X1,ID2_X2,ID3_X1,ID3_X2
FROM FST A
FULL OUTER JOIN SCD B
ON A.RN = B.RN
FULL OUTER JOIN TRD C
ON C.RN = COALESCE(B.RN, A.RN)

Related

How to get specific records in posgtres

In Postgres I have two tables:
Table A { int keyA, Text name}
Table B { int keyB, int keyA, char mark, date start, date end}
Mark from Table B could be 'X', 'Y', 'Z'.
I want to get every record 'X' with dates but only one from 'Y', 'Z'. Also if there are 'X', 'Y', 'Z' i want only 'X'.
From:
keyB
keyA
mark
start
end
1
1
X
15-01-2023
16-01-2023
2
1
X
17-01-2023
18-01-2023
3
1
Y
null
null
4
1
Z
null
null
5
2
Y
null
null
6
2
Z
null
null
7
2
Y
null
null
8
3
Z
null
null
9
3
Y
null
null
10
4
X
19-01-2023
20-01-2023
I want to get
keyB
keyA
mark
start
end
1
1
X
15-01-2023
16-01-2023
2
1
X
17-01-2023
17-01-2023
5
2
Y
null
null
8
3
Z
null
null
10
4
X
19-01-2023
20-01-2023
I tried:
1.
Select A.name,
(select b2.start from B b2 where b2.keyA = A.keyA and b2.mark = 'X') as Start,
(select b2.end from B b2 where b2.keyA = A.keyA and b2.mark = 'X') as End,
from A order by name;
Order is important. I need to have name first.
There is a porblem. In subqueries i have more than one record so i have to add limit 1. But I want to get every X not only one.
If I do this
Select A.name, B.start, B.end
from A inner join B on A.keyA = B.keyB
I'll have X, Y, Z and as I mentioned I want only X or one from Y or Z.
Any idea how should I solve this?
Use the row_number function with your join query as the following:
select name, keyB, keyA, mark, start_dt, end_dt
from
(
select A.name, B.*,
row_number() over (partition by B.keyA order by case when B.mark='X' then 1 else 2 end, B.keyb) rn
from tableB B join tableA A
on B.keyA = A.keyA
) T
where mark = 'X' or rn = 1
order by keyb
See demo

SQL transpose multiple rows to different columns

I have SQL server data Table, like this:
ID
SYMPTOM1
SYMPTOM2
1
A
B
1
C
D
2
E
F
3
A
C
3
D
E
I'd like to obtain this:
ID
SYMPTOM1
SYMPTOM2
SYMPTOM3
SYMPTOM4
1
A
B
C
D
2
E
F
3
A
C
D
E
That should be an "easy" pivot, but I can't figure it out.
How could a SQL query be written?
*EDIT
Excuse me, I have neglected an important detail. The number of rows in the table is very large, some 500.000 so the IDs are very large.
You could do it as a self join - use row number to have another column that is either 1 or 2 (depending on how many times ID appeared), 1 should always appear, 2 sometimes appears, so left joining those rows with 2 onto those rows with 1 (based on the id) gives you the end result..
WITH x AS(
SELECT
t.ID,
t.SYMPTOM1,
t.SYMPTOM2,
ROW_NUMBER() OVER (PARTITION BY t.ID ORDER BY t.SYMPTOM1) as rn
FROM t
)
SELECT
*
FROM
x x1
LEFT JOIN x x2 ON x1.id = x2.id AND x1.rn = 1 AND x2.rn = 2
You can pivot on a row-number
SELECT
t.ID,
SYMPTOM1 = MAX(CASE WHEN t.rn = 1 THEN t.SYMPTOM1 END),
SYMPTOM2 = MAX(CASE WHEN t.rn = 1 THEN t.SYMPTOM2 END),
SYMPTOM3 = MAX(CASE WHEN t.rn = 2 THEN t.SYMPTOM1 END),
SYMPTOM4 = MAX(CASE WHEN t.rn = 2 THEN t.SYMPTOM2 END)
FROM (
SELECT
t.*,
rn = ROW_NUMBER() OVER (PARTITION BY t.ID ORDER BY (SELECT 1))
FROM YourTable t
) t
GROUP BY t.ID;

Select values from different rows of same column INTO multiple variables in Oracle SQL

Here's the example:
ID | value
1 51
2 25
3 11
4 27
5 21
I need to get first three parameters and place them into variables e.g. out_x, out_y, out_z.
Is it possible to do it without multiple selects?
You can do something like this:
select max(case when id = 1 then value end),
max(case when id = 2 then value end),
max(case when id = 3 then value end)
into out_x, out_y, out_z
from t
where id in (1, 2, 3);
However, I think three queries of the form:
select value into out_x
from t
where id = 1;
is a cleaner approach.
You can use a PIVOT:
SELECT x, y, z
INTO out_x, out_y, out_z
FROM your_table
PIVOT ( MAX( value ) FOR id IN ( 1 AS x, 2 AS y, 3 AS z ) )
Or, if you do not know which IDs you need (but just want the first 3) then:
SELECT x, y, z
INTO out_x, out_y, out_z
FROM (
SELECT value, ROWNUM AS rn
FROM ( SELECT value FROM your_table ORDER BY id )
WHERE ROWNUM <= 3
)
PIVOT ( MAX( value ) FOR rn IN ( 1 AS x, 2 AS y, 3 AS z ) )

SQL Transform Crosstab Pivot Data

I am using SQL Server 2008 and would like to transform my data such that:
Dataset:
ID Item Columns Result
1 1 X A
2 1 Y B
3 1 Z C
4 2 X D
5 2 Y E
6 2 Z NULL
7 3 X F
8 3 Y G
9 3 Z H
Results Desired:
Item X Y Z
1 A B C
2 D E NULL
3 F G H
At this time, I am doing the following, then pasting the columns I need into Excel:
Select * from thisTable where Column=X
Select * from thisTable where Column=Y
Select * from thisTable where Column=Z
However, not all of the rows match up to can can't just smack the tables side by side. For columns without a Result, I'd like NULL to show up to fill in the rows to make them all the same number of records.
I looked up PIVOT but I don't think this works here...what is this type of data transformation called? I don't think it's a crosstab...
Thanks!
You can do a crosstab using conditional aggregation:
SELECT
Item,
[X] = MAX(CASE WHEN [Columns] = 'X' THEN Result END),
[Y] = MAX(CASE WHEN [Columns] = 'Y' THEN Result END),
[Z] = MAX(CASE WHEN [Columns] = 'Z' THEN Result END)
FROM thisTable
GROUP BY Item
use PIVOT
select *
from (
select Item, Columns, Result
from thisTable
) t
pivot (
max (Result)
for Columns in (X, Y, Z)
) p

"Cluster" Code Help in SQL

I am relative newcomer to SQL, but have gained many useful ideas through the site. Now I'm stuck on a piece of code that seems simple enough, but for some reason I can't wrap my head around it.
I am trying to create a third column (Column Z) based off of the first two columns below:
Column X Column Y
-------------------
1 a
1 b
1 c
2 a
2 d
2 e
2 f
4 b
5 i
5 c
3 g
3 h
6 j
6 k
6 l
What i need to have happen in Column Z:
For each individual value found in Column Y, note the value of Column X
Likewise, for each individual value in Column X, note the value of Column Y
Then, cluster (RANK/ROW_NUMBER?) these into groups seen below:
Column X Column Y Column Z
-----------------------------
1 a 1
1 b 1
1 c 1
2 a 1
2 d 1
2 e 1
2 f 1
4 b 1
5 i 1
5 c 1
3 g 2
3 h 2
6 j 3
6 k 3
6 l 3
I hope I've been clear enough without over-complicating things. My head has been spinning all morning. Let me know if anyone needs any more info.
Greatly appreciated in advance!
I have faced exactly this problem for some analyses in the past. The only way I could get it to work is by doing a loop, that incrementally adds in the information.
The loop assigns the minimum "x" value within each group as the group id. By your rules, this is guaranteed to be unique. It starts by assigning the current x value to z. It then finds the minimum z along the x and y dimensions. It repeats this process until no records change.
Given your data, the following is an outline of how to do it:
update t set z = x
while 1=1
begin
with toupdate as (
select t.*,
min(z) over (partition by x) as idx,
min(z) over (partition by y) as idy from t
)
update toupdate
set z = (case when idx < idy then idx else idy end)
where z > idx or z > idy;
if (##ROWCOUNT = 0) break;
end;
;with a as
(
select z, dense_rank() over (order by z) newZ from t
)
update a set z = newZ
Maybe not the best way, but it works
SQLFiddle http://sqlfiddle.com/#!3/99532/1
;WITH cte AS (
SELECT *, ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS row_nb
FROM #t
)
, c2 AS (
SELECT e1.*
,CASE WHEN EXISTS(SELECT * FROM cte e2 WHERE e1.Y = e2.Y and e2.row_nb < e1.row_nb) THEN 1 ELSE 0 END as ex
FROM cte e1
)
, c3 AS (
SELECT X,1 - SIGN(SUM(ex)) as ex,MAX(row_nb) as max_row_nb
FROM c2
GROUP BY X
)
SELECT
cte.X,cte.Y
,(SELECT SUM(cc3.ex) FROM c3 cc3 where cc3.max_row_nb<= c3.max_row_nb) AS Z
FROM cte
INNER JOIN c3
ON c3.X = cte.X
ORDER BY cte.row_nb
declare #t table (x tinyint, y char(1), z tinyint)
insert #t (x,y) values(1,'a'),(1,'b'),(1,'c'),(2,'a'),(2,'d'),(2,'e'),(2,'c'),
(2,'f'),(4,'b'),(5,'i'),(5,'c'),(3,'g'),(3,'h'),(6,'j'),(6,'k'),(6,'l'),(7,'v')
;with a as
(
select x,parent from
(
select x, min(x) over (partition by y) parent from #t
) a
where x > parent
), b as
(
select x, parent from a
union all
select a.x, b.parent
from a join b on a.parent = b.x
), c as
(
select x, min(parent) parent
from b
group by x
), d as
(
select t.x,t.y, t.z,
dense_rank() over (order by coalesce(c.parent, t.x)) calculatedZ
from #t t
left join c on t.x = c.x
)
select x,y,calculatedZ as z from d
-- if you want to update instead of selecting, replace last line with:
-- update d set z = newz
-- select x,y,z from #t
option (maxrecursion 0)
Result:
x y z
1 a 1
1 b 1
1 c 1
2 a 1
2 d 1
2 e 1
2 c 1
2 f 1
4 b 1
5 i 1
5 c 1
3 g 2
3 h 2
6 j 3
6 k 3
6 l 3
8 j 3
7 v 4