Below is my data, I'm trying to get the deference between 2 dates (Punch in and schedule in) to create a report for employees who comes early to work.
I used the following code in expression but the results is way off! Can any one help?
Could it be the formatting of date?
=DateDiff("h", Fields!Scheduled_In.Value, Fields!Punched_In.Value)
& ":" & DateDiff("n", Fields!Scheduled_In.Value, Fields!Punched_In.Value) mod 60
I'm not sure how or why you are getting those results, unless you datetime is actually a string that is being converted.
Anyway, I reproduced your data using a standard datetime format, here's the dataset query with the sample data in.
DECLARE #t table (ANumber int identity(1,1), Scheduled_In datetime, Punched_In datetime)
INSERT INTO #t (Scheduled_In, Punched_In) VALUES
('2022-05-30 09:45:00', '2022-05-30 10:04:48'),
('2022-05-30 12:00:00', '2022-05-30 11:46:09'),
('2022-05-30 14:00:00', '2022-05-30 13:56:44'),
('2022-05-30 14:00:00', '2022-05-30 14:01:30'),
('2022-05-30 09:45:00', '2022-05-30 09:21:11'),
('2022-05-30 09:45:00', '2022-05-30 09:57:18'),
('2022-05-30 14:00:00', '2022-05-30 13:55:37')
SELECT * FROM #t
I then used your original expression which actually works as I expected although the results are probably not what you want.
I then added a new column and used the following expression.
=DATEADD(
DateInterval.Second,
ABS(DateDiff(DateInterval.Second , Fields!Scheduled_In.Value, Fields!Punched_In.Value)),
DateSerial(2000, 01, 01)
)
What this does workout the absolute difference in seconds between the two fields.
It then adds this number of seconds to a fixed date (2020-01-01 although the date used does not matter).
So if the difference was say 1 hour and 5 minutes and 15 seconds the result would give us 2020-01-01 01:05:15
Then I changed the format expression of the textbox to this..
=IIF(
DATEDIFF(DateInterval.Second, Fields!Scheduled_In.Value, Fields!Punched_In.Value) > 0
, " - "
, " + "
) & "HH:mm:ss"
All this does is start with a + or - depending on if the scheduled time is earlier or later then the punched time, followed by a standard time format string. Basically it hides the date portion and puts +/- in front of the time.
Note This will not give the correct results if there is more than 24 hours between the times.
here's the result
Related
I am using MySQL Version 5.7.28. I am having json data like below.
CREATE TABLE `week2` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`type` smallint(1),
`json` text ,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
INSERT INTO week2(id,type,json)
VALUES
(121,1,'[{"weekdays":"Sunday"},{"weekdays":"Monday"},{"weekdays":"Tuesday"},{"weekdays":"Wednesday"},{"weekdays":"Thursday"},{"weekdays":"Friday"},{"weekdays":"Saturday"}]'),
(122,1,'[{"weekdays":"Sunday"},{"weekdays":"Monday"}]'),
(123,2,'[{"start_time":"08:00 AM","end_time":"10:00 PM"}]');
As you see, the json column has nested JSON data. so here i am looking to compare today's day ( which is Saturday and we are currently in between start time and end time)
Expected Result:
(121,1,'[{"weekdays":"Sunday"},{"weekdays":"Monday"},{"weekdays":"Tuesday"},{"weekdays":"Wednesday"},{"weekdays":"Thursday"},{"weekdays":"Friday"},{"weekdays":"Saturday"}]'),
(123,2,'[{"start_time":"08:00 AM","end_time":"10:00 PM"}]');
You need to detect day name (1) and time period (2), and combine those two conditions by OR operator at the end.
For (1) : Detect the current day's name by using DAYNAME() function and search whether exists in the JSON data containing weekday keys through use of JSON_CONTAINS function.
For (2) : TIME() function and CASTing strings to TIME data type might be used with a trick to add 12 hours iterations for the cases of PM type times.
So, consider using :
SELECT *
FROM `week2`
WHERE JSON_CONTAINS(`json`->>'$[*].weekdays', CONCAT('"',DAYNAME( NOW() ),'"')) = 1
OR
(
TIME(ADDTIME(NOW(),"8:00:00")) >=
CASE WHEN INSTR(REPLACE(`json`->>'$[0].start_time',"12:00 AM","00:00 AM"),"PM")>0
THEN
CAST(CONCAT(MOD((TIME_FORMAT(REPLACE(`json`->>'$[0].start_time',"12:00 AM","00:00 AM"), "%T")+12),24),":00 AM")
AS TIME)
ELSE
CAST((REPLACE(`json`->>'$[0].start_time',"12:00 AM","00:00 AM")) AS TIME)
END
AND
TIME(ADDTIME(NOW(),"8:00:00")) <=
CASE WHEN INSTR(REPLACE(`json`->>'$[0].end_time',"12:00 AM","00:00 AM"),"PM")>0
THEN
CAST(CONCAT(MOD((TIME_FORMAT(REPLACE(`json`->>'$[0].end_time',"12:00 AM","00:00 AM"), "%T")+12),24),":00 AM")
AS TIME)
ELSE
CAST(REPLACE(`json`->>'$[0].end_time',"12:00 AM","00:00 AM") AS TIME)
END)
Demo
Perhaps, something like this:
SELECT *,CURRENT_TIME
FROM
(SELECT *,JSON_UNQUOTE(JSON_EXTRACT(`json`,'$[0].start_time')) AS st,
JSON_UNQUOTE(JSON_EXTRACT(`json`,'$[0].end_time')) AS et FROM week2) V
WHERE (JSON_SEARCH(`json`, 'one', DAYNAME(CURDATE())) IS NOT NULL
OR
CURRENT_TIME hour >=
CASE WHEN st LIKE '%AM%' THEN REPLACE(st,' AM',':00')
WHEN st LIKE '%PM%' THEN SEC_TO_TIME(TIME_TO_SEC(REPLACE(st,' PM',':00'))+43200)
END
AND
CURRENT_TIME hour <=
CASE WHEN et LIKE '%AM%' THEN REPLACE(et,' AM',':00')
WHEN et LIKE '%PM%' THEN SEC_TO_TIME(TIME_TO_SEC(REPLACE(et,' PM',':00'))+43200)
END);
First query is using JSON_EXTRACT to extract the time and JSON_UNQUOTE to remove the (") then make it as a sub-query.
On the outer-query, find the weekdays value using JSON_SEARCH compare with today's DAYNAME(CURDATE()); if JSON_SEARCH doesn't find it, it will return NULL, hence the IS NOT NULL condition is used.
Append OR to compare CURRENT_TIME with the time value extracted from the json field. But first, using CASE expression to find out if it's AM or PM; if it's AM, just REPLACE the AM with seconds hand (:00). If it's PM, do the REPLACE like AM then convert the time value to seconds using TIME_TO_SEC then add 43200 seconds (12 hours) and convert again to standard hour:minute:second format using SEC_TO_TIME.
P/S: It was my attempt to answer but I didn't post it because I get wrong result with the time.. Only when I put CURRENT_TIME in the fiddle that I realized that the fiddle time zone is different that mine. Also, I was testing on my local DB; which is MariaDB and the results of converting string to time using STR_TO_TIME is different. Honestly, in my opinion, if this is MariaDB, I think there's a chance that the query is much shorter.
I am passing date and time as input.
'2015-01-12 10:30:00' and '2015-01-13 11:30:00' like this.
we can find the days, hours, minuets, seconds etc between the timestamps. by using the FUNCTION called timestampdiff().
I tried this query.
SELECT TIMESTAMPDIFF(HOUR,'2015-01-12 10:30:00','2015-01-13 11:30:00')/24 FROM DUAL
but it gives me output like 1.04.
My requirement is exact day and hours between that two dates.
Ex:
'2015-01-12 10:30:00' and '2015-01-13 11:30:00' for this
OUTPUT :
1 day and 1 hrs.
thank you.
Just call that function twice. To have the result with the sentence you want, you can do something like this:
SELECT CONCAT(
TIMESTAMPDIFF(DAY,'2015-01-12 10:30:00', '2015-01-13 11:30:00'),
" day(s) and ",
TIMESTAMPDIFF(HOUR,'2015-01-12 10:30:00', '2015-01-13 11:30:00') % 24,
" hour(s)");
If you also want month or minute interval, just add one more function call and the appropriate modulo number
This query can give you required output
SELECT CONCAT(
TIMESTAMPDIFF(DAY,'2015-01-12 10:30:00', '2015-01-13 11:30:00'),
"day(s) and ",
TIMESTAMPDIFF(HOUR,'2015-01-12 10:30:00', '2015-01-13 11:30:00') % 24,
" hour(s)");
I am wanting to use a case statement to change the time piece only of a datetime field in SQL Server 2008 I have a query that will change the time piece, but I need to know how to keep the date portion intact so only the time piece is altered. Meaning if the datetime is
01/01/2015 08:45:10.863
with my syntax I would want to alter it to
01/01/2015 08:30:00.000
This is my syntax which as I said will change the time portion but it does not retain the date. How can I keep the date and change the time portion only?
Create Table #Test
(
[charactername] varchar(100)
,[lefttabletime] datetime
)
Insert Into #Test Values
('Bob Goblin', '01/01/2015 08:14:23.000'),
('Grab Crab', '01/01/2015 08:30:56.023'),
('Mike Knight', '01/01/2015 08:45:10.863')
Select
[charactername]
,case when CAST([lefttabletime] As TIME) > '08:40:00.000' THEN '08:30:00.000'
else [lefttabletime]
FROM #Test
Drop Table #Test
EDIT Additional Syntax Attempted
This threw an error of:
Msg 241, Level 16, State 1, Line 10
Conversion failed when converting date and/or time from character string.
And I tried this syntax
Select
[charactername]
,case when CAST([lefttabletime] As TIME) > '08:40:00.000'
THEN CAST(CAST(CONVERT(DATE, [lefttabletime],101) AS VARCHAR)
+ '08:40:00.000' AS DATETIME) else [lefttabletime] end
FROM #Test
I find it easier if you split the leftabletime into a date and a time component first, the recombine them with the new time portion:
;WITH cte AS
(
SELECT charactername,
[date] = CAST(lefttabletime as date),
[time] = CAST(lefttabletime as time)
FROM #test
)
SELECT charactername,
CAST([date] as datetime)
+ CAST(CASE WHEN [time] > '08:40:00' THEN '08:30:00' ELSE [time] END as datetime)
FROM cte
You can merge the two statement together but I like the simplicity and clarity that a separate CTE provides.
I usually try to make datetime rounding problems fit the DATEADD/DATEDIFF pattern, and I've managed to do that here:
Create Table #Test
(
[charactername] varchar(100)
,[lefttabletime] datetime
)
Insert Into #Test Values
('Bob Goblin', '2015-01-01T08:14:23.000'),
('Grab Crab', '2015-01-01T08:30:56.023'),
('Mike Knight', '2015-01-01T08:45:10.863')
select charactername,
CASE WHEN DATEDIFF(minute,'20010101',lefttabletime)%60 >= 40 THEN
DATEADD(minute,((DATEDIFF(minute,'20010101',lefttabletime)/30)*30),'20010101')
ELSE lefttabletime END
from #Test
Drop Table #Test
The expression DATEADD(minute,((DATEDIFF(minute,'20010101',lefttabletime)/30)*30),'20010101') rounds the time of a datetime down to the nearest 30 minutes (20010101 is an arbitrary date and doesn't need to be adjusted in any way).
I also just use a separate DATEDIFF to find the required matching condition. Where possible, with datetime data, I try to keep it in datetime variables, or, at worst, ints. As soon as you convert to strings you have to start worrying about formats, etc, which I'd usually rather avoid.
Result:
charactername
--------------------- -----------------------
Bob Goblin 2015-01-01 08:14:23.000
Grab Crab 2015-01-01 08:30:56.023
Mike Knight 2015-01-01 08:30:00.000
Hello again SQL Server 2008 gurus.
I need to apply the following rules to the setting of a worker's start and end times for their work day (hourly employees) in a SELECT statement. I apologize in advance for my SQL ignorance.
The rule is to set their start time to a value stored in a table field for that worker, if they login on or before their start time (a time stored in the worker starttime column) and therefore get credit for starting at their start time.
If they log out within a 10 minute period before or anytime after their end time stored in a column for the worker, they get credit for their full day, another value stored in a column of the worker table, otherwise they are penalized some percentage of an hour, i.e. their log out time rounded to .25 of an hour less closest to the time they logged out. i.e. if they are set to log out at 4:30, and they log out at 4:18, their log out time is 4:15. If they log out at 4:20, and are set to log out at 4:30, their log out time is 4:30.
The first rule applies to all hourly employees where their workday hours is less than or equal to their expected workday value. The caveat is, for those where overtime is ok (a bit value set to 1). If overtime is permitted, the number of billable hours can exceed the full day value stored for them, and therefore the value of their logout - login time can exceed their fullday value.
The question is, can these rules be calculated in the SELECT statement and if so can I get some help with the code?
The columns containing the information are:
worker.startime (TIME)
worker.endtime (TIME)
worker.overtimeallowed (BIT)
worker.workdayhours (decimal (12,2))
worker.penaltyvalue (decimal (12,2))
If it requires a UDF or stored procedure (since I'm using the Telerik ReportViewer) I'm not sure it would be supported, but that's probably another question.
So far I've gotten some help with applying some CASE logic - calculating whether a worker get's credit for their 1/2 lunch. The code that was supplied works as promised. This, I believe may be an extension to that logic - so I'll provide the code I have here:
-- for testing purposes only.
DECLARE #StartDate AS DateTime
SET #StartDate = CAST('03/25/2012' AS DATE)
DECLARE #EndDate AS DateTime
SET #EndDate = CAST('04/10/2012' AS DATE)
SELECT
w.Firstname
,w.Lastname
,wf.Login
,wf.logout
,ROUND(CAST(DATEDIFF(MI, wf.Login, wf.Logout) AS DECIMAL)/60,2)
- CASE
WHEN DATEDIFF(hour, wf.Login, wf.Logout) < w.MinimumHours THEN
w.LunchDeduction
ELSE
0
END AS [Hours Credited]
FROM Workers AS w
JOIN Workflow AS wf
ON wf.LoggedInWorkerid = w.ID
JOIN Roles AS r
ON w.RoleID = r.RoleID
WHERE (r.Descript = 'Hourly')
AND wf.Login >= #StartDate AND wf.Logout <= #EndDate
ORDER BY w.Lastname, w.Firstname
Here is a sample select dealing with constraints you described. CTEs create tables for testing purposes. Main query shows the calculations. You have worked with datediffs and dateadds so there is no mistery. If you haven't use % before, it is modulo operator used to round time to 15 minutes.
;with worker (ID, overtime, startTime, endTime) as
(
select 1, 1, CAST ('08:30' as time), CAST ('16:30' as time)
union all
select 2, 0, CAST ('08:30' as time), CAST ('16:30' as time)
union all
select 3, 0, CAST ('08:30' as time), CAST ('16:30' as time)
),
-- Test table of workflows
wf (workerID, login, logout) as
(
select 1, CAST ('2012-03-11 08:20' as datetime), CAST ('2012-03-11 19:33' as datetime)
union all
select 2, CAST ('2012-03-11 08:50' as datetime), CAST ('2012-03-11 16:20' as datetime)
union all
select 3, CAST ('2012-03-11 08:22' as datetime), CAST ('2012-03-11 16:18' as datetime)
)
select wf.workerID,
wf.login,
wf.logout,
-- if starttime > login return startTime else login
case when DATEDIFF(MI, w.startTime, cast (wf.login as time)) < 0
then cast(CAST (wf.login AS date) AS datetime) + w.startTime
else wf.login
end roundedLogin,
case when w.overtime = 1 -- Round to 15 minutes whenever finished
OR
-- Round to 15 minutes if left ten or more minutes before endTime
DATEDIFF(MI, cast (wf.logout as time), dateadd (MI, -10, w.endTime)) > 0
then dateadd (MI, -(DATEPART (MI, wf.logout) % 15), wf.logout)
-- stop at endTime if overtime = 0 OR left job at apropriate time
else cast(CAST (wf.logout AS date) AS datetime) + w.endTime
end roundedLogout
from worker w
inner join wf
on w.ID = wf.workerID
There will be a problem with this approach. When you start to integrate mathematics into original query you will notice that you have to write expressions evaluating roundedLogin and roundedLogout again to calculate billable hours. You cannot reuse alias defined in the same scope, but you can create derived table or view or even calculated fields. View returning columns from workflows and all additional expressions would probably be the best.
Using this view in other queries would simplify things by encapsulating logic at one place.
Lets say I have a table that contains the following - id and date (just to keep things simple).
It contains numerous rows.
What would my select query look like to get the average TIME for those rows?
Thanks,
Disclaimer: There may be a much better way to do this.
Notes:
You can't use the AVG() function against a DATETIME/TIME
I am casting DATETIME to DECIMAL( 18, 6 ) which appears to yield a reasonably (+- few milliseconds) precise result.
#1 - Average Date
SELECT
CAST( AVG( CAST( TimeOfInterest AS DECIMAL( 18, 6 ) ) ) AS DATETIME )
FROM dbo.MyTable;
#2 - Average Time - Remove Date Portion, Cast, and then Average
SELECT
CAST( AVG( CAST( TimeOfInterest - CAST( TimeOfInterest AS DATE ) AS DECIMAL( 18, 6 ) ) ) AS DATETIME )
FROM dbo.MyTable;
The second example subtracts the date portion of the DATETIME from itself, leaving only the time portion, which is then cast to a decimal for averaging, and back to a DATETIME for formatting. You would need to strip out the date portion (it's meaningless) and the time portion should represent the average time in the set.
SELECT CAST(AVG(CAST(ReadingDate AS real) - FLOOR(CAST(ReadingDate as real))) AS datetime)
FROM Rbh
I know that, in at least some of the SQL standards, the value expression (the argument to the AVG() function) isn't allowed to be a datetime value or a string value. I haven't read all the SQL standards, but I'd be surprised if that restriction had loosened over the years.
In part, that's because "average" (or arithmetic mean) of 'n' values is defined to be the sum of the values divided by the 'n'. And the expression '01-Jan-2012 08:00' + '03-Mar-2012 07:53' doesn't make any sense. Neither does '01-Jan-2012 08:00' / 3.
Microsoft products have a history of playing fast and loose with SQL by exposing the internal representation of their date and time data types. Dennis Ritchie would have called this "an unwarranted chumminess with the implementation."
In earlier versions of Microsoft Access (and maybe in current versions, too), you could multiply the date '01-Jan-2012' by the date '03-Mar-2012' and get an actual return value, presumably in units of square dates.
If your dbms supports the "interval" data type, then taking the average is straightforward, and does what you'd expect. (SQL Server doesn't support interval data types.)
create table test (
n interval hour to minute
);
insert into test values
('1:00'),
('1:30'),
('2:00');
select avg(n)
from test;
avg (interval)
--
01:30:00