Count data for last 10 days including days without any entries - sql

I want to count all data for each of last 10 days where some column have specific value. I manage to do this:
declare #start datetime = CAST(getdate() as date)
declare #end datetime = dateadd(day, -10, #start)
;with amonth(day) as
(
select #end as day
union all
select day + 1
from amonth where day < CAST(#start as date)
)
select CAST(amonth.day as date) as dat, count(vrijeme) as c
from amonth
left join Dnevnik on CAST(vrijeme as date) = CAST(amonth.day as date)
group by CAST(amonth.day as date) order by dat
It gives me a result like this:
dat c
2017-01-21 0 <--- day without any record
2017-01-22 1
2017-01-23 1
2017-01-24 1
2017-01-25 1
2017-01-26 0 <--- day without any record
2017-01-27 27
2017-01-28 125
2017-01-29 190
2017-01-30 127
2017-01-31 319
But I don't want to count all data for each day, I want to count data where one column has specific value. So I thought it might be something like this (I added where clause):
declare #start datetime = CAST(getdate() as date)
declare #end datetime = dateadd(day, -10, #start)
;with amonth(day) as
(
select #end as day
union all
select day + 1
from amonth
where day < CAST(#start as date)
)
select CAST(amonth.day as date) as dat, count(vrijeme) as c
from amonth
left join Dnevnik on CAST(vrijeme as date) = CAST(amonth.day as date)
where tipZapisa = 6
group by CAST(amonth.day as date) order by dat
but this code gives me results without those empty days (where count is 0)
dat c
2017-01-22 1
2017-01-27 9
2017-01-28 67
2017-01-29 33
2017-01-30 46
2017-01-31 37
In the last result, I just want to include days which doesn't have any records.
Thanks in advance

Keep your join LEFT , stop using LEFT columns in NULL -prohibiting predicates in WHERE. Include it in ON
with amonth(day) as
(
select #end as day
union all
select day + 1
from amonth
where day < CAST(#start as date)
)
select CAST(amonth.day as date) as dat, count(vrijeme) as c
from amonth
left join Dnevnik on CAST(vrijeme as date) = CAST(amonth.day as date)
and tipZapisa = 6
group by CAST(amonth.day as date) order by dat

pre populate #START with the date of the 10th matching record, then feed it into your second query
SELECT #START = MIN(DQ.dat) FROM (select DISTINCT TOP 10 CAST(amonth.day as date) as dat
from Dnevnik WHERE tipZapisa = 6 ORDER BY amonth.day as date DESC) DQ
then you should be able to return the actual 10 days with the matching condition

Related

Finding all dates after a date for a variable number of days

I have a list of dates in a table. For this examples the 1st day of each month. Let's call it table timeperiod with column endTime
endTime
1-1-2019
2-1-2019
3-1-2019
4-1-2019
I want to find all dates x number of days after each date in a list. Lets say x = 4. Then the list should be:
1-1-2019
1-2-2019
1-3-2019
1-4-2019
2-1-2019
2-2-2019
2-3-2019
2-4-2019
3-1-2019
3-2-2019
3-3-2019
3-4-2019
4-1-2019
4-2-2019
4-3-2019
4-4-2019
I have found solutions to find all dates between dates but I keep getting "Subquery returned more than 1 value" error when I try to use it with a list of dates.
Here is an example of something I tried but doesn't work
declare #days DECIMAL = 4
declare #StartDate date = (select convert(varchar, DATEADD(Day, +0, endTime),101) from timeperiod
declare #EndDate date = (select convert(varchar, DATEADD(Day, +#days, endTime),101) from timeperiod;
;WITH cte AS (
SELECT #StartDate AS myDate
UNION ALL
SELECT DATEADD(day,1,myDate) as myDate
FROM cte
WHERE DATEADD(day,1,myDate) <= #EndDate
)
SELECT myDate
FROM cte
OPTION (MAXRECURSION 0)
Here is a row generator that generates 5 rows, 0 to 4:
WITH rg AS (
SELECT 0 AS rn
UNION ALL
SELECT rg.rn + 1
FROM rg
WHERE rn < 4
)
Here we join it with your existing table that has firsts of the month and use DATEADD to add rn numbers of days (between 0 and 4) to the endPeriod. CROSS JOINing it caused the rows in timePeriod to repeat 5 times each:
SELECT
DATEADD(DAY, rg.rn, timePeriod.endTime) as fakeEndTime
FROM
rg CROSS JOIN timePeriod
I wasn't really clear when you say "days X days after the date, say x = 4" - to me if there is a day that is 1-Jan-2000, then the date 4 days after this is 5-Jan-2000
If you only want the 1,2,3 and 4 of Jan make the row generator < 3 instead of < 4
Already +1'd on Caius Jard's recursive cte.
Here is yet another option using an ad-hoc tally table in concert with a CROSS JOIN
Example
Declare #YourTable Table ([endTime] date)
Insert Into #YourTable Values
('1-1-2019')
,('2-1-2019')
,('3-1-2019')
,('4-1-2019')
Select NewDate = dateadd(DAY,N-1,EndTime)
From #YourTable A
Cross Join (
Select Top (4) N=row_number() over (order by (select null))
From master..spt_values N1
) B
Returns
NewDate
2019-01-01
2019-01-02
2019-01-03
2019-01-04
2019-02-01
2019-02-02
2019-02-03
2019-02-04
2019-03-01
2019-03-02
2019-03-03
2019-03-04
2019-04-01
2019-04-02
2019-04-03
2019-04-04

Sort dates order between dates

I have two dates as a parameters and list of current dates and same periods of previous years . I need partition list between parameters. Logic is if date between '20171201' AND '20180301' then 1, if date between '20161201' and '20170301' then 2 and so on. But count of periods in list not static, like:
DECLARE #DateEnd DATE = '20180331'
, #DateBeg DATE = '20171201'
DECLARE #tab TABLE([date] DATE)
INSERT INTO #tab ([date])
VALUES('2014-12-01')
, ('2015-01-01')
, ('2015-02-01')
, ('2015-03-01')
, ('2015-12-01')
, ('2016-01-01')
, ('2016-02-01')
, ('2016-03-01')
, ('2016-12-01')
, ('2017-01-01')
, ('2017-02-01')
, ('2017-03-01')
, ('2017-12-01')
, ('2018-01-01')
, ('2018-02-01')
, ('2018-03-01')
Result should be
Date Cnt
2014-12-01 4
2015-01-01 4
2015-02-01 4
2015-03-01 4
2015-12-01 3
2016-01-01 3
2016-02-01 3
2016-03-01 3
2016-12-01 2
2017-01-01 2
2017-02-01 2
2017-03-01 2
2017-12-01 1
2018-01-01 1
2018-02-01 1
2018-03-01 1
use CASE WHEN satetement to check for the date and return the value accordingly
select *,
Cnt = CASE
WHEN date BETWEEN '20171201' AND '20180331' THEN 1
WHEN date BETWEEN '20161201' AND '20170331' THEN 2
WHEN date BETWEEN '20151201' AND '20160331' THEN 3
WHEN date BETWEEN '20141201' AND '20150331' THEN 4
END
from #tab
EDIT :
Or maybe this is what you want ?
this will gives you the begining of the financial year
dateadd(month, - month(date) % 12, date)
your Cnt is no of years between the financial year and today ?
select *,
Cnt = datediff(year, dateadd(month, - month(date) % 12, date), getdate())
from #tab
Using dateadd() within a case expression should enable this:
select
datecol
, case when datecol between #DateBeg and #DateEnd then 1
when datecol between dateadd(year,-1,#DateBeg) and dateadd(year,-1,#DateEnd) then 2
when datecol between dateadd(year,-2,#DateBeg) and dateadd(year,-2,#DateEnd) then 3
when datecol between dateadd(year,-3,#DateBeg) and dateadd(year,-3,#DateEnd) then 4
end
from #tab
also see: http://rextester.com/BTF82190

SQL Server Group by date and by time of day over a date range

I'm not even sure if this can/should be done is SQL but here goes.
I have a table that stores a start date and an end date like so
userPingId createdAt lastUpdatedAt
1 2017-10-17 11:31:52.160 2017-10-18 14:31:52.160
I want to return a result set that groups the results by date and if they were active between different points between the two date.
The different points are
Morning - Before 12pm
Afternoon - Between 12pm and 5pm
Evening - After 5pm
So for example I would get the following results
sessionDate morning afternoon evening
2017-10-17 1 1 1
2017-10-18 1 1 0
Here is what I have so far and I believe that it's quite close but the fact I can't get the results I need make me think that this might not be possible in SQL (btw i'm using a numbers lookup table in my query which I saw on another tutorial)
DECLARE #s DATE = '2017-01-01', #e DATE = '2018-01-01';
;WITH d(sessionDate) AS
(
SELECT TOP (DATEDIFF(DAY, #s, #e) + 1) DATEADD(DAY, n-1, #s)
FROM dbo.Numbers ORDER BY n
)
SELECT
d.sessionDate,
sum(case when
(CONVERT(DATE, createdAt) = d.sessionDate AND datepart(hour, createdAt) < 12)
OR (CONVERT(DATE, lastUpdatedAt) = d.sessionDate AND datepart(hour, lastUpdatedAt) < 12)
then 1 else 0 end) as Morning,
sum(case when
(datepart(hour, createdAt) >= 12 and datepart(hour, createdAt) < 17)
OR (datepart(hour, lastUpdatedAt) >= 12 and datepart(hour, lastUpdatedAt) < 17)
OR (datepart(hour, createdAt) < 12 and datepart(hour, lastUpdatedAt) >= 17)
then 1 else 0 end) as Afternoon,
sum(case when datepart(hour, createdAt) >= 17 OR datepart(hour, lastUpdatedAt) >= 17 then 1 else 0 end) as Evening
FROM d
LEFT OUTER JOIN MYTABLE AS s
ON s.createdAt >= #s AND s.lastUpdatedAt <= #e
AND (CONVERT(DATE, s.createdAt) = d.sessionDate OR CONVERT(DATE, s.lastUpdatedAt) = d.sessionDate)
WHERE d.sessionDate >= #s AND d.sessionDate <= #e
AND userPingId = 49
GROUP BY d.sessionDate
ORDER BY d.sessionDate;
Building on what you started with the numbers table, you can add the time ranges to your adhoc calendar table using another common table expression using cross apply()
and the table value constructor (values (...),(...)).
From there, you can use an inner join based on overlapping date ranges along with conditional aggregation to pivot the results:
declare #s datetime = '2017-01-01', #e datetime = '2018-01-01';
;with n as (select n from (values(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) t(n))
, d as ( /* adhoc date/numbers table */
select top (datediff(day, #s, #e)+1)
SessionDate=convert(datetime,dateadd(day,row_number() over(order by (select 1))-1,#s))
from n as deka cross join n as hecto cross join n as kilo
cross join n as tenK cross join n as hundredK
order by SessionDate
)
, h as ( /* add time ranges to date table */
select
SessionDate
, StartDateTime = dateadd(hour,v.s,SessionDate)
, EndDateTime = dateadd(hour,v.e,SessionDate)
, v.point
from d
cross apply (values
(0,12,'morning')
,(12,17,'afternoon')
,(17,24,'evening')
) v (s,e,point)
)
select
t.userPingId
, h.SessionDate
, morning = count(case when point = 'morning' then 1 end)
, afternoon = count(case when point = 'afternoon' then 1 end)
, evening = count(case when point = 'evening' then 1 end)
from t
inner join h
on t.lastupdatedat >= h.startdatetime
and h.enddatetime > t.createdat
group by t.userPingId, h.SessionDate
rextester demo: http://rextester.com/MVB77123
returns:
+------------+-------------+---------+-----------+---------+
| userPingId | SessionDate | morning | afternoon | evening |
+------------+-------------+---------+-----------+---------+
| 1 | 2017-10-17 | 1 | 1 | 1 |
| 1 | 2017-10-18 | 1 | 1 | 0 |
+------------+-------------+---------+-----------+---------+
Alternately, you could use pivot() instead of conditional aggregation in the final select:
select UserPingId, SessionDate, Morning, Afternoon, Evening
from (
select
t.userPingId
, h.SessionDate
, h.point
from t
inner join h
on t.lastupdatedat >= h.startdatetime
and h.enddatetime > t.createdat
) t
pivot (count(point) for point in ([Morning], [Afternoon], [Evening])) p
rextester demo: http://rextester.com/SKLRG63092
You can using PIVOT on CTE's to derive solution to this problem.
Below is the test table
select * from ping
Below is the sql query
;with details as
(
select userPingId, createdAt as presenceDate , convert(date, createdAt) as
onlyDate,
datepart(hour, createdAt) as onlyHour
from ping
union all
select userPingId, lastUpdatedAt as presenceDate , convert(date,
lastUpdatedAt) as onlyDate,
datepart(hour, lastUpdatedAt) as onlyHour
from ping
)
, cte as
(
select onlyDate,count(*) as count,
case
when onlyHour between 0 and 12 then 'morning'
when onlyHour between 12 and 17 then 'afternoon'
when onlyHour>17 then 'evening'
end as 'period'
from details
group by onlyDate,onlyHour
)
select onlyDate, coalesce(morning,0) as morning,
coalesce(afternoon,0) as afternoon , coalesce(evening,0) as evening from
(
select onlyDate, count,period
from cte ) src
pivot
(
sum(count)
for period in ([morning],[afternoon],[evening])
) p
Below is the final result
This is a fairly similar answer to the one already posted, I just wanted the practice with PIVOT :)
I use a separate table with the time sections in it. this is then cross joined with the number table to create a date and time range for bucketing. i join this to the data and then pivot it (example: https://data.stackexchange.com/stackoverflow/query/750496/bucketing-data-into-date-am-pm-evening-and-pivoting-results)
SELECT
*
FROM (
SELECT
[userPingId],
dt,
[desc]
FROM (
SELECT
DATEADD(D, number, #s) AS dt,
CAST(DATEADD(D, number, #s) AS datetime) + CAST(s AS datetime) AS s,
CAST(DATEADD(D, number, #s) AS datetime) + CAST(e AS datetime) AS e,
[desc]
FROM #numbers
CROSS JOIN #times
WHERE number < DATEDIFF(D, #s, #e)
) ts
INNER JOIN #mytable AS m
ON m.createdat < ts.e
AND m.[lastUpdatedAt] >= ts.s
) src
PIVOT
(
COUNT([userPingId])
FOR [desc] IN ([am], [pm], [ev])
) piv;
the #times table is just:
s e desc
00:00:00.0000000 12:00:00.0000000 am
12:00:00.0000000 17:00:00.0000000 pm
17:00:00.0000000 23:59:59.0000000 ev

Function to count if X amount amount of days as a full month

We offer services for clients and each client has an Authorization for 90 days
I want to create a function which counts 15 days as full months.
For example, let’s say a client get Authorization on 10/17/2017. It’s means it’s less than 15 days for October so that Authorization will not count for October, but it has to count for November, December and January 2018.
;WITH CTE AS (
select
d.ClientId,
LOC
datediff(day, l.DecisionOn, d.duedate) 'Days',
l.DecisionOn,
d.duedate
from code d
join codeloc l on d.curdocversionid = l.docversionid
join codeaccess a on a.docversionid = d.curdocversionid
where codeid = 69999
and aoca in ('68','69','70','71','72','74')
),
T AS (
SELECT ClientId, LOC, COUNT(*) CNT FROM CTE
WHERE [Days] > 15
AND AuthorizedDecisionOn > DATEADD(MONTH, (CASE WHEN DAY(GETDATE()) > 15 THEN 1 ELSE 0 END) , CAST( GETDATE() as date))
AND duedate < DATEADD(MONTH,3 + (CASE WHEN DAY(GETDATE()) > 15 THEN 1 ELSE 0 END) , CAST( GETDATE() as date))
GROUP BY ClientId, LOC
)
Here's an inline table valued function (iTvf) that will give you what you need.
(note: I use iTvf's because they outperform scalar udfs)
CREATE FUNCTION dbo.monthsBetweenMinDay
(
#fromDate date,
#toDate date,
#minDays tinyint
)
RETURNS TABLE WITH SCHEMABINDING AS RETURN
SELECT Months = m.mb +
CASE WHEN DATEDIFF(day,d.fd,dateadd(month, -m.mb, d.td)) >= #minDays THEN 1 ELSE 0 END
FROM (VALUES (#fromDate, #toDate)) d(fd,td) -- from date and todate
CROSS APPLY (VALUES(
CASE WHEN d.fd > d.td THEN NULL
WHEN DATEPART(day, d.fd) > DATEPART(day, d.td) THEN DATEDIFF(month, d.fd, d.td)-1
ELSE DATEDIFF(month, d.fd, d.td) END)) m(mb);
Here's an example of the function in action:
-- sample data
CREATE TABLE #dates (date1 date, date2 date);
INSERT #dates
SELECT dt.dt, CAST(DATEADD(day, [days].d, DATEADD(month, months.m, dt.dt)) as date)
FROM (VALUES ('20170101')) dt(dt), (VALUES (4),(15),(25)) [days](d), (VALUES(0),(1),(4)) months(m);
-- solution
SELECT *
FROM #dates d
CROSS APPLY dbo.monthsBetweenMinDay(d.date1, d.date2, 15);
Results
date1 date2 Months
---------- ---------- -----------
2017-01-01 2017-01-05 0
2017-01-01 2017-01-16 1
2017-01-01 2017-01-26 1
2017-01-01 2017-02-05 1
2017-01-01 2017-02-16 2
2017-01-01 2017-02-26 2
2017-01-01 2017-05-05 4
2017-01-01 2017-05-16 5
2017-01-01 2017-05-26 5

SQL - creating a list of custom dates between two dates

I am having trouble compiling a query than can do the following:
I have a table which has a startDate and endDate [tblPayments]
I have a column which stores a specific paymentDay [tblPayments]
Data
paymentID startDate endDate paymentDay
1 2016-01-01 2016-12-31 25
2 2015-06-01 2016-06-30 16
I am trying to generate a SELECT query which will split this specific table into separate lines based on the amount of months between these two dates, and set the paymentDay as the day for these queries
Example Output
paymentID expectedDate
1 2016-01-25
1 2016-02-25
1 2016-03-25
1 2016-04-25
1 2016-05-25
1 2016-06-25
1 2016-07-25
1 2016-08-25
1 2016-09-25
1 2016-10-25
1 2016-11-25
1 2016-12-25
2 2015-06-16
2 2015-07-16
2 2015-08-16
2 2015-09-16
2 2015-10-16
2 2015-11-16
2 2015-12-16
2 2016-01-16
2 2016-02-16
2 2016-03-16
2 2016-04-16
2 2016-05-16
I have found a query which will select the months between these dates but its adapting it to my table above, and multiple startDates and endDates I am struggling with
spliting the months
declare #start DATE = '2015-01-01'
declare #end DATE = '2015-12-31'
;with months (date)
AS
(
SELECT #start
UNION ALL
SELECT DATEADD(MM,1,date)
from months
where DATEADD(MM,1,date)<=#end
)
select Datename(MM,date) from months
This query is limited to just one startDate and endDate, so I haven't expanded it to change the DAY of the date.
Use a date table and a simple inner join
DECLARE #tblPayments table (paymentID int identity(1,1), startDate date, endDate date, paymentDay int)
INSERT #tblPayments VALUES
('2016-01-01', '2016-12-31', 25),
('2015-06-01', '2016-06-30', 16)
;WITH dates AS -- Build date within the range of startDate and endDate
(
SELECT MIN(startDate) AS Value, MAX(endDate) AS MaxDate FROM #tblPayments
UNION ALL
SELECT DATEADD(DAY, 1, Value), MaxDate
FROM dates WHERE DATEADD(DAY, 1, Value) <= MaxDate
)
SELECT pay.paymentID, dates.Value AS expectedDate
FROM
#tblPayments pay
INNER JOIN dates ON
dates.Value BETWEEN pay.startDate AND pay.endDate
AND DAY(dates.Value) = paymentDay
OPTION (maxrecursion 0)
I would create an in memory calendar table and then perform a simple query by joining to that:
-- Create a table with all the dates between the min and max dates in the
-- data table
DECLARE #Calendar TABLE
(
[CalendarDate] DATETIME
)
DECLARE #StartDate DATETIME
DECLARE #EndDate DATETIME
SELECT #StartDate = MIN(startdate), #EndDate = MAX(enddate) FROM YourDataTable
WHILE #StartDate <= #EndDate
BEGIN
INSERT INTO #Calendar (CalendarDate)
SELECT #StartDate
SET #StartDate = DATEADD(dd, 1, #StartDate)
END
-- Join to return only dates between the start and end date that match the Payment Day
SELECT D.PaymentId, C.CalendarDate FROM YourDataTable D
INNER JOIN #Calendar C ON C.CalendarDate BETWEEN D.StartDate AND D.EndDate
AND DATEPART(day, C.CalendarDate) = D.PaymentDay