SQL Query: grouping datetime as time slot - sql

I've a query having lot of datetime as result.
Suppose I want to count how much of these datetime are in a time slot like:
07:00 to 08:00
08:01 to 09:00
so I want something like this:
_______________________
| slot | count
|______________|________
| 7:00 - 8:00 | 10
| 8:01 - 9:00 | 2
I really don't know what i can do to have this result.
I've tried something like this but obv i get error:
SELECT something
FROM something
WHERE
CAST(DATEPART(hour, <data>) as varchar(2)) + ':' + CAST(DATEPART(minute, <data>)as varchar(2)) between <slot1> and <slot2>
TIPS: probably I'll get the time slots dynamically from the report services.

Here is query to accomplish what you seem to looking for:
SELECT CAST(DATEPART(HH, time) AS VARCHAR(2))+':00 - '+CAST(DATEPART(HH, time)+1 AS VARCHAR(2))+':00', COUNT(*)
FROM your_table
GROUP BY DATEPART(HH, time)
Here is code at SQL Fiddle
[EDIT]:
AS per the new requirement, below query can accomplish what you want:
SELECT (CAST(DATEPART(HH, time) AS VARCHAR(2)) +
CASE WHEN DATEPART(MI, time) <= 30 THEN ':00 - '+CAST(DATEPART(HH, time) AS VARCHAR(2))+':30'
ELSE ':30 - '+CAST(DATEPART(HH, time)+1 AS VARCHAR(2))+':00' END) AS r,
COUNT(*)
FROM your_table
GROUP BY
(CAST(DATEPART(HH, time) AS VARCHAR(2)) +
CASE WHEN DATEPART(MI, time) <= 30 THEN ':00 - '+CAST(DATEPART(HH, time) AS VARCHAR(2))+':30'
ELSE ':30 - '+CAST(DATEPART(HH, time)+1 AS VARCHAR(2))+':00' END)
Here is the code at SQL Fiddle
If you do not want to use CASE statement two times as in the above query then you can do a GROUP BY in an outer query enclosing the current as an inner query as below:
SELECT r, COUNT(*) AS cnt FROM
(
SELECT (CAST(DATEPART(HH, time) AS VARCHAR(2)) +
CASE WHEN DATEPART(MI, time) <= 30 THEN ':00 - '+CAST(DATEPART(HH, time) AS VARCHAR(2))+':30'
ELSE ':30 - '+CAST(DATEPART(HH, time)+1 AS VARCHAR(2))+':00' END) AS r
FROM your_table
) tab
GROUP BY r
Here is the code at SQL Fiddle
Let me know if it solved your problem.

Related

SQL query SELECT time intervals

I'm trying to SELECT all the rows from a SQL database which are between an hour interval, for every day.
The datetime column is called "Dt" and has the following datetime format: 2019-10-17 16:03:43
I'd like to extract all the rows from this table where the Dt was between 22:00:00 and 02:00:00, for everyday.
SELECT *
FROM MY_TABLE
WHERE "Dt" BETWEEN '*-*- 22:00:00' AND '*-*- 02:00:00';
where * should be any...
Thanks for your support!
EDIT: I forgot to mention: I'm using the integrated SQL interpreter from DB Browser for SQLite
You need to extract the time part of the date and compare that it is within the range. Since midnight is between 22 and 2, you will need to split it to two comparisons, time between 22 and 0 and between 0 and 2.
To see how to extract the time take a look at this question.
With Postgres, assuming dt is defined as timestamp you can do the following:
SELECT *
FROM MY_TABLE
WHERE "Dt" BETWEEN "Dt"::date + time '22:00:00' and ("Dt"::date + 1) + time '02:00:00'
Or if you want to exclude timestamps at precisely 02:00:00
SELECT *
FROM MY_TABLE
WHERE "Dt" >= "Dt"::date + time '22:00:00'
and "Dt" < ("Dt"::date + 1) + time '02:00:00'
select DT_time from (
select cast (substr(to_char(Dt,'dd-mm-yyyy HH:MM:SS'),12,2) as integer ) as DT_time from MY_TABLE )
where DT_time between 2 and 22;
between 22:00:00 and 02:00:00
means:
SELECT *
FROM MY_TABLE
WHERE
substr(Dt, 12) BETWEEN '22:00:00' AND '23:59:59'
OR
substr(Dt, 12) BETWEEN '00:00:00' AND '02:00:00'
This will work ::
SELECT *
FROM MY_TABLE
WHERE DATEPART(HOUR, Dt)>22
AND DATEPART(HOUR, Dt)<2
Update :
SELECT *
FROM MY_TABLE
WHERE Dt Between DATEADD (hour,22,DATEADD(day, DATEDIFF(day, 0, Dt), 0)) AND DATEADD (hour,2,DATEADD(day, DATEDIFF(day, -1, Dt), 0))
SELECT *
FROM MY_TABLE
WHERE DATEPART(HOUR, Dt)>22
OR DATEPART(HOUR, Dt)<2
Above query work for you..
1st one will check only for particular date and consecutive next date along with your time range.
But If you don't care about dates and only looking for time interval in particular hours then 2nd one is for you.
For SQLite :
SELECT *
FROM MY_TABLE
WHERE strftime('%H','Dt')>22
OR strftime('%H','Dt')<2

Redshift: Running query using GETDATE() at specified list of times

So, I have a query that uses GETDATE() in WHERE and HAVING clauses:
SELECT GETDATE(), COUNT(*) FROM (
SELECT 1 FROM events
WHERE (event_time > (GETDATE() - interval '25 hours'))
GROUP BY id
HAVING MAX(event_time) BETWEEN (GETDATE() - interval '25 hours') AND (GETDATE() - interval '24 hours')
)
I'm basically trying to find the number of unique ids that have their latest event_time between 25 and 24 hours ago with respect to the current time.
The problem: I have another table query_dts which contains one column containing timestamps. Instead of running the above query on the current time, using GETDATE(), I need to run in on the timestamp of every entry of the query_dts table. Any ideas?
Note: I'm not really storing query_dts anywhere. I've created it like this:
WITH query_dts AS (
SELECT (
DATEADD(hour,-(row_number() over (order by true)), getdate())
) as n
FROM events LIMIT 48
),
which I got from here
How about avoiding the generator altogether and instead just splitting the intervals:
SELECT
dateadd(hour, -distance, getdate()),
count(0) AS event_count
FROM (
SELECT
id,
datediff(hour, max(event_time), getdate()) AS distance
FROM events
WHERE event_time > getdate() - INTERVAL '2 days'
GROUP BY id) AS events_with_distance
GROUP BY distance;
You can use a JOIN to combine the two queries. Then you just need to substitute the values for your date expression. I think this is the logic:
WITH query_dts AS (
SELECT DATEADD(hour, -(row_number() over (order by true)), getdate()) as n
FROM events
LIMIT 48
)
SELECT d.n, COUNT(*)
FROM (SELECT d.n
FROM events e JOIN
query_dts d
WHERE e.event_time > d.n
GROUP BY id
HAVING MAX(event_time) BETWEEN n - interval '25 hours' AND n
) i;
Here's what I ended up doing:
WITH max_time_table AS
(
SELECT id, max(event_time) AS max_time
FROM events
WHERE (event_time > GETDATE() - interval '74 hours')
GROUP BY id
),
query_dts AS
(
SELECT (DATEADD(hour,-(row_number() over (ORDER BY TRUE) - 1), getdate()) ) AS n
FROM events LIMIT 48
)
SELECT query_dts.n, COUNT(*)
FROM max_time_table JOIN query_dts
ON max_time_table.max_time BETWEEN (query_dts.n - interval '25 hours') AND (query_dts.n - interval '24 hours')
GROUP BY query_dts.n
ORDER BY query_dts.n DESC
Here, I selected 74 hours because I wanted 48 hours ago + 25 hours ago = 73 hours ago.
The problem is that this isn't a general-purpose way of doing this. It's a highly specific solution for this particular problem. Can someone think of a more general way of running a query dependent on GETDATE() using a column of dates in another table?

i want to return only single record with sum of hours

The below query is working perfect but it return two rows of hours which I don't want
SELECT
USERINFO.name, USERINFO.BADGENUMBER,
departments.deptname, APPROVEDHRS.hours,
sum(workingdays) as workingdays,TotalWorkingDays
FROM
(SELECT DISTINCT
(DATEDIFF(DAY, '2014-06-01', '2014-06-30') + 1) -
DATEDIFF(WEEK, '2014-06-01', '2014-06-30') * 2 -
(CASE WHEN DATEPART(WEEKDAY, '2014-06-01') = 5 THEN 1 ELSE 0 END) -
(CASE WHEN DATEPART(WEEKDAY, '2014-06-30') = 6 THEN 1 ELSE 0 END) AS TotalWorkingDays,
COUNT(DISTINCT DATEADD(d, 0,DATEDIFF(d, 0, CHECKINOUT.CHECKTIME))) AS workingdays,
USERINFO.BADGENUMBER, USERINFO.NAME, hours
FROM
USERINFO
LEFT JOIN
CHECKINOUT ON USERINFO.USERID = CHECKINOUT.USERID
LEFT JOIN
departments ON departments.deptid = userinfo.DEFAULTDEPTID
left join APPROVEDHRS on APPROVEDHRS.userid = userinfo.userid AND
(APPROVEDHRS.DATE >='2014-06-01') AND (APPROVEDHRS.DATE <='2014-06-30')
WHERE
(DEPARTMENTS.DEPTNAME = 'xyz')
AND (CHECKINOUT.CHECKTIME >= '2014-06-01')
AND (CHECKINOUT.CHECKTIME <= '2014-06-30')
GROUP BY
hours, USERINFO.BADGENUMBER, deptname, USERINFO.NAME,
CONVERT(VARCHAR(10), CHECKINOUT.CHECKTIME, 103)) blue
GROUP BY
name, BADGENUMBER, workingdays, TotalWorkingDays, deptname, hours
The output of above query :
name BADGENUMBER deptname hours
---------------------------------------------------
abc 1111 xyz 00:07:59
abc 1111 xyz 00:08:00
pqr 2222 qwe NULL
Now the total hours (APPROVEDHRS table) in table is :
BADGENUMBER NAME DATE HOURS
-------------------------------------------------
1111 xyz 2014-06-15 00:07:59
1111 xyz 2014-06-14 00:08:00
1111 xyz 2014-07-14 00:10:00
I am fetching record from 2014-06-01 to 2014-06-30
So I want the below output:
name BADGENUMBER deptname hours
--------------------------------------------------------
abc 1111 xyz 00:15:59
pqr 2222 qwe NULL
Help me to get this desired output.
Thank you
Clearly, if you want to add your durations together, you should be storing them as something you can add together. Generally, this takes the form of a numeric type representing the smallest granularity you're interested in (apparently minutes, in this case). You can wrap it as an actual defined type, that standard operators work on (I'm sure somebody's defined an INTERVAL type for some version of SQL Server), but essentially it's simply backed by an INTEGER or something.
If you can't change the actual type in the db, then you need to convert it for this statement (and back for the display). That's perhaps easiest by declaring a pair of functions based on this:
CREATE FUNCTION dbo.Minutes_From_Duration_String (#Duration AS CHAR(8))
RETURNS INTEGER
BEGIN
RETURN (CAST(SUBSTRING(#Duration, 1, 2) AS INTEGER) * 24 * 60) +
(CAST(SUBSTRING(#Duration, 4, 2) AS INTEGER) * 60) +
(CAST(SUBSTRING(#Duration, 7, 2) AS INTEGER))
END;
CREATE FUNCTION dbo.Duration_String_From_Minutes (#Minutes AS INTEGER)
RETURNS CHAR(10)
BEGIN
RETURN RIGHT('00' + (#Minutes / 60 / 24), 2) + ':' +
RIGHT('00' + ((#Minutes / 60) % 24), 2) + ':' +
RIGHT('00' + (#Minutes % 60), 2)
END;
SQL Fiddle example
(note that these are extremely basic and will blow up at the slightest provocation. The rest is left as an exercise to the reader).
That taken care of, they can be used in your query, as usual.
Note that I think your query should be modified a bit. It's a bit difficult to tell without starting data, but I believe it can run faster, and be clearer.
First, always query positive contiguous-range types (like dates/times/timestamps) as lower-bound inclusive (>=), upper-bound exclusive (<), especially for the listed types on SQL Server. This means you never have to worry about dealing with fractions of things.
Next, if you don't have one already, you really want a Calendar Table. It is, in my opinion, the most useful Dimension table to have. You can put essentially as many indices as you want on it, which means you can use them (and range queries) to actually get index-based aggregates that you couldn't before (ie, by week, etc). It also makes getting non-working days (holidays) much easier, and is critical for one other thing here: the results of DATEPART(WEEKDAY, ....) are dependent on the culture/locale of the current session. That's probably not what you want.
If you can't create one now, you can generate a simple one easily with the use of a recursive CTE:
WITH Calendar_Range AS (SELECT CAST('20140601' AS DATE) AS Calendar_Date,
dbo.ISO_Day_Of_Week(CAST('20140601' AS DATE)) AS Day_Of_Week
UNION ALL
SELECT DATEADD(day, 1, Calendar_Date),
dbo.ISO_Day_Of_Week(DATEADD(day, 1, Calendar_Date))
FROM Calendar_Range
WHERE Calendar_Date < CAST('20140701' AS DATE))
SELECT Calendar_Date, Day_Of_Week
FROM Calendar_Range
SQL Fiddle demo
(This assumes you have some way to get the ISO Day-of-week - where Monday is 1. The demo includes a sample function that does this.)
We actually have three different aggregates, so we need to get them all separately:
First, the total hours approved:
SELECT userid, SUM(dbo.Minutes_From_Duration_String(hours)) AS totalHours
FROM ApprovedHrs
WHERE date >= CAST('20140601' AS DATE)
AND date < CAST('20140701' AS DATE)
GROUP BY userid
Then, total number of days worked.
SELECT CheckInOut.userid, COUNT(DISTINCT Calendar_Range.calendar_date) AS daysWorked
FROM Calendar_Range
JOIN CheckInOut
ON CheckInOut.checkTime >= Calendar_Range.calendar_date
AND CheckInOut.checkTime < DATEADD(day, 1, Calendar_Range.calendar_date)
WHERE Calendar_Range.calendar_date >= CAST('20140601' AS DATE)
AND Calendar_Range.calendar_date < CAST('20140701' AS DATE)
GROUP BY CheckInOut.userid
(I'm assuming Calendar_Range is a full-on Calendar table here, with all possible dates)
Lastly, number of days "available" to be worked:
SELECT COUNT(*) AS totalWorkingDays
FROM Calendar_Range
WHERE Day_Of_Week NOT IN (6, 7)
AND calendar_date >= CAST('20140601' AS DATE)
AND calendar_date < CAST('20140701' AS DATE)
(I'm assuming that there are other non-working days that shouldn't be counted here, like Christmas, but I didn't include a condition for it. Otherwise, you can do the calculation similar to what you did before, just be careful of day-of-week issues. The query I'm using here assumes ISO day-of-week values)
We now have all the pieces we need, so we can assemble the final query:
SELECT UserInfo.name, UserInfo.badgeNumber,
Departments.deptName,
dbo.Duration_String_From_Minutes(COALESCE(SummedHours.totalHours, 0)) AS totalHours,
COALESCE(DaysWorked.daysWorked, 0) AS daysWorked,
WorkingDays.totalWorkingDays
FROM UserInfo
JOIN Departments
ON Departments.deptId = UserInfo.defaultDeptId
AND Departments.deptName = 'xyz'
CROSS JOIN (SELECT COUNT(*) AS totalWorkingDays
FROM Calendar_Range
WHERE Day_Of_Week NOT IN (6, 7)
AND calendar_date >= CAST('20140601' AS DATE)
AND calendar_date < CAST('20140701' AS DATE)) WorkingDays
LEFT JOIN (SELECT userid, SUM(dbo.Minutes_From_Duration_String(hours)) AS totalHours
FROM ApprovedHrs
WHERE date >= CAST('20140601' AS DATE)
AND date < CAST('20140701' AS DATE)
GROUP BY userid) SummedHours
ON SummedHours.userId = UserInfo.userId
LEFT JOIN (SELECT CheckInOut.userid, COUNT(DISTINCT Calendar_Range.calendar_date) AS daysWorked
FROM Calendar_Range
JOIN CheckInOut
ON CheckInOut.checkTime >= Calendar_Range.calendar_date
AND CheckInOut.checkTime < DATEADD(day, 1, Calendar_Range.calendar_date)
WHERE Calendar_Range.calendar_date >= CAST('20140601' AS DATE)
AND Calendar_Range.calendar_date < CAST('20140701' AS DATE)
GROUP BY CheckInOut.userid) DaysWorked
ON DaysWorked.userId = UserInfo.userId
Try this.
WITH CTE (name,BADGENUMBER,deptname,hours)
AS
(
YOUR FULL QUERY
)
SELECT name,BADGENUMBER,deptname,SUM(hours)
FROM CTE
GROUP BY name,BADGENUMBER,deptname
you can sum hours by converting the varchar to time, then to seconds, sum them, back to varchar, back to time :) this should do the sum and the required conversions:
CONVERT(VARCHAR, dateadd(s,SUM(DATEDIFF(SECOND, 0, CAST(hours AS TIME))),0),114)

how can I calculate time in sql?

I have a column called total_hours which is a set of times in Time type and it is in HH:MM:SS format in SQL server. I want to know how can I get the sum of total_hours.
For example:
total_hours
00:10:30
01:20:00
00:20:00
---------
01:50:30
Could you please give me the query to exactly do the same thing with the same format?
Try this
SELECT SUM( DATEPART(SECOND, [time]) + 60 *
DATEPART(MINUTE, [time]) + 3600 *
DATEPART(HOUR, [time] )
) as 'TotalTime'
FROM Table1
(Or)
SELECT dateadd(millisecond,sum(datediff(millisecond,0, [Time])),0)
FROM Table1

SQL how to do 1 hour interval

I have sql query which returns record by date specified
What I want to do is group them by 1 hour interval
My query returns a date and interval.
interval value looks like this
8:00,8:30,9:00,9:30,10:00
as you can see the interval has produce 5 value what I want to do is group them by this
8:00-9:00,9:00-10:00
I have designed a query:
SELECT DATEPART(HOUR,VC.DATE+ VC.INTERVAL) AS DATE
,DATEPART(HOUR,VC.INTERVAL) AS INTERVAL
FROM VMUK_Q1R_IB_CONSOLIDATED VC
But the problem with this it display like this 8,8,9,9,10
How to I achieve this?
What you need is to create a set of hourly values and join back to it based on the hour part of your value. This will make sure the missing 'buckets' are represented. The following CTE will give you the lookup for 24 hours - you could do the same thing with a static lookup table too.
with ranges
as
(
select 0 as value
union all
select r.value+ 1 from ranges r where r.value <= 24
)
select
r.value start
from ranges r
You could fix this by a calculation or formatting. I think formatting would be simpler for this example. Try this:
SELECT Convert(VarChar(20), DATEPART(HOUR,VC.INTERVAL)) + ':00' AS DATE
,DATEPART(HOUR,VC.INTERVAL) AS INTERVAL
FROM VMUK_Q1R_IB_CONSOLIDATED VC
If you want the full date + time shown, rounded down, try this:
SELECT Convert(VarChar(20), VC.Date, 101) + Convert(VarChar(20), DATEPART(HOUR,VC.INTERVAL)) + ':00' AS DATE
,DATEPART(HOUR,VC.INTERVAL) AS INTERVAL
FROM VMUK_Q1R_IB_CONSOLIDATED VC
If you want time ranges too, try this:
SELECT Convert(VarChar(20), VC.Date, 101) + Convert(VarChar(20), DATEPART(HOUR,VC.INTERVAL)) + ':00' AS DATE,
,DatePart(HOUR,VC.INTERVAL)) + ':00 - ' + DatePart(HOUR, DateAdd(HOUR, VC.INTERVAL, 1)) + ':00' AS TimeRange
,DATEPART(HOUR,VC.INTERVAL) AS INTERVAL
FROM VMUK_Q1R_IB_CONSOLIDATED VC
SELECT
DATEPART(HOUR,VC.DATE+ VC.INTERVAL) AS DATE,
case DATEPART(HOUR,VC.INTERVAL)
when 0 then '00:00-00:59'
when 1 then '01:00-01:59'
.
.
etc.
.
.
when 22 then '22:00-22:59'
when 23 then '23:00-23:59'
end AS INTERVAL
FROM VMUK_Q1R_IB_CONSOLIDATED VC