SQL Server Rows to Multi-Columns - sql

I have the following table and data:
CREATE TABLE SourceTbl ([Code] varchar(3), [Total] decimal, [Date] datetime );
INSERT INTO SourceTbl ([Code], [Total], [Date])
VALUES ('AA', 100, '2012-12-01'), ('AA', 200, '2013-02-01'), ('BB', 50, '2012-01-01');
A simple select will return
Code | Total | Date
'AA' | 100 | 2012-12-01
'AA' | 200 | 2013-02-01
'BB' | 50 | 2012-01-01
but what I need is the following
Code | Total | Date | Total | Date
'AA | 200 | 2013-02-01 | 100 | 2012-12-01
'BB | 50 | 2012-01-01 | null | null
I have been trying to do this using a PIVOT operator but without success (based on the question SQL Server Pivot multiple columns based on one column).
Using that example, all I get are two rows with null values.
The Total/Date columns can be repeated 13 times and they must be ordered by Date DESC.
SQL Fiddle: http://sqlfiddle.com/#!3/f37a1/2
Any help is appreciated!
Thanks!

If you need just two columns:
with cte as (
select *, row_number() over(partition by Code order by Date) as rn
from SourceTbl
)
select
code,
max(case when rn = 1 then Total end) as Total1,
max(case when rn = 1 then Date end) as Date1,
max(case when rn = 2 then Total end) as Total2,
max(case when rn = 2 then Date end) as Date2
from cte
group by code
=> sql fiddle demo
dynamic solution:
declare #stmt nvarchar(max)
;with cte as (
select distinct
cast(row_number() over(partition by Code order by Date) as nvarchar(max)) as rn
from SourceTbl
)
select #stmt = isnull(#stmt + ', ', '') +
'max(case when rn = ' + rn + ' then Total end) as Total' + rn + ',' +
'max(case when rn = ' + rn + ' then Date end) as Date' + rn
from cte
order by rn
select #stmt = '
with cte as (
select *, row_number() over(partition by Code order by Date) as rn
from SourceTbl
)
select
code, ' + #stmt + ' from cte group by code'
exec sp_executesql
#stmt = #stmt
=> sql fiddle demo

Are you trying to dynamically create columns in your result set?
If you had a third record of 'AA' with a total of 300 and the date of 03/01/2013 would you that mean you would want something like this displayed?
Code | Total | Date | Total | Date | Total | Date
AA | 200 | 2013-02-01 | 100 | 2012-12-01| 300 | 03-01-13
BB | 50 | 2012-01-01 | null | null | null | null

Related

Convert rows to column by date with pivot

I have read the stuff on MS pivot tables and I am still having problems getting this correct.
Data
wh_id | saledate | qty |
105 | 20190901 | 134.000000 |
105 | 20190902 | 190.000000 |
105 | 20190903 | 148.500000 |
105 | 20190904 | 157.500000 |
105 | 20190905 | 209.500000 |
I would like it to come out as a pivot table, like this:
wh_id | 1 | 2 | 3 | 4 | 5 |
105 | 134 | 190 |148.5 | 157.5 | 209.5 |
this the code :
DECLARE
#cols nvarchar(max)='' ,
#query nvarchar(max)=''
SET #cols = STUFF((SELECT ',' + QUOTENAME(DATEPART(dd, saledate))
FROM sales
WHERE month(saleDATE)=9 and year(saleDATE)=2019 and wh_id=105
GROUP BY saledate
ORDER BY saledate ASC
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'),1,1,'');
set #query = 'SELECT [wh_id], ' + #cols + '
from
(
select [wh_id], QUOTENAME(DATEPART(dd, saledate)) saledate,qty
from sales where month(saleDATE)=9 and year(saleDATE)=2019 and wh_id=105
) x
pivot
(
sum(qty)
for [saledate] in (' + #cols + ')
) p '
execute(#query);
but the result is like this
wh_id | 1 | 2 | 3 | 4 | 5 |
105 | 1 | 2 | 3 | 4 | 5 |
just change the source part from the above query remove QUOTENAME applied over saledate and try executing the query you will find the expected output.
set #query = 'SELECT [wh_id], ' + #cols + '
from
(
select [wh_id], DATEPART(dd, saledate) saledate,qty
from sales where month(saleDATE)=9 and year(saleDATE)=2019 and wh_id=105
) x
pivot
(
sum(qty)
for [saledate] in (' + #cols + ') ) p '
select wh_id,
max(case when rn = 1 then qty end) '1',
max(case when rn = 2 then qty end) '2',
max(case when rn = 3 then qty end) '3',
max(case when rn = 4 then qty end) '4',
max(case when rn = 5 then qty end) '5'
from
(
select wh_id,qty,
row_number() over(partition by wh_id order by qty) rn
from YourTableName
) src
group by wh_id
OutPut:-
Note:- Instead of using Pivot ...you can use this simple query...using ...case when and then with max aggregate function...
In above example....i'm forgot add decimal value in Table....

How to join same records in sql server

I have data like this :
| ID | NAME | PRICE
| 01 | TEST | 5000
| 01 | TEST | 10000
| 02 | EAST | 4500
| 03 | AEST | 5000
| 03 | AEST | 5000
I want to join that same records so the final result is like this :
| ID | NAME | PRICE1 | PRICE2
| 01 | TEST | 5000 | 10000
| 02 | EAST | 4500 | 0
| 03 | AEST | 5000 | 5000
SQL tables represent unordered sets. Given the information you have provided, there is no way to determine the price1 and price2 as you have specified. So, you might as well use MIN() and MAX():
select id, name, min(price) as price1, max(price) as price2
from t
group by id, name;
If you did have a column that specified the ordering, then you could use pivot or conditional aggregation:
select id, name,
max(case when seqnum = 1 then price end) as price1,
max(case when seqnum = 2 then price end) as price2
from (select t.*,
row_number() over (partition by id order by ??) as seqnum
from t
) t
group by id, name;
The ?? is for the column that specifies the ordering.
IF OBJECT_ID('Tempdb..#Temp') IS NOT NULL
Drop table #Temp
;With cte(ID,NAME,PRICE)
AS
(
SELECT 01 , 'TEST' , 5000 UNION ALL
SELECT 01 , 'TEST' , 10000 UNION ALL
SELECT 02 , 'EAST' , 4500 UNION ALL
SELECT 03 , 'AEST' , 5000 UNION ALL
SELECT 03 , 'AEST' , 5000
)
SELECT *, 'Price' + CAST(ROW_NUMBER()Over(PArtition by NAME Order by NAME) AS Varchar(5)) AS PriceCol INTO #Temp FROM cte
DECLARE #Coulmn nvarchar(max),
#Coulmn2 nvarchar(max),
#Sql nvarchar(max)
SELECT #Coulmn=STUFF((SELECT DISTINCT ', '+ '['+ PriceCol +']' From #Temp
FOR XML PATH ('')),1,1,'')
--SELECT #Coulmn
SELECT #Coulmn2=STUFF((SELECT DISTINCT ', '+ 'ISNULL(' + PriceCol + ',''0'')' +' AS ['+PriceCol +']' From #Temp
FOR XML PATH ('')),1,1,'')
--SELECT #Coulmn2
SET #Sql=' SELECT ID,NAME, '+#Coulmn2+ ' From
(
SELECT * From #Temp
)As Src
PIVOT
(
MAX(PRICE) FOR PriceCol IN ('+ #Coulmn +')
)Pvt
Order By Pvt.ID
'
Print #Sql
Exec(#Sql)

Rotate table in T-SQL

I have a table that contains sequential date in first column and type of date (CreatedOn and ClosedOn). I need to with SELECT that has 2 columns (CreatedOn, ClosedOn) from my table.
I have this:
| Date | ColumnName |
|------------|------------|
| 2017-01-01 | ClosedOn |
| 2017-01-02 | CreatedOn |
| 2017-01-03 | ClosedOn |
| 2017-01-04 | CreatedOn |
And I need to get this:
| CreatedOn | ClosedOn |
|------------|------------|
| NULL | 2017-01-01 |
| 2017-01-02 | 2017-01-03 |
| 2017-01-04 | NULL |
I've tried this:
SELECT
CASE [ColumnName]
WHEN 'CreatedOn' THEN [Date]
ELSE NULL
END,
CASE [ColumnName]
WHEN 'ClosedOn' THEN [Date]
ELSE NULL
END
FROM #Temp
but it doesn't work.
This is a typical case of using a PIVOT in SQL Server, to transpose rows into columns:
select *
from table1
pivot (max(colname) for colname in (ClosedOn, CreatedOn)) p
order by date
Try this and hope it helps. You may have to test it and modify as needed. But the logic if my understanding is correct should be sufficient to build on.
;WITH cte_TestData(Date,ColumnName) AS
(
SELECT '2017-01-01','ClosedOn ' UNION ALL
SELECT '2017-01-02','CreatedOn' UNION ALL
SELECT '2017-01-03','ClosedOn ' UNION ALL
SELECT '2017-01-04','CreatedOn'
)
,cte_PreserveSeq AS
(
SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS SeqID,Date,ColumnName
FROM cte_TestData
)
,cte_PreResult AS
(
SELECT *
,LEAD (ColumnName, 1,0) OVER (ORDER BY SeqID) AS NextColumnName
,LEAD (Date, 1,0) OVER (ORDER BY SeqID) AS NextDate
,LAG (ColumnName, 1,0) OVER (ORDER BY SeqID) AS PreviousColumnName
,LAG (Date, 1,0) OVER (ORDER BY SeqID) AS PreviousDate
FROM cte_PreserveSeq
)
SELECT DISTINCT
CASE
WHEN ColumnName = 'CreatedOn' AND NextColumnName = 'ClosedOn' THEN DATE
WHEN ColumnName = 'ClosedOn' AND PreviousColumnName = 'CreatedOn' THEN PreviousDate
WHEN ColumnName = 'CreatedOn' THEN DATE
ELSE NULL
END AS CreatedOn,
CASE
WHEN ColumnName = 'CreatedOn' AND NextColumnName = 'ClosedOn' THEN NextDate
WHEN ColumnName = 'ClosedOn' THEN DATE
ELSE NULL
END AS ClosedOn
FROM cte_PreResult
I'm assuming you have other columns, so let's do this
select A1.OtherColumn, A1.[Date], A2.Date
from #Temp A1
full outer join #Temp A2
on A1.OtherColumn = A2.OtherColumn
and A1.ColumnName = 'CreatedOn'
and A2.ColumnName = 'ClosedOn'
EDIT: If no other columns, try
with MyData as
(
select [Date], ColumnName , row_number() over (order by [Date], ColumnName desc) as rn
from #Temp
)
select M1.[Date], M2.[Date]
from MyData M1
full outer join MyData M2
on M2.rn = M1.rn + 1
and mod(M1.rn, 2) = 1

extend current query, calculated columns

My table looks for example like this:
Name date result
A 2012-01-01 1
A 2012-02-01 2
B 2013-01-01 1
...
For a full example: http://sqlfiddle.com/#!3/0226b/1
At the moment I have a working query that counts the rows by person and year: http://sqlfiddle.com/#!3/0226b/3
This is perfect, but what I want is some extra information for 2014. i need to count how many rows I have for every result.
something like this:
NAME 1 2 3 2014 2013 2012 TOTAL
Person B 4 0 2 6 2 2 10
Person A 2 1 1 4 3 4 11
Person C 1 1 1 3 1 0 4
Even better would be that I give the result-columns a good name (1 = lost, 2= draw, 3=won):
NAME lost draw won 2014 2013 2012 TOTAL
Person B 4 0 2 6 2 2 10
Person A 2 1 1 4 3 4 11
Person C 1 1 1 3 1 0 4
I tried to add some extra code, like:
select #colsResult
= STUFF((SELECT ',' + QUOTENAME(result)
from list
group by result
order by result
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
I have as result:
,[1]
,[2]
,[3]
But if I run the whole code I get an error, invallid column name...
Since you have two columns that you now want to PIVOT, you'll first have to unpivot those columns and then convert those values into the new columns.
Starting in SQL Server 2005, you could use CROSS APPLY to unpivot the columns. The basic syntax will be similar to:
select
name,
new_col,
total
from
(
select name,
dt = year(date),
result,
total = count(*) over(partition by name)
from list
) d
cross apply
(
select 'dt', dt union all
select 'result', result
) c (old_col_name, new_col)
See SQL Fiddle with Demo. This query gets you a list of names, with the "new columns" and then the Total entries for each name.
| NAME | NEW_COL | TOTAL |
|----------|---------|-------|
| Person A | 2012 | 11 |
| Person A | 1 | 11 |
| Person A | 2012 | 11 |
| Person A | 2 | 11 |
You'll see that the dates and the results are now both stored in "new_col". These values will now be used as the new column names. If you have a limited number of columns, then you would simply hard-code the query:
select name, lost = [1],
draw=[2], won = [3],
[2014], [2013], [2012], Total
from
(
select
name,
new_col,
total
from
(
select name,
dt = year(date),
result,
total = count(*) over(partition by name)
from list
) d
cross apply
(
select 'dt', dt union all
select 'result', result
) c (old_col_name, new_col)
) src
pivot
(
count(new_col)
for new_col in([1], [2], [3], [2014], [2013], [2012])
) piv
order by [2014];
See SQL Fiddle with Demo
Now since your years are dynamic, then you'll need to use dynamic sql. But it appears that you have 3 results and potentially multiple years - so I'd use a combination of static/dynamic sql to make this easier:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX),
#orderby nvarchar(max)
select #cols
= STUFF((SELECT ',' + QUOTENAME(year(date))
from list
group by year(date)
order by year(date) desc
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
select #orderby = 'ORDER BY ['+cast(year(getdate()) as varchar(4)) + '] desc'
set #query = 'SELECT name, lost = [1],
draw=[2], won = [3],' + #cols + ', Total
from
(
select
name,
new_col,
total
from
(
select name,
dt = year(date),
result,
total = count(*) over(partition by name)
from list
) d
cross apply
(
select ''dt'', dt union all
select ''result'', result
) c (old_col_name, new_col)
) x
pivot
(
count(new_col)
for new_col in ([1], [2], [3],' + #cols + ')
) p '+ #orderby
exec sp_executesql #query;
See SQL Fiddle with Demo. This gives a result:
| NAME | LOST | DRAW | WON | 2014 | 2013 | 2012 | TOTAL |
|----------|------|------|-----|------|------|------|-------|
| Person B | 7 | 1 | 2 | 6 | 2 | 2 | 10 |
| Person A | 5 | 3 | 3 | 4 | 3 | 4 | 11 |
| Person C | 2 | 1 | 1 | 3 | 1 | 0 | 4 |
If you want to only filter the result columns for the current year, then you can perform this filtering a variety of ways but the easiest you be to include a filter in the unpivot. The hard-coded version would be:
select name, lost = [1],
draw=[2], won = [3],
[2014], [2013], [2012], Total
from
(
select
name,
new_col,
total
from
(
select name,
dt = year(date),
result,
total = count(*) over(partition by name)
from list
) d
cross apply
(
select 'dt', dt union all
select 'result', case when dt = 2014 then result end
) c (old_col_name, new_col)
) src
pivot
(
count(new_col)
for new_col in([1], [2], [3], [2014], [2013], [2012])
) piv
order by [2014] desc;
See SQL Fiddle with Demo. Then the dynamic sql version would be:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX),
#orderby nvarchar(max),
#currentYear varchar(4)
select #currentYear = cast(year(getdate()) as varchar(4))
select #cols
= STUFF((SELECT ',' + QUOTENAME(year(date))
from list
group by year(date)
order by year(date) desc
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
select #orderby = 'ORDER BY ['+ #currentYear + '] desc'
set #query = 'SELECT name, lost = [1],
draw=[2], won = [3],' + #cols + ', Total
from
(
select
name,
new_col,
total
from
(
select name,
dt = year(date),
result,
total = count(*) over(partition by name)
from list
) d
cross apply
(
select ''dt'', dt union all
select ''result'', case when dt = '+#currentYear+' then result end
) c (old_col_name, new_col)
) x
pivot
(
count(new_col)
for new_col in ([1], [2], [3],' + #cols + ')
) p '+ #orderby
exec sp_executesql #query;
See SQL Fiddle with Demo. This version will give a result:
| NAME | LOST | DRAW | WON | 2014 | 2013 | 2012 | TOTAL |
|----------|------|------|-----|------|------|------|-------|
| Person B | 4 | 0 | 2 | 6 | 2 | 2 | 10 |
| Person A | 2 | 1 | 1 | 4 | 3 | 4 | 11 |
| Person C | 1 | 1 | 1 | 3 | 1 | 0 | 4 |

Need to select table data into distinct columns based on a date in a row

I am not sure what I need here, looks sort of like I could use a pivot but I don't think it's that complicated and would like to avoid pivot if I can as I haven't used it much (er, at all).
I have data like this:
ID score notes CreateDate
1661 9.2 8.0 on Sept 2010 7/22/2010
1661 7.6 11/4/2010
1661 7.9 6/10/2011
1661 8.3 9/28/2011
1661 7.9 1/20/2012
I want to organize all that data on to one row with the oldest date being first and then use the next oldest date, then next oldest...until I use 4 or 5 dates. So the end result would look something like this:
ID score1 notes1 date1 score2 notes2 date2 score3 notes3 date3 score4 notes4 date4
1661 9.2 8.0 on Sept 2010 7/22/2010 7.6 blah 11/4/2010 7.9 blah2 6/10/2011 8.3 blah3 9/28/2011
PIVOT would be tricky in this situation, since you have more than one column per test (PIVOT works well if you only wanted to show Score1, Score2, Score3, etc). Fortunately, you can create a simple (if long-winded) solution with CASE statements:
select
ID,
max(case when RowNum = 1 then Score else null end) as Score1,
max(case when RowNum = 1 then Notes else null end) as Notes1,
max(case when RowNum = 1 then CreateDate else null end) as Date1,
max(case when RowNum = 2 then Score else null end) as Score2,
max(case when RowNum = 2 then Notes else null end) as Notes2,
max(case when RowNum = 2 then CreateDate else null end) as Date2,
max(case when RowNum = 3 then Score else null end) as Score3,
max(case when RowNum = 3 then Notes else null end) as Notes3,
max(case when RowNum = 3 then CreateDate else null end) as Date3,
max(case when RowNum = 4 then Score else null end) as Score4,
max(case when RowNum = 4 then Notes else null end) as Notes4,
max(case when RowNum = 4 then CreateDate else null end) as Date4,
max(case when RowNum = 5 then Score else null end) as Score5,
max(case when RowNum = 5 then Notes else null end) as Notes5,
max(case when RowNum = 5 then CreateDate else null end) as Date5
from
(
select
*, row_number() over (partition by ID order by CreateDate) as RowNum
from
mytable
) tt
group by
ID
This is hard-coded to cover 5 tests. It will be OK with less, but won't display a 6th. You can obviously create more CASE statements to handle more tests.
Just because I love pivots, I will show you how this can be done using the PIVOT function. In order to get the result with the PIVOT function you will first want to UNPIVOT your multiple columns score, notes and createdate. The unpivot process will convert the multiple columns into multiple rows.
Since you are using SQL Server 2008 you can use CROSS APPLY to unpivot your data, the first part of the query will be similar to:
;with cte as
(
select id, score, notes, createdate,
row_number() over(partition by id order by createdate) seq
from yourtable
)
select id, col, value
from
(
select t.id,
col = col + cast(seq as varchar(10)),
value
from cte t
cross apply
(
values
('score', cast(score as varchar(10))),
('notes', notes),
('date', convert(varchar(10), createdate, 120))
) c (col, value)
) d;
See SQL Fiddle with Demo. Doing this gets your data in the format:
| ID | COL | VALUE |
| 1661 | score1 | 9.20 |
| 1661 | notes1 | 8.0 on Sept 2010 |
| 1661 | date1 | 2010-07-22 |
| 1661 | score2 | 7.60 |
| 1661 | notes2 | (null) |
| 1661 | date2 | 2010-11-04 |
| 1661 | score3 | 7.90 |
Now you can apply the PIVOT function:
;with cte as
(
select id, score, notes, createdate,
row_number() over(partition by id order by createdate) seq
from yourtable
)
select id, col, value
from
(
select t.id,
col = col + cast(seq as varchar(10)),
value
from cte t
cross apply
(
values
('score', cast(score as varchar(10))),
('notes', notes),
('date', convert(varchar(10), createdate, 120))
) c (col, value)
) d
pivot
(
max(value)
for col in (score1, notes1, date1, score2, notes2, date2,
score3, notes3, date3, score4, notes4, date4,
score5, notes5, date5)
) piv;
See SQL Fiddle with Demo.
Then if you were going to have an unknown number of values for each id, you could implement dynamic SQL to get the result:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(col + cast(seq as varchar(10)))
from
(
select row_number() over(partition by id order by createdate) seq
from yourtable
) d
cross apply
(
select 'score', 1 union all
select 'notes', 2 union all
select 'date', 3
) c (col, so)
group by seq, col, so
order by seq, so
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT id, ' + #cols + '
from
(
select t.id,
col = col + cast(seq as varchar(10)),
value
from
(
select id, score, notes, createdate,
row_number() over(partition by id order by createdate) seq
from yourtable
) t
cross apply
(
values
(''score'', cast(score as varchar(10))),
(''notes'', notes),
(''date'', convert(varchar(10), createdate, 120))
) c (col, value)
) x
pivot
(
max(value)
for col in (' + #cols + ')
) p '
execute sp_executesql #query;
See SQL Fiddle with Demo. Both versions give the result:
| ID | SCORE1 | NOTES1 | DATE1 | SCORE2 | NOTES2 | DATE2 | SCORE3 | NOTES3 | DATE3 | SCORE4 | NOTES4 | DATE4 | SCORE5 | NOTES5 | DATE5 |
| 1661 | 9.20 | 8.0 on Sept 2010 | 2010-07-22 | 7.60 | (null) | 2010-11-04 | 7.90 | (null) | 2011-06-10 | 8.30 | (null) | 2011-09-28 | 7.90 | (null) | 2012-01-20 |