SQL Dynamic Pivot - sql

I have written a dynamic SQL Pivot Query that returns a Quantity per day, the number of columns returned are dynamic based on the number of days between the opening parameters. The issue I am having is that the Columns are not in DateOrder, I am guessing I need some form or Order By to fix this, does anyone have any ideas where I would need to put it in?
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
--declare and set variables
DECLARE #v_Columns VARCHAR(MAX)
,#v_StartDate DATETIME = GETDATE() - 10
,#v_EndDate DATETIME = GETDATE()
,#v_Query VARCHAR(max)
--select results
SELECT #v_Columns = Coalesce(#v_Columns, '[') + convert(VARCHAR, cast(DateCreated AS DATE), 111) + '],['
FROM (
SELECT DISTINCT cast(DateCreated AS DATE) AS DateCreated
FROM TransactionOriginTracking
) tot
WHERE tot.DateCreated BETWEEN #v_StartDate
AND #v_EndDate
SET #v_Columns = SUBSTRING(#v_Columns, 1, Len(#v_Columns) - 2)
SET #v_Query = 'select
*
from
(
SELECT
cast(Tracking.DateCreated as date) as [Date],
th.TotalQuantity [Quantity]
FROM
TransactionHeader th
Inner JOin TransactionOriginTracking as Tracking on Tracking.TransactionHeaderId = th.Id
and Tracking.WorkflowStageId = 9
Inner Join CompanyDivision on CompanyDivision.Id = th.CompanyDivisionId
WHERE
Tracking.DateCreated between ''' + CONVERT(VARCHAR(50), #v_StartDate, 111) + ''' AND ''' + CONVERT(VARCHAR(50), #v_EndDate, 111) + '''
) as src
Pivot
(
SUM(src.Quantity)
For src.Date IN (' + #v_Columns + ')
) as PivotView'
EXEC (#v_Query)

When you build up your column list, sort by date:
--select results
SELECT
#v_Columns = Coalesce(#v_Columns, '[') + convert(VARCHAR, cast(DateCreated AS DATE), 111) + '],['
FROM
(SELECT DISTINCT
CAST(DateCreated AS DATE) AS DateCreated
FROM
TransactionOriginTracking) tot
WHERE
tot.DateCreated BETWEEN #v_StartDate AND #v_EndDate
ORDER BY
tot.DateCreated

Related

NULL Values in Dynamic Pivot Query

I am looking to display the NULL values as blank spaces or dashes to make the resulting data set easier to read as the date range can vary. For example, for a date range of 10 days I could receive NULL values for 5 out of the 10 days for a person's hours. I just want those NULL values to be blank if a person has no hours for that day.
I have seen examples of ISNULL being used on pivot tables, but not a dynamic pivot table. I'm wondering how to specify the NULL replacement for my dynamic list of dates filled into #PivotColumns.
In my query example below, I have omitted non-relevant code such as joins and other informational columns, etc.
SELECT
#PivotColumns = COALESCE(#PivotColumns + ',','') + QUOTENAME(CONVERT(varchar(20),eff_date,110))
FROM
(SELECT DISTINCT
eff_date
FROM
timept
WHERE
eff_date
BETWEEN
#StartDate AND #EndDate) AS TimePivot
ORDER BY
eff_date
SET
#TimeQuery =
'WITH Hours AS (
SELECT
,tp.person_id
,tp.hours
,tp.task_code
,tp.eff_date
,SUM(tp.hours) OVER(PARTITION BY tp.person_id, edr.name, tp.task_code, tp.comments ORDER BY tp.task_code) AS sum_hours
FROM
tables
WHERE
tp.eff_date BETWEEN ''' + #StartDate + ''' AND ''' + #EndDate + '''
)
SELECT
*
FROM
Hours
PIVOT
(SUM(hours)
FOR
eff_date
IN
('+#PivotColumns+')) as p'
EXEC sp_executesql #TimeQuery
Try this:
#TimeQuery =
' WITH Hours AS (
SELECT
,tp.person_id
,ISNULL(tp.hours, '''')
,tp.task_code
,ISNULL(tp.eff_date, '''')
,SUM(tp.hours) OVER(PARTITION BY tp.person_id, edr.name, tp.task_code, tp.comments ORDER BY tp.task_code) AS sum_hours
FROM
tables
WHERE
tp.eff_date BETWEEN ''' + #StartDate + ''' AND ''' + #EndDate + '''
)
SELECT
*
FROM
Hours
PIVOT
(SUM(hours)
FOR
eff_date
IN
('+ #PivotColumns +')) as p'

How to get date as Header in SQL Server 2005

I am trying to get dates as headers that change depending on the current date. Here is an example of what I am trying to do in SQL Server 2005.
select c.[ar cust edi sef type],
SUM(case when
s.[ar sale date] like DATEADD(dd, 0, DATEDIFF(dd, 0, GETDATE()))-7 and s.[ar sale document type] like 'invoice'
then s.[AR SALE Balance Due]
else 0 end) as getdate(),
getdate() does not work. Is there a way I can return a date as the header?
Column headers can not be dynamic. You can see this from the fact that usually you don't specify them as string, but like an identifier.
SELECT 1 as TestColumn
instead of
SELECT 1 as 'TestColumn'
You could do what you want in the form of a query you execute through exec
exec('SELECT 1 AS ' + CONVERT(nvarchar, getdate()))
Is this what you mean for date header?
You can do this without pivot, but mostly mssql uses pivot.
This code will list the dates as column headers. Used dynamic PIVOT to obtained the result. First I create a table with dates and a random text to test the solution. Substitute your table and change the code accordingly.
If you can provide your schema and data the answer will be more applicable and practical.
DECLARE #StartDate DATE = '20110901'
, #EndDate DATE = '20111001'
SELECT DATEADD(DAY, nbr - 1, #StartDate) AS [Date]
,'test' AS [Value]
into #temp_Dates
FROM ( SELECT ROW_NUMBER() OVER ( ORDER BY c.object_id ) AS Nbr
FROM sys.columns c
) nbrs
WHERE nbr - 1 <= DATEDIFF(DAY, #StartDate, #EndDate)
--------------------------------------------------------------------------------------
-----------Dynamic PIVOT section to make the date as a column header------------------
DECLARE #SQL AS VARCHAR(MAX)
, #cols_ AS vARCHAR(MAX)
, #Table_Name VARCHAR(20)
SET #Table_Name = '#temp_Dates'
--Making the column list dynamically
SELECT #cols_ = STUFF((SELECT DISTINCT ', '+QUOTENAME( [T2].[Date])
FROM #temp_Dates [T2]
FOR XML PATH('')), 1, 1, '')
SET #SQL = ' SELECT
pivoted.*
FROM
(
SELECT *
FROM '+#Table_Name+'
) AS [p]
PIVOT
(
MIN([P].[Value])
FOR [P].[Date] IN (' + #cols_ + ')
) AS pivoted;
';
PRINT( #SQL)
EXEC (#SQL)
DROP TABLE #temp_Dates
RESULT:

2005 SSRS/SQL Server PIVOT results need reversing

Preamble: I've read through the three questions/answers here,here, and here, with big ups to #cade-roux. This all stemmed from trying to use the following data in a 2005 SSRS matrix that, I believe, doesn't work because I want to show a member having to take a test multiple times, and SSRS seems to require the aggregate where I want to show all dates.
I get the following results in my table, which seems to be showing all the data correctly:
How do I change the code below to show a) the "tests" at the top of each column with b) if it's called for, the multiple dates that test was taken?
Here's the code I have to produce the table, above. Much of it is commented out as I was just trying to get the pivot to work, but you may notice I am also trying to specify which test column comes first.
CREATE TABLE #tmp ( ---THIS WORKS BUT TESTS ARE VERTICAL
[TEST] [varchar](30) NOT NULL,
[ED] [datetime] NOT NULL
)
--WHERE THE TEST AND ED COME FROM
INSERT #TMP
SELECT DISTINCT
-- N.FULL_NAME
-- , CONVERT(VARCHAR(30), AM.CREATEDATE, 101) AS ACCOUNT_CLAIMED
-- , N.EMAIL
-- , NULL AS 'BAD EMAIL'
-- , CONVERT(VARCHAR(30), AC.EFFECTIVE_DATE, 101) AS EFFECTIVE_DATE
AC.PRODUCT_CODE AS TEST
, CONVERT(VARCHAR(30), AC.EFFECTIVE_DATE, 101) AS ED
-- , CASE
-- WHEN AC.PRODUCT_CODE = 'NewMem_Test' THEN '9'
-- WHEN AC.PRODUCT_CODE = 'NM_Course1' THEN '1'
-- WHEN AC.PRODUCT_CODE = 'NMEP_Course1' THEN '2'
-- WHEN AC.PRODUCT_CODE = 'NMEP_Course2' THEN '3'
-- WHEN AC.PRODUCT_CODE = 'NMEP_Course3' THEN '4'
-- WHEN AC.PRODUCT_CODE = 'NMEP_Course4' THEN '5'
-- WHEN AC.PRODUCT_CODE = 'NMEP_Course5' THEN '6'
-- WHEN AC.PRODUCT_CODE = 'NMEP_Course6' THEN '7'
-- WHEN AC.PRODUCT_CODE = 'NMEP_Course7' THEN '8'
-- END AS 'COLUMN_ORDER'
FROM NAME N
JOIN USERMAIN UM
ON N.ID = UM.CONTACTMASTER
JOIN formTransLog TL
ON UM.USERID = TL.USERNAME
JOIN anet_Users AU
ON UM.USERID = AU.USERNAME
JOIN anet_Membership AM
ON AU.USERID = AM.USERID
JOIN ACTIVITY AC
ON N.ID = AC.ID
AND AC.ACTIVITY_TYPE = 'COURSE'
AND AC.PRODUCT_CODE LIKE 'N%'
--ORDER BY 1, 7
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 TEST, ED, ROW_NUMBER() OVER (PARTITION BY TEST ORDER BY ED) AS PIVOT_CODE
FROM #tmp
) AS rows
) AS PIVOT_CODES
SET #sql = '
;WITH p AS (
SELECT TEST, ED, ROW_NUMBER() OVER (PARTITION BY TEST ORDER BY ED) AS PIVOT_CODE
FROM #tmp
)
SELECT TEST, ' + #select_list + '
FROM p
PIVOT (
MIN(ED)
FOR PIVOT_CODE IN (
' + #pivot_list + '
)
) AS pvt
'
PRINT #sql
EXEC (#sql)
EDIT:
The goal is to have the report in SSRS look like this:
I was able to produce the results you were looking for by adding in a number (RowNum) to the query underneath the PIVOT operator. It doesn't have to be in the final query (though you might want it for client-side sorting), but by having it in the underlying layer the PIVOT operation treats that number like a member of a GROUP BY clause.
Please look through my sample SQL below and let me know if this matches your criteria.
CREATE TABLE #TMP
(
Name VARCHAR(10),
Test VARCHAR(20),
EffectiveDate DATETIME
)
INSERT INTO #TMP (Name, Test, EffectiveDate)
SELECT 'Jane', 'NM_Course1', '01/17/2014' UNION
SELECT 'Jane', 'NMEP_Course1', '12/19/2013' UNION
SELECT 'Jane', 'NMEP_Course1', '12/20/2013' UNION
SELECT 'Jane', 'NMEP_Course2', '12/19/2013' UNION
SELECT 'Jane', 'NMEP_Course2', '12/22/2013' UNION
SELECT 'Jane', 'NMEP_Course2', '01/05/2014' UNION
SELECT 'John', 'NM_Course1', '01/17/2014' UNION
SELECT 'John', 'NMEP_Course1', '01/11/2014'
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 TEST AS PIVOT_CODE
FROM #tmp
) AS rows
) AS PIVOT_CODES
SET #sql = '
SELECT Name, ' + #select_list + '
FROM
(
SELECT b.Name, RowNum, b.EffectiveDate, b.TEST AS PIVOT_CODE
FROM
(
SELECT Name, Test, EffectiveDate, ROW_NUMBER() OVER (PARTITION BY NAME, TEST ORDER BY EffectiveDate) RowNum
FROM #Tmp
) b
) p
PIVOT (
MIN(EffectiveDate)
FOR PIVOT_CODE IN (
' + #pivot_list + '
)
) AS pvt
ORDER BY Name, RowNum
'
PRINT #sql
EXEC (#sql)
DROP TABLE #TMP

sql query including month columns?

I was wondering if I could get some ideas or direction on a sql query that would output column months. Here is my current query..
select A.assetid, A.Acquisition_Cost, B.modepreciaterate from FA00100 A
inner join FA00200 B on A.assetindex = B.assetindex
where MoDepreciateRate != '0'
I would like to add more columns that look as such:
select assetid, acquisition_cost, perdeprrate, Dec_2012, Jan_2013, Feb_2013....
where Dec_2012 = (acquisition_cost - MoDepreciateRate*(# of months))
and Jan_2013 = (acquisition_cost - MoDepreciateRate*(# of months))
where # of months can be changed.
Any help would be really appreciated. Thank you!
Here is an example of what I would like the output to be with '# of months' = 4
assetid SHRTNAME Acquisition_Cost perdeprrate Dec_2012 Jan_2013 Feb_2013 Mar_2013
CS-013 GEH INTEG 17490.14 485.83 17004.31 16518.48 16032.65 15546.82
CS-014 WEB BRD 14560 404.4507 14155.5493 13751.0986 13346.6479 12942.1972
Try This:
--setup
create table #fa00100 (assetId int, assetindex int, acquisitionCost int, dateAcquired date)
create table #fa00200 (assetIndex int, moDepreciateRate int, fullyDeprFlag nchar(1), fullyDeprFlagBit bit)
insert #fa00100
select 1, 1, 100, '2012-01-09'
union select 2, 2, 500, '2012-05-09'
insert #fa00200
select 1, 10, 'N', 0
union select 2, 15, 'Y', 1
.
--solution
create table #dates (d date not null primary key clustered)
declare #sql nvarchar(max)
, #pivotCols nvarchar(max)
, #thisMonth date
, #noMonths int = 4
set #thisMonth = cast(1 + GETUTCDATE() - DAY(getutcdate()) as date)
select #thisMonth
while #noMonths > 0
begin
insert #dates select DATEADD(month,#noMonths,#thisMonth)
set #noMonths = #noMonths - 1
end
select #sql = ISNULL(#sql + NCHAR(10) + ',', '')
--+ ' A.acquisitionCost - (B.moDepreciateRate * DATEDIFF(month,dateAcquired,''' + convert(nvarchar(8), d, 112) + ''')) ' --Original Line
+ ' case when A.acquisitionCost - (B.moDepreciateRate * DATEDIFF(month,dateAcquired,''' + convert(nvarchar(8), d, 112) + ''')) <= 0 then 0 else A.acquisitionCost - (B.moDepreciateRate * DATEDIFF(month,dateAcquired,''' + convert(nvarchar(8), d, 112) + ''')) end ' --new version
+ quotename(DATENAME(month, d) + '_' + right(cast(10000 + YEAR(d) as nvarchar(5)),4))
from #dates
set #sql = 'select A.assetid
, A.acquisitionCost
, B.moDepreciateRate
,' + #sql + '
from #fa00100 A
inner join #fa00200 B
on A.assetindex = B.assetindex
where B.fullyDeprFlag = ''N''
and B.fullyDeprFlagBit = 0
'
--nb: B.fullyDeprFlag = ''N'' has double quotes to avoid the quotes from terminating the string
--I've also included fullyDeprFlagBit to show how the SQL would look if you had a bit column - that will perform much better and will save space over using a character column
print #sql
exec(#sql)
drop table #dates
.
--remove temp tables from setup
drop table #fa00100
drop table #fa00200

Dynamic SQL in Stored Procedure - Datetime parameters

Got a Stored Procedure that has is being converted to Dynamic SQL, the reason is because additional SQL will be passed into the procedure from an external system, before it is executed.
Conversion failed when converting datetime from character string.
Here is the full Stored Procedure:
USE [DBName];
GO
SET ANSI_NULLS ON;
GO
SET QUOTED_IDENTIFIER ON;
GO
ALTER PROCEDURE [DB_Admin].[GetMiniCalendarDataNew]
#userID int, #startDate datetime, #endDate datetime, #JVID int = 0
WITH EXEC AS CALLER
AS
set nocount on
declare #SQLQuery AS NVARCHAR(max)
declare #t as table([day] int, [end] datetime, sortorder int, jv int)
SET #SQLQuery= 'insert into #t([day], [end], sortorder, jv)
select day((A.STARTTIME)) [day], max(a.endtime) ''end'', 3 sortorder,min(a.jv) jv
from DB_Admin.CSTM_CALENDAR a
join DB_Admin.CSTM_CALENDAR b on a.id<>b.id
join DB_Admin.CSTM_CALENDARParticipants m1 on a.id=m1.CalendarID
join DB_Admin.CSTM_CALENDARParticipants m2 on b.id=m2.CalendarID
join DB_Admin.DTree DTree on a.FolderDataID=DTree.DataID
where a.starttime between ' + CAST(#startDate AS DATETIME) + ' AND ' + CAST(#endDate AS DATETIME) +
' AND DTree.OwnerID > 0
and b.starttime between ' + CAST(#startDate AS DATETIME) + ' AND ' + CAST(#endDate AS DATETIME) +
' AND a.starttime<b.endtime --find overlapping meetings
AND a.endtime>b.starttime --find overlapping meetings
AND M1.PARTICIPANT IN (
select id from DB_Admin.kuaf where id in (
select id from DB_Admin.kuafchildren
where childid=' +#userID+')
or id=' +#userID+
')
AND M2.PARTICIPANT IN (
select id from DB_Admin.kuaf where id in (
select id from DB_Admin.kuafchildren
where childid='+#userID+')
or id='+#userID+
')'+
--Filter on JV
' AND ( exists (select 1 where a.jv='+#JVID+')
or '+#JVID+'=0'+
')'+
'group by day(A.STARTTIME)'
+' insert into #t ([day], [end], sortorder, jv)
select day(A.STARTTIME) [day], max(a.endtime) ''end'', 2 SORTORDER,min(a.jv) jv
from DB_Admin.CSTM_CALENDAR a
join DB_Admin.CSTM_CALENDAR b on a.id<>b.id
join DB_Admin.CSTM_CALENDARParticipants m1 on a.id=m1.CalendarID
join DB_Admin.CSTM_CALENDARParticipants m2 on b.id=m2.CalendarID
join DB_Admin.DTree DTree on a.FolderDataID=DTree.DataID
where a.starttime between ' + CAST(#startDate AS DATETIME) +' AND ' +CAST(#endDate AS DATETIME)+
' AND DTree.OwnerID > 0
--Filter on JV
AND ( exists (select 1 where a.jv='+#JVID+')
or '+#JVID+'=0'+')
and M1.PARTICIPANT IN (
select id from DB_Admin.kuaf where id in (
select id from DB_Admin.kuafchildren
where childid='+#userID+')
or id='+#userID+
')
group by (A.STARTTIME)'+
' insert into #t ([day], [end], sortorder, jv)
select day(A.STARTTIME) [day], max(a.endtime) ''end'', 1 SORTORDER,min(a.jv) jv
from DB_Admin.CSTM_CALENDAR a
join DB_Admin.CSTM_CALENDARParticipants m1 on a.ID=m1.CalendarID
join DB_Admin.DTree DTree on a.FolderDataID=DTree.DataID
where a.starttime between '+CAST(#startDate AS DATETIME)+' AND '+CAST(#endDate AS DATETIME)+
' AND DTree.OwnerID > 0
--Filter on JV
AND ( exists (select 1 where a.jv='+#JVID+')
or '+#JVID+'=0'+ ')
and M1.PARTICIPANT NOT IN (
select id from DB_Admin.kuaf where id in (
select id from DB_Admin.kuafchildren
where childid='+#userID+')
or id='+#userID+'
)
group by (A.STARTTIME)'
--format query
+' select [day], max(month('+CAST(#startDate AS DATETIME)+' [month], max(year('+CAST(#endDate AS DATETIME)+')) [year], max([end]) ''end'',
case
when max(sortorder)=3 then ''Overlapping''
when max(sortorder)=2 then ''Participating''
when max(sortorder)=1 then ''Existing''
when max(sortorder)=0 then ''Empty''
end sortOrder , min(jv) JVID
from #t
group by [day]
order by sortorder desc'
--EXEC (#SQLQuery)
PRINT (#SQLQuery)
GO
Well, you need to
Quoting them
Ensure they are string
Make them language/locale safe
So:
...
where a.starttime between ''' + CONVERT(varchar(30), #startDate, 126) +''' AND ''' + ...
...
Edit:
int error. You need to CAST #userID to varchar concatenate it.
SQL doesn't do VBA style implicit CASTs
I think your problem is here:
'where a.starttime between ' + CAST(#startDate AS DATETIME) + ' AND ' + CAST(#endDate AS DATETIME) +
You should be converting it to a string, rather than a datetime. See http://msdn.microsoft.com/en-us/library/ms187928.aspx