SQL Server query split table - sql-server

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

Related

Parse XML into a (XPath, Value) pair

Working with XML in SQL Server, given this XML:
<A>
<B>123</B>
<C>
<Cs>234</Cs>
<Cs>345</Cs>
<Cs>12</Cs>
<Cs>2346</Cs>
</Cs>
</A>
I'd like to produce a result set that looks like this:
xpath
value
(/A/B)[1]
123
(/A/C/Cs)[1]
234
(/A/C/Cs)[2]
345
(/A/C/Cs)[3]
12
(/A/C/Cs)[4]
2346
Is there a trick that can do this without walking through the XML? Added bonus would include the ability to start somewhere other than the document root. You could pass /A/C to this routine and it would only give the paths under that element.
This is one of the rare cases when the archaic OPENXML() is handy.
XQuery 3.0 introduced a real solution for such task: fn:path() function long time ago in 2014. Unfortunately, MS SQL Server supports just a subset of XQuery 1.0
Back to mundane earth.
SQL
DECLARE #xml XML =
N'<A>
<B>123</B>
<C>
<Cs>234</Cs>
<Cs>345</Cs>
<Cs>12</Cs>
<Cs>2346</Cs>
</C>
</A>';
DECLARE #DocHandle INT;
EXEC sp_xml_preparedocument #DocHandle OUTPUT, #xml;
;WITH rs AS
(
SELECT * FROM OPENXML(#DocHandle,'/*')
), cte AS
(
-- anchor
SELECT id
,ParentID
--, nodetype
, [text]
,CAST(id AS VARCHAR(100)) AS [Path]
,CAST('/' + rs.localname AS VARCHAR(1000))
+ N'['
+ CAST(ROW_NUMBER() OVER(ORDER BY (SELECT 1)) AS NVARCHAR)
+ N']' AS [XPath]
FROM rs
WHERE ParentID IS NULL
UNION ALL
--recursive member
SELECT t.id
,t.ParentID
--, nodetype = (SELECT nodetype FROM rs WHERE id = t.ParentID)
, t.[text]
, CAST(a.[Path] + ',' + CAST( t.ID AS VARCHAR(100)) AS VARCHAR(100)) AS [Path]
, CAST(a.[XPath] + '/' + IIF(t.nodetype = 2, '#', '')
+ t.localname AS VARCHAR(1000))
+ N'['
+ TRY_CAST(ROW_NUMBER() OVER(PARTITION BY t.localname ORDER BY (SELECT 1)) AS NVARCHAR)
+ N']' AS [XPath]
FROM rs AS t
INNER JOIN cte AS a ON t.ParentId = a.id
)
SELECT ID, ParentID, /*nodetype,*/ [Path]
, REPLACE([XPath],'#text','text()') AS XPath
, [text] AS [Value]
FROM cte
WHERE [text] IS NOT NULL
--AND CAST([text] AS VARCHAR(30)) = '12345'
ORDER BY [Path];
EXEC sp_xml_removedocument #DocHandle;
Output
+----+----------+----------+----------------------------+-------+
| ID | ParentID | Path | XPath | Value |
+----+----------+----------+----------------------------+-------+
| 8 | 2 | 0,2,8 | /A[1]/B[1]/text()[1] | 123 |
| 9 | 4 | 0,3,4,9 | /A[1]/C[1]/Cs[1]/text()[1] | 234 |
| 10 | 5 | 0,3,5,10 | /A[1]/C[1]/Cs[2]/text()[1] | 345 |
| 11 | 6 | 0,3,6,11 | /A[1]/C[1]/Cs[3]/text()[1] | 12 |
| 12 | 7 | 0,3,7,12 | /A[1]/C[1]/Cs[4]/text()[1] | 2346 |
+----+----------+----------+----------------------------+-------+
You can use a recursive CTE. You pass in the XML document in #xml. If you need to use a table to get the XML, you can use CROSS APPLY YourXml.nodes instead of FROM #XML.nodes.
WITH cte AS (
SELECT
xpath = CONCAT(v.name, '[', ROW_NUMBER() OVER (PARTITION BY v.name ORDER BY (SELECT 1)), ']'),
value = x.nd.value('text()[1]','nvarchar(100)'),
child = x.nd.query('*')
FROM #xml.nodes('*') x(nd)
CROSS APPLY (VALUES (x.nd.value('local-name(.)[1]','nvarchar(max)'))) v(name)
UNION ALL
SELECT
xpath = CONCAT(cte.xpath, '/', v.name, '[', ROW_NUMBER() OVER (PARTITION BY xpath, v.name ORDER BY (SELECT 1)), ']'),
value = x.nd.value('text()[1]','nvarchar(100)'),
child = x.nd.query('*')
FROM cte
CROSS APPLY cte.child.nodes('*') x(nd)
CROSS APPLY (VALUES (x.nd.value('local-name(.)[1]','nvarchar(max)'))) v(name)
)
SELECT
xpath = CONCAT('/', xpath, '/text()[1]'),
value
FROM cte
WHERE value IS NOT NULL;
db<>fiddle
Unfortunately, you cannot use the ancestor:: axis, which would have made this much easier.
If SQL Server supported ancestor:: you could do something like this
SELECT
xpath = '(' + x.nd.query('for $n in ancestor::* return concat("/", local-name($n))') + '/text())[1]',
value = x.nd.value('text()[1]','nvarchar(100)')
FROM #xml.nodes('//*[text()]') x(nd)

Pivoting table in SQL

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)

An Easy Way to Split multiple String using Transact-SQL

I have 3 strings
#NameList = Hi|Hi1|Hi2
#DESCLIST = Hii|Hii1|Hii2
#SEQList = 1|2|3
I am looking for a Transact-SQL function where it can split these strings and insert in to table in below order
pkid Name DESC Seq
1 Hi Hii 1
2 Hi1 Hii1 2
3 Hi2 Hii2 3
As Martin Smith suggested, you just need to use a string splitting function and join the results together:
declare #NameList nvarchar(100) = 'Hi|Hi1|Hi2';
declare #DESCLIST nvarchar(100) = 'Hii|Hii1|Hii2';
declare #SEQList nvarchar(100) = '1|2|3';
select s1.item as Name
,s2.item as [Desc]
,s3.item as Seq
from dbo.fn_StringSplit(#NameList,'|',null) as s1
join dbo.fn_StringSplit(#DESCLIST,'|',null) as s2
on s1.rn = s2.rn
join dbo.fn_StringSplit(#SEQList,'|',null) as s3
on s1.rn = s3.rn;
Results:
+------+------+------+-----+
| pkid | Name | Desc | Seq |
+------+------+------+-----+
| 1 | Hi | Hii | 1 |
| 2 | Hi1 | Hii1 | 2 |
| 3 | Hi2 | Hii2 | 3 |
+------+------+------+-----+
String Split Function:
create function [dbo].[fn_StringSplit]
(
#str nvarchar(max) = ' ' -- String to split.
,#delimiter as nvarchar(255) = ',' -- Delimiting value to split on.
,#num as int = null -- Which value to return, null returns all.
)
returns table
as
return
(
with d as
(
select rn = row_number() over (order by (select null))
,item = y.i.value('(./text())[1]', 'nvarchar(max)')
from(select x = convert(xml, '<i>'
+ replace((select #str for xml path('')), #delimiter, '</i><i>')
+ '</i>'
).query('.')
) AS a
cross apply x.nodes('i') AS y(i)
)
select rn
,item
from d
where rn = #num
or #num is null
);
I would use the function:
STRING_SPLIT (string, separator)
with table values that divides a string into rows of substrings, according to a specified separator character and would return the records by assigning them an id through the Row_number function that returns a sequential number to each row within a partition of a result set:
DECLARE #NameList VARCHAR(50) = 'Hi|Hi1|Hi2'
DECLARE #DESCLIST VARCHAR(50) = 'Hii|Hii1|Hii2'
DECLARE #SEQList VARCHAR(50) = '1|2|3'
select namelist.name,DESCLIST.descr, SEQList.SEQ from
(select value as name ,row_number() over (order by (select null)) as id from STRING_SPLIT(#NameList,'|') )
namelist inner join
(select value as descr ,row_number() over (order by (select null)) as id from STRING_SPLIT(#DESCLIST,'|') )
DESCLIST on namelist.id = DESCLIST.id inner join
(select value as SEQ ,row_number() over (order by (select null)) as id from STRING_SPLIT(#SEQList,'|'))
SEQList on DESCLIST.id = SEQList.id

simple SQL Pivot Query

I apologize for my ignorance. I just am not familiar with pivot queries AT ALL and all the examples I find seem about as clear as mud. I have table that returns GroupName and ID Numbers.
For Example:
SELECT GroupName, IDnumber FROM do.Table_1
Returns
GroupName IDnumber
1 8395
1 A660
1 8396
1 A661
2 8398
2 A662
2 8399
What I want is something more like this:
GroupName ID1 ID2 ID3 ID4
1 8395 A660 8396 A661
2 8398 A662 8399 NULL
How can I do this? Pivot query? Some other method?
I am open to suggestion and appreciate any help you could provide.
Yes, you can do it using PIVOT but not in this shape, you have firstly to generate a row number to use it to format the data in the way you want. Something like this:
WITH Ranked
AS
(
SELECT GroupName, IDnumber,
ROW_NUMBER() OVER(PARTITION BY GroupName ORDER BY GroupName) AS RN
FROM Table1
)
SELECT GroupName,
[1] AS ID1, [2] AS ID2, [3] AS ID3, [4] AS ID4
FROM Ranked AS r
PIVOT
(
MAX(IDnumber)
FOR RN IN([1], [2], [3], [4])
) AS p;
SQL Fiddle Demo
This will give you:
| GROUPNAME | ID1 | ID2 | ID3 | ID4 |
|-----------|------|------|------|--------|
| 1 | 8395 | A660 | 8396 | A661 |
| 2 | 8398 | A662 | 8399 | (null) |
If you want to do it dynamically and not to write the row number by hand in the pivot table operator, you have to do it using dynamic SQL, something like:
DECLARE #cols AS NVARCHAR(MAX);
DECLARE #colnames AS NVARCHAR(MAX);
DECLARE #query AS NVARCHAR(MAX);
SELECT #cols = STUFF((SELECT distinct ',' +
QUOTENAME(RN)
FROM
(
SELECT ROW_NUMBER() OVER(PARTITION BY GroupName ORDER BY GroupName) AS RN
FROM Table1
) AS t
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
, 1, 1, '');
SELECT #colnames = STUFF((SELECT distinct ',' +
QUOTENAME(RN) + 'AS' +
QUOTENAME('ID' + CAST(RN AS NVARCHAR(5)))
FROM
(
SELECT ROW_NUMBER() OVER(PARTITION BY GroupName ORDER BY GroupName) AS RN
FROM Table1
) AS t
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
, 1, 1, '');
SELECT #query = 'WITH Ranked
AS
(
SELECT GroupName, IDnumber,
ROW_NUMBER() OVER(PARTITION BY GroupName ORDER BY GroupName) AS RN
FROM Table1
)
SELECT GroupName, ' + #colnames +
' FROM Ranked AS r
PIVOT
(
MAX(IDnumber)
FOR RN IN(' + #cols + ')' +
') p';
execute(#query);
SQL Fiddle Demo
This should give you the same result:
| GROUPNAME | ID1 | ID2 | ID3 | ID4 |
|-----------|------|------|------|--------|
| 1 | 8395 | A660 | 8396 | A661 |
| 2 | 8398 | A662 | 8399 | (null) |
You may need to use dynamic pivoting since the Id will be dynamic. Here is your sample table
SELECT * INTO #TEMP
FROM
(
SELECT 1 GroupName, '8395' IDnumber
UNION ALL
SELECT 1, 'A660'
UNION ALL
SELECT 1, '8396'
UNION ALL
SELECT 1, 'A661'
UNION ALL
SELECT 2, '8398'
UNION ALL
SELECT 2, 'A662'
UNION ALL
SELECT 2, '8399'
)TAB
Select row number over each Groupname and insert into a temporary table so that it can be used for both selecting the columns for pivoting and inside the pivot
SELECT *,
'ID' + CAST(ROW_NUMBER() OVER(PARTITION BY GroupName ORDER BY GROUPNAME) AS VARCHAR(10)) IDS
INTO #NEWTABLE
FROM #TEMP
Select columns for pivot
DECLARE #cols NVARCHAR (MAX)
SELECT #cols = COALESCE (#cols + ',[' + IDS + ']',
'[' + IDS + ']')
FROM (SELECT DISTINCT IDS FROM #NEWTABLE) PV
ORDER BY IDS
Now pivot dynamically
DECLARE #query NVARCHAR(MAX)
SET #query = '
SELECT * FROM
(
SELECT * FROM #NEWTABLE
) x
PIVOT
(
MAX(IDnumber)
FOR IDS IN (' + #cols + ')
) p
'
EXEC SP_EXECUTESQL #query
Click here to view the result (incase an error is occured on loading page press RUNSQL, it works)
RESULT

PIVOT with multiple columns

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

Resources