SQL Pivot and Total ROLLUP - sql-server

Please help me understand how to implement the ROLLUP on this pivot table.
I have been looking over several of the other written solutions to my requirement, but I seem to be missing something when I apply them to my situation. SQL is not my strength, but I feel like I understand more each time I accomplish something.
Creating this pivot statement below was a little struggle since I need to work with dynamic fields to summarize the total for different dID depending on the cID that the report is running.
I am using a method to dynamically create the NVARCHAR that will represent the columns I am applying SUM to in the pivot table. I've attempted to use the ROLLUP function as part of the GROUP BY condition of the SELECT without success.
The general layout I would like is something like this below. Additionally, I have a second pivot with the same dID from a different dataset that I would like to merge the Totals from both into a new table, and create a GrandTotal. I imagine this would be best accomplished with a UNION
dID, dName, qID's, dID_Total
1, A, 100-113, #
2, B, 100-113, #
3, C, 100-113, #
4, D, 100-113, #
5, E, 100-113, #
DECLARE #sID int = 100
DECLARE #cID int = 5
DECLARE #ColumnName AS NVARCHAR(MAX)
--Get distinct values of the PIVOT Column
SELECT #ColumnName = ISNULL(#ColumnName + ',', '') + QUOTENAME(qID)
FROM(SELECT qID FROM qTable WHERE cID = #cID) AS QuestionID
DECLARE# PivotTableSQL NVARCHAR(MAX)
SET# PivotTableSQL = N'
SELECT
dID, dName, ' + #ColumnName + '
FROM (
SELECT dID, dName, qID, weighted AS [score]
FROM sourceTable
WHERE sID = ' + CAST(#sID AS nvarchar(8)) + ' AND cID = ' + CAST(#cID AS nvarchar(8)) + '
) AS PivotData
PIVOT (
SUM(score)
FOR [qID] IN (
' + #ColumnName + '
)
) AS PivotTable
GROUP BY
dID, dName,' + #ColumnName
EXECUTE(#PivotTableSQL)

Related

How to create comma delimited list from table with dynamic columns

I want to be able to grab all of the records in a table into a comma delimited list that I can then use to insert into a table on another database. Due to permission restrictions on the customer's server I cannot access any of the options when right-clicking on the database name, and all of the solutions I've found so far involve having permission to do so (e.g. Tasks > Export Data...)
I have tried using COALESCE to do this, however the problem is that my table could have any number of columns. Columns can be added/deleted at any time through the UI by the users and therefore I cannot hard code the columns in my select statement.
Here is what I have written so far, using a simple CTE statement where there are three columns (RowCode, RowOrd, RowText) and concatenating them into a variable that I print out. I just want to find a way to grab these column names dynamically instead of hard coding them. I'll also need to account for various types of column names by casting them each as varchar in the variable.
DECLARE #listStr VARCHAR(MAX)
;WITH tableData AS
(
SELECT *
FROM tableRows
)
SELECT
#listStr = ISNULL(#listStr + 'select ','select ') + '''' + RowCode + ''',''' + cast(RowOrd as varchar) + ''',''' + RowText + '''' + Char(13)
FROM
tableData
PRINT #listStr
The tableRows table contains the following records
RowCode RowOrd RowText
-----------------------
RowA 1 Row A
RowB 2 Row B
And the variable #listStr is currently printing this, which is correct
select 'RowA','1.00','Row A'
select 'RowB','2.00','Row B'
Thanks in advance!
With a bit of XML you can dynamically gather and "stringify" your values
Declare #tableRows table (RowCode varchar(50), RowOrd int, RowText varchar(50))
Insert Into #tableRows values
('RowA',1,'Row A'),
('RowB',2,'Row B')
Declare #listStr VARCHAR(MAX) = ''
Select #listStr = #listStr + C.String + char(13)
From #tableRows A
Cross Apply (Select XMLData = cast((Select A.* for XML RAW) as xml)) B
Cross Apply (
Select String = 'select '+Stuff((Select ',' +Value
From (
Select Value = ''''+attr.value('.','varchar(max)')+''''
From B.XMLData.nodes('/row') as A(r)
Cross Apply A.r.nodes('./#*') AS B(attr)
) X
For XML Path ('')),1,1,'')
) C
Select #listStr
Returns
select 'RowA','1','Row A'
select 'RowB','2','Row B'

SQL Pivot table without aggregate

I have a number of text files that are in a format similar to what is shown below.
ENTRY,1,000000,Widget 4000,1,,,2,,
FIELD,Type,A
FIELD,Component,Widget 4000
FIELD,Vendor,Acme
ENTRY,2,000000,PRODUCT XYZ,1,,,3,
FIELD,Type,B
FIELD,ItemAssembly,ABCD
FIELD,Component,Product XYZ - 123
FIELD,Description1,Product
FIELD,Description2,XYZ-123
FIELD,Description3,Alternate Part #440
FIELD,Vendor,Contoso
They have been imported into a table with VARCHAR(MAX) as the only field. Each ENTRY is a "new" item, and all the subsequent FIELD rows are properties of that item. The data next to the FIELD is the column name of the property. The data to the right of the property is the data I want to display.
The desired output would be:
ENTRY Type Component Vendor ItemAssembly Description1
1,000000,Widget 4000 A Widget 4000 Acme
2,000000,Product XYZ B Product XYZ-123 Contoso ABCD Product
I've got the column names using the code below (there are several tables that I have UNIONed together to list all the property names).
select #cols =
STUFF (
(select Distinct ', ' + QUOTENAME(ColName) from
(SELECT
SUBSTRING(ltrim(textFileData),CHARINDEX(',', textFileData, 1)+1,CHARINDEX(',', textFileData, CHARINDEX(',', textFileData, 1)+1)- CHARINDEX(',', textFileData, 1)-1) as ColName
FROM [MyDatabase].[dbo].[MyTextFile]
where
(LEFT(textFileData,7) LIKE #c)
UNION
....
) A
FOR XML PATH(''), TYPE).value('.','NVARCHAR(MAX)'),1,1,'')
Is a Pivot table the best way to do this? No aggregation is needed. Is there a better way to accomplish this? I want to list out data next to the FIELD name in a column format.
Thanks!
Here is the solution in SQL fiddle:
http://sqlfiddle.com/#!3/8f0b0/8
Prepare raw data in format (entry, field, value), use dynamic SQL to make pivot on unknown column count.
MAX() for string is enough to simulate "without aggregate" behavior in this case.
create table t(data varchar(max))
insert into t values('ENTRY,1,000000,Widget 4000,1,,,2,,')
insert into t values('FIELD,Type,A')
insert into t values('FIELD,Component,Widget 4000')
insert into t values('FIELD,Vendor,Acme ')
insert into t values('ENTRY,2,000000,PRODUCT XYZ,1,,,3,')
insert into t values('FIELD,Type,B')
insert into t values('FIELD,ItemAssembly,ABCD')
insert into t values('FIELD,Component,Product XYZ - 123')
insert into t values('FIELD,Description1,Product ')
insert into t values('FIELD,Description2,XYZ-123 ')
insert into t values('FIELD,Description3,Alternate Part #440')
insert into t values('FIELD,Vendor,Contoso');
create type preparedtype as table (entry varchar(max), field varchar(max), value varchar(max))
declare #prepared preparedtype
;with identified as
(
select
row_number ( ) over (order by (select 1)) as id,
substring(data, 1, charindex(',', data) - 1) as type,
substring(data, charindex(',', data) + 1, len(data)) as data
from t
)
, tree as
(
select
id,
(select max(id)
from identified
where type = 'ENTRY'
and id <= i.id) as parentid,
type,
data
from identified as i
)
, pivotsrc as
(
select
p.data as entry,
substring(c.data, 1, charindex(',', c.data) - 1) as field,
substring(c.data, charindex(',', c.data) + 1, len(c.data)) as value
from tree as p
inner join tree as c on c.parentid = p.id
where p.id = p.parentid
and c.parentid <> c.id
)
insert into #prepared
select * from pivotsrc
declare #dynamicPivotQuery as nvarchar(max)
declare #columnName as nvarchar(max)
select #columnName = ISNULL(#ColumnName + ',','')
+ QUOTENAME(field)
from (select distinct field from #prepared) AS fields
set #dynamicPivotQuery = N'select * from #prepared
pivot (max(value) for field in (' + #columnName + ')) as result'
exec sp_executesql #DynamicPivotQuery, N'#prepared preparedtype readonly', #prepared
Here your are, this comes back exactly as you need it. I love tricky SQL :-). This is a real ad-hoc singel-statement call.
DECLARE #tbl TABLE(OneCol VARCHAR(MAX));
INSERT INTO #tbl
VALUES('ENTRY,1,000000,Widget 4000,1,,,2,,')
,('FIELD,Type,A')
,('FIELD,Component,Widget 4000')
,('FIELD,Vendor,Acme ')
,('ENTRY,2,000000,PRODUCT XYZ,1,,,3,')
,('FIELD,Type,B')
,('FIELD,ItemAssembly,ABCD')
,('FIELD,Component,Product XYZ - 123')
,('FIELD,Description1,Product ')
,('FIELD,Description2,XYZ-123 ')
,('FIELD,Description3,Alternate Part #440')
,('FIELD,Vendor,Contoso');
WITH OneColumn AS
(
SELECT ROW_NUMBER() OVER(ORDER BY (SELECT 1)) AS inx
,CAST('<root><r>' + REPLACE(OneCol,',','</r><r>') + '</r></root>' AS XML) AS Split
FROM #tbl AS tbl
)
,AsParts AS
(
SELECT inx
,Each.part.value('/root[1]/r[1]','varchar(max)') AS Part1
,Each.part.value('/root[1]/r[2]','varchar(max)') AS Part2
,Each.part.value('/root[1]/r[3]','varchar(max)') AS Part3
,Each.part.value('/root[1]/r[4]','varchar(max)') AS Part4
,Each.part.value('/root[1]/r[5]','varchar(max)') AS Part5
FROM OneColumn
CROSS APPLY Split.nodes('/root') AS Each(part)
)
,TheEntries AS
(
SELECT DISTINCT *
FROM AsParts
WHERE Part1='ENTRY'
)
SELECT TheEntries.Part2 + ',' + TheEntries.Part3 + ',' + TheEntries.Part4 AS [ENTRY]
,MyFields.AsXML.value('(fields[1]/field[Part2="Type"])[1]/Part3[1]','varchar(max)') AS [Type]
,MyFields.AsXML.value('(fields[1]/field[Part2="Component"])[1]/Part3[1]','varchar(max)') AS Component
,MyFields.AsXML.value('(fields[1]/field[Part2="Vendor"])[1]/Part3[1]','varchar(max)') AS Vendor
,MyFields.AsXML.value('(fields[1]/field[Part2="ItemAssembly"])[1]/Part3[1]','varchar(max)') AS ItemAssembly
,MyFields.AsXML.value('(fields[1]/field[Part2="Description1"])[1]/Part3[1]','varchar(max)') AS Description1
FROM TheEntries
CROSS APPLY
(
SELECT *
FROM AsParts AS ap
WHERE ap.Part1='FIELD' AND ap.inx>TheEntries.inx
AND ap.inx < ISNULL((SELECT TOP 1 nextEntry.inx FROM TheEntries AS nextEntry WHERE nextEntry.inx>TheEntries.inx ORDER BY nextEntry.inx DESC),10000000)
ORDER BY ap.inx
FOR XML PATH('field'), ROOT('fields'),TYPE
) AS MyFields(AsXML)

Dynamic Pivot in Sql Syntax Puzzle

I have a tables as follows. Table #temp
Product Date 1st Pass Count 2nd Pass Count 3rd Pass Count
A 06-07-2015 2 4 5
A 06-07-2015 3 2 1
B 06-07-2015 1 1 1
Now I want a view as follows;
Product 06-07-2015 07-07-2015 08-07-2015
A 17 0 0
B 3 0 0
The date column is a sum of the 1st, 2nd and 3rd pass.
I have tried the query below . 2 problems I need help with.
Problem 1 - More than one row for Product A.
Problem 2 - Cant seem to add all 1st, 2nd and 3rd pass in sql query with pivot. Tried sum ( [1st pass]+[2nd pass]+[3rd pass] ) and it gave a syntax error.
Current code that works before I try things to correct the 2 problems above.
DECLARE #cols as NVARCHAR(MAX)
DECLARE #query as NVARCHAR(MAX)
Select #cols=STUFF ( SELECT ',' +QUOTENAME(PRODUCT) FROM #TEMP group by DATE ORDER BY DATE FOR XML PATH (''), TYPE).value.('.',NVARCHAR(MAX)'),1,1,'') set #query='SELECT [PRODUCT],' + #cols + 'from 'Select [PRODUCT],[DATE],[1st Pass],[2nd Pass],[3rd Pass] from #TEMP)x Pivot (sum [1st pass] FOR DTE in ('+#cols+') )p' execute (#query)
Is there something obvious I am missing here in terms of solving these last 2 problems ?
we can get the above result set by using Pivot and Cross Apply
Normal Pivot
DECLARE #t TABLE (Product Varchar(5),dated varchar(10),firstcount int,secondcount int,thirdcpount int)
INSERT INTO #t (Product,dated,firstcount,secondcount,thirdcpount)values
('A','06-07-2015',2,4,5),
('A','06-07-2015',3,2,1),
('B','06-07-2015',1,1,1)
select Product,SUM(ISNULL([06-07-2015],0)) As [06-07-2015],SUM(ISNULL([07-07-2015],0))As [07-07-2015],SUM(ISNULL([08-07-2015],0))As [08-07-2015] from (
select Product,dated,COL,val from #t
CROSS APPLY (VALUES('firstcount',firstcount),('secondcount',secondcount),('thirdcpount',thirdcpount))CS(COL,val))TT
PIVOT (SUM(VAL) FOR Dated IN ([06-07-2015],[07-07-2015],[08-07-2015]))T
GROUP BY Product
And
by using Dynamic Query Pivot
IF OBJECT_ID('tempdb..#t') IS NOT NULL
DROP TABLE #t
GO
CREATE TABLE #t (Product Varchar(5),dated varchar(10),firstcount int,secondcount int,thirdcpount int)
INSERT INTO #t (Product,dated,firstcount,secondcount,thirdcpount)values
('A','06-07-2015',2,4,5),
('A','06-07-2015',3,2,1),
('B','06-07-2015',1,1,1)
,('A','07-07-2015',2,11,5),
('A','07-07-2015',3,2,1),
('B','07-07-2015',1,1,1)
,('A','08-07-2015',3,11,6),
('A','08-07-2015',1,6,1),
('B','08-07-2015',11,1,6)
DECLARE #statement NVARCHAR(max)
,#columns NVARCHAR(max)
SELECT #columns = ISNULL(#columns + ', ', '') + N'[' + tbl.dated + ']'
FROM (
SELECT DISTINCT dated
FROM #t
) AS tbl
SELECT #statement = ' select Product,SUM(ISNULL([06-07-2015],0)) As [06-07-2015],SUM(ISNULL([07-07-2015],0))As [07-07-2015],SUM(ISNULL([08-07-2015],0))As [08-07-2015] from (
select Product,dated,COL,val from #t
CROSS APPLY (VALUES(''firstcount'',firstcount),(''secondcount'',secondcount),(''thirdcpount'',thirdcpount))CS(COL,val))TT
PIVOT (SUM(VAL) FOR Dated IN (' + #columns + ')) as pvt GROUP BY Product'
EXEC sp_executesql #statement = #statement

Extracting data column-wise from table in SQL Server

I currently have a piece of code that pivots a table in which the row data is inserted dynamically. The code is shown below:
CREATE TABLE Table1
([empname] varchar(6), [empqual] varchar(10), [emprank] int, [empexp] int)
INSERT INTO Table1
([empname], [empqual], [emprank], [empexp])
VALUES
('Joyce', 'UNIVERSITY', 1, 11),
('Angela', 'MASTERS', 2, 10),
('Lily', 'MASTERS', 3, 9),
('Sasha', 'UNIVERSITY', 3, 9),
('Harry', 'UNIVERSITY', 3, 9)
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
SET #cols = STUFF((SELECT distinct ',' + 'Column' + CONVERT(VARCHAR,Row_Number() OVER (Order By emprank))
FROM Table1 c
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
SET #query = '
SELECT *
FROM
(
SELECT ''Column'' + CONVERT(VARCHAR,Row_Number() OVER (Order By emprank)) AS Columns,
CONVERT(VARCHAR,empname) as e
FROM Table1
) p
PIVOT
(
MAX (e)
FOR Columns IN
(
' + #cols + ' )
) as pvt
UNION
SELECT *
FROM
(
SELECT ''Column'' + CONVERT(VARCHAR,Row_Number() OVER (Order By emprank)) AS Columns,
CONVERT(VARCHAR,empqual) as e
FROM Table1
) p
PIVOT
(
MAX (e)
FOR Columns IN
(
' + #cols + ' )
) as pvt
UNION
SELECT *
FROM
(
SELECT ''Column'' + CONVERT(VARCHAR,Row_Number() OVER (Order By emprank)) AS Columns,
CONVERT(VARCHAR,emprank) as e
FROM Table1
) p
PIVOT
(
MAX (e)
FOR Columns IN
(
' + #cols + ' )
) as pvt
UNION
SELECT *
FROM
(
SELECT ''Column'' + CONVERT(VARCHAR,Row_Number() OVER (Order By emprank)) AS Columns,
CONVERT(VARCHAR,empexp) as e,
FROM Table1
) p
PIVOT
(
MAX (e)
FOR Columns IN
(
' + #cols + ' )
) as pvt
'
EXECUTE (#query)
The result of the above code is as shown below:
Column1 Column2 Column3 Column4 Column5
1 2 3 3 3
11 10 9 9 9
Joyce Angela Lily Sasha Harry
UNIVERSITY MASTERS MASTERS UNIVERSITY UNIVERSITY
Now, my application requires that I display each of the columns in this table separately, i.e. each of the columns, and not rows, of this table needs to be exported from this table and transferred, possibly into a temporary table, from which it can be displayed easily.
I am well aware of the fact that relational DBs are designed in such a way so as to consider rows, not columns, as individual entities. However, I am constrained by the application on which I am working, which requires code that extracts the data in this table column-wise so that they can be displayed separately.
How would I go about doing this?
This is a terminology overlap.
In SQL, "row" means (roughly) a single entity, and "column" means a property on the entities.
In UI, "row" means data arranged horizontally, and "column" means data arranged vertically.
Your requirement is that you should display your entities vertically. So, retrieve your entities (SQL rows) and then add code in your application to display this data in UI columns. It's unfortunate and perhaps confusing that the terms are the same here, but remember, your database structure (and choice of terminology) is completely irrelevant to your UI layout.
As to what code in your application is required to display the data... well, you haven't even told us what language it's in, so I can't help there.

PIVOT in sql 2005

I need to pivot one column (Numbers column).
example need this data:
a 1
a 2
b 3
b 4
c 5
d 6
d 7
d 8
d 9
e 10
e 11
e 12
e 13
e 14
Look like this
a 1 2
b 3 4
c 5
d 6 7 8 9
e 10 11 12 13 14
any help would be greatly appreciated...
Using ROW_NUMBER(), PIVOT and some dynamic SQL (but no cursor necessary) :
CREATE TABLE [dbo].[stackoverflow_198716](
[code] [varchar](1) NOT NULL,
[number] [int] NOT NULL
) ON [PRIMARY]
DECLARE #sql AS varchar(max)
DECLARE #pivot_list AS varchar(max) -- Leave NULL for COALESCE technique
DECLARE #select_list AS varchar(max) -- Leave NULL for COALESCE technique
SELECT #pivot_list = COALESCE(#pivot_list + ', ', '') + '[' + CONVERT(varchar, PIVOT_CODE) + ']'
,#select_list = COALESCE(#select_list + ', ', '') + '[' + CONVERT(varchar, PIVOT_CODE) + '] AS [col_' + CONVERT(varchar, PIVOT_CODE) + ']'
FROM (
SELECT DISTINCT PIVOT_CODE
FROM (
SELECT code, number, ROW_NUMBER() OVER (PARTITION BY code ORDER BY number) AS PIVOT_CODE
FROM stackoverflow_198716
) AS rows
) AS PIVOT_CODES
SET #sql = '
;WITH p AS (
SELECT code, number, ROW_NUMBER() OVER (PARTITION BY code ORDER BY number) AS PIVOT_CODE
FROM stackoverflow_198716
)
SELECT code, ' + #select_list + '
FROM p
PIVOT (
MIN(number)
FOR PIVOT_CODE IN (
' + #pivot_list + '
)
) AS pvt
'
PRINT #sql
EXEC (#sql)
Just because I wanted to get some more experience with CTEs, I came up with the following:
WITH CTE(CTEstring, CTEids, CTElast_id)
AS
(
SELECT string, CAST(id AS VARCHAR(1000)), id
FROM dbo.Test_Pivot TP1
WHERE NOT EXISTS (SELECT * FROM dbo.Test_Pivot TP2 WHERE TP2.string = TP1.string AND TP2.id < TP1.id)
UNION ALL
SELECT CTEstring, CAST(CTEids + ' ' + CAST(TP.id AS VARCHAR) AS VARCHAR(1000)), TP.id
FROM dbo.Test_Pivot TP
INNER JOIN CTE ON
CTE.CTEstring = TP.string
WHERE
TP.id > CTE.CTElast_id AND
NOT EXISTS (SELECT * FROM dbo.Test_Pivot WHERE string = CTE.CTEstring AND id > CTE.CTElast_id AND id < TP.id)
)
SELECT
t1.CTEstring, t1.CTEids
FROM CTE t1
INNER JOIN (SELECT CTEstring, MAX(LEN(CTEids)) AS max_len_ids FROM CTE GROUP BY CTEstring) SQ ON SQ.CTEstring = t1.CTEstring AND SQ.max_len_ids = LEN(t1.CTEids)
ORDER BY CTEstring
GO
It might need some tweaking, but it worked with your example
The coalesce function could also be used here, similar to other questions that have been asked about concatenating data.
How to create a SQL Server function to "join" multiple rows from a subquery into a single delimited field?
This related question should have the answer you need: SQL Server: Examples of PIVOTing String data
A Matrix control in SSRS has dynamic columns, if this data is bound for a report anyways then you could use that. Otherwise you'll have to create a sql sproc that generates the sql like in the exaamples dynamicly and then executes it.
I'm not sure that what you're doing is really possible (or at least practical) in SQL - I'm not sure, because I'm still not exactly sure what you want to do.
You could build that pivot table in your client application, for example with:
select distinct Letter from MyTable
to get the list of letters, and then use a parameterized query inside a loop:
select Number from MyTable where Letter=:letter
to get the list of numbers for each letter.

Resources