I have a list of occupants of a property and need to manipulate the data so it instead shows the property as one row with each additional occupant appearing in a new column.
Here is what I've managed to do so far:
with RANKING AS
( Select
Postcode
, Number
, Occupant
, RANK() OVER
(Partition by Postcode order by Occupant) as [Rank]
from Reporting.dbo.Test --order by [Rank] desc
)
The query in RANKING outputs the following table:
Postcode | Number | Occupant | Rank
AA001AA | 12 | D | 1
AA001AA | 12 | E | 2
AA001AA | 12 | K | 3
AA001AA | 12 | M | 4
AA001AA | 12 | T | 5
BB001BB | 8 | M | 1
BB001BB | 8 | R | 2
etc.
I've then tried to use the value of ranking to create columns, like so:
Select distinct
i.Postcode
, i.Number
, case when i.[rank] = 1 then i.Occupant end as [First Tennant]
, case when i.[rank] = 2 then i.Occupant end as [Second Tennant]
, case when i.[rank] = 3 then i.Occupant end as [Third Tennant]
, case when i.[rank] = 4 then i.Occupant end as [Fourth Tennant]
, case when i.[rank] = 5 then i.Occupant end as [Fifth Tennant]
from Reporting.dbo.Test u
inner join RANKING i on i.Postcode = u.Postcode
Now, 2 questions:
1) Is there any way to automate this process so for a rank of x we have x tenant rows
2) The table this outputs is
Postcode | Number | First Tennant | Second Tennant | Third Tennant | Fourth Tennant | Fifth Tennant |
AA001AA | 12 | D | NULL | NULL | NULL | NULL |
AA001AA | 12 | NULL | E | NULL | NULL | NULL |
etc.
How do I condense this list so each postcode only has one row and all the non-null values appear on that row.
To answer your second question first (how do you condense the list), the easiest way would just to group by postcode and number. You can just take the max() of each column (i.e if there's a value, it gets chosen; all the nulls fall out).
To answer the first question (can this be automated), you probably want to look into a dynamic pivot. One such article posted in the comments is this: SQL Server dynamic PIVOT query?
The idea is basically to serialize the distinct tennants, and concantenate that into a dynamic SQL string which performs a pivot. There are several ways to skin this cat, but here's how I approached it.
use tempdb
go
if object_id('tempdb.dbo.#data') is not null drop table #data
create table #data
(
PostCode varchar(10),
Number int,
Occupant char(1),
Rnk int,
ColName as 'Tennant ' + cast(Rnk as varchar(10))
)
insert into #Data(PostCode, Number, Occupant, Rnk)
select 'AA001AA', 12, 'D',1
union all select 'AA001AA', 12, 'E',2
union all select 'AA001AA', 12, 'K',3
union all select 'AA001AA', 12, 'M',4
union all select 'AA001AA', 12, 'T',5
union all select 'BB001BB', 8, 'M',1
union all select 'BB001BB', 8, 'R',2
declare
#PivotColumns nvarchar(max),
#SelectColumns nvarchar(max),
#sql nvarchar(max)
select
#PivotColumns = stuff((select ',' + quotename(ColName)
from #data
group by ColName
order by ColName
for xml path('')), 1, 1, ''),
#SelectColumns = stuff((select ',' + quotename(ColName) + ' = max(' + quotename(ColName) + ')'
from #data
group by ColName
order by ColName
for xml path('')), 1, 1, ''),
#sql = '
select
PostCode,
Number,
' + #SelectColumns + '
from #data d
pivot (max(Occupant) for ColName in (' + #PivotColumns + ' )) p
group by PostCode, Number'
print #sql
exec sp_executesql #sql
you can use dynamic pivot to get your result. please see below code-
create table #tab (Postcode varchar(10) , Number int , Occupant char(1) , Rank int)
insert into #tab
select 'AA001AA' , 12 , 'D' , 1
union all select 'AA001AA' , 12 , 'E' , 2
union all select 'AA001AA' , 12 , 'K' , 3
union all select 'AA001AA' , 12 , 'M' , 4
union all select 'AA001AA' , 12 , 'T' , 5
union all select 'BB001BB' , 8 , 'M' , 1
union all select 'BB001BB' , 8 , 'R' , 2
union all select 'CC001CC' , 8 , 'N' , 1
union all select 'CC001CC' , 8 , 'O' , 2
union all select 'CC001CC' , 8 , 'P' , 3
union all select 'CC001CC' , 8 , 'Q' , 4
union all select 'CC001CC' , 8 , 'R' , 5
union all select 'CC001CC' , 8 , 'S' , 6
union all select 'CC001CC' , 8 , 'T' , 7
union all select 'CC001CC' , 8 , 'U' , 8
union all select 'CC001CC' , 8 , 'V' , 9
union all select 'CC001CC' , 8 , 'W' , 10
declare #mx int , #min int = 1 , #sql nvarchar(max) = '' , #select1 nvarchar(max) = '',#select2 nvarchar(max) = ''
select #mx = MAX(rank) from #tab
while #min<= #mx
begin
set #select1 = #select1 + '[' + cast(#min as varchar(10))+ '] ,'
set #select2 = #select2 + '[' + cast(#min as varchar(10))+ '] as '+ '[Tennant_' + cast(#min as varchar(10))+ '] ,'
set #min = #min + 1
end
set #select1 = SUBSTRING( #select1 , 1 , LEN(#select1)-1)
set #select2 = SUBSTRING( #select2 , 1 , LEN(#select2)-1)
set #sql = '
SELECT Postcode , Number ,'+#select2+'
FROM #tab
PIVOT
(
max(Occupant)
FOR [Rank] IN ('+#select1+')
)AS pvt '
exec sp_executesql #sql
Related
i have a problem with approach how to pivot this table:
How can i convert this:
Would you be kind enough give me hints (not whole code) how to do this? i really dont know where should i start (i dont know whether it is typical pivoting unpivoting)
+------+------+--------+----------+-----------+-----+
| ColI | Col2 | Month | Turnover | Provision | Fee |
+------+------+--------+----------+-----------+-----+
| 123 | Asdf | 201810 | 10000 | 100 | 0,1 |
| 123 | Asdf | 201811 | 20000 | 200 | 0,2 |
| 123 | Asdf | 201812 | 30000 | 300 | 0,3 |
+------+------+--------+----------+-----------+-----+
into this:
+------+------+---------------+-----------------+------------+---------------+-----------------+------------+---------------+-----------------+-----------+
| ColI | Col2 | Turnover20810 | Provision201810 | Fee201810 | Turnover20811 | Provision201811 | Fee201811 | Turnover20812 | Provision201812 | Fee201812 |
+------+------+---------------+-----------------+------------+---------------+-----------------+------------+---------------+-----------------+-----------+
| 123 | Asdf | 10000 | 100 | 0,1 | 20000 | 200 | 0,2 | 30000 | 300 | 0,3 |
+------+------+---------------+-----------------+------------+---------------+-----------------+------------+---------------+-----------------+-----------+
Thank you!
Assuming that each ColI, Col2 group would only have a maximum of three records, then we can try pivoting using the help of ROW_NUMBER:
WITH cte AS (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY ColI, Col2 ORDER BY Month) rn
FROM yourTable
)
SELECT
ColI,
Col2,
MAX(CASE WHEN rn = 1 THEN Turnover END) AS Turnover1,
MAX(CASE WHEN rn = 1 THEN Provision END) AS Provision1,
MAX(CASE WHEN rn = 1 THEN Fee END) AS Fee1,
MAX(CASE WHEN rn = 2 THEN Turnover END) AS Turnover2,
MAX(CASE WHEN rn = 2 THEN Provision END) AS Provision2,
MAX(CASE WHEN rn = 2 THEN Fee END) AS Fee2,
MAX(CASE WHEN rn = 3 THEN Turnover END) AS Turnover3,
MAX(CASE WHEN rn = 3 THEN Provision END) AS Provision3,
MAX(CASE WHEN rn = 3 THEN Fee END) AS Fee3
FROM cte
GROUP BY
ColI,
Col2;
Note that I did not hardwire more specific column names, to keep the query as general as possible. For example, perhaps there might be another ColI, Col2 group which would have a different three months.
By using Dynamic Sql
IF OBJECT_ID('tempdb..#TEMP') IS NOT NULL
DROP TABLE #TEMP
;WITH CTE (ColI , Col2 , Month , Turnover , Provision , Fee )
AS
(
SELECT 123 , 'Asdf' , 201810 , 10000 ,100 ,'0,1' UNION ALL
SELECT 123 , 'Asdf' , 201811 , 20000 ,200 , '0,2' UNION ALL
SELECT 123 , 'Asdf' , 201812 , 30000 ,300 , '0,3'
)
SELECT ColI , Col2,Turnover , Provision , Fee,MixedCol,Reqcol , ROW_NUMBER()OVER(ORDER BY (SELECT NULL)) AS Seq
INTO #Temp
FROM CTE
CROSS APPLY (VALUES (CONCAT('Turnover','_',[Month]),CAST(Turnover AS VARCHAR(20))),
(CONCAT('Provision','_',[Month]),CAST(Provision AS VARCHAR(20))),
(CONCAT('Fee','_',[Month]),CAST(Fee AS VARCHAR(20)))
)DT (MixedCol,Reqcol)
DECLARE #Sql nvarchar(max),
#DynamicColumn nvarchar(max),
#MaxDynamicColumn nvarchar(max)
SELECT #DynamicColumn = STUFF((SELECT ', '+QUOTENAME(CAST(MixedCol AS VARCHAR(100)))
FROM #TEMP ORDER BY Seq FOR XML PATH ('')),1,1,'')
SELECT #MaxDynamicColumn = STUFF((SELECT ', '+'MAX('+QUOTENAME(CAST(MixedCol AS VARCHAR(100)))+') AS '+QUOTENAME(CAST(MixedCol AS VARCHAR(100)))
FROM #TEMP ORDER BY Seq FOR XML PATH ('')),1,1,'')
SET #Sql=' SELECT ColI , Col2,'+ #MaxDynamicColumn+'
FROM
(
SELECT * FROM #TEMP
)AS src
PIVOT
(
MAX(Reqcol) FOR [MixedCol] IN ('+#DynamicColumn+')
) AS Pvt
GROUP BY ColI , Col2 '
EXEC (#Sql)
PRINT #Sql
Q: "...give me hints (not whole code) how to do this"
A: This example with dynamic transpose of table. You can try this. Unpivot your original table and transpose.
CREATE table #tbl (
color varchar(10), Paul int, John int, Tim int, Eric int);
insert #tbl select
'Red' ,1 ,5 ,1 ,3 union all select
'Green' ,8 ,4 ,3 ,5 union all select
'Blue' ,2 ,2 ,9 ,1;
select * FROM #tbl
--1) Transpose. Example without dynamic code. You create list of fields in query
select *
from #tbl
unpivot (value for name in ([Paul],[John],[Tim],[Eric])) up
pivot (max(value) for color in ([Blue],[Green],[Red])) p
--2) Transpose. Example with dynamic code, without names of fields.
DECLARE #cols NVARCHAR(MAX), #query NVARCHAR(MAX), #rows NVARCHAR(MAX)='';
-- XML with all values
SET #cols = STUFF(
(
SELECT DISTINCT
','+QUOTENAME(T.Color) -- Name of first column
FROM #tbl T FOR XML PATH(''), TYPE
).value('.', 'nvarchar(max)'), 1, 1, '');
SELECT #rows=#rows+','+QUOTENAME(Name)
FROM
(SELECT Name,ROW_NUMBER() OVER(ORDER BY column_id) AS 'RowNum'
FROM tempdb.sys.Columns
WHERE Object_ID = OBJECT_ID('tempdb..#tbl')
) AS A WHERE A.RowNum>1
SET #rows=STUFF(#rows,1,1,'')
SET #query='SELECT *
from #tbl
unpivot (value for name in ('+#rows+')) up
pivot (max(value) for color in ('+#Cols+')) p'
EXEC (#query)
I have attached a screen shot. I have mentioned both input and required output. I need a SQL server 2008/2012 Query, to get the output.
You can use dynamic sql query.
Query
declare #sql as varchar(max);
select #sql = 'select ' + stuff((
select ', max(case StudentID when '
+ cast(t.StudentID as varchar(10))
+ ' then StudentKey end) as StudentID'
+ cast(t.StudentID as varchar(10))
+', max(case StudentID when ' + cast(t.StudentID as varchar(10))
+ ' then StudentName end) as StudentName'
+ cast(t.StudentID as varchar(10))
from (select distinct top 3 * from studentTable order by StudentID)t
for xml path('')
), 1, 2, '') + ' from studentTable;';
exec(#sql);
And this will give the result in the coulmn order of 1 StudentId then StudentName and so on. Some thing like below.
Result
+------------+--------------+------------+--------------+------------+--------------+
| StudentID1 | StudentName1 | StudentID2 | StudentName2 | StudentID3 | StudentName3 |
+------------+--------------+------------+--------------+------------+--------------+
| 125 | A | 225 | B | 325 | C |
+------------+--------------+------------+--------------+------------+--------------+
And if you want the result like all the studentId column first then the studentName column. Then
Query
declare #sql as varchar(max);
select #sql = 'select ' + stuff((
select ', max(case StudentID when '
+ cast(t.StudentID as varchar(10))
+ ' then StudentKey end) as StudentID'
+ cast(t.StudentID as varchar(10))
from (select distinct top 3 * from studentTable order by StudentID)t
for xml path('')
), 1, 2, '')
+ ','
+ stuff((
select ', max(case StudentID when '
+ cast(t.StudentID as varchar(10))
+ ' then StudentName end) as StudentName'
+ cast(t.StudentID as varchar(10))
from (select distinct top 3 * from studentTable order by StudentID)t
for xml path('')
), 1, 2, '')
+ ' from studentTable;';
exec(#sql);
Result
+------------+------------+------------+--------------+--------------+--------------+
| StudentID1 | StudentID2 | StudentID3 | StudentName1 | StudentName2 | StudentName3 |
+------------+------------+------------+--------------+--------------+--------------+
| 125 | 225 | 325 | A | B | C |
+------------+------------+------------+--------------+--------------+--------------+
You need to use PIVOT. It should be something like following. If you don't know how PIVOT works, try playing around Excel Pivot with tutorials online and get yourself more familiar with its logic first.
WITH PivotData AS
(
SELECT
AssignmentName,
StudentName,
Grade
FROM TableName
)
SELECT
StudentName,
Assignment1,
Assignment2,
Assignment3
FROM PivotData
PIVOT
(
SUM(Grade)
FOR AssignmentName
IN (Assignment1, Assignment2, Assignment3)
) AS PivotResult
ORDER BY StudentName
PIVOT and UNPIVOT in T-SQL
Pivot in Excel
I have a table in MSSQL 2008R2:
ID | PinAddress
-------------------------------------
1 | 1
1 | 2
1 | 3
1 | 4
1 | 5
1 | 6
1 | 16
1 | 31
2 | 55
2 | 56
2 | 57
2 | 81
2 | 82
2 | 83
2 | 84
3 | 101
3 | 102
3 | 103
3 | 107
3 | 108
3 | 109
What I want is when I search for ID = 1,I want result like
1-6,16,31
When I search for ID = 2,I want result like
55-57,81-84
When I search for ID = 3,I want result like
101-103,107-109
You can use below script to create table and data:
CREATE TABLE PinAddress(ID INT,PinAddress INT)
INSERT INTO PinAddress values(1,1)
INSERT INTO PinAddress values(1,2)
INSERT INTO PinAddress values(1,3)
INSERT INTO PinAddress values(1,4)
INSERT INTO PinAddress values(1,5)
INSERT INTO PinAddress values(1,6)
INSERT INTO PinAddress values(1,16)
INSERT INTO PinAddress values(1,31)
INSERT INTO PinAddress values(2,55)
INSERT INTO PinAddress values(2,56)
INSERT INTO PinAddress values(2,57)
INSERT INTO PinAddress values(2,81)
INSERT INTO PinAddress values(2,82)
INSERT INTO PinAddress values(2,83)
INSERT INTO PinAddress values(2,84)
INSERT INTO PinAddress values(3,101)
INSERT INTO PinAddress values(3,102)
INSERT INTO PinAddress values(3,103)
INSERT INTO PinAddress values(3,107)
INSERT INTO PinAddress values(3,108)
INSERT INTO PinAddress values(3,109)
Thanks
This is a gaps and islands problem, and the key is identifying your continuous ranges, which is done using ROW_NUMBER(). So for ID 3, you have:
ID PinAddress RowNumber
---------------------------
3 101 1
3 102 2
3 103 3
3 107 4
3 108 5
3 109 6
And deducting the row number from the pin address will give you a constant value for each continuous range:
ID PinAddress RowNumber (PinAddress - RowNumber)
---------------------------------------------------
3 101 1 100
3 102 2 100
3 103 3 100
---------------------------------------------------
3 107 4 103
3 108 5 103
3 109 6 103
The query thus far is simply:
SELECT ID,
PinAddress,
GroupingSet = PinAddress - ROW_NUMBER() OVER(PARTITION BY ID ORDER BY PinAddress)
FROM dbo.PinAddress;
Then you can group by your constant value and ID, and use MIN and MAX to get the start and end of each range:
WITH RankedData AS
( SELECT ID,
PinAddress,
GroupingSet = PinAddress - ROW_NUMBER() OVER(PARTITION BY ID ORDER BY PinAddress)
FROM PinAddress
)
SELECT ID,
RangeStart = MIN(PinAddress),
RangeEnd = MAX(PinAddress),
RangeText = CONVERT(VARCHAR(10), MIN(PinAddress)) +
CASE WHEN MIN(PinAddress) = MAX(PinAddress) THEN ''
ELSE ' - ' + CONVERT(VARCHAR(10), MAX(PinAddress))
END
FROM RankedData
GROUP BY ID, GroupingSet;
Which, for ID 3 gives:
ID RangeStart RangeEnd RangeText
-----------------------------------------
3 101 103 101 - 103
3 107 109 107 - 109
Finally, you need to concatenate the RangeText values into a single row, which can be done using SQL Server's XML Extensions.
WITH RankedData AS
( SELECT ID,
PinAddress,
GroupingSet = PinAddress - ROW_NUMBER() OVER(PARTITION BY ID ORDER BY PinAddress)
FROM PinAddress
)
SELECT p.ID,
Ranges = STUFF((SELECT ', ' + CONVERT(VARCHAR(10), MIN(PinAddress)) +
CASE WHEN MIN(PinAddress) = MAX(PinAddress) THEN ''
ELSE ' - ' + CONVERT(VARCHAR(10), MAX(PinAddress))
END
FROM RankedData AS rd
WHERE rd.ID = p.ID
GROUP BY ID, GroupingSet
FOR XML PATH(''), TYPE).value('.', 'VARCHAR(MAX)'), 1, 2, '')
FROM (SELECT DISTINCT ID FROM PinAddress) AS p;
Which gives:
ID Ranges
------------------------------
1 1 - 6, 16 - 16, 31 - 31
2 55 - 57, 81 - 84
3 101 - 103, 107 - 109
Try this code
DECLARE #values VARCHAR(8000)
DECLARE #prevseq int
SET #values = ''
SELECT #values = #values +
(CASE WHEN #values = '' OR #values like '%,' THEN cast(PinAddress as varchar) --first value or new after sequence
WHEN PinAddress - 1 = #prevseq THEN ''
ELSE '-' + cast (#prevseq as varchar) + ',' + cast(PinAddress as varchar)
END),
#prevseq = coalesce(PinAddress, -1)
FROM PinAddress
WHERE ID = 1
ORDER BY PinAddress ASC
SELECT #values = #values +
(CASE WHEN #values not like '%' + cast(#prevseq as varchar) THEN '-' + cast(#prevseq as varchar) ELSE '' END)
PRINT #values
#GarethD your logic with ROW_NUMBER was excellent and works like a charm.
I took help of your query and changed it little bit to get my desired output:
WITH RankedData AS
(
SELECT ID,
PinAddress,
GroupingSet = PinAddress - ROW_NUMBER() OVER(PARTITION BY ID ORDER BY PinAddress)
FROM dbo.PinAddress
WHERE ID = 1
)
SELECT p.ID,
Ranges = STUFF(
(
SELECT CASE WHEN MIN(pinaddress) = MAX(PINADDRESS) THEN
', ' + CONVERT(VARCHAR(10), MIN(PinAddress))
ELSE
', ' + CONVERT(VARCHAR(10), MIN(PinAddress)) + '-' +
CONVERT(VARCHAR(10), MAX(PinAddress))
END
FROM RankedData AS rd
WHERE rd.ID = p.ID
GROUP BY
ID,
GroupingSet
FOR XML PATH(''),
TYPE
).value('.', 'VARCHAR(MAX)'),
1,
2,
''
)
FROM (
SELECT DISTINCT ID
FROM RankedData
) AS p;
Basic Version, If you want to fetch the result set for all ID's then create a scalar function.
declare #id int = 1
declare #formed varchar(max)
Select #Formed = ISNULL(#formed+',','')+Formed
from
(
Select
Formed = convert(varchar,MIN([PinAddress]))
+case when MIN([PinAddress]) != MAX([PinAddress]) then '-'
+convert(varchar,MAX([PinAddress] )) else '' end
from PinAddress where ID = #id
group by [PinAddress]/case when [#PinAddress]/10= 0 then 10 else 5 end)t
select #formed
I am trying to pivot on two columns in SQL Server 2008 on an invoice table. So I have data like the follows:
+--------------+--------+---------+------+
| Invoice Date | Item # | Dollars | Lbs. |
+--------------+--------+---------+------+
| 1/1/14 | A | 1 | 1 |
| 1/2/14 | B | 2 | 2 |
| 1/3/14 | A | 3 | 3 |
| 1/4/14 | B | 4 | 4 |
| 2/1/14 | A | 5 | 5 |
| 2/1/14 | B | 6 | 6 |
+--------------+--------+---------+------+
I would like to display it as
+--------+--------------+-----------------+--------------+-----------------+
| Item # | 1/31/14 Lbs. | 1/31/14 Dollars | 2/28/14 Lbs. | 2/28/14 Dollars |
+--------+--------------+-----------------+--------------+-----------------+
| A | 4 | 4 | 5 | 5 |
| B | 6 | 6 | 6 | 6 |
+--------+--------------+-----------------+--------------+-----------------+
Note the column name is the last day of that month and either dollars or pounds. I can do it just fine one column (either pounds or dollars) however I can't do it on both.
Here is my example code for just pounds:
DECLARE
#v_Columns VARCHAR(MAX),
#v_Query VARCHAR(MAX)
--pivot and delimit values
SELECT #v_Columns = COALESCE(#v_Columns,'[') + convert(varchar(8), InvoiceDate, 1) + ' Lbs.' + '],['
FROM
( SELECT DISTINCT dbo.ufn_GetLastDayOfMonth(InvoiceDate) As InvoiceDate
FROM Invoice
WHERE InvoiceDate BETWEEN #BEGIN_DATE AND #END_DATE
ORDER BY InvoiceDate
--delete last two chars of string (the ending ',[')
SET #v_Columns = SUBSTRING(#v_Columns, 1, LEN(#v_Columns)-2)
PRINT #v_Columns
--construct sql statement
SET #v_Query =
'WITH AllOrders (LastInvoiceDate, Item, Pounds) AS
(
SELECT
CONVERT(varchar(8), dbo.ufn_GetLastDayOfMonth(Invoice.InvoiceDate), 1) + ''' + ' Lbs.' + ''' As LastInvoiceDate,
Item,
Pounds
FROM INVOICE
WHERE InvoiceDate BETWEEN #BEGIN_DATE AND #END_DATE
)
SELECT *
FROM AllOrders
PIVOT
(
SUM(QuantityShipped)
FOR LastInvoiceDate IN (' + #v_Columns + ')
) AS pivotview'
Thank you all in advance!
In order to get the result you are going to have to either PIVOT twice or UNPIVOT the Dollars and Lbs columns into a single column and then apply the PIVOT once. My preference would be to unpivot and then pivot because I find it to be much easier.
Instead of working dynamically first, you should write the query as a static or hard-coded version to get the logic correct, then convert it to dynamic SQL. The example that I have uses your final dates 201-01-31, etc because you are using a function to create those dates and should be able to apply that as needed.
Since you are using SQL Server 2005+, you can use CROSS APPLY to unpivot Dollars and Lbs. The code will be similar to the following:
select
t.ItemNo,
new_col = convert(varchar(10), t.[invoice date], 120) + '_'+ c.col,
c.value
from yourtable t
cross apply
(
select 'Dollars', Dollars union all
select 'Lbs', Lbs
) c (col, value);
See SQL Fiddle with Demo. This converts your data to the following format:
| ITEMNO | NEW_COL | VALUE |
|--------|--------------------|-------|
| A | 2014-01-31_Dollars | 1 |
| A | 2014-01-31_Lbs | 1 |
| B | 2014-01-31_Dollars | 2 |
| B | 2014-01-31_Lbs | 2 |
| A | 2014-01-31_Dollars | 3 |
I've concatenated into new_col the final column names that you'll need. Again you can format the date in whatever format you need, I just used 2014-01-31 and added the Dollars or Lbs to the end of it. Once you've got the data, you will PIVOT the values into your final desired result:
select ItemNo,
[2014-01-31_Lbs], [2014-01-31_Dollars],
[2014-02-28_Lbs], [2014-02-28_Dollars]
from
(
select
t.ItemNo,
new_col = convert(varchar(10), t.[invoice date], 120) + '_'+ c.col,
c.value
from yourtable t
cross apply
(
select 'Dollars', Dollars union all
select 'Lbs', Lbs
) c (col, value)
) d
pivot
(
sum(value)
for new_col in ([2014-01-31_Lbs], [2014-01-31_Dollars],
[2014-02-28_Lbs], [2014-02-28_Dollars])
) p;
See SQL Fiddle with Demo. Now you've got the result you want, so simply convert it to dynamic SQL:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(convert(varchar(10), t.[invoice date], 120) + '_'+ c.col)
from yourtable t
cross apply
(
select 'Lbs', 0 union all
select 'Dollars', 1
) c (col, so)
group by [invoice date], col, so
order by [invoice date], so
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT ItemNo,' + #cols + '
from
(
select
t.ItemNo,
new_col = convert(varchar(10), t.[invoice date], 120) + ''_''+ c.col,
c.value
from yourtable t
cross apply
(
select ''Dollars'', Dollars union all
select ''Lbs'', Lbs
) c (col, value)
) d
pivot
(
sum(value)
for new_col in (' + #cols + ')
) p '
exec sp_executesql #query;
See SQL Fiddle with Demo. This give a final result of:
| ITEMNO | 2014-01-31_LBS | 2014-01-31_DOLLARS | 2014-02-28_LBS | 2014-02-28_DOLLARS |
|--------|----------------|--------------------|----------------|--------------------|
| A | 4 | 4 | 5 | 5 |
| B | 6 | 6 | 6 | 6 |
Here is your sample table
CREATE TABLE #TEMP([Invoice Date] DATE,[Item #] VARCHAR(10),[DollarS] NUMERIC(10,0),[Lbs.] NUMERIC(10,0))
INSERT INTO #TEMP VALUES ('1/1/14', 'A',1,1)
INSERT INTO #TEMP VALUES ('1/2/14', 'B',2,2)
INSERT INTO #TEMP VALUES ('1/3/14', 'A',3,3)
INSERT INTO #TEMP VALUES ('1/4/14', 'B',4,4)
INSERT INTO #TEMP VALUES ('2/1/14', 'A',5,5)
INSERT INTO #TEMP VALUES ('2/1/14', 'B',6,6)
Now you need to apply UNION ALL(instead of UNPIVOT) and bring columns to row and combine the columns, get the order of columns as Date+LBS/DOLLARS.
SELECT DISTINCT DENSE_RANK() OVER(ORDER BY CAST(LASTDAY AS DATE),UNIT DESC)RNO,*,
CAST(DATEPART(MONTH,LASTDAY)AS VARCHAR) +'/'+ CAST(DATEPART(DAY,LASTDAY)AS VARCHAR) +'/' +RIGHT(CAST(YEAR(LASTDAY)AS VARCHAR),2)+' ' +UNIT PIVOTCOL
INTO #NEWTABLE
FROM
(
SELECT [Item #],'DOLLARS' UNIT,
DATEADD(s,-1,DATEADD(mm, DATEDIFF(m,0,[Invoice Date])+1,0))LASTDAY,
SUM([Dollars]) OVER(PARTITION BY [Item #],DATEADD(s,-1,DATEADD(mm, DATEDIFF(m,0,[Invoice Date])+1,0))) VALUE
FROM #TEMP
UNION ALL
SELECT [Item #], 'LBS.',
DATEADD(s,-1,DATEADD(mm, DATEDIFF(m,0,[Invoice Date])+1,0))LASTDAY,
SUM([Lbs.]) OVER(PARTITION BY [Item #],DATEADD(s,-1,DATEADD(mm, DATEDIFF(m,0,[Invoice Date])+1,0))) DOLLARSUM
FROM #TEMP
)TAB
Now declare the query to get the columns dynamically and to set NULL to Zero
DECLARE #cols NVARCHAR (MAX)
DECLARE #NullToZeroCols NVARCHAR (MAX)
SELECT #cols = COALESCE (#cols + ',[' + PIVOTCOL + ']',
'[' + PIVOTCOL + ']')
FROM (SELECT DISTINCT RNO,PIVOTCOL FROM #NEWTABLE) PV
ORDER BY RNO
PRINT #COLS
SET #NullToZeroCols = SUBSTRING((SELECT ',ISNULL(['+PIVOTCOL+'],0) AS ['+PIVOTCOL+']'
FROM(SELECT DISTINCT RNO,PIVOTCOL FROM #NEWTABLE GROUP BY RNO,PIVOTCOL)TAB
ORDER BY RNO FOR XML PATH('')),2,8000)
Now pivot the query
DECLARE #query NVARCHAR(MAX)
SET #query = 'SELECT [Item #],' + #NullToZeroCols + ' FROM
(
SELECT [Item #],VALUE,PIVOTCOL FROM #NEWTABLE
) x
PIVOT
(
SUM(VALUE)
FOR PIVOTCOL IN (' + #cols + ')
) p
ORDER BY [Item #];'
EXEC SP_EXECUTESQL #query
SQL FIDDLE
RESULT
Not sure how it calls, I need to do this:
+---------------+
| param | value |
| 1 | 3 |
| 1 | 3 |
| 2 | 4 |
| 2 | 4 |
+-------+-------+
to
+-----------------+
| param1 | param2 |
| 3 | 4 |
| 3 | 4 |
+-------+---------+
Is it possible without INNER JOIN ?
Where is my error?
SELECT TOP 10
dbo.RW_ReceivedData.[Value] AS Value1,
dbo.RW_ReceivedData.[Value] AS Value2
FROM
RW_ReceivedData
WHERE
dbo.RW_ReceivedData.LogicalName = CAST(0x01000F0800FF AS varbinary(8000)),
dbo.RW_ReceivedData.LogicalName = CAST(0x01000F0800FF AS varbinary(8000))
This should work:
DECLARE #cols NVARCHAR(4000)
= STUFF(
(
SELECT DISTINCT ',[' + CAST(param AS VARCHAR(10)) + ']'
FROM tbl
FOR XML PATH('')
),1,1,'')
DECLARE #colNames NVARCHAR(4000)
= STUFF(
(
SELECT DISTINCT ',[' + CAST(param AS VARCHAR(10)) + '] AS param' + CAST(param AS VARCHAR(10))
FROM tbl
FOR XML PATH('')
),1,1,'')
DECLARE #sql NVARCHAR(4000) =
'
SELECT '+#colNames+'
FROM
(
SELECT *, ROW_NUMBER() OVER (PARTITION BY param ORDER BY param) n
FROM tbl
) t
PIVOT
(
MIN(value) FOR param IN ('+#cols+')
) pvt'
EXEC(#sql)
Here is SQL Fiddle
This approach uses dynamic PIVOT. In order to get the expected result ROW_NUMBER() with partitioning was used since values in param column are not unique.
UPDATE
You can also try it like this, but you have to manually add columns to the query:
SELECT SUM(CASE WHEN param = 1 THEN value ELSE 0 END) param1
, SUM(CASE WHEN param = 2 THEN value ELSE 0 END) param2
, SUM(CASE WHEN param = 3 THEN value ELSE 0 END) param3
FROM
(
SELECT *
, ROW_NUMBER() OVER (PARTITION BY param ORDER BY param) num
FROM tbl
) t
GROUP BY
num
SQL Fiddle
If you don't want columns to be automatically handled, you can just use static PIVOT:
SELECT [1] AS param1
,[2] AS param2
FROM (
SELECT *
, ROW_NUMBER() OVER (PARTITION BY param ORDER BY param) n
FROM tbl) t
PIVOT (
MIN(value) FOR param IN ([1],[2])
) pvt