Creating Dynamic Pivot Table Column Names - sql-server

I have created a pivot table with hard coded column names. The pivot table simply keeps a rolling sum of sales by qty (current month + 11 months back).
It was my first time using the PIVOT function properly and the code works fine.
SELECT
Item_Code_Desc,
ISNULL([Current],0) AS [Current],
ISNULL([1],0) AS [1],
ISNULL([2],0) AS [2],
ISNULL([3],0) AS [3],
ISNULL([4],0) AS [4],
ISNULL([5],0) AS [5],
ISNULL([6],0) AS [6],
ISNULL([7],0) AS [7],
ISNULL([8],0) AS [8],
ISNULL([9],0) AS [9],
ISNULL([10],0) AS [10],
ISNULL([11],0) AS [11]
FROM
(SELECT
CONCAT(ST.Code,' - ', ST.Description_1) AS Item_Code_Desc,
STT.ActualQuantity AS Qty,
CASE
WHEN MONTH(STT.TxDate) = MONTH(GETDATE()) THEN 'Current'
WHEN MONTH(STT.TxDate) = MONTH(DATEADD(MONTH, -1, GETDATE())) THEN '1'
WHEN MONTH(STT.TxDate) = MONTH(DATEADD(MONTH, -2, GETDATE())) THEN '2'
WHEN MONTH(STT.TxDate) = MONTH(DATEADD(MONTH, -3, GETDATE())) THEN '3'
WHEN MONTH(STT.TxDate) = MONTH(DATEADD(MONTH, -4, GETDATE())) THEN '4'
WHEN MONTH(STT.TxDate) = MONTH(DATEADD(MONTH, -5, GETDATE())) THEN '5'
WHEN MONTH(STT.TxDate) = MONTH(DATEADD(MONTH, -6, GETDATE())) THEN '6'
WHEN MONTH(STT.TxDate) = MONTH(DATEADD(MONTH, -7, GETDATE())) THEN '7'
WHEN MONTH(STT.TxDate) = MONTH(DATEADD(MONTH, -8, GETDATE())) THEN '8'
WHEN MONTH(STT.TxDate) = MONTH(DATEADD(MONTH, -9, GETDATE())) THEN '9'
WHEN MONTH(STT.TxDate) = MONTH(DATEADD(MONTH, -10, GETDATE())) THEN '10'
WHEN MONTH(STT.TxDate) = MONTH(DATEADD(MONTH, -11, GETDATE())) THEN '11'
ELSE '0'
END AS [Period]
FROM
_bvSTTransactionsFull AS STT
INNER JOIN
StkItem AS ST ON STT.AccountLink = ST.StockLink
WHERE
STT.TxDate >= DATEADD(MONTH, -11, DATEADD(MONTH, DATEDIFF(MONTH, 0, GETDATE()), 0))
AND STT.Module = 'AR') AS P
PIVOT
(SUM(P.Qty)
FOR P.Period IN ([1],[2],[3],[4],[5],[6],[7],[8],[9],[10],[11],[Current])
) AS PVT
To make the output more dynamic, I wanted to rather show the month and year as the field heading, rather than just the 1,2,3 etc.
To attempt this, I first took out the original CASE, and used the CONCAT function to get the desired result
CONCAT(DATENAME(MONTH,STT.TxDate),' ',YEAR(STT.TxDate)) AS [Period],
Now that the sub-query was showing the transaction date in the format "mmmm yyyy", I wanted the table to pivot on this. I did start wondering how it was going to do this as I was reaching the end, as the column names are not hard coded anymore.
After trying
PIVOT (
SUM(P.Qty)
FOR CONCAT(DATENAME(MONTH,P.TxDate),' ',YEAR(P.TxDate))
) AS PVT
And many other variations I did do some research, and I see this process is slightly more complex than I thought.
I haven't used the STUFF and FOR XML PATH before. I have attempted to convert the above into the examples I have found on the net. But I'm trying in vein as I don't understand the core logic of what I am trying to do.
Please could I have assistance with not only how to get the pivot dynamic, but perhaps some notes to further understand what is happening. Really appreciate some help on this!
After Attempting
This is my attempt to get it right:
DECLARE
#Cols NVARCHAR(MAX),
#Query NVARCHAR(MAX),
#Module NVARCHAR = 'AR'
SELECT
#Cols = STUFF((SELECT DISTINCT ',' + 'CONCAT(DATENAME(MONTH, STT.TxDate),,YEAR(STT.TxDate))' + QUOTENAME(NAME)
FROM _bvSTTransactionsFull AS STT
WHERE STT.Module = 'AR'
FOR XML PATH (''), TYPE).VALUE('.','NVARCHAR(MAX)'),1,1,'')
SELECT
#Query = '
SELECT
Item_Code,
Item_Desc,
' +''''+ #Cols + '''''
FROM
(SELECT
ST.Code AS Item_Code,
ST.Description_1 AS Item_Desc,
STT.ActualQuantity AS Qty,
CONCAT(DATENAME(MONTH, STT.TxDate),,YEAR(STT.TxDate)) AS [Period]
FROM
_bvSTTransactionsFull AS STT
INNER JOIN
StkItem AS ST ON STT.AccountLink = ST.StockLink
WHERE
STT.TxDate >= DATEADD(MONTH, -13, DATEADD(MONTH, DATEDIFF(MONTH, 0,
GETDATE()), 0))
AND STT.Module = '+ #Module +') AS P
PIVOT
(SUM(P.Qty)
FOR P.Period IN ('+#cols+')
) AS PVT '
PRINT #Query
EXEC (#Query)
But I'm getting the following error:
Msg 207, Level 16, State 1, Line 7
Invalid column name 'NAME'.
Where am I off here?

Your result is correct above however, you will not have your columns sorted by the correct date...
The below would cater for that:
DECLARE
#Cols1 VARCHAR(MAX),
#Cols2 VARCHAR(MAX),
#Query VARCHAR(MAX),
#Period VARCHAR(MAX) = -13 ; --/ Select number of months to view back on (excluding current month) \--
declare #tmptbl table (PeriodDate datetime, col1 varchar(100), col2 varchar(100))
insert into #tmptbl (PeriodDate, col1, col2)
SELECT DISTINCT dPeriodDate,'ISNULL('+ QUOTENAME(CONCAT(DATENAME(MONTH, dPeriodDate),' ',YEAR(dPeriodDate)))+',0) AS' + QUOTENAME(CONCAT(DATENAME(MONTH, dPeriodDate),' ',YEAR(dPeriodDate))) col1
, QUOTENAME(CONCAT(DATENAME(MONTH, dPeriodDate),' ',YEAR(dPeriodDate))) col2
FROM _bvSTTransactionsFull AS S join _etblPeriod p on EOMONTH(s.TxDate) = p.dPeriodDate
WHERE S.Module = 'AR' AND S.TxDate > = DATEADD(MONTH, -13, DATEADD(MONTH, DATEDIFF(MONTH, 0, GETDATE()), 0))
SELECT
#Cols1 = STUFF((SELECT ',' + col1
FROM #tmptbl order by PeriodDate
FOR XML PATH (''), TYPE).value('.','NVARCHAR(MAX)'),1,1,'') --/ allows the first SELECT fields to have the ISNULL function and Alias \--
SELECT
#Cols2 = STUFF((SELECT ',' + col2
FROM #tmptbl order by PeriodDate
FOR XML PATH (''), TYPE).value('.','NVARCHAR(MAX)'),1,1,'') --/ becasue of the need for ISNULL above, second #Cols needed for the Pivot (pivot cannot have ISNULL in it) \--
SELECT
#Query =
'SELECT
Item_Code_Desc,
'+#Cols1+'
FROM
(SELECT
CONCAT(ST.Code,'' - '', ST.Description_1) AS Item_Code_Desc,
STT.ActualQuantity AS Qty,
CONCAT(DATENAME(MONTH, STT.TxDate),'' '',YEAR(STT.TxDate)) AS [Period]
FROM
_bvSTTransactionsFull AS STT
INNER JOIN
StkItem AS ST ON STT.AccountLink = ST.StockLink
WHERE
STT.TxDate >= DATEADD(MONTH, '+#Period+', DATEADD(MONTH, DATEDIFF(MONTH, 0, GETDATE()), 0))
AND STT.Module = ''AR'') AS P
PIVOT
(SUM(P.Qty)
FOR P.Period IN ('+#cols2+')
) AS PVT '
PRINT #Query
EXEC (#Query)
I left the changes I made in small caps for you to see the changes...

Solved :)
DECLARE
#Cols1 VARCHAR(MAX),
#Cols2 VARCHAR(MAX),
#Query VARCHAR(MAX),
#Period VARCHAR(MAX) = -12 ; --/ Select number of months to view back on (excluding current month) \--
SELECT
#Cols1 = STUFF((SELECT DISTINCT ',' +'ISNULL('+ QUOTENAME(CONCAT(DATENAME(MONTH, S.TxDate),' ',YEAR(S.TxDate)))+',0) AS' + QUOTENAME(CONCAT(DATENAME(MONTH, S.TxDate),' ',YEAR(S.TxDate)))
FROM _bvSTTransactionsFull AS S
WHERE S.Module = 'AR' AND S.TxDate > = DATEADD(MONTH, -13, DATEADD(MONTH, DATEDIFF(MONTH, 0, GETDATE()), 0))
FOR XML PATH (''), TYPE).value('.','NVARCHAR(MAX)'),1,1,'') --/ allows the first SELECT fields to have the ISNULL function and Alias \--
SELECT
#Cols2 = STUFF((SELECT DISTINCT ',' + QUOTENAME(CONCAT(DATENAME(MONTH, S.TxDate),' ',YEAR(S.TxDate)))
FROM _bvSTTransactionsFull AS S
WHERE S.Module = 'AR' AND S.TxDate > = DATEADD(MONTH, -13, DATEADD(MONTH, DATEDIFF(MONTH, 0, GETDATE()), 0))
FOR XML PATH (''), TYPE).value('.','NVARCHAR(MAX)'),1,1,'') --/ becasue of the need for ISNULL above, second #Cols needed for the Pivot (pivot cannot have ISNULL in it) \--
SELECT
#Query =
'SELECT
Item_Code_Desc,
'+#Cols1+'
FROM
(SELECT
CONCAT(ST.Code,'' - '', ST.Description_1) AS Item_Code_Desc,
STT.ActualQuantity AS Qty,
CONCAT(DATENAME(MONTH, STT.TxDate),'' '',YEAR(STT.TxDate)) AS [Period]
FROM
_bvSTTransactionsFull AS STT
INNER JOIN
StkItem AS ST ON STT.AccountLink = ST.StockLink
WHERE
STT.TxDate >= DATEADD(MONTH, '+#Period+', DATEADD(MONTH, DATEDIFF(MONTH, 0, GETDATE()), 0))
AND STT.Module = ''AR'') AS P
PIVOT
(SUM(P.Qty)
FOR P.Period IN ('+#cols2+')
) AS PVT '
PRINT #Query
EXEC (#Query)

Related

Where to add and declare variables to use them in dynamic SQL?

I have created dynamic pivot sql that works well, but i still have a simple problem - how to change this line in dynamic part of sql WHERE DUE_DATE BETWEEN DATEADD(mm, DATEDIFF(mm, 0, GETDATE()), 0) AND DATEADD (dd, -1, DATEADD(mm, DATEDIFF(mm, 0, GETDATE()) + 1, 0)) so that I can manage dates between using variables (f.e.: #startdate and #enddate). Where i should add and declare variables that will works in this place. Тhe goal is for the user to set the counting period by himself. Help. Any suggestions?
DECLARE #cols AS NVARCHAR(MAX)
DECLARE #selectCols AS NVARCHAR(MAX)
DECLARE #query AS NVARCHAR(MAX)
DECLARE #DATANUO DATETIME
SET #DATANUO = '2021-12-01'
SELECT #selectCols = STUFF((SELECT ', ISNULL(' + QUOTENAME(DATEPART(DAY,DUE_DATE)) + ', 0) AS ' + QUOTENAME(DATEPART(DAY,DUE_DATE))
FROM [DB].[sbo].[SALES]
WHERE DUE_DATE BETWEEN #DATANUO AND EOMONTH(#DATANUO)
GROUP BY DUE_DATE
ORDER BY DUE_DATE
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'),1,1,'')
SELECT #cols = STUFF((SELECT ',' + QUOTENAME(DATEPART(DAY,DUE_DATE))
FROM [DB].[sbo].[SALES]
WHERE DUE_DATE BETWEEN #DATANUO AND EOMONTH(#DATANUO)
GROUP BY DUE_DATE
ORDER BY DUE_DATE
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
SET #query = ' SELECT Branch,' + #selectCols + ' FROM
(
SELECT Branch AS Branch, DATEPART(DAY,DUE_DATE) AS Diena, COUNT(*) AS Sales
FROM [DB].[sbo].[SALES]
WHERE DUE_DATE BETWEEN DATEADD(mm, DATEDIFF(mm, 0, GETDATE()), 0) AND DATEADD (dd, -1, DATEADD(mm, DATEDIFF(mm, 0, GETDATE()) + 1, 0))
GROUP BY Branch, DUE_DATE
) src
PIVOT
(
SUM(Sales)
FOR Diena IN (' + #cols + ')
) p '
EXECUTE(#query)

SQL,SSRS, SQL SERVER,

I have a transaction based table in SQL from which I am creating an SSRS report. I have a filter based on transaction dates, like below:
Label 7-13 value 1
Label 14-28 value 2
Label 7-28 value 3
If the user selects 1 then I need to show all the transactions which happen between 7-13 days from today. And same for others. Please advise me how I do this at the query level.
Here's an approach that includes sample data:
declare #value int
select #value = 1
;with cte as
(
select cast(getdate() as date) dt
union all
select dateadd(day, -1, dt)
from cte
where dt > getdate() - 30
)
select *
from cte
where (#value = 1 and dt between DATEADD(day, -13, cast(getdate() as date)) and DATEADD(day, -7, cast(getdate() as date)))
or (#value = 2 and dt between DATEADD(day, -28, cast(getdate() as date)) and DATEADD(day, -14, cast(getdate() as date)))
or (#value = 3 and dt between DATEADD(day, -28, cast(getdate() as date)) and DATEADD(day, -7, cast(getdate() as date)))
The key is in the where clause:
WHERE (#value = 1 AND [do this if #value = 1])
OR (#value = 2 AND [do this if #value = 2])
etc.

How to speed up the query?

Here's my query (it's a part of stored procedure):
declare #sql varchar(4000)
declare #tab1 table (ActionID int, ActionDate datetime, [Group] varchar(10), GroupUnicode int, GroupList varchar(100), Artist varchar(250), PlaceName varchar(250),
ActionType varchar(25), ActionTypeID smallint, ActionPlaceID int, Picture varchar(512), SearchWords varchar(250), ActionEndDate datetime, StrEndDate varchar(250),
NameForH1 nvarchar(1000), DatesAsPeriod tinyint, DateList varchar(4000))
set #Date2 = DATEADD(day, 1, #Date2)
insert into #tab1(ActionID, ActionDate, Artist, PlaceName, ActionTypeID, ActionPlaceID, SearchWords, ActionEndDate, StrEndDate, NameForH1, DatesAsPeriod, DateList)
select X.ID, convert(datetime, SUBSTRING(X.DateList, 1, 19), 120), X.Artist, PlaceName, X.ActionTypeID, X.ActionPlaceID, X.SearchWords, ActionEndDate,
case when ActionEndDate IS NOT NULL AND convert(datetime, SUBSTRING(X.DateList, 1, 19), 120) < ActionEndDate
then ' - ' + convert(varchar, ActionEndDate, 104) + ' ' + convert(varchar(5), ActionEndDate, 108)
else '' end as StrEndDate, X.NameFOrH1, X.DatesAsPeriod, X.DateList
from (
SELECT DISTINCT
TOP (100) PERCENT a.ID, a.Artist, a.DateBeg, a.DateEnd, ap.PlaceName, a.ProviderID, a.SubSiteID, a.ActionPlaceID,
a.CounterAgentID, a.ActionTypeID, a.GenreTypeID, a.ShowTypeID AS ActionShowTypeID, ad.ShowTypeID, ap.CityID, a.ActionPlaceGroupID,
a.ActionTopTypeID, a.Canceled, a.ETicketEnabled, st.ShowOnMainPage AS ActionShowOnMainPage, st.ShowInActionList AS ActionShowInActionList,
st.ShowInCashdesk AS ActionShowInCashdesk, st1.ShowOnMainPage, st1.ShowInActionList, st1.ShowInCashdesk, a.SearchWords, a.AutoGenerate,
ad.ActionEndDate, a.NameForH1, a.CommonActionId, tca.Name AS CommonActionName, a.AgeRestrictionsId, a.DatesAsPeriod,
dbo.fn_ConvertActionDatesToDateList(a.ID) AS DateList
FROM dbo.T_Action AS a INNER JOIN
dbo.T_ActionDates AS ad ON a.ID = ad.ActionID INNER JOIN
dbo.T_ActionPlace AS ap ON a.ActionPlaceID = ap.ID INNER JOIN
dbo.T_ShowType AS st ON a.ShowTypeID = st.ID LEFT OUTER JOIN
dbo.T_ShowType AS st1 ON ad.ShowTypeID = st1.ID LEFT OUTER JOIN
dbo.T_CommonAction AS tca ON tca.ID = a.CommonActionId
WHERE (a.Visible = 1)) AS X INNER JOIN dbo.T_Action AS a ON X.ID = a.ID
where (ActionShowInActionList = 1) and (ShowInActionList = 1)
and ((X.Artist like '%' + #Artist + '%') or (X.SearchWords like '%' + #Artist + '%'))
and X.ActionTypeID = case when #ActionTypeID > 0 then #ActionTypeID else X.ActionTypeID end
and X.ActionTopTypeID = case when #ActionTopTypeID > 0 then #ActionTopTypeID else X.ActionTopTypeID end
and X.GenreTypeID = case when #GenreTypeID > 0 then #GenreTypeID else X.GenreTypeID end
and X.ActionPlaceID = case when #ActionPlaceID > 0 then #ActionPlaceID else X.ActionPlaceID end
and CityID = case when #CityID > 0 then #CityID else CityID end
and X.ActionPlaceGroupID = case when #GroupPlaceID > 0 then #GroupPlaceID else X.ActionPlaceGroupID end
and ((convert(datetime, SUBSTRING(X.DateList, 1, 19), 120) = '1980-01-01')
or ((convert(datetime, SUBSTRING(X.DateList, 1, 19), 120) >= #Date1 OR ActionEndDate >= #Date1) AND convert(datetime, SUBSTRING(X.DateList, 1, 19), 120) < #Date2))
order by
case when #SortBy = 'byPlace' then PlaceName end,
case when #SortBy = 'byDate' or #SortBy = 'byPlace' then case when convert(datetime, SUBSTRING(X.DateList, 1, 19), 120) = '1980-01-01 00:00' then DATEADD(YY, 1000, convert(datetime, SUBSTRING(X.DateList, 1, 19), 120)) else convert(datetime, SUBSTRING(X.DateList, 1, 19), 120) end end, Artist,
case when #SortBy = 'byAlphabet' then convert(datetime, SUBSTRING(X.DateList, 1, 19), 120) end
As you can see, there is nested query and there is the following construction:
convert(datetime, SUBSTRING(X.DateList, 1, 19), 120)
that repeats several times.
Also, there is the function dbo.fn_ConvertActionDatesToDateList(a.ID):
ALTER FUNCTION [fn_ConvertActionDatesToDateList]
(
#actionId int
)
RETURNS varchar(4000)
AS
BEGIN
declare #dateList varchar(4000)
select distinct #dateList = STUFF(CAST((
SELECT [text()] = ', ' + convert(varchar, ActionDate, 120)
FROM T_ActionDates where ActionID = a.ID and (ActionDate = '1980-01-01' or ActionDate > GETDATE())
FOR XML PATH(''), TYPE) AS VARCHAR(8000)), 1, 2, '') from T_Action as a join T_ActionDates as ad on a.ID = ad.ActionID
where (ad.ActionDate = '1980-01-01' or ad.ActionDate > GETDATE()) and Visible = 1 and CloseForCorrect = 0 and a.ID = #actionId
return #dateList
END
How can I speed it up?
I really can't tell you exactly what to do to fix the issue, but I can point out the spots that are almost certainly causing slow down.
Ensure your columns in all the joins and WHERE clauses are indexed. There is no way for anyone here to know what is and is not indexed unless you tell us.
The sub-select has TOP (100) PERCENT. You should not need this.
You are generating a likely large sub-set of data by having the sub-select without the improving the WHERE clause in the sub-select. It looks like the WHERE in the main select should be moved into the subselect.
Get rid of the sub-select.
With those things said, try this:
declare #sql varchar(4000)
declare #tab1 table (ActionID int, ActionDate datetime, [Group] varchar(10), GroupUnicode int, GroupList varchar(100), Artist varchar(250), PlaceName varchar(250),
ActionType varchar(25), ActionTypeID smallint, ActionPlaceID int, Picture varchar(512), SearchWords varchar(250), ActionEndDate datetime, StrEndDate varchar(250),
NameForH1 nvarchar(1000), DatesAsPeriod tinyint, DateList varchar(4000))
set #Date2 = DATEADD(day, 1, #Date2)
insert into #tab1(ActionID, ActionDate, Artist, PlaceName, ActionTypeID, ActionPlaceID, SearchWords, ActionEndDate, StrEndDate, NameForH1, DatesAsPeriod, DateList)
select
X.ID, convert(datetime, SUBSTRING(X.DateList, 1, 19), 120), X.Artist, PlaceName, X.ActionTypeID, X.ActionPlaceID, X.SearchWords, ActionEndDate,
case
when ActionEndDate IS NOT NULL AND convert(datetime, SUBSTRING(X.DateList, 1, 19), 120) < ActionEndDate
then ' - ' + convert(varchar, ActionEndDate, 104) + ' ' + convert(varchar(5), ActionEndDate, 108)
else ''
end as StrEndDate
, X.NameFOrH1, X.DatesAsPeriod, X.DateList
from (
SELECT DISTINCT
a.ID, a.Artist, a.DateBeg, a.DateEnd, ap.PlaceName, a.ProviderID, a.SubSiteID, a.ActionPlaceID,
a.CounterAgentID, a.ActionTypeID, a.GenreTypeID, a.ShowTypeID AS ActionShowTypeID, ad.ShowTypeID, ap.CityID, a.ActionPlaceGroupID,
a.ActionTopTypeID, a.Canceled, a.ETicketEnabled, st.ShowOnMainPage AS ActionShowOnMainPage, st.ShowInActionList AS ActionShowInActionList,
st.ShowInCashdesk AS ActionShowInCashdesk, st1.ShowOnMainPage, st1.ShowInActionList, st1.ShowInCashdesk, a.SearchWords, a.AutoGenerate,
ad.ActionEndDate, a.NameForH1, a.CommonActionId, tca.Name AS CommonActionName, a.AgeRestrictionsId, a.DatesAsPeriod,
dbo.fn_ConvertActionDatesToDateList(a.ID) AS DateList
FROM dbo.T_Action AS a
INNER JOIN dbo.T_ActionDates AS ad ON a.ID = ad.ActionID
INNER JOIN dbo.T_ActionPlace AS ap ON a.ActionPlaceID = ap.ID
INNER JOIN dbo.T_ShowType AS st ON a.ShowTypeID = st.ID
LEFT OUTER JOIN dbo.T_ShowType AS st1 ON ad.ShowTypeID = st1.ID
LEFT OUTER JOIN dbo.T_CommonAction AS tca ON tca.ID = a.CommonActionId
WHERE a.Visible = 1
AND ActionShowInActionList = 1
and (ShowInActionList = 1)
and (
(Artist like '%' + #Artist + '%')
or (SearchWords like '%' + #Artist + '%')
)
and ActionTypeID = case when #ActionTypeID > 0 then #ActionTypeID else ActionTypeID end
and ActionTopTypeID = case when #ActionTopTypeID > 0 then #ActionTopTypeID else ActionTopTypeID end
and GenreTypeID = case when #GenreTypeID > 0 then #GenreTypeID else GenreTypeID end
and ActionPlaceID = case when #ActionPlaceID > 0 then #ActionPlaceID else ActionPlaceID end
and CityID = case when #CityID > 0 then #CityID else CityID end
and ActionPlaceGroupID = case when #GroupPlaceID > 0 then #GroupPlaceID else ActionPlaceGroupID end
and (
convert(datetime, SUBSTRING(DateList, 1, 19), 120) = '1980-01-01'
or (
(
convert(datetime, SUBSTRING(DateList, 1, 19), 120) >= #Date1
OR ActionEndDate >= #Date1
)
AND convert(datetime, SUBSTRING(DateList, 1, 19), 120) < #Date2
)
)
) AS X
order by
case when #SortBy = 'byPlace' then PlaceName end,
case when #SortBy = 'byDate' or #SortBy = 'byPlace' then case when convert(datetime, SUBSTRING(X.DateList, 1, 19), 120) = '1980-01-01 00:00' then DATEADD(YY, 1000, convert(datetime, SUBSTRING(X.DateList, 1, 19), 120)) else convert(datetime, SUBSTRING(X.DateList, 1, 19), 120) end end, Artist,
case when #SortBy = 'byAlphabet' then convert(datetime, SUBSTRING(X.DateList, 1, 19), 120) end

Adding a further group into a recursive CTE

Referring back to this SO post
If there is a Grouping category "Category" which, for simplicity's sake, can be either X or Y - is it a trivial matter amending this script so that it will add in the missing dates for each of the categories ?
I assume the category will need adding into the CTE?
In other words if I have the following initial table:
...how do I get to the following:
Will upload my attempt shortly
I've called the initial table #x. I'm hoping to adapt a recursive CTE query like the following to include the field Category:
DECLARE #MinDate DATETIME;
SET #MinDate = (SELECT Min(DATE) FROM #x)
DECLARE #MaxDate DATETIME;
SET #MaxDate = (SELECT Max(DATE) FROM #x)
;WITH times AS
(
SELECT #MinDate dt , 1 depth
UNION ALL
SELECT
DATEADD(d, depth, #MinDate) dt
, 1 + depth as depth
FROM times
WHERE DATEADD(d, depth, #MinDate) <= #MaxDate
)
SELECT
*
FROM
TIMES t
LEFT OUTER JOIN #X x
ON
t.dt = x.Date
Ok - I've tied including a CROSS JOIN but it expands things incorrectly:
SELECT DISTINCT Category INTO #Cat FROM #x
DECLARE #MinDate DATETIME;
SET #MinDate = (SELECT Min(DATE) FROM #x)
DECLARE #MaxDate DATETIME;
SET #MaxDate = (SELECT Max(DATE) FROM #x)
;WITH times AS
(
SELECT
Category
, #MinDate dt
, 1 depth
FROM #Cat
UNION ALL
SELECT
c.Category
, DATEADD(d, depth, #MinDate) dt
, 1 + depth as depth
FROM
times t
CROSS JOIN #Cat c
--ON c.Category IS NOT NULL
WHERE DATEADD(d, depth, #MinDate) <= #MaxDate
)
SELECT
*
FROM
TIMES
This seems to have worked ok:
SELECT DISTINCT Category INTO #Cat FROM #x
DECLARE #MinDate DATETIME;
SET #MinDate = (SELECT Min(DATE) FROM #x)
DECLARE #MaxDate DATETIME;
SET #MaxDate = (SELECT Max(DATE) FROM #x)
;WITH times AS
(
SELECT
Category
, #MinDate dt
, 1 depth
FROM #Cat
UNION ALL
SELECT
Category
, DATEADD(d, depth, #MinDate) dt
, 1 + depth as depth
FROM
times t
WHERE DATEADD(d, depth, #MinDate) <= #MaxDate
)
SELECT
*
FROM
TIMES
Here is a solution without a calendar table (which is a must in production). You might have date range in variables, or you might go for min() and max() from the_table.
EDIT: shorter version incorporating categories into date range generation
declare #startdate datetime = '2012-1-1'
declare #enddate datetime = '2012-1-5'
; with dates([date], category) as (
select distinct #startdate, category
from the_table
union all
select dateadd (day, 1, [date]), category
from dates
where [date] < #enddate
)
select dates.date,
dates.category,
isnull(the_table.amount, 0) Amount
from dates
left join the_table
on dates.date = the_table.date
and dates.category = the_table.category
order by dates.category, dates.date
option (maxrecursion 0)
There is live test # Sql Fiddle.
New Sql Fiddle.
Something like this should do the trick:
declare #curDate datetime, #maxDate datetime
declare #count tinyint
select #curDate = convert(datetime, '20120101', 112), #maxDate = getdate()
select #count = 0
while #curDate < #maxDate
begin
select #count = count(1) from tablename where Category = 'X' and convert(varchar(8), Date, 112) = convert(varchar(8), #curDate, 112)
if #count > 0
begin
insert into tablename
select 'X', #curDate, 0
end
select #curDate = dateadd(dd, 1, #curDate)
end

Help with SQL to get Hits per day for today and 1 month prior

I have this SQL now:
CREATE PROCEDURE dbo.sel_Track_HitsLast30Days(
#projectID int
)
AS
BEGIN
DECLARE #FirstDay smalldatetime, #NumberOfMonths int, #priorMonth smalldatetime
set #priorMonth = (SELECT CAST(
(
STR( YEAR( dateadd(m,-1, getDate()) ) ) + '/' +
STR( MONTH( dateadd(m,-1, getDate()) ) ) + '/' +
STR( DAY( dateadd(m,-1, getDate()) ) )
)
AS DateTime
))
Select #FirstDay = #priorMonth, #NumberOfMonths = 1
;WITH Days AS (
SELECT #FirstDay as CalendarDay
UNION ALL
SELECT DATEADD(d, 1, CalendarDay) as CalendarDay
FROM Days
WHERE DATEADD(d, 1, CalendarDay) < DATEADD(m, #NumberOfMonths, #FirstDay+1)
)
SELECT calendarday,foundDate.TotalbyDate,foundDate.date FROM Days
LEFT OUTER JOIN (
SELECT
COUNT(LEFT(visitDateTime, 11)) AS TotalbyDate,substring(convert( char(10), CONVERT( char(10), visitDateTime, 121 ) ), 1, 11) AS date
FROM
dbo.TrackingData
WHERE
visitDateTime >= dateadd(d, datediff(d, 0, getdate()), -30) and projectID = #projectID
GROUP BY substring(convert( char(10), CONVERT( char(10), visitDateTime, 121 ) ), 1, 11)
) foundDate on foundDate.date = CalendarDay
order by
CalendarDay Desc
END
This works ok, but It is not taking into account months with 31 days and I am not getting back today's date for some reason.
Try this (using AdventureWorks as sample database)
DECLARE #today datetime, #NumberOfMonths int, #FirstDay smalldatetime
SELECT #today = '2004-03-09', -- getdate(), tests only
#NumberOfMonths = 1,
#FirstDay = CAST(FLOOR(CAST(
DATEADD(M, -1, #today) AS float)) AS datetime);
WITH Days AS
(
SELECT #FirstDay AS CalendarDay UNION ALL
SELECT DATEADD(d, 1, CalendarDay) AS CalendarDay FROM Days
WHERE DATEADD(d, 1, CalendarDay) < DATEADD(m, #NumberOfMonths, #FirstDay+1)
)
SELECT CONVERT(varchar(10), CalendarDay, 111) as [Date],
COUNT(TransactionDate) as [Count]
FROM Days LEFT JOIN Production.TransactionHistory
ON TransactionDate = Days.CalendarDay
GROUP BY CalendarDay
ORDER BY CalendarDay
Will output
Date Count
---------- -----------
2004/02/09 272
2004/02/10 308
2004/02/11 264
2004/02/12 265
2004/02/13 250
...
EDIT: Updated to include all interval dates
HEre is a modified version of Ruben's answer:
DECLARE #today DATETIME,
#firstDayLastMonth DATETIME,
#daysCount int
SET #today = getDate()
SET #firstDayLastMonth = Dateadd(m,-1,Dateadd(d,-Day(#today) + 1,#today))
Set #daysCount = (select datepart(dd,dateadd(dd,-1,dateadd(mm,1,cast(cast(year(#firstDayLastMonth) as varchar)+'-'+cast(month(#firstDayLastMonth) as varchar)+'-01' as datetime)))))
SELECT substring(convert( char(10), CONVERT( char(10), visitDateTime, 121 ) ), 1, 11) AS [Date], Count(* ) AS [Count]
FROM dbo.TrackingData
WHERE visitdatetime >= Dateadd(d,-#daysCount,#today)
GROUP BY substring(convert( char(10), CONVERT( char(10), visitDateTime, 121 ) ), 1, 11)
ORDER BY substring(convert( char(10), CONVERT( char(10), visitDateTime, 121 ) ), 1, 11)
OK, I got it. Rubens gave me an idea so I modified my SQL like so:
DECLARE #FirstDay SMALLDATETIME,
#NumberOfMonths INT,
#priorMonth SMALLDATETIME,
#firstDayLastMonth DateTime,
#daysCount int
set #firstDayLastMonth = dateadd(m, -1, dateadd(d, -day(getDate()) + 1, getDate()))
set #daysCount = (select datepart(dd,dateadd(dd,-1,dateadd(mm,1,cast(cast(year(#firstDayLastMonth) as varchar)+'-'+cast(month(#firstDayLastMonth) as varchar)+'-01' as datetime)))))
SET #priorMonth = (SELECT Cast((Str(Year(Dateadd(m,-1,Getdate()))) + '/' + Str(Month(Dateadd(m,-1,Getdate()))) + '/' + Str(Day(Dateadd(m,-1,Getdate())))) AS DATETIME))
Declare #before dateTime
Set #before = Dateadd(d,-#daysCount,getdate())
SELECT #FirstDay = #before,
#NumberOfMonths = 1;
WITH days
AS (SELECT #FirstDay AS calendarday
UNION ALL
SELECT Dateadd(d,1,calendarday) AS calendarday
FROM days
WHERE Dateadd(d,1,calendarday) <= Dateadd(m,#NumberOfMonths,#FirstDay))
SELECT Substring(Convert(CHAR(10),Convert(CHAR(10),calendarday,101)),
1,11) ,
founddate.totalbydate,
founddate.DATE
FROM days
LEFT OUTER JOIN (SELECT Count(Left(visitdatetime,11)) AS totalbydate,
Substring(Convert(CHAR(10),Convert(CHAR(10),visitdatetime,101)),
1,11) AS DATE
FROM dbo.trackingdata
WHERE visitdatetime >= Dateadd(d,Datediff(d,0,Getdate()),-29)
AND projectid = 131
GROUP BY Substring(Convert(CHAR(10),Convert(CHAR(10),visitdatetime,101)),
1,11)) founddate
ON founddate.DATE = Substring(Convert(CHAR(10),Convert(CHAR(10),calendarday,101)),
1,11)

Resources