Question

I'm working with a datatable containing incident tickets (dbo.IncidentDimvw referenced in the first variable declaration)

I'm trying to produce a query that will enumerate every date from the earliest ticket, until present day, along with the numbers of tickets "opened" and "closed" on each day (which may be 0), and the number of tickets "open" on each day (different from "opened").

To accomplish this, I need a list of dates (i.e. a calendar) that is independent from my ticket datatable. I was able to use some CTEs to do the job, building the list of dates on the fly within a query.

This part works flawlessly, and you can test my code below if you'd like to see it in action (supply a constant value for @SSOReportDateRangeBegin).

My current query is shown below, with descriptive comments. It returns a complete calendar, with one row for each date from 12/27/2012 to the current date (scalable up to ten years).

The problem I'm encountering is, I can't figure out how to join dbo.IncidentDimvw to this "calendar", to pull the counts of tickets opened, closed, and open on each date.
The CTEs and DISTINCT selection are making it difficult for me to understand how these joins need to be structured.

What I really want, is to have the final result of the calendar CTEs selected as a plain old dataset, from which I can compare tickets - thereby separating the logic of the calendar creation from the logic of ticket selection.

I've tried LEFT JOIN-ing dbo.IncidentDimvw to the calendar query, but when I select
COUNT(OpenedInc.ID) AS 'Incidents Opened' the resulting counts are way too large - which indicates to me that the calendar logic is interfering with the ticket selection logic - or perhaps I don't understand how to GROUP correctly in this situation.

/* Begin our date range with the earliest
   incident ticket creation date on record. (currently 12/27/2012)
   CAST is necessary because the CreatedDate column is a datetime value,
   and we specifically want to truncate the "time" portion
   for the purposes of this query.*/
DECLARE @SSOReportDateRangeBegin AS DATE =
    (SELECT MIN(CAST(inc.CreatedDate AS DATE)) FROM dbo.IncidentDimvw inc);

/* State how many years to include in our date range.
   Currently our range includes 10 years. (12/27/2012 - 12/26/2022)
   Later on, our selection will limit results to only dates <=CAST(GETDATE() AS DATE) */
DECLARE @SSOReportDateRangeYears AS INT = 10;
/* @SSOReportDateRangeYears cannot be greater than 31.
   For larger time periods, add CTEs for decades, centuries, millenia, etc. */

/* Calendar calculater based on a row counter with 31 rows.
   Calculates day 1 through day 31 for all 12 months, for 10 years.

   Since some months don't have 31 days, some DATEADD calculations
   will rollover to the next month, causing duplicate date values.
   (e.g. Feb 31st will be interpreted as Mar 3rd in non-leap year,
   Mar 2nd in leap-year, duplicating the date values for March 2nd or 3rd)

   SELECT DISTINCT is used to eliminate duplicate date values,
   giving us a clean and complete calendar to work with. */
WITH [counter](N) AS
(SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
 SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
 SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
 SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
 SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
 SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
 SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
 SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
 SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
 SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
 SELECT 1),
SSOReportDays(N) AS (SELECT row_number() OVER (ORDER BY (SELECT NULL)) FROM [counter]),
SSOReportMonths(N) AS (SELECT N - 1 FROM SSOReportDays WHERE N <= 12),
SSOReportYears(N) AS (SELECT N - 1 FROM SSOReportDays WHERE N <= @SSOReportDateRangeYears)

SELECT DISTINCT

CAST(DATEADD(DAY, SSOReportDays.n,
     DATEADD(MONTH, SSOReportMonths.n,
     DATEADD(YEAR, SSOReportYears.n,
     DATEADD(DAY, -1, @SSOReportDateRangeBegin)))) AS DATE) AS CalendarDate
     /* Subtract 1 day from @SSOReportDateRangeBegin,
        because the counter will begin with the following day. */

/* CROSS JOIN to compute every possible combination
   of day(1-31) month(1-12) and year(1-10) */
FROM SSOReportYears CROSS JOIN SSOReportMonths CROSS JOIN SSOReportDays

/* Reduce calendar to <= the current date,
   since incident tickets cannot be created with future dates. */
WHERE CAST(DATEADD(DAY, SSOReportDays.n,
           DATEADD(MONTH, SSOReportMonths.n,
           DATEADD(YEAR, SSOReportYears.n,
           DATEADD(DAY, -1, @SSOReportDateRangeBegin)))) AS DATE) <= CAST(GETDATE() AS DATE)
           /* Subtract 1 day from @SSOReportDateRangeBegin,
              because the counter will begin with the following day. */
Was it helpful?

Solution

Nothing stops you from adding more CTEs to your query. You have 4 CTEs already, add a fifth one for the Calendar Dates, a sixth one for the Ticket Counts from your table, and then left join the Calendar CTE with the ticket counts:

WITH [counter](N) AS
(SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
 SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
 SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
 SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
 SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
 SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
 SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
 SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
 SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
 SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
 SELECT 1),
SSOReportDays(N) AS (SELECT row_number() OVER (ORDER BY (SELECT NULL)) FROM [counter]),
SSOReportMonths(N) AS (SELECT N - 1 FROM SSOReportDays WHERE N <= 12),
SSOReportYears(N) AS (SELECT N - 1 FROM SSOReportDays WHERE N <= @SSOReportDateRangeYears),
Calendar AS(
SELECT DISTINCT
CAST(DATEADD(DAY, SSOReportDays.n,
     DATEADD(MONTH, SSOReportMonths.n,
     DATEADD(YEAR, SSOReportYears.n,
     DATEADD(DAY, -1, @SSOReportDateRangeBegin)))) AS DATE) AS CalendarDate
     /* Subtract 1 day from @SSOReportDateRangeBegin,
        because the counter will begin with the following day. */

/* CROSS JOIN to compute every possible combination
   of day(1-31) month(1-12) and year(1-10) */
FROM SSOReportYears CROSS JOIN SSOReportMonths CROSS JOIN SSOReportDays

/* Reduce calendar to <= the current date,
   since incident tickets cannot be created with future dates. */
WHERE CAST(DATEADD(DAY, SSOReportDays.n,
           DATEADD(MONTH, SSOReportMonths.n,
           DATEADD(YEAR, SSOReportYears.n,
           DATEADD(DAY, -1, @SSOReportDateRangeBegin)))) AS DATE) <= CAST(GETDATE() AS DATE)),
TicketCount AS (
SELECT TicketDate, Status, Count(*) cnt -- These are just fake columns. Use your columns
FROM dbo.IncidentDimvw
GROUP BY TicketDate, Status)
SELECT c.CalendarDate, O.cnt, C.cnt, E.cnt
FROM Calendar c
LEFT JOIN TicketCount O ON c.CalendarDate = O.TicketDate AND O.Status = 'Open'
LEFT JOIN TicketCount C ON c.CalendarDate = C.TicketDate AND C.Status = 'Close'
LEFT JOIN TicketCount E ON c.CalendarDate = E.TicketDate AND E.Status = 'etc.' -- keep adding until you get all required statuses
ORDER BY 1
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top