Simplify Dynamic SQL Pivot Table - sql-server

I have written a Dynamic Pivot Table Query based on the following. Here is a SQL FIDDLE for reference.
CREATE TABLE TestTable1 ([idnumber] INT, [DataTypeId] INT)
GO
INSERT INTO TestTable1
VALUES (1, 108), (1, 108), (1, 108), (2, 108),
(2, 108), (3, 108), (1, 109),(1, 109),
(1, 110),(2, 110),(1, 111),(4, 108),(4, 108),
(4, 110),(4, 111)
GO
Here is the Dynamic SQL that I wrote
DECLARE #SQL NVARCHAR(MAX),
#Cols NVARCHAR(MAX),
#ColsP NVARCHAR(MAX)
SELECT #Cols = STUFF((select ',
ISNULL([' + CAST([DataTypeId] as varchar(10)) + '], 0) AS ''' + CAST([DataTypeId] as varchar(10)) + ''''
FROM
(
SELECT [DataTypeId] FROM [TestTable1]
GROUP BY [DataTypeId]
HAVING [DataTypeId] <> ''
) AS d
ORDER BY [DataTypeId] FOR XML PATH(''),type).value('.','varchar(max)'),1,2,'')
-- /////////////THIS IS WHAT I WANT REMOVED ////////////////////
SELECT #ColsP = STUFF((select ',
[' + CAST([DataTypeId] as varchar(10)) + ']'
FROM
(
SELECT [DataTypeId] FROM [TestTable1]
GROUP BY [DataTypeId]
HAVING [DataTypeId] <> ''
) AS d
ORDER BY [DataTypeId] FOR XML PATH(''),type).value('.','varchar(max)'),1,2,'')
-- /////////////////////////////////////////////////////////////
SET #SQL = 'SELECT idnumber,' + #Cols + '
FROM
(SELECT idnumber, COUNT([DataTypeId]) AS Total, [DataTypeId] FROM [TestTable1]
GROUP BY idnumber, [DataTypeId]
HAVING [DataTypeId] <> ''''
) p
PIVOT
(
SUM(Total) FOR [DataTypeId] IN (' + #ColsP + ')
) AS pvt
ORDER BY pvt.idnumber'
-- print #SQL
EXECUTE( #SQL)
I get the result that I want:
| IDNUMBER | 108 | 109 | 110 | 111 |
|----------|-----|-----|-----|-----|
| 1 | 3 | 2 | 1 | 1 |
| 2 | 2 | 0 | 1 | 0 |
| 3 | 1 | 0 | 0 | 0 |
| 4 | 2 | 0 | 1 | 1 |
But I am sure it can be done better. I would like to remove where I populate the variable #ColsP - SELECT #ColsP = STUFF((select...")
There should be a way where I can create this dynamic code with just one loop through TestTable1. As you can see, I loop through it twice. Once to read what columns to create for the select statement, and once for the PIVOT table.
Here is the code that is generated by the Dynamic SQL:
SELECT idnumber,
ISNULL([108], 0) AS '108',
ISNULL([109], 0) AS '109',
ISNULL([110], 0) AS '110',
ISNULL([111], 0) AS '111'
FROM
(
SELECT idnumber, COUNT([DataTypeId]) AS Total, [DataTypeId]
FROM [TestTable2]
GROUP BY idnumber, [DataTypeId]
HAVING [DataTypeId] <> ''
) p
PIVOT
(
SUM(Total) FOR [DataTypeId] IN ([108], [109], [110], [111])
) AS pvt
ORDER BY pvt.idnumber

You can shorten your code considerably. First, you can just use count to aggregate the data in the PIVOT. There is no need for the inner count to aggregate the data or the HAVING clause. Finally, you only need to create the list of columns once. You could easily improve the code to:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(DataTypeId)
from TestTable1
group by DataTypeId
order by DataTypeId
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query
= N'SELECT idnumber, ' + #cols + N'
from
(
select idnumber, DataTypeId
from TestTable1
) x
pivot
(
count(DataTypeId)
for DataTypeId in (' + #cols + N')
) p '
exec sp_executesql #query;
See SQL Fiddle with Demo. This gives the same result:
| IDNUMBER | 108 | 109 | 110 | 111 |
|----------|-----|-----|-----|-----|
| 1 | 3 | 2 | 1 | 1 |
| 2 | 2 | 0 | 1 | 0 |
| 3 | 1 | 0 | 0 | 0 |
| 4 | 2 | 0 | 1 | 1 |

Try replacing it with this.
SET NOCOUNT ON
IF OBJECT_ID('TestTable1') IS NOT NULL
DROP TABLE TestTable1
GO
CREATE TABLE TestTable1 ([idnumber] INT, [DataTypeId] INT)
GO
INSERT INTO TestTable1 VALUES
(1, 108),(1, 108),(1, 108),(2, 108),(2, 108),
(3, 108),(1, 109),(1, 109),(1, 110),(2, 110),
(1, 111),(4, 108),(4, 108),(4, 110),(4, 111)
DECLARE
#AllColumns NVARCHAR(MAX)
SELECT #AllColumns = ''
SELECT #AllColumns = #AllColumns +
'[' + CAST(DataTypeId as NVARCHAR)+'],'
FROM TestTable1
GROUP BY DataTypeId
SET #AllColumns = LEFT(#AllColumns,LEN(#AllColumns)-1)
PRINT #AllColumns

Related

Using Parameters to Add Columns?

I have a stored procedure which adds a currency record to my exchange rate table but I also need a corresponding column for the added currency.
The table structure is as follows:
CurrencyID CurrencyName Rupee Euro Dollar Pound
----------- ------------ ---------------------- ---------------------- ---------------------- ----------------------
1 Rupee 1 0.008 0.009 0.007
2 Euro 121.3 1 1.08 0.84
3 Dollar 111.4 0.91 1 0.77
4 Pound 143.6 1.18 1.28 1
and my stored procedure so far is this:
CREATE PROCEDURE addCurrency
#CurrencyName varchar(30),
#Rupee float,
#Euro float,
#Dollar float,
#Pound float
AS
BEGIN
INSERT into [dbo].[CurrencyTbl] (CurrencyName, Rupee, Euro, Dollar, Pound)
VALUES (#CurrencyName, #Rupee, #Euro, #Dollar, #Pound)
END
BEGIN
DECLARE #SQL VARCHAR(1000)
SELECT #SQL = 'ALTER TABLE [dbo].[CurrencyTbl] ADD ' + #CurrencyName + ' VARCHAR(30)'
END
But the column is not created
Not that I think any of that is a good idea, but you do not actually execute your created #SQL, you are just selecting it.
Your code vulnerable to SQL Injection (because you are directly executing sql with concatenated parameters), so be careful with that code.
Also, if you are storing numbers, why is your datatype varchar(30)? Your other datatypes are float (which should probably be a numeric/decimal instead of float).
You could use exec sp_executesql #SQL like so:
CREATE PROC addCurrency #CurrencyName varchar(30),#Rupee float,
#Euro float,#Dollar float,#Pound float
AS
BEGIN
INSERT into [dbo].[CurrencyTbl] (CurrencyName , Rupee,Euro, Dollar,Pound )
VALUES (#CurrencyName,#Rupee,#Euro,#Dollar,#Pound )
END
BEGIN
Declare #SQL nVarChar(1000)
SELECT #SQL = 'ALTER TABLE [dbo].[CurrencyTbl] ADD ' + #CurrencyName + ' float;'
exec sp_executesql #SQL;
END
dynamic sql
The curse and blessings of dynamic SQL - Erland Sommarskog
sp_executesql
For example:
/* Monies is the term used by Irkens to refer to their form of currency */
exec addCurrency 'Monies',1,1,1,1
select * from CurrencyTbl
rextester demo: http://rextester.com/CUC99912
returns:
+------------+--------------+------------+----------+----------+----------+--------+
| CurrencyID | CurrencyName | Rupee | Euro | Dollar | Pound | Monies |
+------------+--------------+------------+----------+----------+----------+--------+
| 1 | Rupee | 1,000000 | 0,008000 | 0,009000 | 0,007000 | NULL |
| 2 | Euro | 121,300000 | 1,000000 | 1,080000 | 0,840000 | NULL |
| 3 | Dollar | 111,400000 | 0,910000 | 1,000000 | 0,770000 | NULL |
| 4 | Pound | 143,600000 | 1,180000 | 1,280000 | 1,000000 | NULL |
| 5 | Monies | 1,000000 | 1,000000 | 1,000000 | 1,000000 | NULL |
+------------+--------------+------------+----------+----------+----------+--------+
It may be better to consider an alternative form for your table that does not require adding new columns and updating columns using dynamic sql.
Here is one option:
create table CurrencyTbl (FromCurrencyName varchar(30), ExchangeRate decimal(19,6), ToCurrencyName varchar(30))
insert into CurrencyTbl values
('Rupee ',1.000000,'Rupee')
,('Rupee ',0.008000,'Euro')
,('Rupee ',0.009000,'Dollar')
,('Rupee ',0.007000,'Pound')
,('Euro ',121.300000,'Rupee')
,('Euro ',1.000000,'Euro')
,('Euro ',1.090000,'Dollar')
,('Euro ',0.850000,'Pound')
,('Dollar',111.400000,'Rupee')
,('Dollar',0.910000,'Euro')
,('Dollar',1.000000,'Dollar')
,('Dollar',0.770000,'Pound')
,('Pound ',143.600000,'Rupee')
,('Pound ',1.180000,'Euro')
,('Pound ',1.280000,'Dollar')
,('Pound ',1.000000,'Pound')
And you could pivot the table dynamically like so:
declare #cols nvarchar(max);
declare #sql nvarchar(max);
select #cols = stuff((
select distinct
', ' + quotename(ToCurrencyName)
from CurrencyTbl
for xml path (''), type).value('.','nvarchar(max)')
,1,1,'')
select #sql = '
select FromCurrencyName as CurrencyName,' + #cols + '
from CurrencyTbl
pivot (max([ExchangeRate]) for [ToCurrencyName] in (' + #cols + ') ) p'
exec sp_executesql #sql;
rextester demo: http://rextester.com/EQSC62833
returns:
+--------------+----------+----------+----------+------------+
| CurrencyName | Dollar | Euro | Pound | Rupee |
+--------------+----------+----------+----------+------------+
| Dollar | 1,000000 | 0,910000 | 0,770000 | 111,400000 |
| Euro | 1,090000 | 1,000000 | 0,850000 | 121,300000 |
| Pound | 1,280000 | 1,180000 | 1,000000 | 143,600000 |
| Rupee | 0,009000 | 0,008000 | 0,007000 | 1,000000 |
+--------------+----------+----------+----------+------------+
I created Temp Tables but this ought to work with standard ones as well...
CREATE TABLE #Currencies (
Currencyid INTEGER identity(1, 1) PRIMARY KEY
,CurrencyName VARCHAR(255)
)
CREATE TABLE #ConversionRates (
CurrencyId1 INTEGER
,CurrencyId2 INTEGER
,Conversion1To2Rate DECIMAL(19, 6)
)
Then we have a stored proc to handle the work
/************************************************************************************************
Stored Procedure: dbo.usp_AddCurrency
M. Jay Wheeler -- mjwheele#yahoo.com
****************************************************************************************************/
CREATE PROCEDURE [dbo].usp_AddCurrency --
#CurrencyName VARCHAR(255) = NULL
,#CurrencyId INTEGER = NULL
,#Currency2Id INTEGER = NULL
,#Currency1ToCurrency2ExchangeRate DECIMAL(19, 6) = NULL
AS
BEGIN
DECLARE #ErrorMessage VARCHAR(MAX) = ''
DECLARE #ThrowErrorOnAddingAlreadyExists CHAR(1) = 'N' -- If N will automatically update the currency Name.
BEGIN TRY
--#############################################################################
-- Check Parameters
--#############################################################################
IF #CurrencyId IS NULL
AND #CurrencyName IS NULL
SELECT #ErrorMessage += '#CurrencyID and #CurrencyName cannot both be null.' + CHAR(13)
IF #CurrencyId IS NULL
AND NOT EXISTS (
SELECT 1
FROM #Currencies
WHERE CurrencyName = #CurrencyName
)
BEGIN
INSERT INTO #Currencies (CurrencyName)
SELECT #CurrencyName
SELECT #CurrencyId = SCOPE_IDENTITY()
END
SELECT #CurrencyId = CurrencyId
FROM #Currencies
WHERE CurrencyName = #CurrencyName
AND #CurrencyId IS NULL
SELECT #CurrencyName = CurrencyName
FROM #Currencies
WHERE Currencyid = #CurrencyId
AND #CurrencyName IS NULL
IF NOT EXISTS (
SELECT 1
FROM #Currencies
WHERE Currencyid = #CurrencyId
)
SELECT #ErrorMessage += '#CurrencyID: ' + isnull(Cast(#CurrencyId AS VARCHAR(15)), 'NULL') + ' Does Not Exist.' + CHAR(13)
IF EXISTS (
SELECT 1
FROM #Currencies c
WHERE c.Currencyid <> #CurrencyId
AND c.CurrencyName = #CurrencyName
)
SELECT #ErrorMessage += 'Currency: ' + #CurrencyName + ' already exists with an ID of ' + cast((
SELECT currencyId
FROM #Currencies
WHERE CurrencyName = #CurrencyName
) AS VARCHAR(15)) + CHAR(13)
IF EXISTS (
SELECT 1
FROM #Currencies c
WHERE c.Currencyid = #CurrencyId
AND c.CurrencyName <> #CurrencyName
)
UPDATE #Currencies
SET CurrencyName = #CurrencyName
WHERE Currencyid = #CurrencyId
IF #Currency2Id IS NOT NULL
AND #Currency1ToCurrency2ExchangeRate IS NULL
SELECT #Errormessage += 'Improper Syntax no exchange rate to assignmen.' + CHAR(13)
IF #Currency2id IS NULL
AND #Currency1ToCurrency2ExchangeRate IS NOT NULL
SELECT #Errormessage += 'Improper Syntax no "To Currency" for rate assignment.' + CHAR(13)
IF len(#ErrorMessage) > 0
RAISERROR ('%s' ,16 ,1 ,#ErrorMessage)
--#############################################################################
-- you get the idea more error checking needed to bullet proof. Now assume
-- We have all the correct Parameters set up, we've made sure anything coming in
-- needs to be added or updated by this point.
--#############################################################################
--#############################################################################
-- nothing to do if #Currency2Id is null
--#############################################################################
IF #Currency2Id IS NULL
RETURN
--#############################################################################
-- see if we need to add/Update a conversion rate.
--#############################################################################
UPDATE #ConversionRates
SET Conversion1To2Rate = #Currency1ToCurrency2ExchangeRate
WHERE #CurrencyId = CurrencyId1
AND #Currency2Id = CurrencyId2
INSERT INTO #ConversionRates (
CurrencyId1
,CurrencyId2
,Conversion1To2Rate
)
SELECT #CurrencyId
,#Currency2Id
,#Currency1ToCurrency2ExchangeRate
WHERE NOT EXISTS (
SELECT 1
FROM #ConversionRates
WHERE #CurrencyId = CurrencyId1
AND #Currency2Id = CurrencyId2
)
END TRY
BEGIN CATCH
IF ##Trancount > 0
ROLLBACK TRAN
DECLARE #ErrorBlockLineLen INTEGER = 0
DECLARE #ErrorBlockGotTheFormat BIT = 0
DECLARE #ErrorFormatIndent INTEGER = 3
DECLARE #ErrorBlockBeenThrough INTEGER = NULL -- must be set to null
DECLARE #ThisProcedureName VARCHAR(255) = ISNULL(OBJECT_NAME(##PROCID), 'Testing')
DECLARE #ErrorProc VARCHAR(4000) = CONVERT(VARCHAR(4000), ISNULL(ERROR_PROCEDURE(), #ThisProcedureName))
WHILE #ErrorBlockGotTheFormat = 0
BEGIN
IF #ErrorBlockBeenThrough IS NOT NULL
SELECT #ErrorBlockGotTheFormat = 1
SET #errormessage = Space(isnull(#ErrorFormatIndent, 0)) + #ThisProcedureName + ' Reports Error Thrown by: ' + #ErrorProc + CHAR(13)
SET #errormessage += Space(isnull(#ErrorFormatIndent, 0)) + '-------->' + ISNULL(CONVERT(VARCHAR(4000), ERROR_MESSAGE()), 'Unknown') + '<--------' + CHAR(13) -- + Space(isnull(#ErrorFormatIndent,0)) + REPLICATE('=', #ErrorBlockLineLen) + CHAR(13) -- + Space(isnull(#ErrorFormatIndent,0)) + UPPER(#ThisProcedureName + ' Variable dump:') + CHAR(13) -- + Space(isnull(#ErrorFormatIndent,0)) + REPLICATE('=', #ErrorBlockLineLen) + CHAR(13) --
+ CHAR(13) + Space(isnull(#ErrorFormatIndent, 0)) + '#Currency1ToCurrency2ExchangeRate:.....<' + ISNULL(CAST(#Currency1ToCurrency2ExchangeRate AS VARCHAR(25)), 'Null') + '>' + CHAR(13) + SPACE(ISNULL(#ErrorBlockBeenThrough, 0)) --
+ CHAR(13) + Space(isnull(#ErrorFormatIndent, 0)) + '#Currency2Id:..........................<' + ISNULL(CAST(#Currency2Id AS VARCHAR(25)), 'Null') + '>' + CHAR(13) + SPACE(ISNULL(#ErrorBlockBeenThrough, 0)) --
+ CHAR(13) + Space(isnull(#ErrorFormatIndent, 0)) + '#CurrencyId:...........................<' + ISNULL(CAST(#CurrencyId AS VARCHAR(25)), 'Null') + '>' + CHAR(13) + SPACE(ISNULL(#ErrorBlockBeenThrough, 0)) --
+ CHAR(13) + Space(isnull(#ErrorFormatIndent, 0)) + '#CurrencyName:.........................<' + ISNULL(CAST(#CurrencyName AS VARCHAR(25)), 'Null') + '>' + CHAR(13) + SPACE(ISNULL(#ErrorBlockBeenThrough, 0)) --
--SELECT #ErrorBlockLineLen = MAX(LEN(RTRIM(item)))
--FROM dbo.fnSplit(#errormessage, CHAR(13))
SELECT #ErrorBlockLineLen = 120
SELECT #ErrorBlockBeenThrough = 1
END
RAISERROR ('%s' ,16 ,1 ,#ErrorMessage)
END CATCH
END
GO
And this is the code to populate. I used the inverse for some of the rates as it was easier.
set nocount on
exec usp_AddCurrency 'Rupiee'
exec usp_AddCurrency 'Euro'
exec usp_AddCurrency 'Dollar'
exec usp_AddCurrency 'Pound'
exec usp_AddCurrency "Rupiee", Null, 1, 1.000000
exec usp_AddCurrency "Rupiee", Null, 2, 0.008000
exec usp_AddCurrency "Rupiee", Null, 3, 0.009000
exec usp_AddCurrency "Rupiee", Null, 4, 0.007000
Declare #Inverse Decimal(19,6)
Select #Inverse = cast ((1.000000/ (Select Conversion1To2Rate From #ConversionRates Where CurrencyId1 = 1 and CurrencyId2 =2 )) as decimal(19,6))
exec usp_AddCurrency 'Euro' , Null, 1, #Inverse
exec usp_AddCurrency 'Euro' , Null, 2, 1.000000
exec usp_AddCurrency 'Euro' , Null, 3, 1.090000
exec usp_AddCurrency 'Euro' , Null, 4, 0.850000
Select #Inverse = cast ((1.000000/ (Select Conversion1To2Rate From #ConversionRates Where CurrencyId1 = 1 and CurrencyId2 =3 )) as decimal(19,6))
exec usp_AddCurrency 'Dollar' , Null, 1,#Inverse
Select #Inverse = cast ((1.000000/ (Select Conversion1To2Rate From #ConversionRates Where CurrencyId1 = 2 and CurrencyId2 =3 )) as decimal(19,6))
exec usp_AddCurrency 'Dollar' , Null, 2, #Inverse
exec usp_AddCurrency 'Dollar' , Null, 3, 1.000000
exec usp_AddCurrency 'Dollar' , Null, 4, 0.770000
Select #Inverse = cast ((1.000000/ (Select Conversion1To2Rate From #ConversionRates Where CurrencyId1 = 1 and CurrencyId2 =4 )) as decimal(19,6))
exec usp_AddCurrency 'Pound' , Null, 1, #Inverse
Select #Inverse = cast ((1.000000/ (Select Conversion1To2Rate From #ConversionRates Where CurrencyId1 = 2 and CurrencyId2 =4 )) as decimal(19,6))
exec usp_AddCurrency 'Pound' , Null, 2, #Inverse
Select #Inverse = cast ((1.000000/ (Select Conversion1To2Rate From #ConversionRates Where CurrencyId1 = 3 and CurrencyId2 =4 )) as decimal(19,6))
exec usp_AddCurrency 'Pound' , Null, 3, #Inverse
exec usp_AddCurrency 'Pound' , Null, 4, 1.000000
This is the end of your question I think.
I tested the output with this code:
Declare #MySQL nVarChar(max) = 'Create Table ##output ([Currency Name] Varchar(255),' + char(13)
SELECT #MySQL += STUFF(( SELECT ',[' + CurrencyName + '] VARCHAR(255) '
FROM #Currencies
order by Currencyid asc
FOR
XML PATH('')
), 1, 1, '') + ')'
exec sp_executesql #MySQL
select * into #output from ##output
Drop Table ##Output
Declare #PivotThings varchar(max)
select #PivotThings = STUFF(( SELECT ',[' + CurrencyName + '] '
FROM #Currencies
order by Currencyid asc
FOR
XML PATH('')
), 1, 1, '')
Select #MySQL = '
SELECT *
FROM(
select c1.CurrencyName as Rows, c1.Currencyid as r, c2.CurrencyName as Cols, Conversion1To2Rate as [Value] from #ConversionRates r
join #Currencies c1 on c1.Currencyid = r.CurrencyId1
join #Currencies c2 on c2.Currencyid = r.CurrencyId2
) WorkOrders
PIVOT
(
SUM(value)
FOR [cols] IN (
##PivotThings##
)
) AS PivotTable
ORDER BY [r]
'
Select #MySQL = REPLACE(#mysql , '##PivotThings##', #pivotthings)
exec sp_executesql #mysql
Hope you find this useful. It is my first post evah!

How do I sort a SQL Server 2008 pivot result columns and rows?

I'm using This to get pivot results for a table. But I want the resulting columns and rows both in a specific order. So the rows would be sorted by CDATE and the columns would be CDATE, BALANCE, then the DATE columns.
CDATE | BALANCE | 04-2007 | 05-2007 | 06-2007 | TRANS TOT
2003-01-15 | 5000 | 60 | 0 | 0 | 60
2003-02-15 | 4000 | 40 | 0 | 0 | 40
2003-03-15 | 5500 | 20 | 15 | 15 | 50
I edited the answer in the link you provided in your question to achieve the ordering issue. Just add ORDER BY clause to your dynamic query:
create table temp
(
date datetime,
category varchar(3),
amount money
)
insert into temp values ('1/1/2012', 'ABC', 1000.00)
insert into temp values ('2/1/2012', 'DEF', 500.00)
insert into temp values ('2/1/2012', 'GHI', 800.00)
insert into temp values ('2/10/2012', 'DEF', 700.00)
insert into temp values ('3/1/2012', 'ABC', 1100.00)
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX);
SET #cols = STUFF((SELECT distinct ',' + QUOTENAME(c.category)
FROM temp c
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT date, ' + #cols + ' from
(
select date
, amount
, category
from temp
) x
pivot
(
max(amount)
for category in (' + #cols + ')
) p ORDER BY date, ' + #cols
Execute(#query)
drop table temp
OR You can use common table expression to achieve this. like the following:
set #query = 'WITH CTE_1(date, ' + #cols + ') AS (SELECT date, ' + #cols + ' from
(
select date
, amount
, category
from temp
) x
pivot
(
max(amount)
for category in (' + #cols + ')
) p ) SELECT * FROM CTE_1 ORDER BY date, ' + #cols

In Sybase Show row values as column names and fill this rows with values of another column

I have a table like
ID | TableID | FieldNames | Values
1 | 1 | FirstName | Value 1
2 | 1 | LastName | Value 2
3 | 1 | City | Value 3
4 | 2 | FirstName | Value 4
5 | 2 | LastName | Value 5
6 | 2 | City | Value 6
I need to show it in a grid like below
TableID | FirstName | LastName | City
1 | Value1 | Value2 | Value3
2 | Value4 | Value5 | value6
I know the concept PIVOT will help to get this implemented in MS SQL. But I need to implement this in Sybase. Field names are not fixed.
Do a 3-way self-join:
SELECT t1.Value as FirstName, t2.Value as LastName, t3.Value as City
FROM t t1, t t2, t t3
WHERE t1.ID + 1 = t2.ID
AND t1.ID +2 = t3.ID
With the help of various posts I have figured it out that I can do this using a dynamic case statement, like below.
DECLARE #query VARCHAR(800)
DECLARE #fieldName VARCHAR(255)
DECLARE #LoopCounter INT, #MaxCount INT
SET #query = 'select f.tableID, '
SET #fieldName = ''
SET #LoopCounter = 1
SET #MaxCount = (SELECT COUNT(DISTINCT FieldNames) FROM dbo.Table1)
WHILE (#LoopCounter <= #MaxCount)
BEGIN
SET #fieldName = (SELECT FieldNames FROM dbo.table1 WHERE ID= #LoopCounter)
SET #query = #query + ' max(case when f.FieldNames = ''' + #fieldName +''' then f.[Values] end) AS ''' + #fieldName + ''''
IF(#LoopCounter < #MaxCount)
BEGIN
SET #query = #query + ', '
END
SET #LoopCounter = #LoopCounter + 1
END
SET #query = #query + ' FROM dbo.table1 f GROUP BY f.TableID'
exec (#query)

Pivot Rows to Column with SUM if Hours between two Time Value

Im using MS SQL Server 2008
Table name is tblDateTimeLog:
ID INT(AI)
RecordID INT
DateLog DATE
TimeLog TIME
DetachmentCode INT
EntryDate DATETIME
IsDeleted BIT
UserID VARCHAR(50)
I have a table containing this:
RecordID | DateLog | TimeLog
11 | 2013-06-01 | 07:59:00
11 | 2013-06-01 | 19:01:00
11 | 2013-06-02 | 07:57:00
11 | 2013-06-02 | 19:03:00
11 | 2013-06-03 | 07:49:00
11 | 2013-06-03 | 19:11:00
14 | 2013-06-04 | 08:01:00
14 | 2013-06-04 | 19:03:00
14 | 2013-06-05 | 07:52:00
14 | 2013-06-05 | 19:02:00
One Record ID can have multiple TimeLog on the same DateLog
Now I want it to show like this:
The Dates as columns and the Total No. of Hours between MIN(TimeLog) and MAX(TimeLog)
RecordID | 2013-06-01 | 2013-06-02 | 2013-06-03 | 2013-06-04 | 2013-06-05
11 | 11:02:00 | 11:06:00 | 11:22:00 | NULL | NULL
14 | NULL | NULL | NULL | 11:02:00 | 11:10:00
As per Mikael's Answer, this works but as I understand his query, this returns MIN(TimeLog):
DECLARE #SQL NVARCHAR(MAX)
DECLARE #ColumnList NVARCHAR(MAX)
SELECT #ColumnList =
STUFF
(
(
SELECT DISTINCT ','+QUOTENAME(DateLog)
FROM TESTPIS.dbo.tblDateTimeLog
WHERE IsDeleted=0 AND DateLog >= '2013-06-15' AND DateLog <= '2013-06-30'
ORDER BY 1
FOR XML PATH('')
), 1, 1, ''
)
SET #SQL = 'SELECT P.RecordID, '+#ColumnList+'
FROM
(
SELECT RecordID, TimeLog, DateLog FROM TESTPIS.dbo.tblDateTimeLog WHERE isDeleted=0
) AS T
PIVOT
(
MIN(TimeLog) FOR DateLog IN ('+#ColumnList+')
) as P'
EXEC (#SQL)
What I want to return is like DATEDIFF(MINUTES,MIN(TimeLog),MAX(TimeLog)
I tried replacing the MIN(TimeLog) in the query with this
SUM(DATEDIFF(MINUTES,MIN(TimeLog),MAX(TimeLog))
However, I got this error
Incorrect syntax near '('.
select P.RecordID,
P.[2013-06-01],
P.[2013-06-02],
P.[2013-06-03],
P.[2013-06-04],
P.[2013-06-05]
from (
select RecordID,
TimeLog,
DateLog
from YourTable
) as T
pivot(min(TimeLog) for DateLog in ([2013-06-01],
[2013-06-02],
[2013-06-03],
[2013-06-04],
[2013-06-05])) as P
To build the query dynamically you have to first figure out what the column list should contain.
select distinct DateLog
from YourTable
order by 1
The result from that query has to be turned into a comma separated list and since numbers like that is invalid column names you can use quotename() to enclose the value in [].
To build the comma separated list of column you can use for xml path('').
select stuff((select distinct ','+quotename(DateLog)
from YourTable
order by 1
for xml path('')), 1, 1, '')
stuff() is there to remove the first comma.
Then you just need to put it all together and execute the query you have built.
declare #SQL nvarchar(max)
declare #ColumnList nvarchar(max)
select #ColumnList = stuff((select distinct ','+quotename(DateLog)
from YourTable
order by 1
for xml path('')), 1, 1, '')
set #SQL = '
select P.RecordID, '+#ColumnList+'
from (
select RecordID,
TimeLog,
DateLog
from YourTable
) as T
pivot(min(TimeLog) for DateLog in ('+#ColumnList+')) as P'
exec (#SQL)
SQL Fiddle
Update
To get the diff between max and min timelog value for each day you can do a group by on RecordID and DateLog in the derived table and calculate the diff in seconds between min and max and use that value to calculate a new time value using dateadd.
select P.RecordID,
P.[2013-06-01],
P.[2013-06-02],
P.[2013-06-03],
P.[2013-06-04],
P.[2013-06-05]
from (
select RecordID,
dateadd(second, datediff(second, min(TimeLog), max(TimeLog)), convert(time(0), '00:00')) as TimeDiff,
DateLog
from YourTable
group by RecordID,
DateLog
) as T
pivot(min(TimeDiff) for DateLog in ([2013-06-01],
[2013-06-02],
[2013-06-03],
[2013-06-04],
[2013-06-05])) as P
Dynamic version:
declare #SQL nvarchar(max)
declare #ColumnList nvarchar(max)
select #ColumnList = stuff((select distinct ','+quotename(DateLog)
from YourTable
order by 1
for xml path('')), 1, 1, '')
set #SQL = '
select P.RecordID, '+#ColumnList+'
from (
select RecordID,
dateadd(second, datediff(second, min(TimeLog), max(TimeLog)), convert(time(0), ''00:00'')) as TimeDiff,
DateLog
from YourTable
group by RecordID,
DateLog
) as T
pivot(min(TimeDiff) for DateLog in ('+#ColumnList+')) as P'
exec (#SQL)
SQL Fiddle
Try this one -
Query:
IF OBJECT_ID (N'tempdb.dbo.#temp') IS NOT NULL
DROP TABLE #temp
CREATE TABLE #temp
(
RecordID INT
, DateLog DATETIME
, TimeLog TIME
)
INSERT INTO #temp (RecordID, DateLog, TimeLog)
VALUES
(11, '2013-06-01', '08:00:00'), (11, '2013-06-02', '09:00:00'),
(11, '2013-06-03', '10:00:00'), (11, '2013-06-04', '11:00:00'),
(11, '2013-06-05', '12:00:00'), (14, '2013-06-01', '13:00:00'),
(14, '2013-06-02', '14:00:00'), (14, '2013-06-03', '15:00:00'),
(14, '2013-06-04', '16:00:00')
DECLARE #SQL NVARCHAR(MAX)
SELECT #SQL = 'SELECT *
FROM (
SELECT DateLog, RecordID, tt = CAST(TimeLog AS VARCHAR(8))
FROM #temp
) src
PIVOT (
MAX(tt)
FOR DateLog IN (' + STUFF((
SELECT ', [' + CONVERT(VARCHAR(10), DateLog, 120) + ']'
FROM #temp
GROUP BY DateLog
ORDER BY DateLog
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 2, '') + ')
) p'
PRINT #SQL
EXEC sys.sp_executesql #SQL
Output:
SELECT *
FROM (
SELECT DateLog, RecordID, tt = CAST(TimeLog AS VARCHAR(8))
FROM #temp
) src
PIVOT (
MAX(tt)
FOR DateLog IN ([2013-06-01], [2013-06-02], [2013-06-03], [2013-06-04], [2013-06-05])
) p
Results:
RecordID 2013-06-01 2013-06-02 2013-06-03 2013-06-04 2013-06-05
----------- ---------- ---------- ---------- ---------- ----------
11 08:00:00 09:00:00 10:00:00 11:00:00 12:00:00
14 13:00:00 14:00:00 15:00:00 16:00:00 NULL
Update with results:

Adding Grand Totals in Pivot Table

I have created a pivot table with the following code:
DECLARE #SQL AS VARCHAR(MAX)
DECLARE #Columns AS VARCHAR (MAX)
SELECT #Columns =
COALESCE(#Columns + ', ','')+ QUOTENAME(PortfolioID)
FROM
(
SELECT PortfolioID FROM InfoPortal.DBO.fn_Generic_PortfolioGroupMembers (#PortfolioGroup)
) AS B
ORDER BY B.PortfolioID
SET #SQL = '
WITH PivotData AS
(
SELECT
PortfolioID, Percentage, DurationBand, DurationSort
FROM #Worktable
)
SELECT
DurationBand,
' + #Columns + '
FROM PivotData
PIVOT
(
SUM(Percentage)
FOR PortfolioID
IN (' + #Columns + ')
) AS PivotResult
ORDER BY DurationSort'
EXEC (#SQL)
However what i would like to do is add grand totals for each portfolioID, and i'm unsure of how to achieve this. Any help?
You should be able to add a GROUP BY with ROLLUP to produce the totals row, similar to this:
DECLARE #SQL AS VARCHAR(MAX)
DECLARE #Columns AS VARCHAR (MAX)
DECLARE #ColumnsRollup AS VARCHAR (MAX)
SELECT #Columns =
COALESCE(#Columns + ', ','')+ QUOTENAME(PortfolioID)
FROM
(
SELECT distinct PortfolioID
FROM worktable
) AS B
ORDER BY B.PortfolioID
SELECT #ColumnsRollup =
COALESCE(#ColumnsRollup + ', Sum(','Sum(')+ QUOTENAME(cast(PortfolioID as varchar(10)))+') as Portfolio'+cast(PortfolioID as varchar(10))
FROM
(
SELECT distinct PortfolioID
FROM worktable
) AS B
ORDER BY B.PortfolioID
SET #SQL = '
WITH PivotData AS
(
SELECT PortfolioID, Percentage, DurationBand, DurationSort
FROM Worktable
)
SELECT case when DurationBand is not null then cast(durationband as varchar(10))
else ''Total'' end Durationband, ' + #ColumnsRollup+ '
FROM
(
SELECT DurationBand, ' + #Columns + '
FROM PivotData
PIVOT
(
SUM(Percentage)
FOR PortfolioID IN (' + #Columns + ')
) AS PivotResult
) src
GROUP BY DurationBand with ROLLUP'
EXEC (#SQL)
See SQL Fiddle with Demo. Note: sample data is just mocked based on your table structure.
Result:
| DURATIONBAND | PORTFOLIO1 | PORTFOLIO2 | PORTFOLIO3 |
-------------------------------------------------------
| 2 | 78 | (null) | (null) |
| 5 | (null) | (null) | 4 |
| 12 | 10 | 45 | (null) |
| Total | 88 | 45 | 4 |
Based in the need to keep things sorted, you can use:
DECLARE #SQL AS VARCHAR(MAX)
DECLARE #Columns AS VARCHAR (MAX)
DECLARE #ColumnsRollup AS VARCHAR (MAX)
SELECT #Columns =
COALESCE(#Columns + ', ','')+ QUOTENAME(PortfolioID)
FROM
(
SELECT distinct PortfolioID
FROM worktable
) AS B
ORDER BY B.PortfolioID
SELECT #ColumnsRollup =
COALESCE(#ColumnsRollup + ', Sum(','Sum(')+ QUOTENAME(cast(PortfolioID as varchar(10)))+') as '+QUOTENAME(cast(PortfolioID as varchar(10)))
FROM
(
SELECT distinct PortfolioID
FROM worktable
) AS B
ORDER BY B.PortfolioID
SET #SQL = '
WITH PivotData AS
(
SELECT PortfolioID, Percentage, DurationBand, DurationSort
FROM Worktable
)
SELECT *
FROM
(
SELECT DurationBand, ' + #Columns + '
FROM PivotData
PIVOT
(
SUM(Percentage)
FOR PortfolioID IN (' + #Columns + ')
) AS PivotResult
UNION ALL
select ''Grand Total'', '+#ColumnsRollup+'
from
(
SELECT DurationBand, ' + #Columns + '
FROM PivotData
PIVOT
(
SUM(Percentage)
FOR PortfolioID IN (' + #Columns + ')
) AS PivotResult
)tot
) src
'
EXEC (#SQL)
See SQL Fiddle with Demo

Resources