SQL Server 2005 - Dynamic PIVOT query - sql

I require your kind help on a PIVOT query problem.
The scenario is I have a voting system. Comprises of 3 tables.
Elections, Candidates, Votes.
The PIVOT relates to PIVOTing the Candidate names as Columns and having the VOTE data as the data.
So far I have attemped this and not got it to work, I've gotten into a muddle with it :(
CREATE TABLE #MYELECTIONS (E_POSITION_CODE INT, E_POSITIONNAME VARCHAR(50))
INSERT INTO #MYELECTIONS VALUES (147,'MANAGER')
INSERT INTO #MYELECTIONS VALUES (148,'CHEF')
INSERT INTO #MYELECTIONS VALUES (149,'WAITER')
CREATE TABLE #MYCANDIDATES (C_CANDIDATE_CODE INT,
C_CANDIDATENAME VARCHAR (50), C_POSITION_CODE INT)
INSERT INTO #MYCANDIDATES VALUES (100,'TOM CRUISE', 147)
INSERT INTO #MYCANDIDATES VALUES (101,'MICKY MOUSE', 147)
INSERT INTO #MYCANDIDATES VALUES (103,'DONALD DUCK', 147)
INSERT INTO #MYCANDIDATES VALUES (100,'TOM CRUISE', 148)
CREATE TABLE #MYVOTES (V_POSITION_CODE INT,
V_CANDIDATE_CODE INT, VOTINGPREFERENCE SMALLINT)
INSERT INTO #MYVOTES VALUES (147,100,1)
INSERT INTO #MYVOTES VALUES (147,100,1)
INSERT INTO #MYVOTES VALUES (147,100,1)
INSERT INTO #MYVOTES VALUES (147,100,1)
INSERT INTO #MYVOTES VALUES (147,101,1)
INSERT INTO #MYVOTES VALUES (147,101,1)
INSERT INTO #MYVOTES VALUES (147,103,1)
INSERT INTO #MYVOTES VALUES (148,100,1)
INSERT INTO #MYVOTES VALUES (148,100,1)
DECLARE #DynamicPivotQuery AS NVARCHAR(MAX)
DECLARE #ColumnName AS NVARCHAR(MAX)
DECLARE #MyPositionCode AS INT
SET #MyPositionCode = 147
DECLARE #MyOutput AS NVARCHAR(MAX)
--Get distinct values of the PIVOT Column
SELECT #ColumnName = ISNULL(#ColumnName + ',','')
+ QUOTENAME(C_CANDIDATENAME)
FROM (SELECT TOP 100 PERCENT C_CANDIDATENAME
FROM #MYCANDIDATES
WHERE C_POSITION_CODE = #MyPositionCode
ORDER BY C_CANDIDATE_CODE) AS C_CANDIDATENAME
--Prepare the `PIVOT` query using the dynamic sql
SET #DynamicPivotQuery =
N'SELECT ROW_NUMBER() OVER (ORDER BY E_POSITIONNAME) AS Idnum,
E_POSITIONNAME AS [Position Name], ' + #ColumnName + '
FROM #MYVOTES
INNER JOIN #MYELECTIONS
ON (#MYVOTES.V_POSITION_CODE = #MYELECTIONS.E_POSITION_CODE)
PIVOT(SUM(VOTINGPREFERENCE))
FOR C_CANDIDATENAME IN (' + #ColumnName + ') AS PVTTable
WHERE #MYELECTIONS.E_POSITION_CODE = 147'
--Execute the Dynamic Pivot Query
EXEC sp_executesql #DynamicPivotQuery,
#MyOutput = #MyOutput OUTPUT
SELECT #MyOutput
DROP TABLE #MYELECTIONS
DROP TABLE #MYCANDIDATES
DROP TABLE #MYVOTES
It has to be a dynamic PIVOT as the Candidate Names that I want to Pivot as Columns, could be dynamic depending on how many Candidates there are for an Election position.
The desired output for the above would be like this:-
Election #MyPositionCode = 147
Idnum Position_Name TOM CRUISE MICKY MOUSE DONALD DUCK...
1 MANAGER 4 2 1
Election #MyPositionCode = 148
Idnum Position_Name TOM CRUISE MICKY MOUSE DONALD DUCK...
1 CHEF 2 0 0

Try this
DECLARE #MyPositionCode AS INT
SET #MyPositionCode = 147
Declare the variables for selecting dynamic columns
DECLARE #DynamicPivotQuery AS NVARCHAR(MAX)
DECLARE #ColumnName AS NVARCHAR(MAX)
DECLARE #cols NVARCHAR (MAX)
SELECT #cols = COALESCE (#cols + ',[' + C_CANDIDATENAME + ']', '[' + C_CANDIDATENAME + ']')
FROM
(
SELECT TOP 100 PERCENT C_CANDIDATENAME
FROM #MYCANDIDATES
WHERE C_POSITION_CODE = #MyPositionCode
ORDER BY C_CANDIDATE_CODE
) PV
ORDER BY C_CANDIDATENAME desc
If you want to replace NULL with zero
DECLARE #NulltoZeroCols NVARCHAR (MAX)
SELECT #NullToZeroCols = SUBSTRING((SELECT ',ISNULL(['+C_CANDIDATENAME+'],0) AS ['+C_CANDIDATENAME+']'
FROM
(
SELECT TOP 100 PERCENT C_CANDIDATENAME
FROM #MYCANDIDATES
WHERE C_POSITION_CODE = #MyPositionCode
ORDER BY C_CANDIDATE_CODE
)TAB
ORDER BY C_CANDIDATENAME desc FOR XML PATH('')),2,8000)
Now pivot it dynamically. I have written the logic inside
DECLARE #query NVARCHAR(MAX)
SET #query = '-- You can apply ROW_NUMBER() here which is the data after pivot
SELECT ROW_NUMBER() OVER (ORDER BY E_POSITIONNAME) AS Idnum,
,E_POSITIONNAME,'+#NullToZeroCols+'
FROM
(
-- Select data before pivot
SELECT TOP 100 PERCENT ME.E_POSITIONNAME,MC.C_CANDIDATENAME,
COUNT(MC.C_CANDIDATENAME) OVER(PARTITION BY ME.E_POSITIONNAME,MC.C_CANDIDATENAME) CNT
FROM #MYELECTIONS ME
JOIN #MYCANDIDATES MC ON ME.E_POSITION_CODE=MC.C_POSITION_CODE
JOIN #MYVOTES MV ON MC.C_CANDIDATE_CODE=MV.V_CANDIDATE_CODE
AND MV.V_POSITION_CODE=MC.C_POSITION_CODE
WHERE MC.C_POSITION_CODE = ' + CAST(#MyPositionCode AS VARCHAR(30)) + '
) x
PIVOT
(
MIN(CNT)
FOR C_CANDIDATENAME IN (' + #cols + ')
) p
;'
EXEC SP_EXECUTESQL #query
Click the below link. If the result is not auto-generated, press RUN QUERY button.
Click here to view result

Related

Dynamic pivot in SQL server: insert spaces in concatenation and referencing a pivot

I have a table in long format which I convert to wide format dynamically.
The code was heavily influenced by: SQL Server dynamic PIVOT query?
create table #temp
(
ID int,
category varchar(15),
Answer varchar (5)
)
insert into #temp values ('1', 'breakfast','yes')
insert into #temp values ('1', 'lunch','no')
insert into #temp values ('1', 'dinner','yes')
insert into #temp values ('2', 'breakfast','no')
insert into #temp values ('2', 'lunch', 'yes')
insert into #temp values ('2', 'dinner', 'no')
select * from #temp
Which I can convert into wide format:
DECLARE #cols AS VARCHAR(MAX)='';
DECLARE #query AS VARCHAR(MAX)='';
SELECT #cols = #cols + QUOTENAME(category) + ',' FROM (select distinct category from #temp ) as tmp
select #cols = substring(#cols, 0, len(#cols))
exec (
'SELECT ID, '+#cols+', concat('+#cols+' )as NewCol from
(
select ID, category,answer from #temp
) pivotexample
pivot
(
max(Answer) for category in (' + #cols + ')
) as pivotexample2'
)
drop table #temp
The distinct values in the category column can change so I needed a dynamic solution (as above). This give the below pivoted output:
The issue I have is how can I insert a separator in the concatenation part that creates NewColumn in the pivot.
Also when I then run a select * from pivotexample2 query, it says Invalid object name 'pivotexample2'. I don't understand why this is, because this is the alias I have given it and want to reference it for things like joins further in the pipeline. How can I give it an alias so I can refence it again? Is it possible to put the pivot within a CTE so I can refence it again?
You can use concat_ws:
DECLARE #cols AS VARCHAR(MAX)='';
DECLARE #query AS VARCHAR(MAX)='';
SELECT #cols = #cols + QUOTENAME(category) + ',' FROM (select distinct category from #temp ) as tmp
select #cols = substring(#cols, 0, len(#cols))
exec (
'SELECT ID, '+#cols+', concat_ws('','', '+#cols+' )as NewCol from
(
select ID, category,answer from #temp
) pivotexample
pivot
(
max(Answer) for category in (' + #cols + ')
) as pivotexample2'
)
drop table #temp
It would return:
ID
breakfast
dinner
lunch
NewCol
1
yes
yes
no
yes,yes,no
2
no
no
yes
no,no,yes
DBFiddle: https://dbfiddle.uk/?rdbms=sqlserver_2019&fiddle=45dce502caf3b71662b963fb52dff94e

Calculation in SQL Server 2012

This is my code which is not working properly. I need only one record inserted for an employee but it inserts multiple
CREATE TABLE #Table1 (ID VARCHAR(5), formula VARCHAR(MAX))
CREATE TABLE #table2 (employeid VARCHAR(5), payhead VARCHAR(5), amount MONEY)
CREATE TABLE #table3 (employeid VARCHAR(5), payiD VARCHAR(5), amount MONEY)
INSERT INTO #Table1
VALUES ('PAY1', NULL), ('PAY2', '(PAY1*12%)'), ('PAY3', 'PAY1 + PAY2')
INSERT INTO #Table2
VALUES ('E001','PAY1', 2000),('E002','PAY1', 5000),('E003','PAY1', 3000)
INSERT INTO #Table2
VALUES ('E001','PAY3', 1000),('E002','PAY3', 3000),('E003','PAY3', 2000)
DECLARE #SQL VARCHAR (MAX) = ''
SELECT #SQL = #SQL + '
CROSS APPLY (
SELECT ' + ID + ' = ' + REPLACE(formula,'%','/100') + '
)
AS ' + ID + '
'
FROM #Table1
WHERE formula IS NOT NULL
SET #SQL = ';WITH cte AS
(
SELECT employeid, Amount as Pay1
FROM #table2
WHERE EMPLOYEID = ''E001''
)
SELECT *
INTO #TMP -- insert the result into a new temp table
FROM
cte ' + #SQL + '
INSERT #Table3
SELECT EmployeID, PayID, Amount
FROM #TMP
CROSS APPLY (
VALUES ' + STUFF(
(SELECT ',' + '(''' + ID + ''',' + ID + ')' FROM
#Table1 FOR XML PATH ('')), 1, 1, ''
) + '
) A(PayID, Amount)
SELECT * FROM #TMP
DROP TABLE #TMP'
PRINT(#SQL)
EXEC(#SQL)
SELECT * FROM #table3 WHERE employeid='E001'
DROP TABLE #table1
DROP TABLE #table2
DROP TABLE #table3
my problem is there are 2 employee E001, E002, and three pay code.so after executing it should show one record as one employee.but it shows multiple.secondly, if add pay3 like below
('PAY2', '(PAY1+PAY3*12%)') Then it not working.

dynamic pivoting for each column in master and for all dates

i have table tbl_product_details like :
create table tbl_product_details (productdate date, productname varchar(100), nav float , ncv float)
insert into tbl_product_details
values('2017-10-04 00:00:00', 'UU8899', 10.23, 13.89),
('2017-10-05 00:00:00', 'UU8899', 12.23, 14.89)
and one master table where name of required pivoting column stored like.
create table dynamiccols(id int , colname varchar(100))
insert into dynamiccols
values(1,'nav'),(1,'ncv')
i need to create dynamic pivoting to show pivoted data for all dates columns along with each column mentioned in dynamiccols
tables like :
productname nav~~04-10-2017 nav~~05-10-2017 ncv~~04-10-2017 ncv~~05-10-2017
UU8899 10.23 12.23 13.89 14.89
i have tried it but not able to make it complete dyanmic for each column in dynamiccols
DECLARE #DynamicPivotQuery AS NVARCHAR(MAX)
DECLARE #ColumnName_header AS NVARCHAR(MAX)
DECLARE #ColumnName_pivot AS NVARCHAR(MAX)
declare #kpiname varchar(100)='ncv'
--Get distinct values of the PIVOT Column
SELECT #ColumnName_header= ISNULL(#ColumnName_header + ',','')
+ 'sum('+QUOTENAME(productdate) +') as ' ++QUOTENAME(#kpiname+'~~'+convert(varchar(11),productdate,105)),
#ColumnName_pivot= ISNULL(#ColumnName_pivot + ',','')
+ QUOTENAME(productdate)
FROM (SELECT DISTINCT productdate
FROM tbl_product_details
where productdate between '02-oct-2017' and '05-oct-2017') AS productdate
SET #DynamicPivotQuery =
N' select *
from ( SELECT productname ,' + #ColumnName_header + '
FROM tbl_product_details
PIVOT(sum('+#kpiname+')
FOR productdate IN (' + #ColumnName_pivot + ')) AS PVTTable
group by productname ) '+ char(97)
print #DynamicPivotQuery
exec(#DynamicPivotQuery)

How to provide custom name to column in pivoting

I have a table like this:
id unit
1 mm
2 cm
3 kg
When I perform pivot operation on this, I am getting result as follows:
1 2 3
mm cm kg
Is it possible to get custom column names here, something like this:
d1 d2 d3
mm cm kg
I am using Pivot for this as:
IF OBJECT_ID('tempdb..#t') IS NOT NULL
DROP TABLE #t
GO
CREATE table #t
(id varchar(max),unit varchar(max))
insert into #t (id,unit)values
(1,'kg'),
(2,'cm'),
(3,'mm'),
(4,'m')
DECLARE #statement NVARCHAR(max)
,#columns NVARCHAR(max)
SELECT #columns = ISNULL(#columns + ',', '') + N'[' + cast(tbl.id as varchar(max)) + ']'
FROM (
SELECT DISTINCT id
FROM #t
) AS tbl
SELECT #statement = 'select *
INTO ##temp
from (
SELECT id,[unit]
FROM #t
) as s
PIVOT
(max(unit) FOR id in(' + #columns + ')) as pvt
'
EXEC sp_executesql #statement = #statement
SELECT * FROM ##temp
DROP TABLE #t
DROP TABLE ##temp
Is it possible?
Thanks
IF OBJECT_ID('tempdb..#t') IS NOT NULL
DROP TABLE #t
GO
CREATE TABLE #t (
id VARCHAR(10),
unit VARCHAR(100)
)
INSERT INTO #t (id, unit)
VALUES
('1', 'kg'),
('2', 'cm'),
('3', 'mm'),
('4', 'mm')
DECLARE #SQL NVARCHAR(MAX), #columns NVARCHAR(MAX)
SELECT #columns = STUFF((
SELECT ',[D' + id + ']'
FROM #t
FOR XML PATH('')), 1, 1, '')
SELECT #SQL = '
SELECT *
FROM (
SELECT [unit], col = N''D'' + id
FROM #t
) s
PIVOT (MAX(unit) FOR col IN (' + #columns + ')) p'
EXEC sys.sp_executesql #SQL
Just add a prefix to your ID. Example
SELECT #statement = 'select * INTO ##temp from
( SELECT [id] = ''d''+id,[unit] FROM #t ) as s
PIVOT
(max(unit) FOR id in(' + #columns + ')) as pvt '
Also it's terrible practice to use global temp tables! Especially one named ##temp
You can use a CASE expression with a dynamic sql query.
CREATE TABLE #t
(
id INT,
unit VARCHAR(2)
);
INSERT INTO #t VALUES
(1,'mm'),
(2,'cm'),
(3,'kg');
DECLARE #query AS VARCHAR(MAX);
SELECT #query = 'SELECT ' +
STUFF
(
(
SELECT DISTINCT ',MAX(CASE WHEN id = '+ CAST(id AS VARCHAR(10))
+ ' THEN unit END) AS d' + CAST(id AS VARCHAR(10))
FROM #t
FOR XML PATH('')
),
1,1,'');
SELECT #query += ' FROM #t;';
EXECUTE(#query);
Result
+----+----+----+
| d1 | d2 | d3 |
+----+----+----+
| mm | cm | kg |
+----+----+----+
SELECT #statement = 'select * INTO ##temp from ( SELECT ''d''+id AS [id],[unit] FROM #t ) as s PIVOT (max(unit) FOR id in(' + #columns + ')) as pvt '

Getting a Dynamically-Generated Pivot-Table into a Temp Table

I've seen this, so I know how to create a pivot table with a dynamically generated set of fields. My problem now is that I'd like to get the results into a temporary table.
I know that in order to get the result set into a temp table from an EXEC statement you need to predefine the temp table. In the case of a dynamically generated pivot table, there is no way to know the fields beforehand.
The only way I can think of to get this type of functionality is to create a permanent table using dynamic SQL. Is there a better way?
Ran in to this issue today, and posted on my blog. Short description of solution, is to create a temporary table with one column, and then ALTER it dynamically using sp_executesql. Then you can insert the results of the dynamic PIVOT into it. Working example below.
CREATE TABLE #Manufacturers
(
ManufacturerID INT PRIMARY KEY,
Name VARCHAR(128)
)
INSERT INTO #Manufacturers (ManufacturerID, Name)
VALUES (1,'Dell')
INSERT INTO #Manufacturers (ManufacturerID, Name)
VALUES (2,'Lenovo')
INSERT INTO #Manufacturers (ManufacturerID, Name)
VALUES (3,'HP')
CREATE TABLE #Years
(YearID INT, Description VARCHAR(128))
GO
INSERT INTO #Years (YearID, Description) VALUES (1, '2014')
INSERT INTO #Years (YearID, Description) VALUES (2, '2015')
GO
CREATE TABLE #Sales
(ManufacturerID INT, YearID INT,Revenue MONEY)
GO
INSERT INTO #Sales (ManufacturerID, YearID, Revenue) VALUES(1,2,59000000000)
INSERT INTO #Sales (ManufacturerID, YearID, Revenue) VALUES(2,2,46000000000)
INSERT INTO #Sales (ManufacturerID, YearID, Revenue) VALUES(3,2,111500000000)
INSERT INTO #Sales (ManufacturerID, YearID, Revenue) VALUES(1,1,55000000000)
INSERT INTO #Sales (ManufacturerID, YearID, Revenue) VALUES(2,1,42000000000)
INSERT INTO #Sales (ManufacturerID, YearID, Revenue) VALUES(3,1,101500000000)
GO
DECLARE #SQL AS NVARCHAR(MAX)
DECLARE #PivotColumnName AS NVARCHAR(MAX)
DECLARE #TempTableColumnName AS NVARCHAR(MAX)
DECLARE #AlterTempTable AS NVARCHAR(MAX)
--get delimited column names for various SQL statements below
SELECT
-- column names for pivot
#PivotColumnName= ISNULL(#PivotColumnName + N',',N'') + QUOTENAME(CONVERT(NVARCHAR(10),YearID)),
-- column names for insert into temp table
#TempTableColumnName = ISNULL(#TempTableColumnName + N',',N'') + QUOTENAME('Y' + CONVERT(NVARCHAR(10),YearID)),
-- column names for alteration of temp table
#AlterTempTable = ISNULL(#AlterTempTable + N',',N'') + QUOTENAME('Y' + CONVERT(NVARCHAR(10),YearID)) + ' MONEY'
FROM (SELECT DISTINCT [YearID] FROM #Sales) AS Sales
CREATE TABLE #Pivot
(
ManufacturerID INT
)
-- Thats it! Because the following step will flesh it out.
SET #SQL = 'ALTER TABLE #Pivot ADD ' + #AlterTempTable
EXEC sp_executesql #SQL
--execute the dynamic PIVOT query into the temp table
SET #SQL = N'
INSERT INTO #Pivot (ManufacturerID, ' + #TempTableColumnName + ')
SELECT ManufacturerID, ' + #PivotColumnName + '
FROM #Sales S
PIVOT(SUM(Revenue)
FOR S.YearID IN (' + #PivotColumnName + ')) AS PivotTable'
EXEC sp_executesql #SQL
SELECT M.Name, P.*
FROM #Manufacturers M
INNER JOIN #Pivot P ON M.ManufacturerID = P.ManufacturerID
you could do this:
-- add 'loopback' linkedserver
if exists (select * from master..sysservers where srvname = 'loopback')
exec sp_dropserver 'loopback'
go
exec sp_addlinkedserver #server = N'loopback',
#srvproduct = N'',
#provider = N'SQLOLEDB',
#datasrc = ##servername
go
declare #myDynamicSQL varchar(max)
select #myDynamicSQL = 'exec sp_who'
exec('
select * into #t from openquery(loopback, ''' + #myDynamicSQL + ''');
select * from #t
')
EDIT: addded dynamic sql to accept params to openquery
Let me try this explanation of select into instead. I'm running SQL Server 2005 as well. Because you have PIVOT tables I'm going to assume the same or 2008.
select
o.*,
OtherField1,
OtherField2
INTO #temp
FROM
OriginalOtherData as ood
PIVOT (
MAX([Value])
FOR Field in (OtherField1, OtherField2)
) as piv
RIGHT OUTER join
Original o on o.OriginalSD = piv.OriginalSD
select * from #temp
Drop table #temp
The only difference between a normal select and a select into is that INTO #table part.
for query (select col1, col2, col3 from tablename
col1 becomes rowlabels
col2 becomes columnheaders
col3 is the dataset
also gets rid of the global table
if OBJECT_ID('tempdb..#3') is not null drop table #3
if OBJECT_ID('tempdb..##3') is not null drop table ##3
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME( col2 ) from tablename FOR XML PATH(''), col2).value('.', 'NVARCHAR(MAX)'),1,1,'')
set #query = 'SELECT col1, ' + #cols + ' into ##3 from ( select col1, col2, col3 from tablename ) x pivot ( max(col3)for col2 in (' + #cols + ')) p '
execute(#query)
select * into #3 from ##3 if OBJECT_ID('tempdb..##3') -- is not null drop table ##3