Not sure if this is a unique question or not.
I'm needing to get calculated punch in and punch out times for a labor tracking system. Our crew has breaks from 10-10:20 AM and from 1-1:20 PM.
What I need to figure out is how to subtract this time from a total if they are still punched in during these breaks.
For example, if Joe punches in to a job at 09:53 and punches out at 10:23, I want it to show 10 minutes instead of 30.
How could I do this for few "blackout" times of 10-10:20, 1-1:20, 11-11:20, and 5-5:20?
This return the total of work minutes. This check if the worker time overlap with each break time and then change it to indicate how much overlap was.
Then calculate the total of minutes in break and finally subtract for the total of time worker punch in_out.
SQL DEMO
WITH time_off as (
SELECT * ,
CASE WHEN w.in_w < b.out_b AND w.out_w > b.in_b
THEN 'overlap'
END as overlap,
CASE WHEN w.in_w < b.in_b
THEN b.in_b
ELSE w.in_w
END as break_start,
CASE WHEN w.out_w > b.out_b
THEN b.out_b
ELSE w.out_w
END as break_end
FROM workers w
CROSS JOIN breaks b
), break_total as (
SELECT worker_id, in_w, out_w, SUM (CASE WHEN overlap = 'overlap'
THEN datediff(minute, break_start,break_end)
ELSE 0
END) as break_total
FROM time_off
GROUP BY worker_id, in_w, out_w
)
SELECT worker_id,
datediff(minute, in_w, out_w) - break_total as total_minutes
FROM break_total
For some debug do:
SELECT * FROM time_off;
SELECT * FROM break_total;
Related
I have a main table ( TMS ) that holds the date and time an emploiee has clocked on to the plant for the day, joined to this I have derrived tables, one of which contains the time spent working on a task. for each day clocked in I can see the time they spent working on a task. I have an instant where the user might not clock on to the plant, but book time to a task. In my output Matrix becasue there is not a record of them attending site it will not show the time they booked to a task. Is there a way to show every date in the TMS table regardless if they have booked ?
I created a new Table that showed every date from 01-01-19 to 01-01-25, in the hope that when I joined the above tables I got "null" for any date we did not clock in or book time to a task.When I join this to the above sets I still dont get a date where no clocking in took place.
Coding copied out of the SQL box from SSRS 2017 ( I tend to use the Grid pane for my coding rather then typing the SQL ):
SELECT DISTINCT
WOL_TMS_INFO.ORG_UNIT, WOL_TMS_INFO.SAP_NO, format(WOL_TMS_INFO.DATE, 'MM') AS MONTH_PARAM, WOL_TMS_INFO.SITE, WOL_TMS_INFO.FIRSTNAME, WOL_TMS_INFO.SURNAME,
YEAR(WOL_TMS_INFO.DATE) AS YEAR, WOL_TMS_INFO.DATE, WOL_TMS_INFO.HOURS_ATT, format(WOL_TMS_INFO.DATE, 'ddd') AS DATE1, WOL_TMS_INFO.PAID_ABSENCES, WOL_TMS_INFO.HOLIDAY,
WOL_TMS_INFO.HOURS_SICK, HOURS_CUST_PAID_CERV_TBL.HOURS_CUST_PAID, ZPEFF_NP_TIME.NP_WORKING_TIME, CASE WHEN WORKING_TIME IS NULL
THEN 0 ELSE WORKING_TIME END AS WORKING_TIME
FROM WOL_TMS_INFO LEFT OUTER JOIN
(SELECT TOP (100) PERCENT PERS_NO, PLNT, POSTG_DATE, SUM(WORKING_TIME + CASE WHEN UNIT = 'MIN' THEN ACTUAL_WORK / 60 ELSE ACTUAL_WORK END) AS WORKING_TIME
FROM WOL_ZPEFF_DETAIL
GROUP BY PERS_NO, PLNT, POSTG_DATE
ORDER BY POSTG_DATE) AS zpeff_derv_tbl_working_time ON WOL_TMS_INFO.DATE = zpeff_derv_tbl_working_time.POSTG_DATE AND WOL_TMS_INFO.SAP_NO = zpeff_derv_tbl_working_time.PERS_NO LEFT OUTER JOIN
(SELECT TOP (100) PERCENT PERS_NO, PLNT, POSTG_DATE, SUM(WORKING_TIME) AS NP_WORKING_TIME
FROM (SELECT TOP (100) PERCENT PERS_NO, PLNT, POSTG_DATE, SUM(WORKING_TIME + ACTUAL_WORK / 60) AS WORKING_TIME, REASON_FOR
FROM WOL_ZPEFF_DETAIL AS WOL_ZPEFF_DETAIL_1
GROUP BY PERS_NO, PLNT, POSTG_DATE, REASON_FOR
HAVING (REASON_FOR IS NOT NULL)
ORDER BY POSTG_DATE) AS ZPEFF_DERV_DERV_TBL
GROUP BY PERS_NO, PLNT, POSTG_DATE
ORDER BY POSTG_DATE) AS ZPEFF_NP_TIME ON WOL_TMS_INFO.SAP_NO = ZPEFF_NP_TIME.PERS_NO AND WOL_TMS_INFO.DATE = ZPEFF_NP_TIME.POSTG_DATE LEFT OUTER JOIN
(SELECT TOP (100) PERCENT SAP_NO, DATE, SUM(HOURS_CUST_PAID) AS HOURS_CUST_PAID
FROM WOL_TMS_INFO AS WOL_TMS_INFO_1
GROUP BY SAP_NO, DATE
ORDER BY DATE) AS HOURS_CUST_PAID_CERV_TBL ON WOL_TMS_INFO.SAP_NO = HOURS_CUST_PAID_CERV_TBL.SAP_NO AND WOL_TMS_INFO.DATE = HOURS_CUST_PAID_CERV_TBL.DATE
WHERE (WOL_TMS_INFO.ORG_UNIT IN (#PROD_TEAM_PARAM)) AND (WOL_TMS_INFO.SAP_NO IN (#SAP_NO)) AND (format(WOL_TMS_INFO.DATE, 'MM') IN (#MONTH_PARAM)) AND (WOL_TMS_INFO.SITE = 'wolverton') AND
(WOL_TMS_INFO.DATE > '01/01/2019')
ORDER BY MONTH_PARAM, WOL_TMS_INFO.SAP_NO
The Matrix works apart from not showing time booked where no time is attended to site
You might try as part of your selection. It should give you the clock into the plant time, but if that is null, then the time booked to the task.
Pseudo Code
ISNULL(ClockInPlantTime,BookToTaskTime)
I am trying to build a query that calculates number of patients in the emergency room by hour. I have each patients arrival and departure times. I tried building a boolean style query but all it did was give me the arrivals by hour using this logic
SELECT MRN,
,CASE WHEN CAST(EDArrival AS TIME) between '00:00:00.000' and '00:59:59.000' then 1 else 0 end as Hour0
,CASE WHEN CAST(EDArrival AS TIME) between '01:00:00.000' and '01:59:59.000' then 1 else 0 end as Hour1
,CASE WHEN CAST(EDArrival AS TIME) between '02:00:00.000' and '02:59:59.000' then 1 else 0 end as Hour2
FROM EDArrivals
WHERE EDArrival between '2012-06-01' and '2013-07-01'
I was thinking maybe the query could place a column for each hour with a 1 or 0 in they were in the ED during those hours. What I ultimately want to get to is average patients in the ED by hour over the course of a year. If anyone can think of an easier method I would greatly appreciate the help.
Thank you
This probably won't perform great, but it will give the average for each hour over the time span you specify. The perf issue will be because of the function in the JOIN criteria in the CTE. If you need to do this for a very large number of rows it probably makes sense to break that out to another table and populate a column with the hour.
DECLARE #Hours TABLE (Hr smallint)
INSERT INTO #Hours
(Hr)
VALUES
(0)
,(1)
,(2)
,(3)
,(4)
,(5)
,(6)
,(7)
,(8)
,(9)
,(10)
,(11)
,(12)
,(13)
,(14)
,(15)
,(16)
,(17)
,(18)
,(19)
,(20)
,(21)
,(22)
,(23)
WITH ByDate
AS
(
SELECT
CAST(ED.EDArrival AS date) AS 'Dt',h.Hr, COUNT(*) AS 'PatientCount'
FROM
EDArrivals ED
JOIN
#Hours AS h
ON DATEPART(HOUR, ED.EDArrival) = h.Hr
WHERE
ED.EDArrival BETWEEN '2012-06-01' AND '2013-07-01'
GROUP BY
CAST(ED.EDArrival AS date)
,h.Hr
)
SELECT
Hr, AVG(PatientCount)
FROM
ByDate
GROUP BY
hr
ORDER BY
hr
I should also note that though you don't list it in your requirements, it probably makes more sense to also filter on the departure time is >= the given hour. You likely need to know not just how many patients show up but how many are sticking around at any given time.
I managed to create an example of my comment in SQLFiddle.
http://sqlfiddle.com/#!6/5234e/6
It's similar to JNK answer (hey, I commented first!)
By the way, creating that table variable will not be great, consider keeping a domain table with the hours.
If do you need performance consider also persisting the date part values. Evaluating them for each row is a performance killer.
Also take care with null departures date times and patients staying at midnight.
Have you tried using DateDiff:
SELECT DateDiff(n, startdate, enddate) FROM MyTable
SELECT COUNT(*) [TotalArrivals]
, DATEPART(hh, [EDArrival]) [Hour]
FROM [EDArrivals]
GROUP BY DATEPART(hh, [EDArrival])
This will get you the total arrivals grouped by hour. You can then use this to do your averages per hour / whatever other calculations you need. This wont give you the hours with no arrivals, but that should be easy to fit in to your calculations at the end.
I'm trying to figure out how to calculate the overtime on timecard bills that have been or need to be paid. The problem is that bills cover more than one weeks worth of hours and the query grabs more than one employee's history at a time. Any suggestions on how to do this, perhaps with a case statement of some kind?
For example, suppose in my list of employees one works 39 hours one week and 45 the next. The bill would show 84 hours worked, and would also need to show 5 hours of overtime (not four!). This needs to be done in the context of the below query, which handles multiple bills and multiple employees.
Note the query below shows how this would work if the billing period was only one week.
select
username,
CASE
WHEN paidOn IS NULL THEN 'Unpaid'
ELSE paidOn
END as paid,
round(sum(TIME_TO_SEC(TIMEDIFF(timeOut, timeIn)))/3600,2) AS hours
, CASE
WHEN round(sum(TIME_TO_SEC(TIMEDIFF(timeOut, timeIn)))/3600,2) > 40
THEN round((sum(TIME_TO_SEC(TIMEDIFF(timeOut, timeIn)))/3600 - 40) * payrate + 40 * payrate,2)
ELSE
round(sum(TIME_TO_SEC(TIMEDIFF(timeOut, timeIn)))/3600 * payrate, 2)
END as pay
from
timecard
LEFT JOIN
employees
ON
employees.userID = timecard.userID
WHERE
paid != 'd'
GROUP BY
paidOn, timecard.userID
ORDER BY
paid DESC
LIMIT 30;
The way I understand it is that this works fine if the billing period is only 1 week, but not when you extend it to multiple weeks. Then I would simply use the query you have above as a subquery and then aggregate your weeks together. Here's a short example of what I mean:
SELECT
EmployeeId,
SUM(RegularPayHours) * RegularPayRate,
SUM(OverTimeHours) * OverTimeRate
FROM
(SELECT
EmployeeId,
DATEPART(week,TimeCardDate) AS [WorkWeek],
CASE WHEN SUM(HoursWorked) > 40 THEN 40 ELSE SUM(HoursWorked) END AS [RegularPayHours],
CASE WHEN SUM(HoursWorked) > 40 THEN SUM(HoursWorked) - 40 ELSE 0 END AS [OvertimeHours]
FROM
TimeCard
WHERE
TimeCardDate BETWEEN StartDate AND EndDate
GROUP BY
EmployeeId,
DATEPART(week,TimeCardDate)
) a
WHERE
WorkWeek IN (1,2)
GROUP BY
EmployeeId
This will give you the first two work weeks combined as one result but with the overtime calculations done at the week level. You can essentially create any custom pay period that is X weeks long with this method.
You may need to custom define what a work-week is by setting the DATEFIRST value.
first of all sorry for that title, but I have no idea how to describe it:
I'm saving sessions in my table and I would like to get the count of sessions per hour to know how many sessions were active over the day. The sessions are specified by two timestamps: start and end.
Hopefully you can help me.
Here we go:
http://sqlfiddle.com/#!2/bfb62/2/0
While I'm still not sure how you'd like to compare the start and end dates, looks like using COUNT, YEAR, MONTH, DAY, and HOUR, you could come up with your desired results.
Possibly something similar to this:
SELECT COUNT(ID), YEAR(Start), HOUR(Start), DAY(Start), MONTH(Start)
FROM Sessions
GROUP BY YEAR(Start), HOUR(Start), DAY(Start), MONTH(Start)
And the SQL Fiddle.
What you want to do is rather hard in MySQL. You can, however, get an approximation without too much difficulty. The following counts up users who start and stop within one day:
select date(start), hour,
sum(case when hours.hour between hour(start) and hours.hour then 1 else 0
end) as GoodEstimate
from sessions s cross join
(select 0 as hour union all
select 1 union all
. . .
select 23
) hours
group by date(start), hour
When a user spans multiple days, the query is harder. Here is one approach, that assumes that there exists a user who starts during every hour:
select thehour, count(*)
from (select distinct date(start), hour(start),
(cast(date(start) as datetime) + interval hour(start) hour as thehour
from sessions
) dh left outer join
sessions s
on s.start <= thehour + interval 1 hour and
s.end >= thehour
group by thehour
Note: these are untested so might have syntax errors.
OK, this is another problem where the index table comes to the rescue.
An index table is something that everyone should have in their toolkit, preferably in the master database. It is a table with a single id int primary key indexed column containing sequential numbers from 0 to n where n is a number big enough to do what you need, 100,000 is good, 1,000,000 is better. You only need to create this table once but once you do you will find it has all kinds of applications.
For your problem you need to consider each hour and, if I understand your problem you need to count every session that started before the end of the hour and hasn't ended before that hour starts.
Here is the SQL fiddle for the solution.
What it does is use a known sequential number from the indextable (only 0 to 100 for this fiddle - just over 4 days - you can see why you need a big n) to link with your data at the top and bottom of the hour.
I am trying to have a go at the infamous repeating events on calendars using PHP/MySQL. I've finally found something that seems to work. I found my answer here but I'm having a little difficulty finishing it off.
My first table 'events'.
ID NAME
1 Sample Event
2 Another Event
My second table 'events_meta that stores the repeating data.
ID event_id meta_key meta_value
1 1 repeat_start 1336312800 /* May 7th 2012 */
2 1 repeat_interval_1 432000 /* 5 days */
With repeat_start being a date with no time as a unix timestamp, and repeat_interval an amount in seconds between intervals (432000 is 5 days).
I then have the following MySQL which I modified slightly from the above link. The timestamp used below (1299132000 which is 12th May 2012) is the current day with no time.
SELECT EV.*
FROM `events` EV
RIGHT JOIN `events_meta` EM1 ON EM1.`event_id` = EV.`id`
RIGHT JOIN `events_meta` EM2 ON EM2.`meta_key` = CONCAT( 'repeat_interval_', EM1.`id` )
WHERE EM1.meta_key = 'repeat_start'
AND (
( CASE ( 1336744800 - EM1.`meta_value` )
WHEN 0
THEN 1
ELSE ( 1336744800 - EM1.`meta_value` ) / EM2.`meta_value`
END
)
) = 1
In the above MySQL, the following code deducts the repeat_start field (EM1.'meta_value') from the current date and then divides it by the repeat interval field (EM2.'meta_value').
ELSE ( 1336744800 - EM1.`meta_value` ) / EM2.`meta_value`
OR
TODAYS DATE - START DATE / 5 DAYS
So here's the maths:
1336744800 - 1336312800 = 432000
432000 / 432000 = 1
Now that works perfect. But if I change the current timestamp 5 days ahead to 1336312800 which is 17th Mat 2012, it looks a bit like this:
1336312800 - 1336312800 = 864000
86400 / 432000 = 2
Which doesn't work because it equals 2 and in the MySQL it needs to equal 1. So I guess my question is, how do I get the MySQL to recognise a whole number rather than having to do this?
...
WHERE EM1.meta_key = 'repeat_start'
AND (
( CASE ( 1336744800 - EM1.`meta_value` )
WHEN 0
THEN 1
ELSE ( 1336744800 - EM1.`meta_value` ) / EM2.`meta_value`
END
)
) = IN (1,2,3,4,5,6,7,8,....)
Hope I'm making sense and I hope it's just a simple maths thing or a function that MySQL has that will help :) Thanks for your help!
EDIT: THE ANSWER
Thanks to #eggypal below, I found my answer and of course it was simple!
SELECT EV.*
FROM elvanto_calendars_events AS EV
RIGHT JOIN elvanto_calendars_events_meta AS EM1 ON EM1.`event_id` = EV.`id`
RIGHT JOIN elvanto_calendars_events_meta AS EM2 ON EM2.`meta_key` = CONCAT( 'repeat_interval_', EM1.`id` )
WHERE EM1.meta_key = 'repeat_start'
AND ( ( 1336744800 - EM1.`meta_value` ) % EM2.`meta_value`) = 0
It's not entirely clear what you want your query to do, but the jist of your question makes me lean toward suggesting that you look into modular arithmetic: in SQL, a % b returns the remainder when a is divided by b - if there is no remainder (i.e. a % b = 0), then a must be an exact multiple of b.
In your case, I think you're trying to find events where the time between the event start and some given literal is an exact multiple of the event interval: that is, (literal - event_start) % event_interval = 0. If it's non-zero, the value is the time to the next occurrence after literal (and, therefore, to determine whether that next occurrence occurs within some period of time, say a day, one would test to see if the remainder is less than such constant e.g. (literal - event_start) % event_interval < 86400).
If this isn't what you're after, please clarify exactly what your query is trying to achieve.
set #dat_ini = '2023-05-20',#dat_fim = '2022-11-20'; select (DATEDIFF( #dat_fim,#dat_ini )) % 60
THIS < 10
It only works for a short period.
To do this, take the start date and change the Month that is on the screen and add a year, then subtract it from the start date, then it works.