Select data within specified time periods in a day SQL Query - sql

I need to get items received within different specified time periods within a day. An output like this;
Do anyone have idea how i can implement a sql query to return result table like this, Thanks in advance.

You can do this:
SELECT Item,
SUM(CASE WHEN CAST(DateTimeCol AS Time) >= '08:00:00' AND CAST(DateTimeCol AS Time) < '10:00:00' THEN 1 ELSE 0 END) AS [8AM-10AM],
SUM(CASE WHEN CAST(DateTimeCol AS Time) >= '10:00:00' AND CAST(DateTimeCol AS Time) < '12:00:00' THEN 1 ELSE 0 END) AS [8AM-10AM],
SUM(CASE WHEN CAST(DateTimeCol AS Time) >= '12:00:00' AND CAST(DateTimeCol AS Time) < '14:00:00' THEN 1 ELSE 0 END) AS [10AM-12AM],
SUM(CASE WHEN CAST(DateTimeCol AS Time) >= '14:00:00' AND CAST(DateTimeCol AS Time) < '16:00:00' THEN 1 ELSE 0 END) AS [12PM-2PM],
SUM(CASE WHEN CAST(DateTimeCol AS Time) >= '16:00:00' THEN 1 ELSE 0 END) AS [>4PM]
FROM table1
GROUP BY Item;
Demo
Sample Results:
| Item | 8AM-10AM | 10AM-12AM | 12PM-2PM | 2AM-4PM | >4PM |
|--------|----------|-----------|----------|---------|------|
| Item 1 | 2 | 4 | 1 | 0 | 0 |
| Item 2 | 0 | 0 | 1 | 2 | 2 |
Update:
You can use the PIVOT table operator with temporary table with all the time ranges as following:
SELECT *
FROM
(
SELECT
Item,
Alias
FROM Table1
INNER JOIN Ranges ON CAST(DateTimeCol AS TIME) >= [Start]
AND CAST(DateTimeCol AS TIME) < [End]
) AS t
PIVOT
(
COUNT(Alias)
FOR Alias IN([8AM-10AM], [10AM-12AM], [12PM-2PM],[2PM-4PM], [>4PM])
) AS p;
pivot demo

Related

How to achieve the bucket values in SQL?

I have schedule table like this (table name = testSch)
ID Amount scheduleDate
1 7230.00 2018-07-13
1 7272.00 2018-07-27
1 7314.00 2018-08-10
1 7356.00 2018-08-24
1 7398.00 2018-09-07
1 7441.00 2018-09-21
1 7439.00 2018-10-08
1 7526.00 2018-10-22
1 7570.00 2018-11-05
1 7613.00 2018-11-19
1 5756.00 2018-12-03
I need to sum the Amount field based on specific bucket values as shown below
Principal_7To30_Days
Principal_1To3_Months
Principal_3To6_Months
Principal_6To12_Months
Principal_1To3_Years
by giving an input date
And my input date is 2018-07-09 and below is my query;
;with cteSchedule as (
select *,DATEDIFF(DAY,'20180709',scheduleDate) as datedifference,
DATEDIFF(MONTH,'20180709',scheduleDate) as monthdifference from testSch)
select ISNULL((SELECT SUM(cteSchedule.Amount)
FROM cteSchedule
WHERE cteSchedule.datedifference <7),0) AS Principal_0To7_Days,
ISNULL((SELECT SUM(cteSchedule.Amount)
FROM cteSchedule
WHERE cteSchedule.datedifference>=7 and cteSchedule.datedifference<30),0)
AS Principal_7To30_Days,
ISNULL((SELECT SUM(cteSchedule.Amount)
FROM cteSchedule
WHERE cteSchedule.datedifference>=30 and cteSchedule.datedifference<90),0) AS Principal_1To3_Months,
ISNULL((SELECT SUM(cteSchedule.Amount)
FROM cteSchedule
WHERE cteSchedule.datedifference>=90 and cteSchedule.datedifference<180),0) AS Principal_3To6_Months,
ISNULL((SELECT SUM(cteSchedule.Amount)
FROM cteSchedule
WHERE cteSchedule.datedifference>=180 and cteSchedule.datedifference<365),0) AS Principal_6To12_Months
And below is my output
Principal_0To7_Days Principal_7To30_Days Principal_1To3_Months Principal_3To6_Months Principal_6To12_Months
7230.00 7272.00 29509.00 35904.00 0.00
But the correct output should be
Principal_0To7_Days Principal_7To30_Days Principal_1To3_Months Principal_3To6_Months Principal_6To12_Months
7230.00 7272.00 36948.00 28465.00 0.00
So the problem is i'm getting wrong values for Principal_1To3_Months and Principal_3To6_Months, When I asked my client how do they calculate this in their legacy system, they replied that they calculate using +-months by adding number of months and not days. So if today is 2018-07-09 + 3 months we will get 2018-10-09.
So I used the month difference in my cte query as below
DATEDIFF(MONTH,'20180709',scheduleDate) as monthdifference
And use this in my overall query as below
ISNULL((SELECT SUM(cteSchedule.Amount)
FROM cteSchedule
WHERE cteSchedule.monthdifference>=1 and cteSchedule.monthdifference<=3),0) AS Principal_1To3_Months
But this time also I get the same values as mentioned in my very first output.
Can someone please point out where is my mistake and how to achieve this values as mentioned in correct output
I wouldn't use DATEDIFF to calculation day or month difference days, because there is some month have 31 days, others month have 30 days.
Therefore, the calculated difference days are not accurate.
I would use DATEADD instead of DATEDIFF to do the condition.
;with cteSchedule as (
select *,'20180709' compareDay
from testSch
)
SELECT Sum(CASE
WHEN t.scheduleDate < DATEADD(day, 7, compareDay)
THEN t.amount
ELSE 0
END) AS Principal_0To7_Days,
Sum(CASE
WHEN t.scheduleDate >=DATEADD(day, 7, compareDay) AND t.scheduleDate < DATEADD(day, 30, compareDay)
THEN t.amount
ELSE 0
END) AS Principal_7To30_Days,
Sum(CASE
WHEN t.scheduleDate >=DATEADD(month,1,compareDay) AND t.scheduleDate < DATEADD(month,3,compareDay)
THEN t.amount
ELSE 0
END) AS Principal_1To3_Months,
Sum(CASE
WHEN t.scheduleDate >=DATEADD(month,3,compareDay) AND t.scheduleDate < DATEADD(month,6,compareDay)
THEN t.amount
ELSE 0
END) AS Principal_3To6_Months,
Sum(CASE
WHEN t.scheduleDate >=DATEADD(month,6,compareDay) AND t.scheduleDate < DATEADD(month,12,compareDay)
THEN t.amount
ELSE 0
END) AS Principal_6To12_Months
from cteSchedule t
SQLFiddle
[Results]:
| Principal_0To7_Days | Principal_7To30_Days | Principal_1To3_Months | Principal_3To6_Months | Principal_6To12_Months |
|---------------------|----------------------|-----------------------|-----------------------|------------------------|
| 7230 | 7272 | 36948 | 28465 | 0 |
Note
You can use CASE WHEN with SUM
Aggregate function instead of select subquery, the performance will be better.

How to count different data on the basis of date for distinct users in SQL Server

I have the following schema :
Table T
|ITEM_ID| |USER_ID| | DUE_DATE |
1 1 2018-01-2
1 1 2018-01-2
1 1 2018-01-3
1 2 2018-01-2
2 1 2018-01-2
2 2 2018-01-2
What I want is to get the count of actions performed by a unique user on that particular day, one week.
This is the select query which I'm using however it does not takes care of the fact about unique user.
select ITEM_ID,
sum(case when cast(due_date as date) = cast(getdate() as date) then 1 else 0 end) as today,
sum(case when cast(due_date as date) >= cast(getdate() - 7 as date) then 1 else 0 end) as week,
sum(case when cast(due_date as date) >= dateadd(month, -1, cast(getdate() as date)) then 1 else 0 end) as month
from t
group by ITEM_ID;
How can I the select count of actions performed by a unique user on that particular day, one week.
For example : let's suppose today is 2018-01-2
So the expected result should be :
ITEM_ID, COUNT_TODAY, COUNT_TOMORROW
1 2 1
2 1 1
What I need is the unique user performing the transaction of that particular item type for that day.
That is in the above example, the user having id 1 performed the transaction of type 1 2 times on 2018-01-2. So it should be counted as 1 instead of 2.
How can I achieve this?
Is this what you want.
select USER_ID, ITEM_ID,
sum(case when cast(due_date as date) = cast(getdate() as date) then 1 else 0 end) as today,
sum(case when cast(due_date as date) >= cast(getdate() - 7 as date) then 1 else 0 end) as week,
sum(case when cast(due_date as date) >= dateadd(month, -1, cast(getdate() as date)) then 1 else 0 end) as month
from t
group by USER_ID, ITEM_ID;

Is it possible to return fields conditionally in SQL?

I'd like to build a query that returns if tasks are very late/late/near on time/on time.
Task status :
early if -2 days
near on time if -1 day
late if 1 day
vary late if 2 days
What i've tried :
SELECT field_1, diff,
COUNT(CASE WHEN diff <= -2 THEN 1 END) onTime,
COUNT(CASE WHEN diff <= -1 THEN 1 END) nearOnTime,
Count(CASE WHEN diff >= 2 THEN 1 END) veryLate,
Count(CASE WHEN diff >= 0 THEN 1 END) Late
FROM(
SELECT field_1, DATEDIFF(day,Max(predicted_date), realization_date) as diff
FROM table
Group by field_1, realization_date
HAVING end_date is not null) as req1
GROUP BY field_1, diff)
diff : difference between a predicated date and a realization date
=> returns the number of day between these two dates
It returns :
field_1 | diff | onTime | nearOnTime | veryLate | Late
---------+--------+----------+--------------+------------+-------
task1 | -3 | 1 | 1 | 0 | 0
task2 | 2 | 0 | 0 | 1 | 1
I think my approach is bad, so what is or are my options to returns task status?
maybe something along these lines.. ( a fiddle would help - this has not been tested)
SELECT field_1, diff,
CASE WHEN diff <= -2 THEN 'On Time',
WHEN diff <= -1 THEN 'nearOnTime',
WHEN diff >= 2 THEN 'veryLate',
WHEN diff >= 0 THEN 'Late'
else 'OK' END as status
FROM(
SELECT field_1, DATEDIFF(day,Max(predicted_date), realization_date) as diff
FROM table
Group by field_1, realization_date
HAVING end_date is not null) as req1
GROUP BY field_1, diff)

SQL calculate a time slice for each day for a datetime range

Let's say I have a datetime range defined in SQL:
declare #STARTTIME datetime = '2014-09-10 13:00'
declare #ENDTIME datetime = '2014-09-12 13:00'
and I have a time slice:
declare #sliceStart time = '12:00'
declare #sliceEnd time = '16:00'
(see the picture)
I'd like to calculate how long does this slice overlap in each day of the duration of the time range. In the first day the overlap was for 3 hours (from 13 to 16), in the second day it was for 4 hours and in the final day only for 1 hour.
The ranges are in fact inside a table and there are millions of them and I'd like to calculate the total duration considering these time slices. In my example there is only one datetime range, but my current best guess is that I'd need to create a function out of this that I'll apply to each row, hence the example shows directly the range start and end as variables.
I know there could be a more efficient way, but you can try the following. Here is a Fiddle of it.
DECLARE #STARTTIME DATETIME
SET #STARTTIME = '2014-09-10 13:00'
DECLARE #ENDTIME DATETIME
SET #ENDTIME = '2014-09-12 13:00'
DECLARE #sliceStart TIME
SET #sliceStart = '12:00'
DECLARE #sliceEnd TIME
SET #sliceEnd = '16:00'
;WITH SlicesCTE AS
(
SELECT #STARTTIME AS StartTime
UNION ALL SELECT DATEADD(hh,1,StartTime) AS StartTime
FROM SlicesCTE
WHERE StartTime < #ENDTIME
)
SELECT CONVERT(DATE,StartTime),
SUM
(
CASE
WHEN CONVERT(TIME,StartTime) >= #sliceStart
AND CONVERT(TIME,StartTime) < #sliceEnd
THEN 1
ELSE 0
END
)
FROM SlicesCTE
GROUP BY CONVERT(DATE,StartTime)
[sss_M_M_M_M_M_M_M_M_eee]
|- - - -|- - - -|- - - -|- - - -|
Consider each "range" as a collection of:
a start period (sss)
an end period (eee)
a middle period duration of zero or more complete days (_M_M_M_)
And:
A slice might be before, or overlap, or be within the start period
A slice might be within, or overlap, or be after the end period
A slice will occur for each day in the middle
The start period is known by the time of the "range" start date , and the end period is known by the time of the "range" end date. If a "range" start date and end date are different, the middle period is > 0 and is calculated by (DATEDIFF(DAY, starttime, endtime) - 1)
In the query below, by using cross apply, we are able to give some needed calculations column aliases that can be reused elsewhere in the query. Hence the second cross apply is simplified by using aliases established in the first cross apply.
The case expression in the second cross apply caters for the conditions 1, 2, & 3. noted earlier:
if range start & end are the same day (middle = 0)
then calculate any overlap of slice and range
else (middle > 0)
1 overlap for each day in the middle
+ any overlap in the start period (1 or less)
+ any overlap in the end period (1 or less)
Note: To cater for partial hours in the slice and/or range start times/end times that don't align to full hours minutes are used as the unit of measure; hence the slice is likewise treated as minutes but from this we are able to work back to hours if needed and the number of "overlaps" (really "overlap equivalents") can also be calculated.
Sample results:
| DATE_START | DATE_END |TIME_START| TIME_END | SLICE_MIN | OVERLAP_MIN | OVERLAPS | OVERLAP_HOURS |
|------------|------------|----------|----------|-----------|-------------|----------|---------------|
| 2014-09-10 | 2014-09-12 | 13:00:00 | 13:00:00 | 240 | 480 | 2 | 8 |
| 2014-09-10 | 2014-09-12 | 12:00:00 | 16:00:00 | 240 | 720 | 3 | 12 |
| 2014-09-10 | 2014-09-12 | 13:15:00 | 17:00:00 | 240 | 645 | 2.6875 | 10.75 |
| 2014-08-10 | 2014-09-12 | 13:15:00 | 17:00:00 | 240 | 8085 | 33.6875 | 134.75 |
Query: (assumes "ranges" exist in a table being compared to a slice)
DECLARE #sliceStart TIME
SET #sliceStart = '12:00'
DECLARE #sliceEnd TIME
SET #sliceEnd = '16:00'
SELECT
ca1.*
, ca2.*
, ca2.overlap_min / (ca1.slice_min * 1.0) AS overlaps
, ca2.overlap_min / 60.0 AS overlap_hours
FROM Ranges
cross apply (
select
CAST(starttime AS date) AS date_start
, CAST(endtime AS date) AS date_end
, CAST(starttime AS time) AS time_start
, CAST(endtime AS time) AS time_end
, DATEDIFF(MINUTE, #slicestart, #sliceend) AS slice_min
) ca1
cross apply (
select
(CASE
WHEN ca1.date_start = ca1.date_end THEN
(CASE
WHEN #sliceend > ca1.time_start AND #slicestart < ca1.time_end THEN DATEDIFF(MINUTE,
(CASE
WHEN #slicestart < ca1.time_start THEN ca1.time_start ELSE #slicestart END),
(CASE
WHEN #sliceend > ca1.time_end THEN ca1.time_end ELSE #sliceend END)
)
ELSE 0 END)
ELSE (DATEDIFF(DAY, starttime, endtime) - 1) * ca1.slice_min
+ (CASE
WHEN #sliceend > ca1.time_start THEN DATEDIFF(MINUTE, (CASE
WHEN #slicestart < ca1.time_start THEN ca1.time_start ELSE #slicestart END),
#sliceend)
ELSE 0 END)
+ (CASE
WHEN #slicestart < ca1.time_end THEN DATEDIFF(MINUTE, #slicestart,
(CASE
WHEN #sliceend > ca1.time_end THEN ca1.time_end ELSE #sliceend END)
)
ELSE 0
END)
END) AS overlap_min
) as ca2
;
See this SQLFiddle demo
By the way, this method is a variation on calculating working days
Here is a method. Count the number of hours of overlap on the first day and last day and then add in the complete days in-between.
SQL Server makes this a bit harder than necessary, because it is hard to add times. Here is the code using hours as the difference:
select t.*,
(case when cast(starttime as date) = cast(endtime as date)
then (case when sliceend > cast(starttime as time) and
slicestart < cast(endtime as time)
then datediff(hour,
(case when slicestart < cast(starttime as time) then cast(starttime as time) else slicestart end),
(case when sliceend > cast(endtime as time) then cast(endtime as time) else sliceend end)
)
else 0
end)
then datediff(hour,
(case when slicestart < cast(starttime as time) then cast(starttime as time) else slicestart end),
(case when sliceend > cast(endtime as time) then cast(endtime as time) else sliceend end)
)
else (datediff(day, starttime, endtime) - 1) * datediff(hour, slicestart, sliceend) +
(case when sliceend > cast(starttime as time)
then datediff(hour, (case when slicestart < cast(starttime as time) then cast(starttime as time) else slicestart end),
sliceend)
else 0
end) +
(case when slicestart < cast(endtime as time)
then datediff(hour, slicestart,
(case when sliceend > cast(endtime as time) then cast(endtime as time) else sliceend end)
)
else 0
end)
end) as hours
from (select cast('2014-09-10 13:00' as datetime) as starttime,
cast('2014-09-12 13:00' as datetime) as endtime,
cast('12:00' as time) as slicestart,
cast('16:00' as time) as sliceend
) t;
Here is a SQL Fiddle with the query.

Complex SQL query

I have a table that tracks emails sent from applications on my server. I would like to write a query that shows how many emails were sent by each application in a certain time period. Here is the table:
----------------------------------------------------------
| emailID | SentDT | ApplicationName |
----------------------------------------------------------
| 1 | 2011-08-04 14:43:31.080 | Term Form |
----------------------------------------------------------
| 2 | 2011-08-04 13:59:46.062 | Term Form |
----------------------------------------------------------
| 3 | 2011-08-03 10:38:15.015 | Request Form |
----------------------------------------------------------
| 4 | 2011-08-03 05:52:29.005 | Term Form |
----------------------------------------------------------
| 5 | 2011-08-01 19:58:31.094 | Recruiting Form |
----------------------------------------------------------
I would like to see number of emails sent Today, Last 24 hours, Last 7 days, This Month, Last Month, All time.
I know how to do each of these queries by themselves, but I have no clue how to do it in one trip to the database.
For example:
--------------------------------------------------------------
| ApplicationName | Today | Last24 | Last7days | ThisMonth |
--------------------------------------------------------------
| Term Form | 2 | 5 | 10 | 19 |
--------------------------------------------------------------
| Request Form | 9 | 18 | 36 | 75 |
--------------------------------------------------------------
| Recruiting Form | 15 | 35 | 100 | 250 |
--------------------------------------------------------------
I tried using a nested select for each subset of times, but I can't use a group by in the nested select. My query that doesn't produce results:
select COUNT(emailID), ApplicationName, (select COUNT(emailID) from emaillog where SentDT > '08/02/2011') as TwoDaysAgo
from emaillog
group by ApplicationName
order by ApplicationName
I think it's much easier to do all the date calculations up front, then you can refer to local variables with logical names instead of embedding all the datediff/case etc. calculations in the query logic.
Made a couple of assumptions here. (1) that no data in EmailLog is in the future (2) that by "Last 7 days" you mean today and the full 6 days preceding. I've also included a grand total - even though it's not listed in your desired output, it seems you were trying to get it with the COUNT() outside the subquery.
DECLARE #now SMALLDATETIME = SYSDATETIME();
DECLARE #today DATE = #now,
#24hrsago SMALLDATETIME = DATEADD(DAY, -1, #now);
DECLARE #7daysago DATE = DATEADD(DAY, -6, #today),
#ThisMonth DATE = DATEADD(DAY, 1-DATEPART(DAY, #today), #today);
--SELECT #now, #today, #24hrsago, #7daysago, #ThisMonth;
WITH d AS
(
SELECT ApplicationName, c = COUNT(*)
FROM EmailLog
GROUP BY ApplicationName
),
g AS
(
SELECT
ApplicationName,
[Today] = SUM(CASE WHEN SentDt >= #today THEN 1 ELSE 0 END),
[Last24] = SUM(CASE WHEN SentDt >= #24hrsago THEN 1 ELSE 0 END),
[Last7Days] = SUM(CASE WHEN SentDt >= #7daysago THEN 1 ELSE 0 END),
[ThisMonth] = SUM(CASE WHEN SentDt >= #ThisMonth THEN 1 ELSE 0 END)
FROM EmailLog
GROUP BY ApplicationName
)
SELECT d.ApplicationName,
Total = d.c,
[Today] = COALESCE(g.[Today], 0),
[Last24] = COALESCE(g.[Last24], 0),
[Last7days] = COALESCE(g.Last7days, 0),
[ThisMonth] = COALESCE(g.ThisMonth, 0)
FROM d LEFT OUTER JOIN g
ON d.ApplicationName = g.ApplicationName;
EDIT
If my assumption was wrong and you don't need the total count by application name, the query becomes much simpler:
DECLARE #now SMALLDATETIME = SYSDATETIME();
DECLARE #today DATE = #now,
#24hrsago SMALLDATETIME = DATEADD(DAY, -1, #now);
DECLARE #7daysago DATE = DATEADD(DAY, -6, #today),
#ThisMonth DATE = DATEADD(DAY, 1-DATEPART(DAY, #today), #today);
SELECT ApplicationName,
[Today] = SUM(CASE WHEN SentDt >= #today THEN 1 ELSE 0 END),
[Last24] = SUM(CASE WHEN SentDt >= #24hrsago THEN 1 ELSE 0 END),
[Last7Days] = SUM(CASE WHEN SentDt >= #7daysago THEN 1 ELSE 0 END),
[ThisMonth] = SUM(CASE WHEN SentDt >= #ThisMonth THEN 1 ELSE 0 END)
FROM EmailLog
GROUP BY ApplicationName;
Ordering optional of course.
try:
Select ApplicationName, COunt(*) numEmails
From table
where SentDT Between #startDateTime and #EndDateTime
Group By ApplicationName
NOTE: startDateTime and EndDateTime are oundary limits on records to be processed.
if you also want to establish buckets around specified datetiome ranges, you simply need to define those datetime range buckets in another group by expression (and output that same expression in the select clause ... as an example, say the datetime ranges are calendar months...
Select DateAdd(month, DateDiff(month, 0, SentDT), 0) CalMonth,
ApplicationName, Count(*) numEmails
From table
where SentDT Between #startDateTime and #EndDateTime
Group By DateAdd(month, DateDiff(month, 0, SentDT), 0),
ApplicationName
Something like this should do the trick
select
ApplicationName,
sum(case when daterange = 0 then cnt else 0 end) as Today,
sum(case when daterange = 1 then cnt else 0 end) as yesterday,
sum(case when daterange <=2 then cnt else 0 end) as Week,
sum(case when daterange <=3 then cnt else 0 end) as month,
sum(cnt) as AllTime
from
(select
ApplicationName,
case
when days = 0 then '0'
when days = 1 then '1'
when days <= 7 then '2'
when days <= 30 then '3'
else 4
end as
DateRange,
Count(emailid) cnt
from
(select ApplicationName, EmailID, datediff(dd, SentDT, getdate()) as Days
from
dbo.[YourTableGoesHere]
) as foo
Group by
ApplicationName,
case when days < 1 then '0'
when days = 1 then '1'
when days <= 7 then '2'
when days <= 30 then '3'
else 4
end) as bar
group by
ApplicationName