How to convert many rows into Columns in SQL Server? - sql

How would you convert a field that is stored as multiple rows into columns?
I listed the code below as well. Below is an example of what is needed but it can really go up to 20 columns. Thanks!
COL1 COL2 COL3
----------------
TEST 30 NY
TEST 30 CA
TEST2 10 TN
TEST2 10 TX
I would like the output to be :
COL1 COL2 COL3 COL4
------------------------
TEST 30 NY CA
TEST2 10 TN TX
select * from (
select
ID,
Name,
STORE,
Group,
Type,
Date,
State,
row_number() over(partition by ID, state order by Date desc) as rn
from
#test
) t
where t.rn = 1

declare #Table AS TABLE
(
Col1 VARCHAR(100) ,
Col2 INT ,
Col3 VARCHAR(100)
)
INSERT #Table
( Col1, Col2, Col3 )
VALUES
( 'TEST', 30 ,'NY' ),
( 'TEST', 30 ,'CA' ),
( 'TEST2', 10 ,'TN' ),
( 'TEST2', 10 ,'TX' )
SELECT
xQ.Col1,
xQ.Col2,
MAX(CASE WHEN xQ.RowNumber = 1 THEN xQ.Col3 ELSE NULL END) AS Col3,
MAX(CASE WHEN xQ.RowNumber = 2 THEN xQ.Col3 ELSE NULL END) AS Col4
FROM
(
SELECT * , RANK() OVER(PARTITION BY T.Col1,T.Col2 ORDER BY T.Col1,T.Col2,T.Col3) AS RowNumber
FROM #Table AS T
)AS xQ
GROUP BY
xQ.Col1,
xQ.Col2

There are multiple options to convert data from rows into columns. In SQL, you can use PIVOT to transform data from rows into columns.
CREATE table #tablename
(Id int, Value varchar(10), ColumnName varchar(15);
INSERT INTO #tablename
(ID, Value, ColumnName)
VALUES
(1, ‘Lucy’, 'FirstName'),
(2, ‘James’, ‘LastName’),
(3, ‘ABCDXX’, ‘Adress’),
(4, ’New York’, ‘City’),
(5, '8572685', ‘PhoneNo’);
select FirstName, LastName, Address, City, PhoneNo
from
(
select Value, ColumnName
from #tablename
) d
pivot
(
max(Value)
for ColumnName in (FirstName, LastName, Address, City, PhoneNo)
) piv;
Refer the below link for other options of transforming data from rows to columns:
https://www.sqlshack.com/multiple-options-to-transposing-rows-into-columns/

Related

How to write a SQL query for the below?

I have two tables with n of columns from Col1 to Col30
Table 1.
Templateid Col1 Col2 Col3 Col4 ...
95 2019-05-28 1234 test123 123456
Table 2.
Templateid DisplayName ColumnName
95 date col1
95 rank col2
95 purpose col3
95 sign col4
Expected Results.
Col1Name Col1Value Col2Name Col2Value Col3Name Col3Value ....
date 2019-05-28 rank 1234 purpose test123
This is a crude way of doing it and if you do not know the number of columns in each table you would need to use dynamic sql to enumerate them out but for the purposes of this example I have assumed you do know the number of columns and the names you want to populate.
The union query allows you to pre-populate the desired column names using the col1 syntax, then the pivot allows you to match up the displaynames and the display values. A case statement is required to ensure the correct values are shown and you do need to populate your derived column names for the pivot query but you do get the desired outcome this way.
declare #table1 table (
Templateid int,
Col1 date,
col2 int,
col3 nvarchar(10),
col4 int
);
insert into #table1 (Templateid, col1, col2, col3, col4)
values
(95, '2019-05-28', '1234', 'test123', '123456');
declare #table2 table (
Templateid int,
Displayname nvarchar(10),
ColumnName nvarchar(10)
);
insert into #table2 (Templateid, Displayname, ColumnName)
values
(95, 'date', 'col1'),
(95, 'rank', 'col2'),
(95, 'purpose', 'col3'),
(95, 'sign', 'col4');
select * from
(
select columnname+'Name' as columnname, Displayname
from #table2 t2
union
select columnname+'Value', case when columnname='col1' then cast(col1 as nvarchar(15))
when columnname='col2' then cast(col2 as nvarchar(15))
when columnname='col3' then cast(col3 as nvarchar(15))
when columnname='col4' then cast(col4 as nvarchar(15)) end
from #table1 t1 inner join #table2 t2 on t1.Templateid=t2.Templateid) src
pivot
(max(displayname) for columnname in ([col1Name],[col1Value], [col2Name],[col2Value], [col3Name],[col3Value], [col4Name],[col4Value])) piv;

Get top n row of each group of two columns

This question is different from Get top 1 row of each group. In my question, each group is consists of two columns (col1, col2), while in his question each group is consists of only one column (col1). I also tried to modify the answer in his question but failed.
Example:
Suppose n = 1
Input:
col1 col2 x Amt
A B x1 100
A B x2 200
C D x3 400
C D x4 500
...more data ...
Output:
col1 col2 x Amt
A B x2 200
C D x4 500
...more data ...
What I tried ...select *, row_numne() over ( partition by (col1, col2) order by ...
You can still use row_number within a CTE. The idea is to get all the rows, per your grouping, that is <= the number you pass in. This is similar to getting the top n rows for your pairing based on the order of AMT
declare #count int = 1
with cte as(
select
col1,
col2,
x,
Amt,
row_number() over (partition by col1, col2 order by Amt desc) as rn
from yourTable)
select
col1,
col2,
x,
Amt
from cte
where rn <= #count
why not simple max works for you?
select col1, col2, max(x), Max(Amt) from yourtable
group by col1, col2
Declare #Top int = 1
Select col1,col2,x,Amt
From (
Select *
,RN=Row_Number() over (Partition By Col1,Col2 Order By Amt Desc)
From YourTable ) A
Where RN<=#Top
Returns
col1 col2 x Amt
A B x2 200
C D x4 500
And here is the CROSS APPLY option, with test tables to confirm its functionality:
DECLARE #MyTable TABLE (Col1 varchar(4) not null, Col2 varchar(4) not null, x varchar(8) not null, amt int not null)
INSERT INTO #myTable VAlues ('A', 'B', 'x1', 100)
INSERT INTO #myTable VAlues ('A', 'B', 'x2', 200)
INSERT INTO #myTable VAlues ('C', 'D', 'x4', 400)
INSERT INTO #myTable VAlues ('C', 'D', 'x3', 500)
DECLARE #n int
SET #n = 1
SELECT DISTINCT
m.Col1,
m.Col2,
m2.x,
m2.Amt
FROM #MyTable m
CROSS APPLY (
SELECT TOP(#n) Amt, x
FROM #MyTable
WHERE col1 = m.Col1
AND col2 = m.col2
ORDER BY Amt Desc, x Desc
) m2

How to create separate rows for each unique value in source data

I have following table:
Cus_ID Work_Phone Home_Phone Mobile_Phone
1 x Blank x
2 x x Blank
3 x x x
.
.
. and so on (1000s of rows)
Work_Phone, Home_Phone, Mobile_Phone - varchar
x = some value present
I need to select from Source data to move it Target system like below, I need to create separate row for unique values for each Cus_ID. How do i do it?
Cus_ID Type ContactNo
1 Work x
1 Mobile x
2 Work x
2 Home x
3 Work x
3 Home x
3 Mobile x
.. and so on
Type, ContactNo - varchar
x = Should be the corresponding value from Source table
above result we can achieve using UNPIVOT or Cross Apply also by basing on your assumed data
declare #t table (PK varchar(1),col1 varchar(1),col2 varchar(1),col3 varchar(1))
insert into #t(PK,col1,col2,col3)values
('X','a','','c'),
('y','a','b',''),
('z','a','b','c')
Cross Apply :
select PK,value
from #t
cross apply
(
values
('I1', col1),
('I2', col2),
('I3', col3)
) c(col, value)
where value is not null AND value <> ''
order by PK, col
UNPIVOT
select PK,value
from #t
unpivot
(
value
for col in (col1, col2, col3)
) un
WHERE value <> ''
order by PK, col;
Assuming col1, col2 and col3 are of the same type, then:
SELECT pk, col2 AS target_value FROM your_table WHERE col2 IS NOT NULL
UNION
SELECT pk, col3 AS target_value FROM your_table WHERE col3 IS NOT NULL
UNION
SELECT pk, col4 AS target_value FROM your_table WHERE col4 IS NOT NULL
ORDER BY pk
Edit edit: here's the version with ISNULL tests, column headings and the rest, in response to your revised question:
SELECT Cus_ID, 'Work' AS Type, Work_Phone AS ContactNo FROM your_table
WHERE ISNULL(Work_Phone, '') <> ''
UNION
SELECT Cus_ID, 'Home' AS Type, Home_Phone AS ContactNo FROM your_table
WHERE ISNULL(Home_Phone, '') <> ''
UNION
SELECT Cus_ID, 'Mobile' AS Type, Mobile_Phone AS ContactNo FROM your_table
WHERE ISNULL(Mobile_Phone, '') <> ''
ORDER BY 1
If there's a chance the "blank" column may contain whitespace characters, then refine it yet further to:
... ISNULL(LTRIM(Work_Phone), '') <> ''
etc.

I have a SQL table with multiple rows that I want into one row with multiple columns

I have a SQL table that has mulitiple rows of data for a user. I want to query that data and return one row for each user. So I want to take the multiple rows and combine them into one row with multiple columns. Is this possible?
Here is what I currently have
UserID Value
8111 396285
8111 812045789854
8111 Secretary
Here is what I am after
UserID Column1 Column2 Column3
8111 396285 812045789854 Secretary
You can use the PIVOT function to get the result. I used the row_number() function to generate the values that will be converted to columns.
If you know how many values you will have ahead of time, then you can hard-code the query:
select userid, Col1, Col2, Col3
from
(
select userid, value,
'Col'+cast(row_number() over(partition by userid
order by (select 1)) as varchar(10)) rn
from yt
) d
pivot
(
max(value)
for rn in (Col1, Col2, Col3)
) piv;
See SQL Fiddle with Demo.
If you have an unknown number of values, then you can use dynamic SQL:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME('Col'+cast(row_number() over(partition by userid
order by (select 1)) as varchar(10)))
from yt
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT userid,' + #cols + '
from
(
select userid, value,
''Col''+cast(row_number() over(partition by userid
order by (select 1)) as varchar(10)) rn
from yt
) x
pivot
(
max(value)
for rn in (' + #cols + ')
) p '
execute(#query);
See SQL Fiddle with Demo. Both give the result:
| USERID | COL1 | COL2 | COL3 |
----------------------------------------------
| 8111 | 396285 | 812045789854 | Secretary |
If you cannot or don't want to use PIVOT/UNPIVOT another option would be to join the columns one by one to the users:
DECLARE #Users TABLE(
UserID int NOT NULL
)
INSERT INTO #Users (UserID) VALUES (1)
INSERT INTO #Users (UserID) VALUES (2)
INSERT INTO #Users (UserID) VALUES (3)
DECLARE #AnyTable TABLE(
UserID int NOT NULL,
FieldNo int NOT NULL,
Value varchar(50) NULL
)
INSERT INTO #AnyTable (UserID, FieldNo, Value) VALUES (1, 1, 'abc')
INSERT INTO #AnyTable (UserID, FieldNo, Value) VALUES (1, 2, 'def')
INSERT INTO #AnyTable (UserID, FieldNo, Value) VALUES (1, 3, 'ghi')
INSERT INTO #AnyTable (UserID, FieldNo, Value) VALUES (2, 1, '123')
INSERT INTO #AnyTable (UserID, FieldNo, Value) VALUES (2, 3, '789')
SELECT u.UserID,
col1.Value as Column1,
col2.Value as Column2,
col3.Value as Column3
FROM #Users u
LEFT JOIN #AnyTable col1
ON col1.UserID = u.UserID
AND col1.FieldNo = 1
LEFT JOIN #AnyTable col2
ON col2.UserID = u.UserID
AND col2.FieldNo = 2
LEFT JOIN #AnyTable col3
ON col3.UserID = u.UserID
AND col3.FieldNo = 3
The result would be:
UserID Column1 Column2 Column3
1 abc def ghi
2 123 NULL 789
3 NULL NULL NULL

How to combine multiple rows into one with nulled values where row values differ

How can I do with SQL Server to get a single row where the only non-null values are the ones that are consistent and non-null through all the selected rows.
A B C D
10 NULL text NULL
4 abc text NULL
4 def text NULL
Should give the following row:
A B C D
NULL NULL text NULL
create table #t (col1 int, col2 char(3), col3 char(4), col4 int)
go
insert into #t select 10, null, 'text', null
insert into #t select 4, 'abc', 'text', null
insert into #t select 4, 'def', 'text', null
go
select
case when count(distinct isnull(col1, 0)) > 1 then null else max(col1) end as 'col1',
case when count(distinct isnull(col2, '')) > 1 then null else max(col2) end as 'col2',
case when count(distinct isnull(col3, '')) > 1 then null else max(col3) end as 'col3',
case when count(distinct isnull(col4, 0)) > 1 then null else max(col4) end as 'col4'
from
#t
go
drop table #t
go
EDIT: I added ISNULL to handle the issue identified by t-clausen.dk but this will only work if the 'default' values (i.e. zero and empty string) do not appear in the real data.
Daniel's comment about data types is also correct, but since we don't know the data types involved it's not easy to suggest an alternative. Providing a self-contained test script that uses the real data types is the best way to ask questions like this.
declare #t table(A int, b varchar(10), c varchar(max), d int)
insert #t values(10, null, 'text', null)
insert #t values(4, 'abc', 'text', null)
insert #t values(10, 'def', 'text', null)
select case when max(rna) > 1 then null else min(a) end,
case when max(rnb) > 1 then null else min(b) end,
case when max(rnc) > 1 then null else min(c) end,
case when max(rnd) > 1 then null else min(d) end
from
(
select rna = rank() over(order by a),
rnb = rank() over(order by b),
rnc = rank() over(order by c),
rnd = rank() over(order by d),
a, b,c,d
from #t
) e
If you have text columns replace the column type with varchar(max). Text columns are outdated.
Using count(distinct col1) was by first thought, but it doesn't count null values.
select count(distinct a) from (select cast(null as int) a) b
returns 0 rows
SELECT
CASE WHEN COUNT(DISTINCT col1) = 1
AND COUNT(col1) = COUNT(*)
THEN MIN(col1)
END AS col1
, CASE WHEN COUNT(DISTINCT col2) = 1
AND COUNT(col2) = COUNT(*)
THEN MIN(col2)
END AS col2
, CASE WHEN COUNT(DISTINCT col3) = 1
AND COUNT(col3) = COUNT(*)
THEN MIN(col3)
END AS col3
, CASE WHEN COUNT(DISTINCT col4) = 1
AND COUNT(col4) = COUNT(*)
THEN MIN(col4)
END AS col4
FROM
tableX