Break a date range into hours per day for each job - sql

Yesterday I had asked for an efficient way to break a date range into hours per day and received an answer at the following link...
Is there an efficient way to break a date range into hours per day?
Now I need to go a step further and generate the same thing for each job in a list. I have a table with the following sample information...
+-------+-------------------------+-------------------------+
| JobID | StartDate | EndDate |
+-------+-------------------------+-------------------------+
| 1 | 2015-01-27 07:32:35.000 | 2015-01-28 14:39:35.000 |
| 2 | 2015-01-27 07:32:35.000 | 2015-01-29 16:39:35.000 |
| 3 | 2015-03-02 09:46:25.000 | 2015-03-05 17:24:15.000 |
+-------+-------------------------+-------------------------+
And I need to get a list like the following...
+-------+------------+-------+
| JobID | Date | Hours |
+-------+------------+-------+
| 1 | 2015-01-27 | 16.47 |
| 1 | 2015-01-28 | 14.65 |
| 2 | 2015-01-27 | 16.47 |
| 2 | 2015-01-28 | 24.00 |
| 2 | 2015-01-29 | 16.65 |
| 3 | 2015-03-02 | 14.23 |
| 3 | 2015-03-03 | 24.00 |
| 3 | 2015-03-04 | 24.00 |
| 3 | 2015-03-05 | 17.40 |
+-------+------------+-------+
Can the recursive CTE (from the link I included) be modified to include a JobID?
Thanks,
Carl

Here is what I came up with for a solution...
DECLARE #testTable TABLE (JobID INT, startdate DATETIME, enddate DATETIME);
INSERT INTO #testTable VALUES (1,'2015-01-27 07:32:35.000','2015-01-28 14:39:35.000');
INSERT INTO #testTable VALUES (2,'2015-01-27 07:32:35.000','2015-01-29 16:39:35.000');
INSERT INTO #testTable VALUES (3,'2015-03-02 09:46:25.000','2015-03-02 17:24:15.000');
WITH cte AS (
SELECT JobID,CAST(startdate AS DATE) startdate,DATEDIFF(minute, startdate, DATEADD(DAY, 1, CAST(startdate AS DATE) ) ) / 60.0 hours,enddate from #testTable
UNION ALL
SELECT JobID,DATEADD(DAY,1, startdate), DATEDIFF(minute, DATEADD(DAY,1, startdate), CASE WHEN DATEADD(DAY,2, startdate) > enddate
THEN enddate ELSE DATEADD(DAY,2, startdate) END) / 60.0, enddate
FROM cte
WHERE startdate <> CAST(enddate AS DATE)
)
SELECT * FROM cte
ORDER BY JobID, startdate

Related

How to find time average of two datetime rows in SQL?

I have a table with datatime and person identity in which I wanted to find difference between time for each day and find the average in SQL.
The input table,
+---------------------+----------+--------+
| Datetime | Identity | Type |
+---------------------+----------+--------+
| 28/08/2019 4:00:00 | ABC | Entry |
| 28/08/2019 14:00:00 | ABC | Exit |
| 29/08/2019 6:00:00 | ABC | Entry |
| 29/08/2019 6:10:00 | ABC | Exit |
| 30/08/2019 8:00:00 | ABC | Entry |
| 30/08/2019 17:00:00 | ABC | Exit |
+---------------------+----------+--------+
Is it possible to create another table with the below data in SQL?
+------------+----------+-----------+
| Date | Identity | Time(Min) |
+------------+----------+-----------+
| 28/08/2019 | ABC | 600 |
| 29/08/2019 | ABC | 10 |
| 30/08/2019 | ABC | 540 |
+------------+----------+-----------+
Thank you in advance.
You can try below - it'll work for mysql
select cast(datetime as date),
TIMESTAMPDIFF(MINUTE,min(case when Type='Entry' then datetime end),
max(case when Type='Exit' then datetime end))
from tablename
group by cast(datetime as date)
OR for sql server -
select cast(datetime as date),
datediff(mi,min(case when Type='Entry' then datetime end),
max(case when Type='Exit' then datetime end))
from tablename
group by cast(datetime as date)
I think this should give you what you need, in SQL Server:
select cast([datetime] as date) as [Date]
, [Identity]
, datediff(minute, min([datetime]), max([datetime])) as [Time(Min)]
from table_name
group by cast([datetime] as date)
, [Identity]

Date Difference Changes to 42000 while using LAG function in SQL Server

Question: Find out no follow-up appointments to the call within the following 7 days for a particular Patient
My query:
select *, DATEDIFF(DAY, (APPOINTMENT_DATE - LAG(APPOINTMENT_DATE)
over (ORDER BY PATIENT_ID)), APPOINTMENT_DATE) as DIFFERENCE from [dbo].
[Appointment Data]
Problems:
1.DIFFERENCE CHANGES to some crazy format because of datetime may be.
2.Is my query right? How do I find difference for each customer? I know I have to apply group by but I am little confused.
PLS HELP!
Dataset:
APPOINTMENT_DATE PATIENT_ID DIFFERENCE
2010-05-06 00:00:00.000 00051101 NULL
2010-04-11 00:00:00.000 00101005 40302
2010-05-06 00:00:00.000 00130521 40277
2010-02-07 00:00:00.000 00130521 40302
It seems that you have several mistakes in your query:
1) You should use column PATIENT_ID in partitioning and order by APPOINTMENT_DATE in LAG function
2) You have unnecessary subtraction in DATEDIFF function
So, your query should be something like:
select
*, datediff(dd, lag(APPOINTMENT_DATE) over (partition by PATIENT_ID order by APPOINTMENT_DATE), APPOINTMENT_DATE)
from
[dbo].[Appointment Data]
select *,
DATEDIFF(DAY, LAG(APPOINTMENT_DATE) over (ORDER BY PATIENT_ID), APPOINTMENT_DATE) as DIFFERENCE
from [dbo].[Appointment Data]
Result:
+-----------------------+------------+------------+
| APPOINTMENT_DATE | PATIENT_ID | DIFFERENCE |
+-----------------------+------------+------------+
| 5/6/2010 12:00:00 AM | 00051101 | null |
| 4/11/2010 12:00:00 AM | 00101005 | -25 |
| 5/6/2010 12:00:00 AM | 00130521 | 25 |
| 2/7/2010 12:00:00 AM | 00130521 | -88 |
+-----------------------+------------+------------+
If you switch the dates, the result will be different.
select *,
DATEDIFF(DAY, APPOINTMENT_DATE, LAG(APPOINTMENT_DATE) over (ORDER BY PATIENT_ID)) as DIFFERENCE
from [dbo].[Appointment Data]
Result:
+-----------------------+------------+------------+
| APPOINTMENT_DATE | PATIENT_ID | DIFFERENCE |
+-----------------------+------------+------------+
| 5/6/2010 12:00:00 AM | 00051101 | null |
| 4/11/2010 12:00:00 AM | 00101005 | 25 |
| 5/6/2010 12:00:00 AM | 00130521 | -25 |
| 2/7/2010 12:00:00 AM | 00130521 | 88 |
+-----------------------+------------+------------+

Get count for each time row appears in the range between 2 dates

I'am trying to calculate how many times a row "appears" a in the range between 2 dates and grouping them by the month.
So, let's say i have rows that look like this:
Name | StartDate | EndDate
-----------|-----------------|------------
Mathias | 2017-01-01 | 2017-04-01
Lucas | 2017-01-01 | 2017-04-01
i would like to get the output that shows how many records exists between the 2 dates in a query, so something like the following output:
Count | Year | Month
-----------|-----------------|------------
2 | 2017 | 1
2 | 2017 | 2
2 | 2017 | 3
2 | 2017 | 4
0 | 2017 | 5
0 | 2017 | 6
what i've tried is:
SELECT COUNT(*) as COUNT, YEAR(StartDate) YEAR, MONTH(StartDate) MONTH
FROM NamesTable
WHERE Start >= '2017-01-01 00:00:00'
AND Slut <= '2017-06-01 00:00:00'
group by YEAR(StartDate), MONTH(StartDate)
where this is giving me the expected output of:
Count | Year | Month
-----------|-----------------|------------
2 | 2017 | 1
0 | 2017 | 2
0 | 2017 | 3
0 | 2017 | 4
0 | 2017 | 5
0 | 2017 | 6
Because of grouping by the "start date", how can i count rows in the month for every one it expands across?
You need a table with the months range
Table allMonths
+---------+------------+------------+
| monthId | StartDate | EndDate |
+---------+------------+------------+
| 1 | 2017-01-01 | 2017-01-02 |
| 2 | 2017-01-02 | 2017-01-03 |
| 3 | 2017-01-03 | 2017-01-04 |
| 4 | 2017-01-04 | 2017-01-05 |
| 5 | 2017-01-05 | 2017-01-06 |
| 6 | 2017-01-06 | 2017-01-07 |
| 7 | 2017-01-07 | 2017-01-08 |
| 8 | 2017-01-08 | 2017-01-09 |
| 9 | 2017-01-09 | 2017-01-10 |
| 10 | 2017-01-10 | 2017-01-11 |
| 11 | 2017-01-11 | 2017-01-12 |
| 12 | 2017-01-12 | 2018-01-01 |
+---------+------------+------------+
Then your query is:
SELECT am.startDate, COUNT(y.Name)
FROM allMonths am
LEFT JOIN yourTable y
ON am.StartDate <= y.EndDate
AND am.EndDate >= y.StartDate
GROUP BY am.startDate
NOTE: You need to check border cases. Maybe you need change >= to > or change EndDate to the last day of the month.
So, what i ended up doing was something like Juan Carlos proposed, but instead of creating a table i made it up with CTE instead for a cleaner approach:
Declare #todate datetime, #fromdate datetime, #firstOfMonth datetime, #lastOfMonth datetime
Select
#fromdate='2017-01-11',
#todate='2017-12-21',
#firstOfMonth = DATEADD(month, DATEDIFF(month, 0, #fromdate), 0), ----YEAR(#fromdate) + MONTH(#fromdate) + DAY(1),
#lastOfMonth = DATEADD(month, ((YEAR(#fromdate) - 1900) * 12) + MONTH(#fromdate), -1)
;with MonthTable (MonthId, StartOfMonth, EndOfMonth) as
(
SELECT MONTH(#firstOfMonth) as MonthId, #firstOfMonth as StartOfMonth, #lastOfMonth as EndOfMonth
UNION ALL
SELECT MONTH(DATEADD(MONTH, 1, StartOfMonth)), DATEADD(MONTH, 1, StartOfMonth), DATEADD(MONTH, 1, EndOfMonth)
FROM MonthTable
WHERE StartOfMonth <= #todate
)
SELECT am.StartOfMonth, COUNT(y.Start) as count
FROM MonthTable am
left JOIN clientList y
ON y.Start <= am.StartOfMonth
AND y.End >= am.EndOfMonth
GROUP BY am.StartOfMonth

Group By with Min and Max Date using T-SQL

I am trying to do some SQL on the data from the SF Bay Area Bike Share Kaggel Data
When I run this SQL:
/****** Select Bikes ******/
SELECT count(t.[id]) as TripCount
,t.[bike_id]
FROM [dbo].[trip] t
Group By t.[bike_id]
having count(t.[id]) < 25
Order By TripCount asc
I get this result:
+-----------+---------+
| TripCount | bike_id |
+-----------+---------+
| 6 | 876 |
| 18 | 323 |
| 20 | 565 |
| 24 | 476 |
| 24 | 697 |
+-----------+---------+
Where what I really want is this:
+-----------+---------+------------+------------+
| TripCount | bike_id | Min_date | Max_date |
+-----------+---------+------------+------------+
| 6 | 876 | 2014-05-22 | 2014-05-27 |
| 18 | 323 | 2013-08-30 | 2013-09-07 |
| 20 | 565 | 2013-08-29 | 2013-09-07 |
| 24 | 476 | 2013-08-29 | 2013-09-07 |
| 24 | 697 | 2013-10-15 | 2013-12-20 |
+-----------+---------+------------+------------+
where the Min Date & Max Date give respectively the first & last date the bike made a trip.
I can do this manually for each bike by running this:
SELECT
min(cast([start_date] as date)) as Min_Date
,max(cast([start_date] as date)) as Max_Date
,[bike_id]
FROM [dbo].[trip]
where bike_id = '697'
Group By bike_id
I tried this query:
/****** Script for SelectTopNRows command from SSMS ******/
SELECT
count (t.[id]) as TripCount
,min(cast(t.[start_date] as date)) as Min_Date
,max(cast(t.[start_date] as date)) as Max_Date
,[bike_id]
FROM [dbo].[trip] t
Group By count (t.[id]), bike_id
and obviously got the following error:
Cannot use an aggregate or a subquery in an expression used for the group by list of a GROUP BY clause.
Just not quite sure how to wrap my head around this...
You were close
SELECT
[bike_id]
,count (t.[id]) as TripCount
,min(cast(t.[start_date] as date)) as Min_Date
,max(cast(t.[start_date] as date)) as Max_Date
FROM [dbo].[trip] t
Group By bike_id

SQL - Grouping with aggregation

I have a table (TABLE1) that lists all employees with their Dept IDs, the date they started and the date they were terminated (NULL means they are current employees).
I would like to have a resultset (TABLE2) , in which every row represents a day starting since the first employee started( in the sample table below, that date is 20090101 ), till today. (the DATE field). I would like to group the employees by DeptID and calculate the total number of employees for each row of TABLE2.
How do I this query? Thanks for your help, in advance.
TABLE1
DeptID EmployeeID StartDate EndDate
--------------------------------------------
001 123 20100101 20120101
001 124 20090101 NULL
001 234 20110101 20120101
TABLE2
DeptID Date EmployeeCount
-----------------------------------
001 20090101 1
001 20090102 1
... ... 1
001 20100101 2
001 20100102 2
... ... 2
001 20110101 3
001 20110102 3
... ... 3
001 20120101 1
001 20120102 1
001 20120103 1
... ... 1
This will work if you have a date look up table. You will need to specify the department ID. See it in action.
Query
SELECT d.dt, SUM(e.ecount) AS RunningTotal
FROM dates d
INNER JOIN
(SELECT b.dt,
CASE
WHEN c.ecount IS NULL THEN 0
ELSE c.ecount
END AS ecount
FROM dates b
LEFT JOIN
(SELECT a.DeptID, a.dt, SUM([count]) AS ecount
FROM
(SELECT DeptID, EmployeeID, 1 AS [count], StartDate AS dt FROM TABLE1
UNION ALL
SELECT DeptID, EmployeeID,
CASE
WHEN EndDate IS NOT NULL THEN -1
ELSE 0
END AS [count], EndDate AS dt FROM TABLE1) a
WHERE a.dt IS NOT NULL AND DeptID = 1
GROUP BY a.DeptID, a.dt) c ON c.dt = b.dt) e ON e.dt <= d.dt
GROUP BY d.dt
Result
| DT | RUNNINGTOTAL |
-----------------------------
| 2009-01-01 | 1 |
| 2009-02-01 | 1 |
| 2009-03-01 | 1 |
| 2009-04-01 | 1 |
| 2009-05-01 | 1 |
| 2009-06-01 | 1 |
| 2009-07-01 | 1 |
| 2009-08-01 | 1 |
| 2009-09-01 | 1 |
| 2009-10-01 | 1 |
| 2009-11-01 | 1 |
| 2009-12-01 | 1 |
| 2010-01-01 | 2 |
| 2010-02-01 | 2 |
| 2010-03-01 | 2 |
| 2010-04-01 | 2 |
| 2010-05-01 | 2 |
| 2010-06-01 | 2 |
| 2010-07-01 | 2 |
| 2010-08-01 | 2 |
| 2010-09-01 | 2 |
| 2010-10-01 | 2 |
| 2010-11-01 | 2 |
| 2010-12-01 | 2 |
| 2011-01-01 | 3 |
| 2011-02-01 | 3 |
| 2011-03-01 | 3 |
| 2011-04-01 | 3 |
| 2011-05-01 | 3 |
| 2011-06-01 | 3 |
| 2011-07-01 | 3 |
| 2011-08-01 | 3 |
| 2011-09-01 | 3 |
| 2011-10-01 | 3 |
| 2011-11-01 | 3 |
| 2011-12-01 | 3 |
| 2012-01-01 | 1 |
Schema
CREATE TABLE TABLE1 (
DeptID tinyint,
EmployeeID tinyint,
StartDate date,
EndDate date)
INSERT INTO TABLE1 VALUES
(1, 123, '2010-01-01', '2012-01-01'),
(1, 124, '2009-01-01', NULL),
(1, 234, '2011-01-01', '2012-01-01')
CREATE TABLE dates (
dt date)
INSERT INTO dates VALUES
('2009-01-01'), ('2009-02-01'), ('2009-03-01'), ('2009-04-01'), ('2009-05-01'),
('2009-06-01'), ('2009-07-01'), ('2009-08-01'), ('2009-09-01'), ('2009-10-01'),
('2009-11-01'), ('2009-12-01'), ('2010-01-01'), ('2010-02-01'), ('2010-03-01'),
('2010-04-01'), ('2010-05-01'), ('2010-06-01'), ('2010-07-01'), ('2010-08-01'),
('2010-09-01'), ('2010-10-01'), ('2010-11-01'), ('2010-12-01'), ('2011-01-01'),
('2011-02-01'), ('2011-03-01'), ('2011-04-01'), ('2011-05-01'), ('2011-06-01'),
('2011-07-01'), ('2011-08-01'), ('2011-09-01'), ('2011-10-01'), ('2011-11-01'),
('2011-12-01'), ('2012-01-01')
you need somthing along these lines.
SELECT *
, ( SELECT COUNT(EmployeeID) AS EmployeeCount
FROM TABLE1 AS f
WHERE t.[Date] BETWEEN f.BeginDate AND f.EndDate
)
FROM ( SELECT DeptID
, BeginDate AS [Date]
FROM TABLE1
UNION
SELECT DeptID
, EndDate AS [Date]
FROM TABLE1
) AS t
EDIT since OP clarified that he wants all the dates here is the updated solution
I have excluded a Emplyee from Count if his job is ending on that date.But if you want to include change t.[Date] < f.EndDate to t.[Date] <= f.EndDate in the below solution. Plus I assume the NULL value in EndDate mean Employee still works for Department.
DECLARE #StartDate DATE = (SELECT MIN(StartDate) FROM Table1)
,#EndDate DATE = (SELECT MAX(EndDate) FROM Table1)
;WITH CTE AS
(
SELECT DISTINCT DeptID,#StartDate AS [Date] FROM Table1
UNION ALL
SELECT c.DeptID, DATEADD(dd,1,c.[Date]) AS [Date] FROM CTE AS c
WHERE c.[Date]<=#EndDate
)
SELECT * ,
EmployeeCount=( SELECT COUNT(EmployeeID)
FROM TABLE1 AS f
WHERE f.DeptID=t.DeptID AND t.[Date] >= f.StartDate
AND ( t.[Date] < f.EndDate OR f.EndDate IS NULL )
)
FROM CTE AS t
ORDER BY 1
OPTION ( MAXRECURSION 0 )
here is SQL Fiddler demo.I have added another department and added an Employee to it.
http://sqlfiddle.com/#!3/5c4ec/1