Select count with 0 count - sql-server

Lets say I have following query:
SELECT top (5) CAST(Created AS DATE) as DateField,
Count(id) as Counted
FROM Table
GROUP BY CAST(Created AS DATE)
order by DateField desc
Lets say it will return following data set
DateField Counted
2016-01-18 34
2016-01-17 99
2016-01-14 1
2015-12-28 1
2015-12-27 6
But when I have Counted = 0 for certain Date I would like to get that in result set. So for example it should look like following
DateField Counted
2016-01-18 34
2016-01-17 99
2016-01-16 0
2016-01-15 0
2016-01-14 1
Thank you!

Expanding upon KM's answer, you need a date table which is like a numbers table.
There are many examples on the web but here's a simple one.
CREATE TABLE DateList (
DateValue DATE,
CONSTRAINT PK_DateList PRIMARY KEY CLUSTERED (DateValue)
)
GO
-- Insert dates from 01/01/2015 and 12/31/2015
DECLARE #StartDate DATE = '01/01/2015'
DECLARE #EndDatePlus1 DATE = '01/01/2016'
DECLARE #CurrentDate DATE = #StartDate
WHILE #EndDatePlus1 > #CurrentDate
BEGIN
INSERT INTO DateList VALUES (#CurrentDate)
SET #CurrentDate = DATEADD(dd,1,#CurrentDate)
END
Now you have a table
then you can rewrite your query as follows:
SELECT top (5) DateValue, isnull(Count(id),0) as Counted
FROM DateList
LEFT OUTER JOIN Table
on DateValue = CAST(Created AS DATE)
GROUP BY DateValue
order by DateValue desc
Two notes:
You'll need a where clause to specify your range.
A join on a cast isn't ideal. The type in your date table should match the type in your regular table.

One more solution as a single query:
;WITH dates AS
(
SELECT CAST(DATEADD(DAY, ROW_NUMBER() OVER (ORDER BY [object_id]) - 1, '2016-01-14') as date) 'date'
FROM sys.all_objects
)
SELECT TOP 5
[date] AS 'DateField',
SUM(CASE WHEN Created IS NULL THEN 0 ELSE 1 END) AS 'Counted'
FROM dates
LEFT JOIN Table ON [date]=CAST(Created as date)
GROUP BY [date]
ORDER BY [date]

For a more edgy solution, you could use a recursive common table expression to create the date list. PLEASE NOTE: do not use recursive common table expressions in your day job! They are dangerous because it is easy to create one that never terminates.
DECLARE #StartDate date = '1/1/2016';
DECLARE #EndDate date = '1/15/2016';
WITH DateList(DateValue)
AS
(
SELECT DATEADD(DAY, 1, #StartDate)
UNION ALL
SELECT DATEADD(DAY, 1, DateValue)
FROM DateList
WHERE DateList.DateValue < #EndDate
)
SELECT DateValue, isnull(Count(id),0) as Counted
FROM DateList
LEFT OUTER JOIN [Table]
ON DateValue = CAST(Created AS DATE)
GROUP BY DateValue
ORDER BY DateValue DESC

Related

How to combine a date series with my regular selection?

I am using the following MSSQL query to count timestamps within a table. Each row stands for one transaction (one car washed) at my carwash company.
SELECT
count(st.date) as NumberOfWashes,
cast(st.date as date) as DayOfWashes
FROM
POS.dbo.sales_transaction_line_item as stli
join POS.dbo.sales_transaction as st on st.sales_transaction_id = stli.fk_sales_transaction
join POS.dbo.sales_item as si on si.sales_item_id = stli.fk_sales_item
WHERE
st.fk_sales_status <> 3
and si.fk_sales_item_type = 1
and st.date BETWEEN #start_date and #end_date
Group by
cast(st.date as date)
order by
cast(st.date as date) desc
I am using the two joins to eliminate cancelled washes (sales_status) and transactions which sell products but no car wash (sales_item_type).
The result of this list looks like:
NumberofWashes DayOfWashes
42 2023-01-26
71 2023-01-25
57 2023-01-24
87 2023-01-23
104 2023-01-21
114 2023-01-20
As you can see the Date 2023-01-22 is missing (it's a sunday and we are closed).
However, I need that day as well with 0 washes.
Therefore I have the code like this:
DECLARE #start_date DATE = '2021-01-26';
DECLARE #end_date DATE = '2023-01-27';
WITH AllDays
AS ( SELECT #start_date AS [Date]
UNION ALL
SELECT DATEADD(DAY, 1, [Date])
FROM AllDays
WHERE [Date] < #end_date )
SELECT [Date]
FROM AllDays
OPTION (MAXRECURSION 0)
It produces a simple list with all dates:
2023-01-20
2023-01-21
2023-01-22
2023-01-23
2023-01-24
2023-01-25
2023-01-26
How can I combine those two statements so the DayOfWashes includes all available dates?
Just combine the 2 queries in a nice way, something like this:
DECLARE #start_date DATE = '2021-01-26';
DECLARE #end_date DATE = '2023-01-27';
WITH AllDays
AS ( SELECT #start_date AS [Date]
UNION ALL
SELECT DATEADD(DAY, 1, [Date])
FROM AllDays
WHERE [Date] < #end_date ),
WashData as (
SELECT
count(st.date) as NumberOfWashes,
cast(st.date as date) as DayOfWashes
FROM
POS.dbo.sales_transaction_line_item as stli
join POS.dbo.sales_transaction as st on st.sales_transaction_id = stli.fk_sales_transaction
join POS.dbo.sales_item as si on si.sales_item_id = stli.fk_sales_item
WHERE
st.fk_sales_status <> 3
and si.fk_sales_item_type = 1
and st.date BETWEEN #start_date and #end_date
Group by
cast(st.date as date)
)
SELECT
[Date]
,NumberOfWashes
FROM AllDays ad
left join WashData wd
on ad.[Date] = wd.DayOfWashes
OPTION (MAXRECURSION 0)

How do I create dynamic column names in a WHILE DATE Loop query?

Here is my working query:
DECLARE #StartDate AS DATETIME
DECLARE #EndDate AS DATETIME
DECLARE #CurrentDate AS DATETIME
SET #StartDate = '2021-01-01'
SET #EndDate = GETDATE()
SET #CurrentDate = #StartDate
WHILE (#CurrentDate < #EndDate)
BEGIN
SELECT
SUM(CASE WHEN [CreatedDateTime] BETWEEN #CurrentDate AND #CurrentDate + 6 THEN 1 ELSE 0 END)
FROM Table
WHERE OwnedBy = 'Service Desk'
SET #CurrentDate = convert(varchar(30), dateadd(day,6, #CurrentDate), 101);
END
which produces the following result:
I'm trying to create a dynamic column name that shows the Date range for each result.
For example:
The 1st result should have the column name: "Jan 1-Jan 7"
The 2nd result should have the column name: "Jan 8-Jan 14"
etc.
Is there a way to do this?
Thanks.
This is a very unusual way to approach this kind of database problem. You might instead consider using two columns, without loops, like this:
DECLARE #StartDate AS DATETIME = '20210101';
WITH e1(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
)-- 10*1
,e2(n) AS (SELECT 1 FROM e1 CROSS JOIN e1 AS b) -- 10*10
,e3(n) AS (SELECT 1 FROM e1 CROSS JOIN e2) -- 10*100
,Dates as (
SELECT DATEADD(d, ROW_NUMBER() over (ORDER BY n)-1, #StartDate) AS BaseDate
FROM e3
)
SELECT d.BaseDate As PeriodStart -- datetime value
, FORMAT(d.BaseDate, 'MMM dd')+ '-' + FORMAT(DATEADD(d, 6, d.BaseDate), 'MMM dd') as PeriodRange -- string value
, COUNT(t.*) As TicketCount
FROM Dates d
INNER JOIN [Table] t ON t.[CreatedDateTime] >= d.BaseDate
AND t.[CreatedDateTime] < DATEADD(d, 7, d.BaseDate)
AND t.OwnedBy='Service Desk'
WHERE d.BaseDate < CURRENT_TIMESTAMP
GROUP BY d.BaseDate;
This is likely to run many times faster, and will generally be easier to consume in client code and reporting tools.
One other notable change in here is instead of a BETWEEN expression for six days in the future, I used two separate conditions where the ending condition is an exclusive upper bound for seven days in the future, which is also generally the better way to handle date ranges.

How To Count Rows Based on Values of Two Variables in SSIS

I am fairly new to SSIS, and now I have this requirement to exclude weekends in order to do a performance management. Now I have created a calendar and marked the weekends; what I am trying to do, using SSIS, is get the start and end date of every status and count how many weekends are there. I am kind of struggling to know which component to use to achieve this task.
So I have mainly two tables:
1- Table Calendar
2- Table History-Log
Calendar has the following columns:
1- ID
2- date
3- year
4- month
5- day of week
6- isweekend
History-Log has the following:
1- ID
2- Status
3- startdate
4- enddate
Your help is really appreciated.
I'm not an SSIS user, so apologies if this answer does not help, but if I wanted to get the result you describe, based on some test data:
DECLARE #Calendar TABLE (
ID INT,
[Date] DATETIME,
[Year] INT,
[Month] INT,
[DayOfWeek] VARCHAR(10),
IsWeekend BIT
)
DECLARE #HistoryLog TABLE (
ID INT,
[Status] INT,
StartDate DATETIME,
EndDate DATETIME
)
DECLARE #StartDate DATE = '20100101', #NumberOfYears INT = 10
DECLARE #CutoffDate DATE = DATEADD(YEAR, #NumberOfYears, #StartDate);
INSERT INTO #Calendar
SELECT ROW_NUMBER() OVER (ORDER BY d) AS ID,
d AS [Date],
DATEPART(YEAR,d) AS [Year],
DATEPART(MONTH,d) AS [Month],
DATENAME(WEEKDAY,d) AS [DayOfWeek],
CASE WHEN DATENAME(WEEKDAY,d) IN ('Saturday','Sunday') THEN 1 ELSE 0 END AS IsWeekend
FROM
(
SELECT d = DATEADD(DAY, rn - 1, #StartDate)
FROM
(
SELECT TOP (DATEDIFF(DAY, #StartDate, #CutoffDate))
rn = ROW_NUMBER() OVER (ORDER BY s1.[object_id])
FROM sys.all_objects AS s1
CROSS JOIN sys.all_objects AS s2
ORDER BY s1.[object_id]
) AS x
) AS y;
INSERT INTO #HistoryLog
SELECT 1, 3, '2016-01-05', '2016-01-20'
UNION
SELECT 2, 7, '2016-01-08', '2016-01-25'
UNION
SELECT 3, 4, '2016-01-01', '2016-02-03'
UNION
SELECT 4, 3, '2016-02-09', '2016-02-10'
I would use a query like this to return all of the HistoryLog records with a count of the number of weekend days between their StartDate and EndDate:
SELECT h.ID,
h.[Status],
h.StartDate,
h.EndDate,
COUNT(c.ID) AS WeekendDays
FROM #HistoryLog h
LEFT JOIN #Calendar c ON c.[Date] >= h.StartDate AND c.[Date] <= h.EndDate AND c.IsWeekend = 1
GROUP BY h.ID, h.[Status], h.StartDate, h.EndDate
ORDER BY 1
If you wanted to know the number of weekends, rather than the number of weekend days, we'd need to slightly amend this logic (and define how a range containing only one weekend day - or one starting on a Sunday and ending on a Saturday inclusive - should be handled). Assuming you just want to know how many distinct weekends are at least partially within the date range, you could do:
SELECT h.ID,
h.[Status],
h.StartDate,
h.EndDate,
COUNT(weekends.ID) AS Weekends
FROM #HistoryLog h
LEFT JOIN
(
SELECT c.ID,
c.[Date] AS SatDate,
DATEADD(DAY,1,c.[Date]) AS SunDate
FROM #Calendar c
WHERE c.[DayOfWeek] = 'Saturday'
) weekends ON h.StartDate BETWEEN weekends.SatDate AND weekends.SunDate
OR h.EndDate BETWEEN weekends.SatDate AND weekends.SunDate
OR (h.StartDate <= weekends.SatDate AND h.EndDate >= weekends.SunDate)
GROUP BY h.ID, h.[Status], h.StartDate, h.EndDate

Max Date between 2 dates

How can I find the latest date in a column but constrain it between 2 dates
SELECT [Weight]
FROM [weighinevent] w
WHERE [Date] = (SELECT MAX([Date]) WHERE [Date] BETWEEN #StartDate AND #EndDate AND w.[userid] = #userid )
This is what I have. Is that correct?
No, it is not correct. Subqueries need to define the table too from which they are selecting. But you can order by the date and take only the first record
SELECT top 1 Weight
FROM weighinevent
WHERE Date BETWEEN #StartDate AND #EndDate
AND userid = #userid
ORDER BY Date DESC

Return 0 for NULL values in a simple count and group by date

I'm doing a simple count query for an SSRS chart, and while it looks like a common question, I've not really found an answer that suits my situation. This is my query:
SELECT TOP 30 CAST(qa.Created As Date) As 'Date',
COUNT(qa.Created) As 'Count'
FROM QAs qa
GROUP BY CAST(qa.Created As Date)
ORDER BY 'Date' DESC
This returns something like:
Date | Count
2014-11-10 | 2
2014-11-08 | 3
2014-11-07 | 8
Which when put into a line chart, doesn't show the dip down to 0 on the 9th and is a bit confusing for my users. What I want to do is have all of the last 30 days appear in order, even if they are at 0. I've been told to do this with COALESCE() but I can't seem to get that working either. Where am I going wrong?
Use a Recursive CTE to generate dates for last 30 days.
;WITH cte
AS (SELECT Cast(dateadd(day,-30,Getdate()) AS DATE) AS dates
UNION ALL
SELECT Dateadd(day, 1, dates)
FROM cte
WHERE dates < cast(Getdate() as date)
SELECT a.Dates AS [Date],
Count(qa.Created) AS [Count]
FROM cte a
LEFT JOIN QAs qa
ON a.dates = Cast(qa.Created AS DATE)
GROUP BY a.Dates
ORDER BY a.Dates
I'd probably go with NoDisplayName's recursive CTE but for an alternative, if you happen to be stuck in something prior to 2005 which I was for a long time.
--Build a table of dates
DECLARE #dates AS TABLE([date] date)
DECLARE #i int
SET #i = 30
WHILE #i > 0
BEGIN
INSERT INTO #dates([date])
SELECT DATEADD(d, -1 * #i, GETDATE())
SET #i = (#i - 1)
END
--Join into those dates so that no date is excluded
SELECT [date], SUM(dateCount)
FROM (
SELECT d.[date], CASE WHEN qa.Created IS NULL THEN 0 ELSE 1 END AS dateCount
FROM #dates d
LEFT JOIN QAs qa ON CAST(qa.Created AS date) = d.[date]
) AS dateCounts
GROUP BY [date]

Resources