How to convert table columns to rows [duplicate] - sql-server

This question already has answers here:
Convert Rows to columns using 'Pivot' in SQL Server
(9 answers)
Closed 6 years ago.
I have a table like below:
+-----------+------------+--------+
| Col1 | Col2 | Col3 |
+-----------+------------+--------+
| 12345678 | FirstName | John |
| 12345678 | LastName | Smith |
| 987456321 | LastName | Clancy |
| 987456321 | MiddleName | T |
| 987456321 | Height | 176cm |
| 654125878 | FirstName | Tan |
| 654125878 | Weight | 150lb |
+-----------+------------+--------+
How to convert that to:
+-----------+-----------+------------+----------+--------+--------+
| ID | FirstName | MiddleName | LastName | Height | Weight |
+-----------+-----------+------------+----------+--------+--------+
| 12345678 | John | null | Smith | null | null |
| 987456321 | null | T | Clancy | 176cm | null |
| 654125878 | Tan | null | null | null | 150lb |
+-----------+-----------+------------+----------+--------+--------+

I think a conditional aggregation would do the trick here.
Assuming you don't need dynamic
Select ID = Col1
,FirstName = max(case when Col2='FirstName' then Col3 else null end)
,MiddleName = max(case when Col2='MiddleName ' then Col3 else null end)
,LastName = max(case when Col2='LastName ' then Col3 else null end)
,Height = max(case when Col2='Height' then Col3 else null end)
,Weight = max(case when Col2='Weight' then Col3 else null end)
From YourTable
Group By Col1
If you need dynamic
Declare #SQL varchar(max) = Stuff((Select Distinct ',' + QuoteName([Col2]) From Yourtable Order by 1 For XML Path('')),1,1,'')
Select #SQL = '
Select [Col1] as ID ,' + #SQL + '
From YourTable
Pivot (max(Col3) For [Col2] in (' + #SQL + ') ) p'
Exec(#SQL);
If it helps, the generated SQL for the dynamic Pivot is as follows:
Select [Col1] as ID ,[FirstName],[Height],[LastName],[MiddleName],[Weight]
From YourTable
Pivot (max(Col3) For [Col2] in ([FirstName],[Height],[LastName],[MiddleName],[Weight]) ) p

Related

Pivot table with custom values

i have table say FIDDLE HERE
+----+------+------+-----+-----+
| id | year | sell | buy | own |
+----+------+------+-----+-----+
| 1 | 2016 | 9 | 2 | 10 |
| 1 | 2017 | 9 | | 10 |
| 1 | 2018 | | 2 | 10 |
| 2 | 2016 | 7 | 2 | 11 |
| 2 | 2017 | 2 | | |
| 2 | 2018 | | | 18 |
+----+------+------+-----+-----+
create table test(id varchar(20), year varchar(20),
sell varchar(20), buy varchar(20),
own varchar(20));
insert into test values('1', '2016','9','2','10' )
insert into test values('1', '2017','9',NULL,'10' )
insert into test values('1', '2018',NULL,'2','10' )
insert into test values('2', '2016','7','2','11' )
insert into test values('2', '2017','2',NULL,'17' )
insert into test values('2', '2018','5','2','18' )
I'm trying to PIVOT but instead of aggregate the values, i wanted to keep some letters if it is not null (S-Sell,B-Buy,O-Own). If there are values for all columns for particular year then i need S_B_O for that year. If there are values only for sell and buy then S_B etc., so Expected output is
+----+-------+------+------+
| ID | 2016 | 2017 | 2018 |
+----+-------+------+------+
| 1 | S_B_O | S_O | B_O |
+----+-------+------+------+
| 2 | S_B_O | S | O |
+----+-------+------+------+
The closest i have got is using conditional aggrgarion( MAX and concat) instead of PIVOT but this is also giving null if any one is NULL. Please suggest a solution.
select ID,
MAX(CASE WHEN Year = '2016' AND sell is not null THEN 'S_' END +
CASE WHEN Year = '2016' AND buy is not null THEN 'B_' END +
CASE WHEN Year = '2016' AND own is not null THEN 'O' END)
AS [2016],
MAX(CASE WHEN Year = '2017' AND sell is not null THEN 'S_' END +
CASE WHEN Year = '2017' AND buy is not null THEN 'B_' END +
CASE WHEN Year = '2017' AND own is not null THEN 'O' END)
AS [2017]
/* ......for all year */
from test
group by id
FIDDLE HERE
You can use CONCAT function, which will handle NULLs automatically.
select ID,
CONCAT(MAX(CASE WHEN Year = '2016' AND sell is not null THEN 'S_' END) ,
MAX(CASE WHEN Year = '2016' AND buy is not null THEN 'B_' END) ,
MAX(CASE WHEN Year = '2016' AND buy is not null THEN 'O' END))
AS [2016],
CONCAT(MAX(CASE WHEN Year = '2017' AND sell is not null THEN 'S_' END) ,
MAX(CASE WHEN Year = '2017' AND buy is not null THEN 'B_' END) ,
MAX(CASE WHEN Year = '2017' AND buy is not null THEN 'O' END))
AS [2017]
from test
group by id
+----+-------+------+
| ID | 2016 | 2017 |
+----+-------+------+
| 1 | S_B_O | S_ |
| 2 | S_B_O | S_ |
+----+-------+------+
UPDATE Dynamic query. As #Larnu told, You should have asked this as separate question. You should not change the requirement.
DECLARE #lst_Years NVARCHAR(MAX) , #query NVARCHAR(MAX)
SET #lst_Years = STUFF((SELECT distinct ',' + QUOTENAME([Year])
FROM test
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
SET #query = 'SELECT * FROM
(
select ID, [Year],
CONCAT(MAX(CASE WHEN sell is not null THEN ''S_'' END) ,
MAX(CASE WHEN buy is not null THEN ''B_'' END) ,
MAX(CASE WHEN buy is not null THEN ''O'' END))
AS [Value]
from test
group by id, [year]) as t
pivot
(
max(value) FOR YEAR IN (' + #lst_Years + ')
) as pvt'
EXEC(#query)
+----+-------+------+-------+
| ID | 2016 | 2017 | 2018 |
+----+-------+------+-------+
| 1 | S_B_O | S_ | B_O |
| 2 | S_B_O | S_ | S_B_O |
+----+-------+------+-------+

Compare historical rows (LAG rows) and combine changed values to single column

Compare historical rows (LAG rows based on ResultChngDt) and combine changed column values to single column. Looking for help in writing elegant/efficient SQL Server 2016 TSQL Code(without cursors).
I have a table with the structure and data like this:
+----+-------+--------------+---------------+--------+--------+--------------+
| ID | RepID | CollctedDate | CompletedDate | Result | Tcode | ResultChngDt |
+----+-------+--------------+---------------+--------+--------+--------------+
| 1 | 101 | 11/20/2017 | 12/13/2017 | | L-2190 | 12/13/2017 |
| 1 | 101 | 11/22/2017 | 12/15/2017 | POS | L-Afb | 1/5/2018 |
| 1 | 102 | 11/22/2017 | 12/15/2017 | | L-2191 | 12/15/2017 |
| 1 | 102 | 11/22/2017 | 12/15/2017 | POS | L-2192 | 12/31/2017 |
+----+-------+--------------+---------------+--------+--------+--------------+
I need to generate a report/result as follows:
+----+-------+---------------------------+--------------------------+--+
| ID | RepID | Previous | Current | |
+----+-------+---------------------------+--------------------------+--+
| 1 | 101 | CollctedDate:11/20/2017 | CollctedDate:11/22/2017 | |
| | | CompletedDate:12/13/2017 | CompletedDate:12/15/2017 | |
| | | Result: | Result:POS | |
| | | Tcode:L-2190 | Tcode:L-Afb | |
| 1 | 102 | CollctedDate:11/22/2017 | CollctedDate:11/22/2017 | |
| | | CompletedDate:12/15/2017 | CompletedDate:12/15/2017 | |
| | | Result: | Result:POS | |
| | | Tcode:L-2191 | Tcode:L-2192 | |
+----+-------+---------------------------+--------------------------+--+
CREATE TABLE [dbo].[Table1]
(
[ID] INT NULL,
[RepID] INT NULL,
[CollctedDate] DATETIME NULL,
[CompletedDate] DATETIME NULL,
[Result] VARCHAR(3) NULL,
[Tcode] VARCHAR(10) NULL,
[ResultChngDt] DATETIME NULL
) ON [PRIMARY];
GO
INSERT INTO [dbo].[Table1] ([ID], [RepID], [CollctedDate], [CompletedDate], [Result], [Tcode], [ResultChngDt])
VALUES (1, 101, N'11/20/2017', N'12/13/2017', N'', N'L-2190', N'12/13/2017')
, (1, 101, N'11/22/2017', N'12/15/2017', N'POS', N'L-Afb', N'1/5/2018')
, (1, 102, N'11/22/2017', N'12/15/2017', N'', N'L-2191', N'12/15/2017')
, (1, 102, N'11/22/2017', N'12/15/2017', N'POS', N'L-2192', N'12/31/2017')
Here's my query for your question:
WITH cte_LEADLAG AS(
SELECT ID,
RepID,
CollctedDate,
CompletedDate,
Result,
Tcode,
ResultChngDt,
CONCAT('CollectedDate:',CAST(CollctedDate AS DATETIME2), ' CompletedDate:', CAST(CompletedDate AS DATETIME2), ' Result:', Result, ' Tcode', Tcode) AS dates,
LAG(CollctedDate) OVER(PARTITION BY RepID ORDER BY CollctedDate) AS 'LAGCollectedDate' ,
lead(CollctedDate) OVER(PARTITION BY RepID ORDER BY CollctedDate) AS 'LEADCollectedDate',
LAG(CompletedDate) OVER(PARTITION BY RepID ORDER BY CompletedDate) AS 'LAGCompDate' ,
lead(CompletedDate) OVER(PARTITION BY RepID ORDER BY CompletedDate) AS 'LEADcompDate' ,
LEAD(Result) OVER(PARTITION BY RepID ORDER BY CompletedDate) AS 'LEADResult' ,
LEAD(Tcode) OVER(PARTITION BY RepID ORDER BY CompletedDate) AS 'LEADTcode'
FROM #temp
),
cte_FINAL AS(
SELECT distinct ID,
RepID,
CASE WHEN cte.LAGCollectedDate IS NULL THEN CONCAT('CollectedDate:',CAST(CollctedDate AS DATETIME2), ' CompletedDate:', CAST(CompletedDate AS DATETIME2), ' Result:', Result, ' Tcode', Tcode) end AS 'Previous',
CASE WHEN cte.LEADCollectedDate IS not NULL THEN CONCAT('CollectedDate:',CAST(cte.LEADCollectedDate AS DATETIME2), ' CompletedDate:', CAST(LEADcompDate AS DATETIME2), ' Result:', cte.LEADResult, ' Tcode', cte.LEADTcode) end AS 'Current'
FROM cte_LEADLAG AS cte
WHERE cte.LEADCollectedDate IN (SELECT MAX(LEADCollectedDate) FROM cte_LEADLAG WHERE cte_LEADLAG.RepID = cte.RepID))
)
SELECT *
FROM cte_FINAL;
Result:
with data as (
select *, row_number() over (partition by RepID order by ResultChgDt desc) as rn
from dbo.Table1
)
select
from data as d1 left outer join data as d2 on d2.rn = d1.rn + 1
where d1.rn = 1 -- I suppose you only want the two most recent??
This gives you all the data you need in a single row. You can handle report formatting to suit whatever requirements you have in whatever tool you're using for that.

SQL Server - Multiple rows to columns by unique identifier

I have the following database table:
| Article | Material |
|------------|-------------|
| article001 | material001 |
| article001 | material002 |
| article002 | material002 |
| article002 | material003 |
| article002 | material004 |
| article003 | material002 |
| article004 | material003 |
| article004 | material004 |
| article004 | material005 |
| article004 | material006 |
I want to achive a result like this:
| Article | Material1 | Material2 | Material3 | Material4 |
|------------|-------------|-------------|-------------|-------------|
| article001 | material001 | material002 | | |
| article002 | material002 | material003 | material004 | |
| article003 | material002 | | | |
| article004 | material003 | material004 | material005 | material006 |
I have already tried various possibilities (Pivot, CTE, Temp Tables). Unfortunately I do not get it. I'm pretty new to SQL.
A simple solution is a conditional aggregation (if you have a max number of materials). Let me know if you need to dynamic.
Select Article
,Material1 = max(case when RN=1 then Material else '' end)
,Material2 = max(case when RN=2 then Material else '' end)
,Material3 = max(case when RN=3 then Material else '' end)
,Material4 = max(case when RN=4 then Material else '' end)
From (
Select *
,RN = Row_Number() over (Partition By Article Order by Material)
From YourTable
) A
Group By Article
Returns
EDIT - Dynamic Pivot
Declare #SQL varchar(max) = Stuff((Select Distinct ',' + QuoteName('Material'+cast(RN as varchar(25))) From (Select Distinct RN = Row_Number() over (Partition By Article Order by Material) From Yourtable) A Order by 1 For XML Path('')),1,1,'')
Select #SQL = '
Select [Article],' + #SQL + '
From (
Select Article,Material
,Item = ''Material''+cast(Row_Number() over (Partition By Article Order by Material) as varchar(25))
From YourTable
) A
Pivot (max(Material) For [Item] in (' + #SQL + ') ) p'
Exec(#SQL);

Generating unique identifiers as a set based query

I am moving a bunch of code over from entirely cursor based to set based and generating this has been doing my head in. We create a 6 character shortcode (unique) for each company inserted into the database and I (want) to achieve this outside of a cursor.
Example of where I am at so far:
CREATE TABLE #customers (name VARCHAR(50), shortname VARCHAR(10))
INSERT INTO #customers VALUES
('Michael Smith', 'Michae')
,('Michael Douglas', 'Mich_1')
,('Michael Yang', 'Mich_2')
CREATE TABLE #newcustomers (name VARCHAR(50), shortname VARCHAR(10) NULL)
INSERT INTO #newcustomers (name) VALUES
('Michael Black')
,('Michael White')
SELECT * FROM #customers
SELECT * FROM #newcustomers
DECLARE #shortname VARCHAR(10)
DECLARE #iteration INT = 0
WHILE EXISTS(SELECT shortname FROM #customers WHERE shortname = #shortname)
BEGIN
SELECT #shortname = LEFT(name, 6) FROM #newcustomers
UPDATE #newcustomers SET shortname = #shortname
SET #shortname = LEFT(#shortname, 4) + '_' + #iteration
SET #iteration = #iteration + 1
END
Hopefully the example is sufficient in identifying where I am trying to get to, any suggestions or examples would be very helpful. My example does not work.
Try this
Your table as mock-up
CREATE TABLE #customers (ID INT IDENTITY, name VARCHAR(50), shortname VARCHAR(10))
INSERT INTO #customers VALUES
('Michael Smith', 'Michae')
,('Michael Douglas', 'Mich_1')
,('Michael Yang', 'Mich_3')
,('Testman', 'Testma')
,('Testman1', 'Test_1');
CREATE TABLE #newcustomers (ID INT IDENTITY,name VARCHAR(50), shortname VARCHAR(10) NULL)
INSERT INTO #newcustomers (name) VALUES
('Michael Black')
,('Michael White')
,('Testman2')
,('Someone new');
--This CTE will combine all existing names
WITH AllNames AS
(
SELECT '1_old' AS datasource,ID,name,shortname FROM #customers
UNION ALL SELECT '2_new',ID,name,shortname FROM #newcustomers
)
--This CTE will use the combined list and calculate the right "index"
,ShortNames AS
(
SELECT c.*
,A.First6
,ROW_NUMBER() OVER(PARTITION BY A.First6 ORDER BY datasource,ID) AS NrTotal
,ROW_NUMBER() OVER(PARTITION BY datasource,A.First6 ORDER BY datasource,ID) AS Nr
,CASE WHEN ISNUMERIC(SUBSTRING(shortname+' ',6,10))=1
THEN CAST(SUBSTRING(shortname+' ',6,10) AS INT) ELSE 0 END AS ExistIndex
FROM AllNames AS c
CROSS APPLY(SELECT LEFT(name + ' ',6)) AS A(First6)
)
--All new with NrTotal=1 get the 6 letters as is, all other get the index
SELECT *
,CASE WHEN datasource='1_old' THEN shortname ELSE
CASE WHEN datasource='2_new' AND NrTotal=1 THEN First6
ELSE LEFT(First6,4) + '_' + CAST(Nr + (SELECT ISNULL(MAX(x.ExistIndex),1)
FROM ShortNames AS x
WHERE x.First6=ShortNames.First6) AS VARCHAR(5))
END
END
FROM ShortNames
GO
DROP TABLE #customers;
DROP TABLE #newcustomers;
The result
+------------+----+-----------------+-----------+--------+---------+----+------------+--------------------+
| datasource | ID | name | shortname | First6 | NrTotal | Nr | ExistIndex | (Kein Spaltenname) |
+------------+----+-----------------+-----------+--------+---------+----+------------+--------------------+
| 1_old | 1 | Michael Smith | Michae | Michae | 1 | 1 | 0 | Michae |
+------------+----+-----------------+-----------+--------+---------+----+------------+--------------------+
| 1_old | 2 | Michael Douglas | Mich_1 | Michae | 2 | 2 | 1 | Mich_1 |
+------------+----+-----------------+-----------+--------+---------+----+------------+--------------------+
| 1_old | 3 | Michael Yang | Mich_3 | Michae | 3 | 3 | 3 | Mich_3 |
+------------+----+-----------------+-----------+--------+---------+----+------------+--------------------+
| 1_old | 4 | Testman | Testma | Testma | 1 | 1 | 0 | Testma |
+------------+----+-----------------+-----------+--------+---------+----+------------+--------------------+
| 1_old | 5 | Testman1 | Test_1 | Testma | 2 | 2 | 1 | Test_1 |
+------------+----+-----------------+-----------+--------+---------+----+------------+--------------------+
| 2_new | 1 | Michael Black | NULL | Michae | 4 | 1 | 0 | Mich_4 |
+------------+----+-----------------+-----------+--------+---------+----+------------+--------------------+
| 2_new | 2 | Michael White | NULL | Michae | 5 | 2 | 0 | Mich_5 |
+------------+----+-----------------+-----------+--------+---------+----+------------+--------------------+
| 2_new | 4 | Someone new | NULL | Someon | 1 | 1 | 0 | Someon |
+------------+----+-----------------+-----------+--------+---------+----+------------+--------------------+
| 2_new | 3 | Testman2 | NULL | Testma | 3 | 1 | 0 | Test_2 |
+------------+----+-----------------+-----------+--------+---------+----+------------+--------------------+
One option is to use a computed column.
A table design along these lines would work:
- Sample table.
DECLARE #Sample TABLE
(
Id INT IDENTITY(1, 1),
FullName VARCHAR(255),
ShortName AS LEFT(FullName, 4) + '_' + CAST(Id AS VARCHAR(255))
)
;
-- Add set containing two companies.
INSERT INTO #Sample
(
FullName
)
VALUES
('ABC LTD'),
('XYZ PLC')
;
Returns
Id FullName ShortName
1 ABC LTD ABC _1
2 XYZ PLC XYZ _1
The Id and ShortName columns will be managed by SQL Server. You only need to add the FullName.
EDIT
Reworked example using table variable, to make it easier to play along.

T-SQL Transposing column headers into Rows

I want to transpose my table.
I have simple 'Person' table as shown below.
+---+----------+------------+------------+----------------------+
|ID | Person | BirthDate | Phone | Email |
+---+----------+------------+------------+----------------------+
| 1 | Tom | 1985-11-08 | 1111111111 | tom#somedomain.com |
+---+----------+------------+------------+----------------------+
| 2 | Dick | 1982-02-24 | 2222222222 | dick#otherdomain.com |
+---+----------+------------+------------+----------------------+
| 3 | Harry | 1986-04-17 | 3333333333 | harry#thatdomain.com |
+---+----------+------------+------------+----------------------+
And I want this table to be transposed like below.
+-----------+--------------------+----------------------+----------------------+
| Key | Value1 | Value2 | Value3 |
+-----------+--------------------+----------------------+----------------------+
| ID | 1 | 2 | 3 |
+-----------+--------------------+----------------------+----------------------+
| Person | Tom | Dick | Harry |
+-----------+--------------------+----------------------+----------------------+
| BirthDate | 1985-11-08 | 1982-02-24 | 1986-04-17 |
+-----------+--------------------+----------------------+----------------------+
| Phone | 1111111111 | 2222222222 | 3333333333 |
+-----------+--------------------+----------------------+----------------------+
| Email | tom#somedomain.com | dick#otherdomain.com | harry#thatdomain.com |
+-----------+--------------------+----------------------+----------------------+
I am using MS SQL server 2008 R2.
Try this.. First u need to unpivot the columns using Cross apply to get the data in single row. Then pivot that row to get the result.
CREATE TABLE #tt
(ID INT,Person VARCHAR(50),BirthDate DATE,
Phone BIGINT,Email VARCHAR(50)
)
INSERT INTO #tt
VALUES (1,'Tom','1985-11-08',1111111111,'tom#somedomain.com' ),
( 2,'Dick','1982-02-24',2222222222,'dick#otherdomain.com'),
( 3,'Harry ','1986-04-17',3333333333,'harry#thatdomain.com' )
SELECT [key],
Max([value1]) [value1],
Max([value2]) [value2],
Max([value3]) [value3]
FROM (SELECT 'value' + CONVERT(VARCHAR(30), id) valued,
*
FROM #tt
CROSS apply (VALUES ('ID',
CONVERT(VARCHAR(50), ID)),
('Person',Person),
('BirthDate',CONVERT(VARCHAR(50), BirthDate)),
('Phone',CONVERT(VARCHAR(50), Phone)),
('Email',Email)) cp ([key], data))a
PIVOT (Max(data)
FOR valued IN([value1],[value2],[value3])) piv
GROUP BY [key]
DYNAMIC VERSION
Declare #cols varchar(max)='',#aggcols varchar(max)='',#sql nvarchar(max)
SELECT #cols+= ',value' + CONVERT(VARCHAR(30), id)
FROM #tt
SELECT #aggcols+= ',max([value' + CONVERT(VARCHAR(30), id) +']) value' + CONVERT(VARCHAR(30), id)
FROM #tt
select #cols= right(#cols,LEN(#cols)-1)
select #aggcols =right(#aggcols,LEN(#aggcols)-1)
set #sql = 'SELECT [key],
'+#aggcols+'
FROM (SELECT ''value'' + CONVERT(VARCHAR(30), id) valued,
*
FROM #tt
CROSS apply (VALUES (''ID'',CONVERT(VARCHAR(50), ID)),
(''Person'',Person),
(''BirthDate'',CONVERT(VARCHAR(50), BirthDate)),
(''Phone'',CONVERT(VARCHAR(50), Phone)),
(''Email'',Email)) cp ([key], data))a
PIVOT (Max(data)
FOR valued IN('+#cols+')) piv
GROUP BY [key] '
execute sp_executesql #sql
OUTPUT
+----------+--------------------+---------------------+----------------------+
|key | value1 | value2 | value3 |
+----------+--------------------+---------------------+----------------------+
|BirthDate | 1985-11-08 | 1982-02-24 | 1986-04-17 |
+----------+--------------------+---------------------+----------------------+
|Email | tom#somedomain.com |dick#otherdomain.com | harry#thatdomain.com |
+----------+--------------------+---------------------+----------------------+
|ID | 1 | 2 | 3 |
+----------+--------------------+---------------------+----------------------+
|Person | Tom | Dick | Harry |
+----------+--------------------+---------------------+----------------------+
|Phone | 1111111111 | 2222222222 | 3333333333 |
+----------+--------------------+---------------------+----------------------+

Resources