SQL select, pad with chronological missing months - sql

Notice the 2015-02 and 2015-03 months are missing in the output from the following group by SQL. If there is no data for a month I want to show the month and 0. Anyone know how to go about this?
SELECT convert(char(7), MeterReadDate, 121),count(*)
FROM [myTable]
where (MeterReadDate > dateadd(d,-356,getdate()))
group by convert(char(7), MeterReadDate, 121)
order by convert(char(7), MeterReadDate, 121)
Sample data:
YYYY-MM COUNT
2014-06 23
2014-07 42
2014-08 80
2014-09 92
2014-10 232
2014-11 88
2014-12 8
2015-01 5
2015-04 2
2015-05 1
Still cannot clear the missing rows, here is where I am with it..
DECLARE #StartDate DATETIME = dateadd(m,-12,getdate()), #EndDate DATETIME = getdate(), #DATE DATETIME
DECLARE #TEMP AS TABLE (MeterReadDate datetime)
SET #DATE = #StartDate
WHILE #DATE <= #EndDate
BEGIN
INSERT INTO #TEMP VALUES ( #DATE)
SET #DATE = DATEADD(MONTH,1,#DATE)
END
SELECT convert(char(7), t.MeterReadDate, 121),count(*)
FROM #TEMP m left join
[myTable] t
on convert(char(7), t.MeterReadDate, 121) = convert(char(7), m.MeterReadDate, 121)
where (t.MeterReadDate > dateadd(m,-12,getdate()))
group by convert(char(7), t.MeterReadDate, 121)
order by convert(char(7), t.MeterReadDate, 121)

If you don't want to go beyond your min and max dates of your results then you can do the following:
WITH cte
AS ( SELECT convert(char(7), MeterReadDate, 121) AS [Date], COUNT(*) AS [Count]
FROM [myTable]
WHERE (MeterReadDate > dateadd(d,-356,getdate()))
GROUP by convert(char(7), MeterReadDate, 121)
),
minmax
AS ( SELECT CAST(MIN([Date] + '-01') AS DATE) AS mind ,
CAST(MAX([Date] + '-01') AS DATE) maxd
FROM cte
),
calendar
AS ( SELECT mind ,
CONVERT(CHAR(7), mind, 121) AS cmind
FROM minmax
UNION ALL
SELECT DATEADD(mm, 1, calendar.mind) ,
CONVERT(CHAR(7), DATEADD(mm, 1, calendar.mind), 121)
FROM calendar
CROSS JOIN minmax
WHERE calendar.mind < minmax.maxd
)
SELECT c.cmind AS [Date],
ISNULL(cte.[Count], 0) AS [Count]
FROM calendar c
LEFT JOIN cte ON c.cmind = cte.[Date]
OPTION ( MAXRECURSION 0 )

You need a list of dates/months that covers the entire period. Here is one method using a recursive CTE:
with months as (
select cast(getdate() - 365) as thedate
union all
select date_add(1, month, thedate)
from months
where thedate <= getdate()
)
select convert(char(7), m.thedate, 121) as yyyy-mm, count(t.MeterReadDate)
from months m left join
[myTable] t
on convert(char(7), MeterReadDate, 121) = convert(char(7), m.thedate, 121)
group by convert(char(7), m.thedate, 121)
order by convert(char(7), m.thedate, 121);

The best way to do this would be to join onto a table containing calendar information, one way to do this without actually altering the database schema (and is more dynamic in my opinion) would be to use a table variable and the DATEADD() function.
DECLARE #StartDate DATETIME = '2015-01-01', #EndDate DATETIME = '2015-12-01', #DATE DATETIME
DECLARE #TEMP AS TABLE ([DATE] DATETIME)
SET #DATE = #StartDate
WHILE #DATE <= #EndDate
BEGIN
INSERT INTO #TEMP VALUES (#DATE)
SET #DATE = DATEADD(MONTH,1,#DATE)
END
SELECT * FROM #TEMP
Set #Start and #End to your required dates, then just join onto your results set!
UPDATE:
To extract the date in the format you gave above under "sample data", meaning you would be able to join onto the table, use:
SELECT
CONCAT(YEAR([DATE]),'-',right('0' + cast(month([DATE]) as varchar),2))
FROM #TEMP
LEFT JOIN MyTable ON...

Related

my end goal is to see end of month data for previous month

My end goal is to see end of month data for previous month.
Our processing is a day behind so if today is 7/28/2021 our Process date is 7/27/2021
So, I want my data to be grouped.
DECLARE
#ProcessDate INT
SET #ProcessDate = (SELECT [PrevMonthEnddatekey] FROM dbo.dimdate WHERE datekey = (SELECT [datekey] FROM sometable [vwProcessDate]))
SELECT
ProcessDate
, LoanOrigRiskGrade
,SUM(LoanOriginalBalance) AS LoanOrigBalance
,Count(LoanID) as CountofLoanID
FROM SomeTable
WHERE
ProcessDate in (20210131, 20210228,20210331, 20210430, 20210531, 20210630)
I do not want to hard code these dates into my WHERE statement. I have attached a sample of my results.
I am GROUPING BY ProcessDate, LoanOrigRiskGrade
Then ORDERING BY ProcessDate, LoanOrigIRskGrade
It looks like you want the last day of the month for months within a specified range. You can parameterize that.
For SQL Server:
DECLARE #ProcessDate INT
SET #ProcessDate = (
SELECT [PrevMonthEnddatekey]
FROM dbo.dimdate
WHERE datekey = (
SELECT [datekey]
FROM sometable [vwProcessDate]
)
)
DECLARE #startDate DATE
DECLARE #endDate DATE
SET #startDate = '2021-01-01'
SET #endDate = '2021-06-30'
;
with d (dt, eom) as (
select #startDate
, convert(int, replace(convert(varchar(10), eomonth(#startDate), 102), '.', ''))
union all
select dateadd(month, 1, dt)
, eomonth(dateadd(month, 1, dt))
from d
where dateadd(month, 1, dt) < #endDate
)
SELECT ProcessDate
, LoanOrigRiskGrade
, SUM(LoanOriginalBalance) AS LoanOrigBalance
, Count(LoanID) as CountofLoanID
FROM SomeTable
inner join d on d.eom = SomeTable.ProcessDate
Difficult to check without sample data.

SQL Count with zero values

I want to create a graph for my dataset for the last 24 hours.
I found a solution that works but this is pretty bad since the table I am outer joining cotains every single row in the DB since I am using the (now deprecated) "all" parameter in the group by.
Here is the solution that currently kind of works.
First I declare the date intervals that is 24 hours back in time from now. I declare it twice so I can use it later in the procedure aswell.
Declare #StartDate datetime = dateadd(hour, -24, getdate())
Declare #StartDateProc datetime = dateadd(hour, -24, getdate())
Declare #EndDate datetime = getdate()
I populate the dates into a temp table including a special formated datetsring.
create table #tempTable
(
Date datetime,
DateString varchar(11)
)
while #StartDate <= #EndDate
begin
insert into #tempTable (Date, DateString)
values (#StartDate, convert(varchar(8), #StartDate, 5) + '-' + convert(varchar(2), #StartDate, 108));
SET #StartDate = dateadd(hour,1, #StartDate);
end
This gives me data that looks like this:
Date DateString
---------------------------------------------
2015-12-09 13:59:01.970 09-12-15-13
2015-12-09 14:59:01.970 09-12-15-14
2015-12-09 15:59:01.970 09-12-15-15
2015-12-09 16:59:01.970 09-12-15-16
So what I want is to join my dataset on the matching date string and show the date even if the matching rows is zero.
Here is the rest of the query
select
Date = c.Date,
Amount = sum(c.Amount)
from
DbTable a
outer apply
(select
Date = b.DateString,
Amount = count(*)
from
#tempTable b
where
convert(varchar(8), a.DateColumn, 5) + '-' + convert(varchar(2), a.DateColumn, 108) = b.DateString
group by all
b.DateString) c
where
a.SomeParameter = 'test' and
a.DateColumn >= #StartDateProc and
a.DateColumn <= #EndDate
group by
c.Date
drop table #tempTable
Test to show actual data:
Declare #StartDate datetime = dateadd(hour, -24, getdate())
Declare #EndDate datetime = getdate()
select
dateString = convert(varchar(8),a.DateColumn,5) + '-' + convert(varchar(2),a.DateColumn, 108),
Amount = COUNT(*)
from
DbTable a
where
a.someParameter = 'test' and
a.DateColumn>= dateadd(hour, -24, getdate()) and
a.DateColumn<= getdate()
group by
convert(varchar(8),a.DateColumn,5) + '-' + convert(varchar(2),a.DateColumn, 108)
First output rows:
dateString Amount
09-12-15-14 1
09-12-15-15 1
09-12-15-16 1
09-12-15-17 3
09-12-15-18 1
09-12-15-22 3
09-12-15-23 2
As you can see here there is no data for the times from 19.00 to 21.00. This is how I want the data to be displayed:
dateString Amount
09-12-15-14 1
09-12-15-15 1
09-12-15-16 1
09-12-15-17 3
09-12-15-18 1
09-12-15-19 0
09-12-15-20 0
09-12-15-21 0
09-12-15-22 3
09-12-15-23 2
Normally, this would be approached with left join rather than outer apply. The logic is simple: keep all rows in the first table along with any matching information from the second. This means put the dates table first:
select tt.DateString, count(t.DateColumn) as Amount
from #tempTable tt left join
DbTable t
on convert(varchar(8), t.DateColumn, 5) + '-' + convert(varchar(2), t.DateColumn, 108) = tt.DateString and
t.SomeParameter = 'test'
where tt.Date >= #StartDateProc and
tt.Date <= #EndDate
group by tt.DateString;
In addition, your comparison for the dates seems overly complex, but if it works for you, it works.
The best bet here would be to use DATETIME type itself and not to lose the opportunity to use indexes:
Declare #d datetime = GETDATE()
;WITH cte1 AS(SELECT TOP 25 -1 + ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) h
FROM master..spt_values),
cte2 AS(SELECT DATEADD(hh, -h, #d) AS startdate,
DATEADD(hh, -h + 1, #d) AS enddate
FROM cte1)
SELECT c.startdate, c.enddate, count(*) as amount
FROM cte2 c
LEFT JOIN DbTable a ON a.DateColumn >= c.startdate AND
a.DateColumn < c.enddate AND
a.SomeParameter = 'test'
GROUP BY c.startdate, c.enddate

Create missing months dynamically from group on date field [duplicate]

This question already has answers here:
SQL select, pad with chronological missing months
(3 answers)
Closed 7 years ago.
In the following SQL i'm trying to insert rows to fill in the missing months in the results. The solution is very close thanks to post SQL select, pad with chronological missing months
But yet this code runs gr8 but still have missing months, issue is how to join/union the temp table
DECLARE #StartDate DATETIME = dateadd(m,-12,getdate()), #EndDate DATETIME = getdate(), #DATE DATETIME
DECLARE #TEMP AS TABLE (MeterReadDate datetime)
SET #DATE = #StartDate
WHILE #DATE <= #EndDate
BEGIN
INSERT INTO #TEMP VALUES ( #DATE)
SET #DATE = DATEADD(MONTH,1,#DATE)
END
SELECT convert(char(7), t.MeterReadDate, 121),count(*)
FROM #TEMP m left join
[PremiseMeterReadProviders] t
on convert(char(7), t.MeterReadDate, 121) = convert(char(7), m.MeterReadDate, 121)
where (t.MeterReadDate > dateadd(m,-12,getdate()))
group by convert(char(7), t.MeterReadDate, 121)
order by convert(char(7), t.MeterReadDate, 121)
Try something like this....
;WITH Months AS
(
SELECT TOP 12
CONVERT(CHAR(7),
DATEADD(MONTH
, - ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
, GETDATE()
)
,121) MonthNo
FROM master..spt_values
)
SELECT convert(char(7), t.MeterReadDate, 121),count(*)
FROM Months m
LEFT JOIN [PremiseMeterReadProviders] t
ON convert(char(7), t.MeterReadDate, 121) = m.MonthNo
AND (t.MeterReadDate > dateadd(m,-12,getdate()))
group by convert(char(7), t.MeterReadDate, 121)
order by convert(char(7), t.MeterReadDate, 121)
I think you need to reconstruct the query as below
DECLARE #StartDate DATETIME = dateadd(m,-12,getdate()), #EndDate DATETIME = getdate(), #DATE DATETIME
DECLARE #TEMP AS TABLE (MeterReadDate datetime)
SET #DATE = #StartDate
WHILE #DATE <= #EndDate
BEGIN
INSERT INTO #TEMP VALUES ( #DATE)
SET #DATE = DATEADD(MONTH,1,#DATE)
END
SELECT convert(char(7), m.MeterReadDate, 121),count(*)
FROM #TEMP m left join
[Announcement] t
on convert(char(7), t.ExpiryDate, 121) = convert(char(7), m.MeterReadDate, 121)
WHERE (t.ExpiryDate IS NULL OR t.ExpiryDate > dateadd(m,-12,getdate()))
group by convert(char(7), m.MeterReadDate, 121)
order by convert(char(7), m.MeterReadDate, 121)
In the where condition, I added t.ExpiryDate IS NULL, because this will be null for the missing month.

Complicated query involving res

I have the table as below and I need to have the query how many tasks created for the day and how many are resolved and I need to capture the Tasks which are passed from previous day with active status. Ex: TaskId 101 is created on 11/10/2014 and it is resolved on 11/12/2014 so it should show in the count of 11/11/2014 and 11/12/2014 also.
TaskId CreateDate Status ResolvedDate
101 11/10/2014 Resolved 11/12/2014
102 11/10/2014 Resolved 11/10/2014
103 11/11/2014 Active NULL
104 11/11/2014 Resolved 11/13/2014
105 11/13/2014 Active 11/13/2014
Please help me as I am not able to think of any solution. Sorry I was trying to post the table schema in table format but not able to create table and I am new to this forum.
#radar, You just gave me the hope with the query and I have changed the query as below and it started showing result. But the TotalActive showing less value than TotalCreated.
declare #start_date datetime = getdate()-30
declare #end_date datetime = getdate()
;WITH CTE AS
(
SELECT #start_date AS date
UNION ALL
SELECT DATEADD(day, 1, date) as date
FROM CTE
WHERE DATEADD(day, 1, date) <= #end_date
)
select cte.date,
sum( case when CONVERT(varchar, CreateDate, 101) <= CONVERT(varchar, cte.date, 101) and CONVERT(varchar, cte.date, 101) <= CONVERT(varchar, ResolveDate, 101) then 1 else 0 end
) as TotalActive,
sum( case when CONVERT(varchar, cte.date, 101) = CONVERT(varchar, CreateDate, 101) then 1 else 0 end
) as TotalCreated,
sum( case when CONVERT(varchar, cte.date, 101) = CONVERT(varchar, ResolveDate, 101) then 1 else 0 end
) as TotalResolved
from cte
left join [WarehouseIncidents] T
ON CONVERT(varchar, CreateDate, 101) >= CONVERT(varchar, cte.date, 101)
GROUP BY CTE.DATE
you can get the result using case based aggregation
The CTE generates list of dates for a given range.
For each day, total active ( created that day and created previously but not resolved) and total resolved that day are calculated using case, max and group by
declare #start_date datetime = '2014-11-10'
declare #end_date datetime ='2014-11-13'
;WITH CTE AS
(
SELECT #start_date AS date
UNION ALL
SELECT DATEADD(day, 1, date) as date
FROM CTE
WHERE DATEADD(day, 1, date) <= #end_date
)
select cte.date,
sum( case when createDate <= cte.date and ( resolveddate is null or ResolvedDate >= cte.date) then 1 else 0 end
) as TotalActive,
sum( case when cte.date = ResolvedDate then 1 else 0 end
) as TotalResolved
from cte
left join Table1 T
ON T.createDate <= cte.date
GROUP BY CTE.DATE

Converting 1 record with a start and end date into multiple records for each day

I'd like to know an efficient way of taking event records with a start date and end date and basically replicating that record for each day between the start and end date
So a record with a Start Date as 2014-01-01 and End Date as 2014-01-03 would become 3 records, one for each day
I have a date table if that helps. I'm using SQL Server 2012
Thanks
As you already have date table, You can JOIN your table with date table to get all dates to have same record as your start and end date
SELECT A.data,
DT.startDate,
DT.endDate
FROM
DateTable DT
JOIN A
ON A.StartDate >= DT.startDate
And A.EndDate <= DT.endDate
use this query
declare #startDate datetime = getdate()
declare #endDate datetime = dateadd(day,10,getdate())
;with days as
(
select
#startDate as StartDate,
#endDate as EndDate,
#startDate as CurrentDate,
0 as i
union all
select
d.StartDate,
d.EndDate,
dateadd(day,d.i + 1,#startDate) as CurrentDate,
d.i + 1 as i
from days d
where dateadd(day,d.i + 1,#startDate) < d.EndDate
)
select
*
from days d
Try this
DECLARE #dt1 Datetime='2014-01-01'
DECLARE #dt2 Datetime='2014-01-03'
;WITH ctedaterange
AS (SELECT [Dates]=#dt1
UNION ALL
SELECT [dates] + 1
FROM ctedaterange
WHERE [dates] + 1<= #dt2)
SELECT [dates]
FROM ctedaterange
OPTION (maxrecursion 0)
SELECT COl1,COL2, split.a.value('.', 'VARCHAR(100)') data
FROM (SELECT COl1,COL2, Cast ('<M>'
+ Replace(LEFT(Replicate(NAME+',', datediff(dd,startdate,endate), Len(Replicate(NAME+',', datediff(dd,startdate,endate)))-1), ',''</M><M>')
+ '</M>' AS XML) AS Data
FROM tablename) AS A
CROSS apply data.nodes ('/M') AS Split(a)
Query
DECLARE #StartDate DATE = '2014-01-01',
#EndDate DATE = '2014-01-03';
SELECT TOP (DATEDIFF(DAY, #StartDate, #EndDate) + 1)
DATEADD(DAY, ROW_NUMBER() OVER(ORDER BY a.number) - 1, #StartDate) AS Date_Range
FROM master..spt_values a
CROSS APPLY master..spt_values b;
Result
Date_Range
2014-01-01
2014-01-02
2014-01-03