I want to create a Calendar Table without using the recursion as I have prepared earlier. How can I achieve this task. All the required columns are mentioned in code below and few other details in code comments.
...........................................................................................................................................................................................................................................................................
DECLARE #StartDate date = '20200101'
DECLARE #CutoffDate date = GETDATE()
;WITH seq(n) AS
(
SELECT 0 UNION ALL SELECT n + 1 FROM seq
WHERE n < DATEDIFF(DAY, #StartDate, #CutoffDate)
),
d(d) AS
(
SELECT DATEADD(DAY, n, #StartDate) FROM seq
),
src AS /*SOURCE TABLE WITH OBJECT DEFINITION*/
(
SELECT
TheDate = CONVERT(date, d),
TheDay = DATEPART(DAY, d),
TheDayName = DATENAME(WEEKDAY, d),
TheWeek = DATEPART(WEEK, d),
TheDayOfWeek = DATEPART(WEEKDAY, d),
TheMonth = DATEPART(MONTH, d),
TheMonthName = DATENAME(MONTH, d),
TheQuarter = Concat('Q',DATEPART(Quarter, d)),
Financial_Year = DATEPART(YEAR, d),
Financial_Quarter=Datepart(QUARTER,d),
TheYear = DATEPART(YEAR, d),
TheFirstOfMonth = DATEFROMPARTS(YEAR(d), MONTH(d), 1),
TheFirstOfFYear = DATEFROMPARTS(YEAR(d), 4, 1),
TheFirstOfYear = DATEFROMPARTS(YEAR(d), 1, 1),
TheLastOfYear = DATEFROMPARTS(YEAR(d), 12, 31),
TheDayOfYear = DATEPART(DAYOFYEAR, d)
FROM d
),
Dimension AS
(
SELECT
TheDate,
TheDay,
TheDayName,
TheDayOfWeek,
TheDayOfWeekInMonth = CONVERT(tinyint, ROW_NUMBER() OVER
(PARTITION BY TheFirstOfMonth, TheDayOfWeek ORDER BY TheDate)),
TheDayOfYear,
TheWeek,
TheFirstOfWeek = DATEADD(DAY, 1 - TheDayOfWeek, TheDate),
TheLastOfWeek = DATEADD(DAY, 6, DATEADD(DAY, 1 - TheDayOfWeek, TheDate)),
TheWeekOfMonth = CONVERT(tinyint, DENSE_RANK() OVER
(PARTITION BY TheYear, TheMonth ORDER BY TheWeek)),
TheMonth,
TheMonthName,
TheFirstOfMonth,
TheLastOfMonth = MAX(TheDate) OVER (PARTITION BY TheYear, TheMonth),
TheFirstOfNextMonth = DATEADD(MONTH, 1, TheFirstOfMonth),
TheLastOfNextMonth = DATEADD(DAY, -1, DATEADD(MONTH, 2, TheFirstOfMonth)),
TheQuarter,
TheFirstOfQuarter = MIN(TheDate) OVER (PARTITION BY TheYear, TheQuarter),
TheLastOfQuarter = MAX(TheDate) OVER (PARTITION BY TheYear, TheQuarter),
TheYear,
TheFirstOfYear = DATEFROMPARTS(TheYear, 1, 1),
TheFirstOfFYear = DATEFROMPARTS(TheYear, 4, 1),
TheLastOfYear,
MMYYYY = CONVERT(char(2), CONVERT(char(8), TheDate, 101))
+ CONVERT(char(4), TheYear),
Financial_Quarter = Datepart(Quarter,DATEADD(MONTH, -3, TheFirstOfMonth)), /*Starting Financial Quarter from April*/
Financial_Year =CASE
WHEN Financial_Quarter = 1 THEN DATEPART(Year,Dateadd(Year,-1,TheFirstofYear)) ELSE THEYEAR END
FROM src
)
SELECT * FROM Dimension
ORDER BY TheDate
OPTION (MAXRECURSION 0);
As I mentioned in the comments, use a Tally. These are significantly faster than a rCTE as they aren't recursive. I use an inline tally here:
DECLARE #StartDate date = '20200101';
DECLARE #CutoffDate date = GETDATE();
/*
; is a terminator, not a "beginingator". It goes at the end of ALL your statements,
not at the start of statements that require the PREVIOUS statement to be properly terminated.
*/
WITH N AS
(SELECT N
FROM (VALUES (NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL)) N(N)),
Tally AS
(SELECT 0 AS I
UNION ALL
SELECT TOP (DATEDIFF(DAY, #StartDate, #CutoffDate))
ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS I
FROM N N1,N N2,N N3), --Up to 1000 rows. Add more cross joins for more rows
D AS
(SELECT DATEADD(DAY, T.I, #StartDate) AS d
FROM Tally T),
Src AS /*SOURCE TABLE WITH OBJECT DEFINITION*/
(SELECT CONVERT(date, d) AS TheDate,
DATEPART(DAY, d) AS TheDay,
DATENAME(WEEKDAY, d) AS TheDayName,
DATEPART(WEEK, d) AS TheWeek,
DATEPART(WEEKDAY, d) AS TheDayOfWeek,
DATEPART(MONTH, d) AS TheMonth,
DATENAME(MONTH, d) AS TheMonthName,
CONCAT('Q', DATEPART(QUARTER, d)) AS TheQuarter,
DATEPART(YEAR, d) AS Financial_Year,
DATEPART(QUARTER, d) AS Financial_Quarter,
DATEPART(YEAR, d) AS TheYear,
DATEFROMPARTS(YEAR(d), MONTH(d), 1) AS TheFirstOfMonth,
DATEFROMPARTS(YEAR(d), 4, 1) AS TheFirstOfFYear,
DATEFROMPARTS(YEAR(d), 1, 1) AS TheFirstOfYear,
DATEFROMPARTS(YEAR(d), 12, 31) AS TheLastOfYear,
DATEPART(DAYOFYEAR, d) AS TheDayOfYear
FROM d),
Dimension AS
(SELECT TheDate,
TheDay,
TheDayName,
TheDayOfWeek,
CONVERT(tinyint, ROW_NUMBER() OVER (PARTITION BY TheFirstOfMonth, TheDayOfWeek ORDER BY TheDate)) AS TheDayOfWeekInMonth,
TheDayOfYear,
TheWeek,
DATEADD(DAY, 1 - TheDayOfWeek, TheDate) AS TheFirstOfWeek,
DATEADD(DAY, 6, DATEADD(DAY, 1 - TheDayOfWeek, TheDate)) AS TheLastOfWeek,
CONVERT(tinyint, DENSE_RANK() OVER (PARTITION BY TheYear, TheMonth ORDER BY TheWeek)) AS TheWeekOfMonth,
TheMonth,
TheMonthName,
TheFirstOfMonth,
MAX(TheDate) OVER (PARTITION BY TheYear, TheMonth) AS TheLastOfMonth,
DATEADD(MONTH, 1, TheFirstOfMonth) AS TheFirstOfNextMonth,
DATEADD(DAY, -1, DATEADD(MONTH, 2, TheFirstOfMonth)) AS TheLastOfNextMonth,
TheQuarter,
MIN(TheDate) OVER (PARTITION BY TheYear, TheQuarter) AS TheFirstOfQuarter,
MAX(TheDate) OVER (PARTITION BY TheYear, TheQuarter) AS TheLastOfQuarter,
TheYear,
DATEFROMPARTS(TheYear, 1, 1) AS TheFirstOfYear,
DATEFROMPARTS(TheYear, 4, 1) AS TheFirstOfFYear,
TheLastOfYear,
CONVERT(char(2), CONVERT(char(8), TheDate, 101)) + CONVERT(char(4), TheYear) AS MMYYYY,
DATEPART(QUARTER, DATEADD(MONTH, -3, TheFirstOfMonth)) AS Financial_Quarter, /*Starting Financial Quarter from April*/
CASE
WHEN Financial_Quarter = 1 THEN DATEPART(YEAR, DATEADD(YEAR, -1, TheFirstOfYear))
ELSE TheYear
END AS Financial_Year
FROM src)
SELECT *
FROM Dimension
ORDER BY TheDate;
Related
I have a subquery that is taking multiple minutes to execute. If I pull out just the initial rows that are being added up, it only takes half a second with 2,400ish rows so I don't understand why the main query doing the sum is taking so long.
What I'm trying to do is for all the transactions in a date range, for all the workers assigned to those transactions, add up the scheduled hours for each worker.
The query is returning the correct data, it's just taking FOREVER to do it.
QUERY
SELECT scheduled_hours = COALESCE(sum(hours), 0), worker_sysid
FROM (
SELECT DISTINCT
B.DateR1,
B.DateR2,
hours = ABS((B.DAteR1 - B.DateR2) / 3600),
B.worker_sysid
FROM Trans A
OUTER APPLY (
SELECT
DateR1 = MIN(TRANS_START),
DateR2 = MAX(TRANS_END),
worker_sysid
FROM Trans
JOIN trans_workers ON trans_workers.trans_sysid = Trans.SYSID
LEFT JOIN Service ON Service.SYSID = Trans.SERVICESYSID
WHERE
TRANS_START <= A.TRANS_END AND TRANS_END >= A.TRANS_START
AND TRANS_START IS NOT NULL AND TRANS_END IS NOT NULL
AND TRANS_START != '' AND TRANS_END != ''
AND Trans.CHARGEBY IN ('Hours', 'Hour')
AND (
COALESCE(Service.overnight, 0) != 1
OR
COALESCE(Service.active_overnight, 0) = 1
)
AND TRANSDATE BETWEEN 80387 AND 80400 ### These are Clarion dates
AND trans_workers.deleted_at IS NULL
GROUP BY worker_sysid
) B
) A
WHERE worker_sysid IS NOT NULL
GROUP BY worker_sysid
TABLES
Trans: SYSID (int, pk), TRANSDATE (int, clarion-formatted date), TRANS_START / TRANS_END (UNIX timestamp), SERVICESYSID (int, fk), CHARGEBY (varchar)
trans_workers: trans_sysid, worker_sysid, deleted_at
Service: SYSID (int, pk)
UPDATE
Moving the trans_workers join out of the OUTER APPLY has reduced the execution time from 1 minute down to 16 seconds, so that's an improvement.
SELECT scheduled_hours = COALESCE(sum(hours), 0), worker_sysid
FROM (
SELECT DISTINCT
B.DateR1,
B.DateR2,
hours = ABS((B.DateR1 - B.DateR2) / 3600),
worker_sysid
FROM Trans A
JOIN trans_workers ON A.SYSID = trans_workers.trans_sysid
OUTER APPLY (
SELECT
DateR1 = MIN(TRANS_START),
DateR2 = MAX(TRANS_END),
Trans.SYSID
FROM Trans
LEFT JOIN Service ON Service.SYSID = Trans.SERVICESYSID
WHERE
TRANS_START <= A.TRANS_END AND TRANS_END >= A.TRANS_START
AND TRANS_START IS NOT NULL AND TRANS_END IS NOT NULL
AND TRANS_START != '' AND TRANS_END != ''
AND Trans.CHARGEBY IN ('Hours', 'Hour')
AND COALESCE(Service.overnight, 0) != 1
AND TRANSDATE BETWEEN 80387 AND 80400
GROUP BY Trans.SYSID
) B
) A
WHERE worker_sysid IS NOT NULL
GROUP BY worker_sysid
ORDER BY worker_sysid
Thanks to https://www.sqlservercentral.com/forums/topic/consolidate-overlapping-date-periods I have a query that executes in under a second and returns what appear to be the correct hours. Only problem being I don't understand what's happening.
DECLARE #start INTEGER, #end INTEGER;
SET #start = 80401; --06/02/2021
SET #end = 80414; --19/02/2021
WITH cteTemp
AS (
SELECT
worker_sysid,
BeginDate =
CASE
WHEN ROW_NUMBER() OVER (PARTITION BY worker_sysid ORDER BY theDate) - openCnt = 0 THEN theDate
END,
EndDate =
CASE
WHEN ROW_NUMBER() OVER (PARTITION BY worker_sysid ORDER BY theDate) - closeCnt = 0 THEN theDate
END
FROM (
SELECT
worker_sysid,
theDate = DATEADD(day, 0, DATEDIFF(day, 0, (dateadd(day,[TRANSDATE]-(4),'1801-01-01')))) + DATEADD(day, 0 - DATEDIFF(day, 0, DATEADD(second, TRANS_START - DATEDIFF(S, GETDATE(), GETUTCDATE()), '1970-01-01')), DATEADD(second, TRANS_START - DATEDIFF(S, GETDATE(), GETUTCDATE()), '1970-01-01')),
closeCnt = NULL,
openCnt = (ROW_NUMBER() OVER (PARTITION BY worker_sysid ORDER BY DATEADD(day, 0, DATEDIFF(day, 0, (dateadd(day,[TRANSDATE]-(4),'1801-01-01')))) + DATEADD(day, 0 - DATEDIFF(day, 0, DATEADD(second, TRANS_START - DATEDIFF(S, GETDATE(), GETUTCDATE()), '1970-01-01')), DATEADD(second, TRANS_START - DATEDIFF(S, GETDATE(), GETUTCDATE()), '1970-01-01'))) * 2) - 1
FROM
Trans
INNER JOIN trans_workers ON trans_workers.trans_sysid = Trans.SYSID
JOIN Service ON Service.SYSID = Trans.SERVICESYSID
WHERE
worker_sysid IS NOT NULL
AND Trans.deleted_at IS NULL
AND trans_workers.deleted_at IS NULL
AND Trans.CHARGEBY IN ('Hour', 'Hours')
AND (transCancelled IS NULL OR transCancelled != 1)
AND (
COALESCE(Service.overnight, 0) = 0
)
AND TRANSDATE BETWEEN #start AND #end
UNION ALL
SELECT
worker_sysid,
theDate = DATEADD(day, 0, DATEDIFF(day, 0, (dateadd(day,[TRANSDATE]-(4),'1801-01-01')))) + DATEADD(day, 0 - DATEDIFF(day, 0, DATEADD(second, TRANS_END - DATEDIFF(S, GETDATE(), GETUTCDATE()), '1970-01-01')), DATEADD(second, TRANS_END - DATEDIFF(S, GETDATE(), GETUTCDATE()), '1970-01-01')),
closeCnt = ROW_NUMBER() OVER (PARTITION BY worker_sysid ORDER BY worker_sysid, DATEADD(day, 0, DATEDIFF(day, 0, (dateadd(day,[TRANSDATE]-(4),'1801-01-01')))) + DATEADD(day, 0 - DATEDIFF(day, 0, DATEADD(second, TRANS_END - DATEDIFF(S, GETDATE(), GETUTCDATE()), '1970-01-01')), DATEADD(second, TRANS_END - DATEDIFF(S, GETDATE(), GETUTCDATE()), '1970-01-01'))) * 2,
openCnt = NULL
FROM
Trans
JOIN trans_workers ON trans_workers.trans_sysid = Trans.SYSID
JOIN Service ON Service.SYSID = Trans.SERVICESYSID
WHERE
worker_sysid IS NOT NULL
AND Trans.deleted_at IS NULL
AND trans_workers.deleted_at IS NULL
AND Trans.CHARGEBY IN ('Hour', 'Hours')
AND (transCancelled IS NULL OR transCancelled != 1)
AND (
COALESCE(Service.overnight, 0) = 0
)
AND TRANSDATE BETWEEN #start AND #end
)
AS baseSelected
)
SELECT scheduled_hours = SUM(hours), worker_sysid
FROM (
SELECT
dt.worker_sysid,
hours = CAST(ABS(DATEDIFF(second, MIN(dt.BeginDate), MAX(dt.EndDate))) / 3600.0 AS DECIMAL(10,2))
FROM (
SELECT
worker_sysid,
BeginDate,
EndDate,
grpID =
IIF(BeginDate IS NOT NULL, ROW_NUMBER() OVER (PARTITION BY worker_sysid ORDER BY worker_sysid, BeginDate), ROW_NUMBER() OVER (PARTITION BY worker_sysid ORDER BY worker_sysid, EndDate))
FROM
cteTemp
WHERE
BeginDate IS NOT NULL OR EndDate IS NOT NULL
)
AS dt
GROUP BY dt.worker_sysid,grpID
) AS final_table
GROUP BY worker_sysid ORDER BY worker_sysid
Bonus points to myself for conversions because the DATE of each transaction is in Clarion and the TIME of each transaction is a Unix timestamp
I would like to arrange the sum of OrderValue by the period. My SQL query now displays in a tabular format and I want it to be in one line. If the OrderValue due is in this current, it should be under column 0, and if due next month, then it must me under column 1 and so on. Please see my SQL query
ALTER PROCEDURE [dbo].[sp_GetInvoicedPayments]
#CustomerID int
AS
BEGIN
DECLARE #endOfCurrentMonth DATE = EOMONTH(GETDATE())
SELECT [data].CustomerID, [data].[Period], SUM([data].OrderValue) AS
OrderValue
FROM (
SELECT pms.CustomerID, pms.OrderValue,
CASE
WHEN ((SELECT CAST(pms.ExpectedDate AS DATE)) <= paymentInfo.CurrentDueMonth) THEN 0
WHEN ((SELECT CAST(pms.ExpectedDate AS DATE)) <= DATEADD(s,0,DATEADD(mm, DATEDIFF(m,0, DATEADD(MONTH, 1, paymentInfo.CurrentDueMonth)) + 1,- 1)) ) THEN 1
WHEN ((SELECT CAST(pms.ExpectedDate AS DATE)) <= DATEADD(s,0,DATEADD(mm, DATEDIFF(m,0, DATEADD(MONTH, 2, paymentInfo.CurrentDueMonth)) + 1,- 1))) THEN 2
WHEN ((SELECT CAST(pms.ExpectedDate AS DATE)) <= DATEADD(s,0,DATEADD(mm, DATEDIFF(m,0, DATEADD(MONTH, 3, paymentInfo.CurrentDueMonth)) + 1,- 1))) THEN 3
WHEN ((SELECT CAST(pms.ExpectedDate AS DATE)) <= DATEADD(s,0,DATEADD(mm, DATEDIFF(m,0, DATEADD(MONTH, 4, paymentInfo.CurrentDueMonth)) + 1,- 1))) THEN 4
END AS [Period]
FROM PaymentMilestoneSummary pms
INNER JOIN (
SELECT cus.ID AS CustomerID,
CASE
WHEN cus.PaymentStatusID = 1 THEN #endOfCurrentMonth
WHEN cus.PaymentStatusID = 2 THEN (SELECT CAST(DATEADD(month, - 1, #endOfCurrentMonth) AS DATE))
WHEN cus.PaymentStatusID = 3 THEN (SELECT CAST(DATEADD(month, - 2, #endOfCurrentMonth) AS DATE))
WHEN cus.PaymentStatusID = 4 THEN (SELECT CAST(DATEADD(month, - 3, #endOfCurrentMonth) AS DATE))
WHEN cus.PaymentStatusID = 5 THEN (SELECT CAST(DATEADD(month, - 4, #endOfCurrentMonth) AS DATE))
END AS CurrentDueMonth
FROM Company cus
) paymentInfo ON pms.CustomerID = paymentInfo.CustomerID AND paymentInfo.CustomerID= #CustomerID
)[data]
GROUP BY [data].CustomerID, [data].[Period]
END
This is what I get:
This is an example of how I would like it to be:
You can do it like following using PIVOT and CTE
;with cte as
(
--wrap you existing query
)
SELECT
[0], [1], [2], [3], [4]
FROM
(select Period, OrderValue from cte) AS SourceTable
PIVOT
(
max(OrderValue)
FOR Period IN ([0], [1], [2], [3], [4])
) AS PivotTable;
Your procedure should look like this.
ALTER PROCEDURE [dbo].[sp_GetInvoicedPayments]
#CustomerID int
AS
BEGIN
DECLARE #endOfCurrentMonth DATE = EOMONTH(GETDATE())
;with CTE AS
(
SELECT [data].CustomerID, [data].[Period], SUM([data].OrderValue) AS
OrderValue
FROM (
SELECT pms.CustomerID, pms.OrderValue,
CASE
WHEN ((SELECT CAST(pms.ExpectedDate AS DATE)) <= paymentInfo.CurrentDueMonth) THEN 0
WHEN ((SELECT CAST(pms.ExpectedDate AS DATE)) <= DATEADD(s,0,DATEADD(mm, DATEDIFF(m,0, DATEADD(MONTH, 1, paymentInfo.CurrentDueMonth)) + 1,- 1)) ) THEN 1
WHEN ((SELECT CAST(pms.ExpectedDate AS DATE)) <= DATEADD(s,0,DATEADD(mm, DATEDIFF(m,0, DATEADD(MONTH, 2, paymentInfo.CurrentDueMonth)) + 1,- 1))) THEN 2
WHEN ((SELECT CAST(pms.ExpectedDate AS DATE)) <= DATEADD(s,0,DATEADD(mm, DATEDIFF(m,0, DATEADD(MONTH, 3, paymentInfo.CurrentDueMonth)) + 1,- 1))) THEN 3
WHEN ((SELECT CAST(pms.ExpectedDate AS DATE)) <= DATEADD(s,0,DATEADD(mm, DATEDIFF(m,0, DATEADD(MONTH, 4, paymentInfo.CurrentDueMonth)) + 1,- 1))) THEN 4
END AS [Period]
FROM PaymentMilestoneSummary pms
INNER JOIN (
SELECT cus.ID AS CustomerID,
CASE
WHEN cus.PaymentStatusID = 1 THEN #endOfCurrentMonth
WHEN cus.PaymentStatusID = 2 THEN (SELECT CAST(DATEADD(month, - 1, #endOfCurrentMonth) AS DATE))
WHEN cus.PaymentStatusID = 3 THEN (SELECT CAST(DATEADD(month, - 2, #endOfCurrentMonth) AS DATE))
WHEN cus.PaymentStatusID = 4 THEN (SELECT CAST(DATEADD(month, - 3, #endOfCurrentMonth) AS DATE))
WHEN cus.PaymentStatusID = 5 THEN (SELECT CAST(DATEADD(month, - 4, #endOfCurrentMonth) AS DATE))
END AS CurrentDueMonth
FROM Company cus
) paymentInfo ON pms.CustomerID = paymentInfo.CustomerID AND paymentInfo.CustomerID= #CustomerID
)[data]
GROUP BY [data].CustomerID, [data].[Period]
)
SELECT [0], [1], [2], [3], [4]
FROM
(select Period, OrderValue from cte) AS SourceTable
PIVOT
(
max(OrderValue)
FOR Period IN ([0], [1], [2], [3], [4])
) AS PivotTable;
END
Edit:
how would I then sum up those values I get from that one line?
To sum, you can change the pivot query like following.
SELECT
[0], [1], [2], [3], [4] , s as [Sum]
FROM
(select Period, OrderValue, sum(OrderValue) over() s from cte) AS SourceTable
PIVOT
(
max(OrderValue)
FOR Period IN ([0], [1], [2], [3], [4])
) AS PivotTable;
I would like to get the count of EventType by month.
I've trying the next 2 possibility:
First possibility
DECLARE #StartDate date
SET #StartDate = GETDATE() - 365;
WITH theDates AS
(SELECT #StartDate as theDate
UNION ALL
SELECT DATEADD(MONTH, 1, theDate)
FROM theDates
WHERE DATEADD(MONTH, 1, theDate) <= GETDATE()
)
SELECT 'Diagnosis' as EventType,
MONTH(theDate),
YEAR(theDate),
concat(MONTH(theDate),'/', YEAR(theDate)),
Count(fpd.DateOfServiceID) as Eventcount
FROM theDates dd
LEFT JOIN fact.FactPatientDiagnosis fpd ON fpd.DateOfServiceID = concat(YEAR(theDate), MONTH(theDate), DAY(theDate))
GROUP BY YEAR(dd.theDate), MONTH(dd.theDate)
I've getting the value 0 for the EventCount Column
But i know that are in the table more than 500 Diagnosis
Second Possibility
DECLARE #StartDate date
SET #StartDate = GETDATE() - 365;
WITH theDates AS
(SELECT #StartDate as theDate
UNION ALL
SELECT DATEADD(MONTH, 1, theDate)
FROM theDates
WHERE DATEADD(MONTH, 1, theDate) <= GETDATE()
)
SELECT 'Diagnosis' as EventType,
MONTH(theDate),
YEAR(theDate),
concat(MONTH(theDate),'/', YEAR(theDate)),
Count(fpd.DateOfServiceID) as Eventcount
FROM theDates dd
LEFT JOIN fact.FactPatientDiagnosis fpd ON fpd.DateOfServiceID is not NULL
GROUP BY YEAR(dd.theDate), MONTH(dd.theDate)
In this Select, I've getting the value 503 for the EventCount column
I assume you want:
SET #StartDate = GETDATE() - 365;
WITH theDates AS (
SELECT #StartDate as theDate
UNION ALL
SELECT DATEADD(MONTH, 1, theDate)
FROM theDates
WHERE DATEADD(MONTH, 1, theDate) <= GETDATE()
)
SELECT 'Diagnosis' as EventType,
YEAR(theDate), MONTH(theDate),
concat(MONTH(theDate), '/', YEAR(theDate)),
Count(fpd.DateOfServiceID) as Eventcount
FROM theDates dd LEFT JOIN
fact.FactPatientDiagnosis fpd
ON fpd.DateOfServiceID >= dd.theDate AND
fpd.DateOfServiceID < DATEADD(month, 1, dd.theDate)
GROUP BY YEAR(dd.theDate), MONTH(dd.theDate)
ORDER BY YEAR(dd.theDate), MONTH(dd.theDate);
You are generating 1 date for each month in CTE (picking current DAY), and comparing it with dateofserviceid, even some date matches, every day you will get different output.
Ideally you should be comparing only YEAR and MONTH part for the JOIN.
LEFT JOIN fact.factpatientdiagnosis fpd ON
month(fpd.dateofserviceid) = month(thedate)
AND
year(fpd.dateofserviceid) = year(thedate)
Your overall query should look like following now.
DECLARE #StartDate DATE
SET #StartDate = Getdate() - 365;
WITH thedates
AS (SELECT #StartDate AS theDate
UNION ALL
SELECT Dateadd(month, 1, thedate)
FROM thedates
WHERE Dateadd(month, 1, thedate) <= Getdate())
SELECT 'Diagnosis' AS EventType,
Month(thedate),
Year(thedate),
Concat(Month(thedate), '/', Year(thedate)),
Count(fpd.dateofserviceid) AS Eventcount
FROM thedates dd
LEFT JOIN fact.factpatientdiagnosis fpd
ON Month(fpd.dateofserviceid) = Month(thedate)
AND Year(fpd.dateofserviceid) = Year(thedate)
GROUP BY Year(dd.thedate),
Month(dd.thedate)
Why would you ever want to use Int for a date IDK. I assume your DateOfServiceID is a date representation in int format like 20180102 (yyyyMMdd). If so:
WITH myData (theDate)
AS (SELECT CONVERT(DATE, CAST(DateOfServiceId AS VARCHAR(8)), 112)
FROM fact.FactPatientDiagnosis
WHERE DateOfServiceId >= CAST(CONVERT(VARCHAR(10), DATEADD(DAY, -365, CAST(GETDATE() AS DATE)), 112) AS INT))
SELECT 'Diagnosis' AS EventType,
MONTH(theDate) AS mn,
YEAR(theDate) AS yr,
SUBSTRING(CONVERT(VARCHAR(10), theDate, 103), 4, 7) AS MonthYear,
COUNT(theDate) AS Eventcount
FROM myData
GROUP BY MONTH(theDate),
YEAR(theDate),
SUBSTRING(CONVERT(VARCHAR(10), theDate, 103), 4, 7)
ORDER BY YEAR(theDate),
MONTH(theDate);
I am trying to create function in SQL Server that returns months between range of dates passed to function. Below is my current script -
CREATE FUNCTION [dbo].[fn_get_date_range] (
#startDate datetime,
#endDate datetime,
#frequency varchar(20)
)
RETURNS
#dateRanges TABLE
(
report_date datetime
)
AS
BEGIN
WITH DateRange (report_date) AS
(
SELECT row_number() OVER (ORDER BY object_id) FROM sys.all_objects)
SELECT DATEADD(s,-1,DATEADD(mm, DATEDIFF(m,0,#StartDate)+report_date,0)
) AS report_date
insert into #dateRanges (report_date)
Select report_date FROM DateRange
WHERE report_date <= DATEDIFF(month, #StartDate, #EndDate)+1
RETURN
END
But when I try to create these function, I get below error -
Common table expression defined but not used.
I have tried different approach but it doesn't work. Any pointers in right direction would be appreciated. Thank you.
Here is my TVF used for dynamic datetime ranges.
Similar to your approach, but I do have the DATEPART as a parameter as well as the increment.
Example - Month Increment 1:
Select * from [dbo].[tvf-Range-Date]('2016-01-01','2017-01-01','MM',1)
Returns
Example - Minute Increment 15:
Select * from [dbo].[tvf-Range-Date]('2017-01-01','2017-01-02','MI',15)
Returns
The UDF if Interested
CREATE FUNCTION [dbo].[tvf-Range-Date] (#R1 datetime,#R2 datetime,#Part varchar(10),#Incr int)
Returns Table
Return (
with cte0(M) As (Select 1+Case #Part When 'YY' then DateDiff(YY,#R1,#R2)/#Incr When 'QQ' then DateDiff(QQ,#R1,#R2)/#Incr When 'MM' then DateDiff(MM,#R1,#R2)/#Incr When 'WK' then DateDiff(WK,#R1,#R2)/#Incr When 'DD' then DateDiff(DD,#R1,#R2)/#Incr When 'HH' then DateDiff(HH,#R1,#R2)/#Incr When 'MI' then DateDiff(MI,#R1,#R2)/#Incr When 'SS' then DateDiff(SS,#R1,#R2)/#Incr End),
cte1(N) As (Select 1 From (Values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) N(N)),
cte2(N) As (Select Top (Select M from cte0) Row_Number() over (Order By (Select NULL)) From cte1 a, cte1 b, cte1 c, cte1 d, cte1 e, cte1 f, cte1 g, cte1 h ),
cte3(N,D) As (Select 0,#R1 Union All Select N,Case #Part When 'YY' then DateAdd(YY, N*#Incr, #R1) When 'QQ' then DateAdd(QQ, N*#Incr, #R1) When 'MM' then DateAdd(MM, N*#Incr, #R1) When 'WK' then DateAdd(WK, N*#Incr, #R1) When 'DD' then DateAdd(DD, N*#Incr, #R1) When 'HH' then DateAdd(HH, N*#Incr, #R1) When 'MI' then DateAdd(MI, N*#Incr, #R1) When 'SS' then DateAdd(SS, N*#Incr, #R1) End From cte2 )
Select RetSeq = N+1
,RetVal = D
From cte3,cte0
Where D<=#R2
)
/*
Max 100 million observations -- Date Parts YY QQ MM WK DD HH MI SS
Syntax:
Select * from [dbo].[tvf-Range-Date]('2016-10-01','2020-10-01','YY',1)
Select * from [dbo].[tvf-Range-Date]('2016-01-01','2017-01-01','MM',1)
*/
EDIT - As Requested
CREATE FUNCTION [dbo].[tvf-Range-Date-Span] (#R1 datetime,#R2 datetime,#Part varchar(10),#Incr int)
Returns Table
Return (
with cte0(M) As (Select 1+Case #Part When 'YY' then DateDiff(YY,#R1,#R2)/#Incr When 'QQ' then DateDiff(QQ,#R1,#R2)/#Incr When 'MM' then DateDiff(MM,#R1,#R2)/#Incr When 'WK' then DateDiff(WK,#R1,#R2)/#Incr When 'DD' then DateDiff(DD,#R1,#R2)/#Incr When 'HH' then DateDiff(HH,#R1,#R2)/#Incr When 'MI' then DateDiff(MI,#R1,#R2)/#Incr When 'SS' then DateDiff(SS,#R1,#R2)/#Incr End),
cte1(N) As (Select 1 From (Values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) N(N)),
cte2(N) As (Select Top (Select M from cte0) Row_Number() over (Order By (Select NULL)) From cte1 a,cte1 b,cte1 c,cte1 d,cte1 e,cte1 f,cte1 g,cte1 h ),
cte3(N,D) As (Select 0,#R1 Union All Select N,Case #Part When 'YY' then DateAdd(YY,N*#Incr,#R1) When 'QQ' then DateAdd(QQ,N*#Incr,#R1) When 'MM' then DateAdd(MM,N*#Incr,#R1) When 'WK' then DateAdd(WK,N*#Incr,#R1) When 'DD' then DateAdd(DD,N*#Incr,#R1) When 'HH' then DateAdd(HH,N*#Incr,#R1) When 'MI' then DateAdd(MI,N*#Incr,#R1) When 'SS' then DateAdd(SS,N*#Incr,#R1) End From cte2 )
Select RetSeq = N+1
,RetVal1 = D
,RetVal2 = LEAD(D,1,#R2) over (Order By D)
From cte3,cte0
Where N<cte0.M-1
)
--Max 100 million observations -- Date Parts YY QQ MM WK DD HH MI SS
--Select * from [dbo].[tvf-Range-Date-Span]('2016-10-01','2020-10-01','YY',1)
Returns something like this
Edit 2 - For 2008
;with cte as (
Select * from [dbo].[tvf-Range-Date]('2016-01-01','2017-01-01','MM',1)
)
Select DateR1 = A.RetVal
,DateR2 = B.NxtDate
From cte A
Cross Apply (Select NxtDate=min(RetVal) from cte where RetVal > A.RetVal ) B
Where B.NxtDate is not null
Returns
It is not allowed that CTE is not followed by SELECT statement, cite from MS:
•A CTE must be followed by a single SELECT statement. INSERT, UPDATE,
DELETE, and MERGE statements are not supported.
UPDATE
CREATE FUNCTION [dbo].[fn_get_date_range] (
#startDate datetime,
#endDate datetime,
#frequency varchar(20)
)
RETURNS
#dateRanges TABLE
(
report_date datetime
)
AS
BEGIN
WITH DateRange (report_date) AS
(
SELECT row_number() OVER (ORDER BY object_id) FROM sys.all_objects)
SELECT DATEADD(s,-1,DATEADD(mm, DATEDIFF(m,0,#StartDate)+report_date,0)
) AS report_date
Select * FROM DateRange
INTO #temp_table
WHERE report_date <= DATEDIFF(month, #StartDate, #EndDate)+1
insert into #dateRanges (report_date)
select * from #temp_table
drop table #temp_table
RETURN
END
This is a variant that's similar to John's function...
CREATE FUNCTION dbo.tfn_BuildDateRanges
(
#startDate DATETIME,
#endDate DATETIME,
#dateInterval VARCHAR(20),
#interval INT
)
RETURNS TABLE WITH SCHEMABINDING AS
RETURN
WITH
cte_n1 (n) AS (SELECT 1 FROM (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) n (n)),
cte_n2 (n) AS (SELECT 1 FROM cte_n1 a CROSS JOIN cte_n1 b),
cte_n3 (n) AS (SELECT 1 FROM cte_n2 a CROSS JOIN cte_n2 b),
cte_Calendar AS (
SELECT TOP ((CASE
WHEN #dateInterval IN ('year', 'yy', 'yyyy') THEN DATEDIFF(year, #startDate, #endDate)
WHEN #dateInterval IN ('quarter', 'qq', 'q') THEN DATEDIFF(quarter, #startDate, #endDate)
WHEN #dateInterval IN ('month', 'mm', 'm') THEN DATEDIFF(month, #startDate, #endDate)
WHEN #dateInterval IN ('week wk, ww') THEN DATEDIFF(week, #startDate, #endDate)
WHEN #dateInterval IN ('day', 'dd', 'd') THEN DATEDIFF(day, #startDate, #endDate)
WHEN #dateInterval IN ('hour', 'hh') THEN DATEDIFF(hour, #startDate, #endDate)
WHEN #dateInterval IN ('minute', 'mi', 'n') THEN DATEDIFF(minute, #startDate, #endDate)
WHEN #dateInterval IN ('second', 'ss', 's') THEN DATEDIFF(second, #startDate, #endDate)
END / #interval + 1))
BegOfRange = CASE
WHEN #dateInterval IN ('year', 'yy', 'yyyy') THEN DATEADD(year, (ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) -1) * #interval, #startDate)
WHEN #dateInterval IN ('quarter', 'qq', 'q') THEN DATEADD(quarter, (ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) -1) * #interval, #startDate)
WHEN #dateInterval IN ('month', 'mm', 'm') THEN DATEADD(month, (ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) -1) * #interval, #startDate)
WHEN #dateInterval IN ('week wk, ww') THEN DATEADD(week, (ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) -1) * #interval, #startDate)
WHEN #dateInterval IN ('day', 'dd', 'd') THEN DATEADD(day, (ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) -1) * #interval, #startDate)
WHEN #dateInterval IN ('hour', 'hh') THEN DATEADD(hour, (ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) -1) * #interval, #startDate)
WHEN #dateInterval IN ('minute', 'mi', 'n') THEN DATEADD(minute, (ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) -1) * #interval, #startDate)
WHEN #dateInterval IN ('second', 'ss', 's') THEN DATEADD(second, (ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) -1) * #interval, #startDate)
END,
EndOfRange = CASE
WHEN #dateInterval IN ('year', 'yy', 'yyyy') THEN DATEADD(year, (ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) ) * #interval, #startDate)
WHEN #dateInterval IN ('quarter', 'qq', 'q') THEN DATEADD(quarter, (ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) ) * #interval, #startDate)
WHEN #dateInterval IN ('month', 'mm', 'm') THEN DATEADD(month, (ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) ) * #interval, #startDate)
WHEN #dateInterval IN ('week wk, ww') THEN DATEADD(week, (ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) ) * #interval, #startDate)
WHEN #dateInterval IN ('day', 'dd', 'd') THEN DATEADD(day, (ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) ) * #interval, #startDate)
WHEN #dateInterval IN ('hour', 'hh') THEN DATEADD(hour, (ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) ) * #interval, #startDate)
WHEN #dateInterval IN ('minute', 'mi', 'n') THEN DATEADD(minute, (ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) ) * #interval, #startDate)
WHEN #dateInterval IN ('second', 'ss', 's') THEN DATEADD(second, (ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) ) * #interval, #startDate)
END
FROM
cte_n3 a CROSS JOIN cte_n3 b
)
SELECT
c.BegOfRange,
c.EndOfRange
FROM
cte_Calendar c;
GO
This is my stored procedure which I was able to create
CREATE PROCEDURE [dbo].[spGetAllStaffCollectiveAttendanceByMonth]
#Month nvarchar(9)
AS
BEGIN
Declare #StartDate DATE,
#EndDate DATE
;WITH CteMonths(n, m) AS(
SELECT 1, 'January' UNION ALL
SELECT 2, 'February' UNION ALL
SELECT 3, 'March' UNION ALL
SELECT 4, 'April' UNION ALL
SELECT 5, 'May' UNION ALL
SELECT 6, 'June' UNION ALL
SELECT 7, 'July' UNION ALL
SELECT 8, 'August' UNION ALL
SELECT 9, 'September' UNION ALL
SELECT 10, 'October' UNION ALL
SELECT 11, 'November' UNION ALL
SELECT 12, 'December'
)
SELECT #StartDate = DATEADD(MONTH, n - 1, DATEADD(YEAR, DATEDIFF(YEAR, 0, GETDATE()), 0)),
#EndDate = DATEADD(DAY, -1, DATEADD(MONTH, n, DATEADD(YEAR, DATEDIFF(YEAR, 0, GETDATE()), 0)))
FROM CteMonths
WHERE m = #month
SELECT
StaffAttendance.StaffId,
DATENAME(MONTH, #StartDate) AS [ForMonth], #StartDate AS StartDate, #EndDate AS EndDate,
(DATEDIFF(dd, #StartDate, #EndDate) + 1)-(DATEDIFF(wk, #StartDate, #EndDate) * 1)-(CASE WHEN DATENAME(dw, #StartDate) = 'Sunday' THEN 1 ELSE 0 END) AS TotalWorkingDays,
SUM(StaffAttendance.AttendanceStatusId) AS TotalDaysWorked
FROM StaffAttendance
WHERE [Date] BETWEEN #StartDate AND #EndDate AND StaffAttendance.AttendanceStatusId = 1 GROUP BY StaffAttendance.StaffId
END
GO
This, here, is my function which I am trying to create but can't. Its the same thing as above except for what makes it a function.
CREATE FUNCTION [dbo].[funcGetAllStaffCollectiveAttendanceByMonth]
(
#Month nvarchar(9)
)
RETURNS TABLE
AS
RETURN
(
Declare #StartDate DATE,
#EndDate DATE
;WITH CteMonths(n, m) AS(
SELECT 1, 'January' UNION ALL
SELECT 2, 'February' UNION ALL
SELECT 3, 'March' UNION ALL
SELECT 4, 'April' UNION ALL
SELECT 5, 'May' UNION ALL
SELECT 6, 'June' UNION ALL
SELECT 7, 'July' UNION ALL
SELECT 8, 'August' UNION ALL
SELECT 9, 'September' UNION ALL
SELECT 10, 'October' UNION ALL
SELECT 11, 'November' UNION ALL
SELECT 12, 'December'
)
SELECT #StartDate = DATEADD(MONTH, n - 1, DATEADD(YEAR, DATEDIFF(YEAR, 0, GETDATE()), 0)),
#EndDate = DATEADD(DAY, -1, DATEADD(MONTH, n, DATEADD(YEAR, DATEDIFF(YEAR, 0, GETDATE()), 0)))
FROM CteMonths
WHERE m = #month
SELECT
StaffAttendance.StaffId,
DATENAME(MONTH, #StartDate) AS [ForMonth], #StartDate AS StartDate, #EndDate AS EndDate,
(DATEDIFF(dd, #StartDate, #EndDate) + 1)-(DATEDIFF(wk, #StartDate, #EndDate) * 1)-(CASE WHEN DATENAME(dw, #StartDate) = 'Sunday' THEN 1 ELSE 0 END) AS TotalWorkingDays,
SUM(StaffAttendance.AttendanceStatusId) AS TotalDaysWorked
FROM StaffAttendance
WHERE [Date] BETWEEN #StartDate AND #EndDate AND StaffAttendance.AttendanceStatusId = 1 GROUP BY StaffAttendance.StaffId
END
)
But this is what I am getting
What am I doing wrong?
You have used the syntax for an inline table-valued function but you have multiple statements.
Comparison of inline and multi-statement table-valued functions.
So you could either refactor to use a single statement (higher performance) or use the multi-statement syntax as described in that link (easier).
If you wanted to do it inline you could do something along these lines (note, I don't have SSMS available right now so there may be some minor syntax errors like unmatched brackets):
CREATE FUNCTION [dbo].[funcGetAllStaffCollectiveAttendanceByMonth]
(
#Month nvarchar(9)
)
RETURNS TABLE
AS
RETURN
(
-- The StartDate should be the first day of the month that is passed as a parameter, in the current year.
-- The EndDate should be the last day of the month which the StartDate begins.
WITH cteDates AS (
SELECT StartDate = Convert(date, Concat('01', #Month, Convert(varchar(4), DatePart(Year, GetDate()))), 106),
EndDate = DateAdd(Day, -1,
DateAdd(Month, 1,
Convert(date, Concat('01', #Month, Convert(varchar(4), DatePart(Year, GetDate()))) , 106)
)
)
)
SELECT
Sa.StaffId,
DATENAME(MONTH, cteDates.StartDate) AS [ForMonth],
cteDates.StartDate,
cteDates.EndDate,
(DATEDIFF(dd, cteDates.StartDate, cteDates.EndDate) + 1)-
(DATEDIFF(wk, cteDates.StartDate, cteDates.EndDate) * 1)-
(CASE WHEN DATENAME(dw, cteDates.StartDate) = 'Sunday' THEN 1 ELSE 0 END) AS TotalWorkingDays,
SUM(sa.AttendanceStatusId) AS TotalDaysWorked
FROM StaffAttendance As sa
JOIN cteDates ON sa.[Date] BETWEEN cteDates.StartDate AND cteDates.EndDate
WHERE sa.AttendanceStatusId = 1
GROUP BY
Sa.StaffId,
DATENAME(MONTH, cteDates.StartDate),
cteDates.StartDate,
cteDates.EndDate,
(DATEDIFF(dd, cteDates.StartDate, cteDates.EndDate) + 1)-
(DATEDIFF(wk, cteDates.StartDate, cteDates.EndDate) * 1)-
(CASE WHEN DATENAME(dw, cteDates.StartDate) = 'Sunday' THEN 1 ELSE 0 END)
END
)