Show LastDateOfMonth even where there is no value [duplicate] - sql-server

This question already has answers here:
Left Join With Where Clause
(7 answers)
Closed 4 years ago.
Here is what I´m doing:
WITH cte AS (
SELECT * FROM TimeDim
)
SELECT t.TimeDimPK, c.ID
FROM CTE AS t
LEFT OUTER JOIN TABLE c ON c.TimeDimFK = t.TimeDimPK
ORDER BY t.TimeDimPK
WHERE c.ID = 1
Result, which is missing dates as shown below:
TimeDimPK ID
20120930 1
20121231 1
20130131 1
What I´m trying to get
TimeDimPK ID
20120930 1
20121031 NULL
20121130 NULL
20121231 1
20130131 1

It looks like your WHERE clause is getting rid of the other dates. Try this instead:
WITH cte
AS ( SELECT TimedimPK
FROM TimeDim
)
SELECT t.TimeDimPK ,
c.ID
FROM cte t
LEFT OUTER JOIN TABLEname c ON c.TimeDimFK = t.TimeDimPK
WHERE c.ID = 1
OR c.ID IS NULL
ORDER BY t.TimeDimPK

You can try to use a "number/date generator", this queries will fill in all missing days, then select the last day of the month. I am not sure about all your data details, so I am currently giving you two suggestions:
1 - Compact suggestion:
DECLARE #minDate DATETIME, #maxDate DATETIME;
SELECT #minDate = MIN(TimeDimPK), #maxDate = MAX(TimeDimPK) FROM TimeDim;
WITH DateGenerator AS
(
--Create a list with a lot of dates from #minDate
SELECT TimeDimPK = CONVERT(DATE, DATEADD(dd, ROW_NUMBER() OVER (ORDER BY OBJECT_ID), #minDate)) FROM sys.objects
)
--List all days, including missing days
SELECT TimeDimPK
, c.ID
FROM DateGenerator n
LEFT JOIN AnotherTable c ON c.TimeDimFK = n.TimeDimPK
WHERE
--Stop the number generator at the last day of the last month from the cte table
n.TimeDimPK <= CONVERT(DATE, DATEADD(s,-1,DATEADD(mm, DATEDIFF(m,0, #maxDate )+1,0)))
--This will get the last day of every month
AND TimeDimPK = CONVERT(DATE, DATEADD(s,-1,DATEADD(mm, DATEDIFF(m,0,TimeDimPK)+1,0)))
2 - Suggestion with your cte clause, if you want to use it for some tweaking:
WITH NumberGenerator AS
(
--Create a number list 1,2,3,4,5,6,7,8,++
SELECT Number = ROW_NUMBER() OVER (ORDER BY OBJECT_ID) FROM sys.objects
), cte AS (
--Your cte query with a date number based on the days between the firts and current days
SELECT
TimeDimPK = CONVERT(DATETIME, TimeDimPK)
--Get the number of days to add from the first day in your table
, DateNumber = DATEDIFF(dd, MIN(TimeDimPK) OVER (), TimeDimPK)
FROM #TimeDim
), TableWithMissingDates AS
(
--Fill missing days
SELECT TimeDimPK = CONVERT(DATE, DATEADD(dd, n.Number - 1, MIN(t.TimeDimPK) OVER ()))
, c.ID
FROM NumberGenerator n
LEFT JOIN cte t ON t.DateNumber = n.Number
LEFT JOIN #Test2 c ON c.TimeDimFK = t.TimeDimPK
--Stop the number generator at the last day of the last month from the cte table
WHERE CONVERT(DATE, DATEADD(dd, n.Number - 1, (SELECT MIN(TimeDimPK) FROM cte))) < CONVERT(DATE, DATEADD(s,-1,DATEADD(mm, DATEDIFF(m,0, (SELECT MAX(TimeDimPK) FROM cte) )+1,0)))
)
SELECT * FROM TableWithMissingDates
WHERE
--This will get the last day of every month
TimeDimPK = CONVERT(DATE, DATEADD(s,-1,DATEADD(mm, DATEDIFF(m,0,TimeDimPK)+1,0)))
Both queries will return the same table:

Related

Adding the total number of distinct customers to a cte table in Microsoft SQL Server

I am using Microsoft SQL Server and am trying to achieve the following
Date
Distinct Customers last 30Days
2020-12-01
20000
2020-12-02
23000
What I am trying to get is that between 2020-11-01 and 2020-12-01 I had 20000 distinct customers.
I have created a cte table with the List of Dates as can be seen below:
WITH listdate AS
(
SELECT CAST('2020-11-01' AS datetime) DateValue
UNION ALL
SELECT DateValue + 1
FROM listdate
WHERE DateValue + 1 < getdate()
)
SELECT
cast(DateValue as date) as DateValue
FROM listdate d
Now I am trying to join the customer and usage table with the list of dates table, however, I am not getting the correct end result. The following is what I have tried doing:
WITH listdate AS
(
SELECT CAST('2020-11-01' AS datetime) DateValue
UNION ALL
SELECT DateValue + 1
FROM listdate
WHERE DateValue + 1 < getdate()
)
SELECT
cast(DateValue as date) as DateValue
,count(distinct case when m.CallDate between dateadd(dd,-30,cast(d.datevalue as date)) and cast(d.datevalue as date) then m.Customerid end) as Distinct_CID
FROM listdate d
join Usage m on d.DateValue=m.CallDate
left join Customer c on c.CustomerID=m.Customer
where c.customertype = 'type A'
group by d.DateValue
OPTION (MAXRECURSION 0)
Can someone maybe suggest a different way of how to solve such a query?
Thanks
I would go for a lateral join to bring the count of distinct customers for the last 30 days:
with listdate as (
select cast('20201101' as date) as datevalue
union all
select dateadd(day, 1, datevalue) from listdate where datevalue < cast(getdate() as date)
)
select ld.datevalue, x.cnt
from listdate ld
cross apply (
select count(distinct c.customerid) as cnt
from usage u
inner join customer c on c.customerid = u.customerid
where
c.customertype = 'type A'
and c.calldate >= dateadd(day, -29, datevalue)
and c.calldate < dateadd(day, 1, datevalue)
) x
option (maxrecursion 0)
Note that I simplified the parts related to dates: this uses proper literal dates and date arithmetics in the recursive query; the where clause of the subquery implements what I understand as the last 30 days (today + the preceding 29 days), and properly handles the time portion of calldate, if any.

Get a list of end of months dates between two months in SQL Server

This is similar to this mySQL question except I only want the end of month dates: Get a list of dates between two dates
I want to be able to enter two dates, something like:
SELECT EndOfMonth('1/1/2015', '1/1/2017');
And the returned results should look like
EndOfMonth
1/31/2015
2/28/2015
3/31/2015
.
.
.
10/31/2016
11/30/2016
12/31/2016
The SQL Server version I'm using is 2008, so I actually don't have access to EOMONTH().
Using a numbers table makes things easier. If you don't already have a numbers table you can use the following sql to create one (taken from this SO post):
SELECT TOP 10000 IDENTITY(int,0,1) AS Number
INTO Tally
FROM sys.objects s1
CROSS JOIN sys.objects s2
ALTER TABLE Tally ADD CONSTRAINT PK_Tally PRIMARY KEY CLUSTERED (Number)
To learn more about the numbers table and how to use it, read Jeff Moden's The "Numbers" or "Tally" Table: What it is and how it replaces a loop article.
Once you have a numbers table, it's fairly easy with versions 2012 or higher, using the EOMONTH built in function:
DECLARE #Start date = '2015-01-01', #End date = '2017-01-01'
SELECT EOMONTH(DATEADD(MONTH, Number, #Start))
FROM Tally
WHERE Number < DATEDIFF(MONTH, #Start, #End)
For earlier versions, you can use DATEADD with DATEDIFF to get the last day of the previous month, and then simply add one month:
SELECT DATEADD(DAY, -DATEPART(DAY, #Start), (DATEADD(MONTH, Number+1, #Start)))
FROM Tally
WHERE Number < DATEDIFF(MONTH, #Start, #End)
See a live demo on rextester
You can achieve this easily using Common Table expressions(CTE).
--Declaration of start date and end date
declare #StartDate datetime='2017-01-01', #endDate datetime='2017-12-01'
-- Expression
;WITH monthcte
AS (SELECT EOMONTH(#StartDate) AS dates
UNION ALL
SELECT DATEADD(month, 1, dates) AS dates
FROM monthcte
WHERE dates <#endDate
)
-- Select query over expression
SELECT EOMONTH(dates) AS EndOfMonth
FROM monthcte
Order by EndOfMonth
If you are not able to use EOMonth you can generate like this below:
declare #d1 date = '1/1/2015'
declare #d2 date = '1/1/2017'
select top (datediff(MONTH,#d1,#d2)+1) Dates = DateAdd(day,-1, Dateadd(M,Row_number() over(order by (select null)), #d1)) from
master..spt_values n1, master..spt_values n2
If EoMonth is available in your SQL Server Version then you can try as below:
select top (datediff(MONTH,#d1,#d2)+1) Dates = EoMonth(Dateadd(M,Row_number() over(order by (select null))-1, #d1)) from
master..spt_values n1, master..spt_values n2
List of Month:
DECLARE #StartDate datetime
DECLARE #EndDate datetime
set #StartDate = '01/01/2015'
set #EndDate = '01/01/2017'
;WITH cte1 (S) AS (
SELECT 1 FROM (VALUES (1), (1), (1), (1), (1), (1), (1), (1), (1), (1)) n (S)
),
cte2 (S) AS (SELECT 1 FROM cte1 AS cte1 CROSS JOIN cte1 AS cte2),
cte3 (S) AS (SELECT 1 FROM cte1 AS cte1 CROSS JOIN cte2 AS cte2)
select distinct cast(result as date) result from
(SELECT TOP (DATEDIFF(day, #StartDate, #EndDate) + 1)
result = DATEADD(day, ROW_NUMBER() OVER(ORDER BY S) - 1, #StartDate)
FROM cte3) as res

How to get a list of months and year between two dates in SQL Server

I have to get the list of months and year in between my dates. Currently it only returns month and year for dates that has data associated with it.
for example my dates is between: '8'+'/1'+'/'+'2015' and DATEADD(mm, 15, '8'+'/1'+'/'+'2016'
It only prints out: May2016, June2016, July2016, Auguest2016, September2016
I want it to print out all of the months and year in between. Here is my sql queries:
select d.id_base as case_id,
c.C_LAST_ACTION AS Docketed,
c.C_CASE_TYPE AS caseType,
ct.C_NAME As caseName,
ct.C_DESCRIPTION AS caseNameDescription,
case when d.c_mod_decision_id is not null then '' else DATENAME(mm, d.c_issue_date) + DATENAME(yyyy, d.c_issue_date) end as display
from t_case_decision d JOIN T_CASE_INPUT c on c.id = d.id_base JOIN T_CASE_TYPE ct on C_CASE_TYPE = ct.id
where cast(d.c_issue_date AS date) BETWEEN '8'+'/1'+'/'+'2015' and DATEADD(mm, 15, '8'+'/1'+'/'+'2016')
First, create a numbers table
CREATE TABLE Numbers(N INT)
insert into Numbers(N)
select top 1000000 row_number() over(order by t1.number) as N
from master..spt_values t1
cross join master..spt_values t2
then use DATEADD to list dates between desired values, like this
declare #iniDate as date
set #iniDate='20150801'
select dateadd(MONTH,N,#iniDate) dates
from Numbers
where N<15 order by N
These returns dates from #iniDate up to 15 months later
EDIT: try this, I don't have sql right now
select datename(mm, dateadd(MONTH,N,#iniDate))+datename(yyyy ,dateadd(MONTH,N,#iniDate)) display
from ( select top 15row_number() over(order by t1.number) as N
from master..spt_values t1
cross join master..spt_values t2) numbers right join (
select d.id_base as case_id,
c.C_LAST_ACTION AS Docketed,
c.C_CASE_TYPE AS caseType,
ct.C_NAME As caseName,
ct.C_DESCRIPTION AS caseNameDescription,
case when d.c_mod_decision_id is not null then '' else DATENAME(mm, d.c_issue_date) + DATENAME(yyyy, d.c_issue_date) end as display
from t_case_decision d JOIN T_CASE_INPUT c on c.id = d.id_base JOIN T_CASE_TYPE ct on C_CASE_TYPE = ct.id
where cast(d.c_issue_date AS date) BETWEEN '8'+'/1'+'/'+'2015' and DATEADD(mm, 15, '8'+'/1'+'/'+'2016')
sql-server
) qq
on datename(mm, dateadd(MONTH,N,#iniDate))+datename(yyyy ,dateadd(MONTH,N,#iniDate)) = qq.display
where N<15 order by N
If I understand what you're trying to accomplish, a recursive CTE might help. Here's a quick example of what you can do. The CTE will expand out into a list of dates, which you can then use as the base for your query.
The contents of the TargetData CTE may need to be adjusted, as I don't have a complete picture of your data structure.
DECLARE #startDate DATE = '1/1/2015';
DECLARE #endDate DATE = '7/31/2016';
-- Recursive CTE to generate a list of months within the date range:
WITH Months AS (
SELECT CONVERT(DATE, DATEADD(D, -(DAY(#startDate)) + 1, #startDate)) [MonthDate]
UNION ALL
SELECT DATEADD(M, 1, MonthDate)
FROM Months
WHERE MonthDate <= DATEADD(M, -1, #endDate)
),
TargetData AS (
-- This is a slightly modified version of the original query:
select
d.id_base as case_id,
c.C_LAST_ACTION AS Docketed,
c.C_CASE_TYPE AS caseType,
ct.C_NAME As caseName,
ct.C_DESCRIPTION AS caseNameDescription,
case when d.c_mod_decision_id is not null then '' else DATENAME(mm, d.c_issue_date) + DATENAME(yyyy, d.c_issue_date) end as display,
-- Return the "MonthDate" so that it can be left joined to the Months table:
DATEADD(D, -(DAY(d.c_issue_date)) + 1, d.c_issue_date) [MonthDate]
from t_case_decision d JOIN T_CASE_INPUT c on c.id = d.id_base JOIN T_CASE_TYPE ct on C_CASE_TYPE = ct.id
where cast(d.c_issue_date AS date) BETWEEN #startDate AND #endDate
)
SELECT
m.MonthDate,
DATENAME(mm, m.MonthDate) + DATENAME(yyyy, m.MonthDate),
td.*
FROM Months m
LEFT JOIN TargetData td ON td.MonthDate = m.MonthDate;
You need to join on primary keys between tables, I haven't seen a between statement with that syntax. So I suggest trying the following:
SELECT d.id_base as case_id, c.C_LAST_ACTION AS 'Docketed',c.C_CASE_TYPE AScaseType,ct.C_NAME As 'caseName', ct.C_DESCRIPTION AS 'caseNameDescription'
,CASE
WHEN d.c_mod_decision_id is not null THEN '' AS 'null_val'
ELSE CONCAT(YEAR(d.c_issue_dateDATENAME), MONTH(d.c_issue_date))
END AS 'display'
FROM t_case_decision d INNER JOIN T_CASE_INPUT c on c.id = d.id_base
INNER JOIN T_CASE_TYPE ct on c.id = ct.id
WHERE CONVERT(DATE,d.c_issue_date) BETWEEN '08/01/2015'
AND '08/01/2016';
I hope this helps or points you in the right direction :)

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]

How to return values from SQL server database within intervals

Hi I have an SQL server database with 3 columns Activity[start_date(datetime),end_date(datetime),title(string)]
and I wish to count for a whole year, how many activities have start_date in each month, I mean I would like a return of 12 values(12 months) that count the activities within the months, thanks.
If there is an index on start_date and/or if you need to include months in the result even if there was no activity in that month, you might consider this one:
DECLARE #year INT;
SET #year = 2012;
;WITH n AS
(
SELECT TOP (12) m = DATEADD(MONTH, ROW_NUMBER() OVER
(ORDER BY name)-1, DATEADD(YEAR, #year-1900, 0))
FROM sys.all_objects ORDER BY name
)
SELECT [Month] = n.m, ActivityCount = COUNT(t.title)
FROM n
LEFT OUTER JOIN dbo.unspecified_table_name AS t
ON t.start_date >= n.m
AND t.start_date < DATEADD(MONTH, 1, n.m)
GROUP BY n.m
ORDER BY [Month];
(If you don't want a row when there were zero activities in a given month, then change LEFT OUTER to INNER.)
select month(start_date) as Month, count(*) as Count
from Activity
where year(start_date) = 2011
group by month(start_date)
order by month(start_date)

Resources