Converting string to datetime problem - sql

Using SQL Server 2000:
SELECT PERSONID,
CARDEVENTDATE,
INTIME,
CASE
WHEN OUTTIME = INTIME THEN
'No PunchOut'
ELSE
OUTTIME
END AS OUTTIME,
CONVERT(char(8), CASE
WHEN DateAdd(Day, - DateDiff(Day, 0, OutTime), OutTime) > '18:00:00' THEN
Cast('18:00:00' AS datetime)
ELSE
DateAdd(Day, - DateDiff(Day, 0, OutTime), OutTime)
END - CASE
WHEN DateAdd(Day, - DateDiff(Day, 0, InTime), InTime) < '09:00:00' THEN
Cast('09:00:00' AS datetime)
ELSE
DateAdd(Day, - DateDiff(Day, 0, InTime), InTime)
END, 8) AS WorkTime
FROM (SELECT T_PERSON.PERSONID,
T_CARDEVENT.CARDEVENTDATE,
MIN(T_CARDEVENT.CARDEVENTTIME) AS INTIME,
MAX(T_CARDEVENT.CARDEVENTTIME) AS OUTTIME
FROM T_PERSON
INNER JOIN T_CARDEVENT ON T_PERSON.PERSONID = T_CARDEVENT.PERSONID
GROUP BY T_PERSON.PERSONID, T_CARDEVENT.CARDEVENTDATE) DERIVEDTBL
T_cardevent.cardeventtime column datatype is Varchar.
In table Cardeventtime values are 080002, 091235.... so on...,
When I executing the above query it showing Arithmetic Express Overflow error for converting expression to datatype Datetime.

So this "080002" stands for? 8 hours, 0 minutes, 2 seconds?
This is definitely not a valid DATETIME format out of the box - and it doesn't comply with any of the valid SQL Server CONVERT styles, either.
So you'll have to do some conversions yourself, manually. Is there any chance you could wrap the table with this column into a view which could handle the conversion?
You'd have to do something along the lines of:
CONVERT(DATETIME, SUBSTRING(CardEventTime, 1, 2) + ':' +
SUBSTRING(CardEventTime, 3, 2) + ':' +
SUBSTRING(CardEventTime, 5, 2), 8)
and this should turn your "080002" into "08:00:02" which can then be converted to a DATETIME (no separate time datatype until SQL Server 2008) using the style no. 8 (hh:mm:ss).
Marc

I've made a series of assumptions here, and worked without being able to test it, but here's a possible solution:
SELECT PERSONID,
CARDEVENTDATE,
INTIME,
CASE
WHEN OUTTIME = INTIME THEN
'No PunchOut'
ELSE
OUTTIME
END AS OUTTIME,
Stuff(Stuff(Right('000000' +
CONVERT(varchar(5), CASE
WHEN Convert(int,OutTime) > 180000 THEN
180000
ELSE
Convert(int,OutTime)
END - CASE
WHEN Convert(int,InTime) < 90000 THEN
90000
ELSE
Convert(int,InTime)
END), 6),5,0,':'),3,0,':')
AS WorkTime
FROM (SELECT T_PERSON.PERSONID,
T_CARDEVENT.CARDEVENTDATE,
MIN(T_CARDEVENT.CARDEVENTTIME) AS INTIME,
MAX(T_CARDEVENT.CARDEVENTTIME) AS OUTTIME
FROM T_PERSON
INNER JOIN T_CARDEVENT ON T_PERSON.PERSONID = T_CARDEVENT.PERSONID
GROUP BY T_PERSON.PERSONID, T_CARDEVENT.CARDEVENTDATE) DERIVEDTBL
I've not used Stuff very frequently, so the insertion points may be off.

Related

Sybase - Filter records with current date efficiently

I am trying to filter records based on current date (date part only) against a column "date_sent" which is a DateTime datatype. Though this can be written in multiple ways, I want to know which method will be the most efficient. The table has 11-12 millions of records ant any given time.
Let's say the current date is 16th April 2018
SELECT *
FROM TABLE_NAME
WHERE datepart(YEAR, date_sent) = 2018
AND datepart(MONTH,date_sent) = 4
AND datepart(DAY,date_sent) = 16;
SELECT *
FROM TABLE_NAME
WHERE convert(char(8), date_sent, 112) = convert(char(8), getdate(), 112);
Any other suggestions.
I would start with:
select *
from table_name
where date_sent >= dateadd(day, datediff(day, 0, getdate()), 0) and
date_sent < dateadd(day, 1 + datediff(day, 0, getdate()), 0)
The right hand side removes the time component of the current date. The comparisons should allow Sybase to still us an index on date_sent.
EDIT:
Perhaps Sybase doesn't permit 0 as a date value. You can also do:
select *
from table_name
where date_sent >= dateadd(day, datediff(day, cast('2000-01-01' as date), getdate()), cast('2000-01-01' as date)) and
date_sent < dateadd(day, 1 + datediff(day, cast('2000-01-01' as date), getdate()), cast('2000-01-01' as date))

How to convert sql dates range to Daily Row

How do I convert a date range so each day is 1 row with the start and end time for that day?
I looked at this post about date ranges to row - but this is a different problem. The other solution linked above does not give the time from start to finish for each day - and thus does not allow for duty factor or utilization calculations, and or the build of a Gantt chart.
We would have an ID field, a Start Date, and an End Date as our base table. We want to convert this to contain the ID Field per day with how much time was consumed in that range.
This is very useful when converting a start and end dates to daily duty factor and a host of other needs like equipment utilization.
I had a lot of help from this community figuring this out. I wanted to put the final SQL script here for others to use as well.
WITH cte ([VID],[StartTime],[EndTime]) AS
( SELECT tbl.[ID] as 'VID'
,CONVERT(VARCHAR(19), tbl.[StartDT], 120) AS 'StartTime'
,CASE
WHEN tbl.[EndDT] <= CONVERT(VARCHAR(11), tbl.[StartDT]+1, 120) + '00:00:00' THEN CONVERT(VARCHAR(19), tbl.[EndDT], 120)
ELSE CONVERT(VARCHAR(11), tbl.[StartDT]+1, 120) + '00:00:00'
END as 'EndTime'
FROM [SourceTable] as tbl
WHERE DATEDIFF(DAY,tbl.[StartDT],tbl.[EndDT] )<=365
UNION ALL
SELECT tbl.[ID] as 'VID'
,CONVERT(VARCHAR(11), DATEADD(DAY, 1, cte.[StartTime]), 120) + '00:00:00' AS 'StartTime'
,CASE
WHEN CONVERT(VARCHAR(19), tbl.[EndDT], 120) < CONVERT(VARCHAR(11), DATEADD(DAY, 2, cte.[StartTime]), 120) + '00:00:00'
THEN CONVERT(VARCHAR(19), tbl.[EndDT], 120)
ELSE CONVERT(VARCHAR(11), DATEADD(DAY, 2, cte.[StartTime]), 120) + '00:00:00'
END AS 'EndTime'
FROM cte
INNER JOIN [SourceTable] as tbl
ON cte.VID = tbl.ID
WHERE CONVERT(VARCHAR(11), cte.[StartTime], 120) < CONVERT(VARCHAR(11), tbl.[EndDT], 120))
SELECT VID AS ID
,[StartTime]
,[EndTime]
,DateDiff (second,[StartTime],[EndTime]) / 3600 As 'Hours'
,DateDiff (second,[StartTime],[EndTime])/60 % 60 as 'Minutes'
,((DateDiff (Second,[StartTime],[EndTime]) / 3600)*60)+(DateDiff (second,starttime,endtime)/60 % 60) as 'Total Minutes'
,DATEPART(week,[StartTime]) AS weeknum
,MONTH([StartTime]) AS MonthNumber
,DATENAME(month,[StartTime]) AS MonthName
FROM cte order by Id, [StartTime]
option (maxrecursion 0);

how can I get last 7 days of data being my date field a number?

I need to get last 7 days data excluding Sunday being my date field a number. How can I do it? Field structure 20140425. For example is I run the statement today it should give me date range between 20140424 - 20140417 excluding 20140420.
The hitch is of course converting the number based date to a real date. This seems to work:
select convert(datetime, convert(char(10), 20140425))
To expand, your query would look like this:
select *
from [Table]
where convert(datetime, convert(char(10), [columnname])) between convert(varchar, getdate() - 8, 101) and convert(varchar, getdate() - 1, 101)
and datepart(DW, convert(datetime, convert(char(10), [columnname]))) <> 1
The convert(varchar, getdate - 1, 101) will return you 12:00am yesterday morning. My first pass didn't include that and would've only given a 6 day range.
SELECT *
FROM Table_Name
WHERE CAST(DateField AS DATE) >= DATEADD(DAY, -7, GETDATE())
AND CAST(DateField AS DATE) <= GETDATE()
AND DATEPART(DW,CAST(DateField AS DATE)) <> 1

Split time records across midnight

I'm trying to run some reports and having to deal with the whole issue of employee labor hours crossing midnight. It occurs to me though that I could split the records that cross midnight into two records as if the employee clocked out at midnight and simultaneously clocked back in at midnight thus avoiding the midnight problem altogether.
So if I have:
EmployeeId InTime OutTime
--- ----------------------- -----------------------
1 2012-01-18 19:50:04.437 2012-01-19 03:30:02.433
What do you suppose would be the most elegant way to split this record like so:
EmployeeId InTime OutTime
--- ----------------------- -----------------------
1 2012-01-18 19:50:04.437 2012-01-19 00:00:00.000
1 2012-01-19 00:00:00.000 2012-01-19 03:30:02.433
And yes, I have thoroughly thought through what effects this might have on existing functionality... which is why I'm opting to do this in a temporary table that will not affect existing functionality.
This might help:
DECLARE #tbl TABLE
(
EmployeeId INT,
InTime DATETIME,
OutTime DATETIME
)
INSERT INTO #tbl(EmployeeId,InTime,OutTime) VALUES (1,'2012-01-18 19:50:04.437','2012-01-19 03:30:02.433')
INSERT INTO #tbl(EmployeeId,InTime,OutTime) VALUES (2,'2012-01-18 19:50:04.437','2012-01-18 20:30:02.433')
INSERT INTO #tbl(EmployeeID,InTime,OutTime) VALUES (3,'2012-01-18 16:15:00.000','2012-01-19 00:00:00.000')
INSERT INTO #tbl(EmployeeID,InTime,OutTime) VALUES (4,'2012-01-18 00:00:00.000','2012-01-18 08:15:00.000')
SELECT
tbl.EmployeeId,
tbl.InTime,
DATEADD(dd, DATEDIFF(dd, 0, tbl.OutTime), 0) AS OutTime
FROM
#tbl AS tbl
WHERE
DATEDIFF(dd,tbl.InTime,tbl.OutTime)=1
UNION ALL
SELECT
tbl.EmployeeId,
CASE WHEN DATEDIFF(dd,tbl.InTime,tbl.OutTime)=1
THEN DATEADD(dd, DATEDIFF(dd, 0, tbl.OutTime), 0)
ELSE tbl.InTime
END AS InTime,
tbl.OutTime
FROM #tbl AS tbl
ORDER BY EmployeeId
The following solution uses a numbers table (in the form of a subset of the master..spt_values system table) to split the time ranges. It can split ranges spanning an arbitrary number of days (up to 2048 with spt_values, but with your own numbers table you can set a different maximum). The specific cases of 1- and 2-day spanning ranges are not addressed here, but I believe the method is lightweight enough for you to try:
;
WITH LaborHours (EmployeeId, InTime, OutTime) AS (
SELECT
1,
CAST('2012-01-18 19:50:04.437' AS datetime),
CAST('2012-01-18 03:30:02.433' AS datetime)
),
HoursSplit AS (
SELECT
h.*,
SubInTime = DATEADD(DAY, DATEDIFF(DAY, 0, h.InTime) + v.number + 0, 0),
SubOutTime = DATEADD(DAY, DATEDIFF(DAY, 0, h.InTime) + v.number + 1, 0)
FROM LaborHours h
INNER JOIN master..spt_values v
ON number BETWEEN 0 AND DATEDIFF(DAY, h.InTime, h.OutTime)
WHERE v.type = 'P'
),
HoursSubstituted AS (
SELECT
EmployeeId,
InTime = CASE WHEN InTime > SubInTime THEN InTime ELSE SubInTime END,
OutTime = CASE WHEN OutTime < SubOutTime THEN OutTime ELSE SubOutTime END
FROM HoursSplit
)
SELECT *
FROM HoursSubstituted
Basically, it's a two-step method.
First we use the numbers table to duplicate every row so many times as the number of days the range spans and to prepare ‘standard’ sub-ranges starting at midnight and ending at the next midnight.
Next, we compare the beginning of a sub-range with the beginning of the range to see whether it is the first sub-range, in which case we use InTime as its beginning. Similarly, we compare the endings to see whether we should use OutTime or just the midnight as the end of that subrange.
If for the report, then you should just be able to do a query / union that give two records during those conditions from the original one starting... Without having SQL-Server 2008, I can only offer a pseudo-code query for you.
The first part of the gets all records based on whatever your range condition to show. The value of the "OutTime" is conditional... if its on the same day, then no cross over, just use the out time. If it IS on the next day, use casting to dynamically build out a 'YYYY-MM-DD' date (which will default to 00:00:00 time) as you want as the OUT time.
The UNION will ONLY grab those same records qualified in the FIRST where the in/out dates are DIFFERENT. As such, we KNOW we want whatever the OutTime was to act as the InTime, but based on the "00:00:00" time, so the exact same casting of a date/time field is performed, and for these records, just use the final "OutTime" value as-is.
The extra column for "TimeSplit" of '1' or '2' is to make sure that we can still group by employee ID, but from that, ensure that the '1' entries (starting shift) are first, followed by any for the respective same person have a '2' entry for the day overlap in their shift.
select
tc.EmployeeID,
'1' as TimeSplit,
tc.InTime,
case when datepart( dd, tc.InTime ) = datepart( dd, tc.OutTime )
then tc.OutTime
else CAST( CAST( datepart(yyyy, tc.OutTime ) AS varchar)
+'-'+ CAST( datepart( mm, tc.OutTime ) AS varchar)
+'-'+ CAST( datepart( dd, tc.OutTime ) AS varchar) AS DATETIME)
end as OutTime
from
TimeCard tc
where
YourDateRangeConditions...
ORDER BY
tc.EmployeeID,
TimeSplit
UNION ALL
select
tc.EmployeeID,
'2' as TimeSplit,
CAST( CAST( datepart(yyyy, tc.OutTime ) AS varchar)
+'-'+ CAST( datepart( mm, tc.OutTime ) AS varchar)
+'-'+ CAST( datepart( dd, tc.OutTime ) AS varchar) AS DATETIME)
end as InTime
tc.OutTime
from
TimeCard tc
where
YourDateRangeConditions...
AND NOT datepart( dd, tc.InTime ) = datepart( dd, tc.OutTime )
Building on accepted answer, as a newbie on mysql I expanded the code to better understand each scenario
SELECT
tbl.EmployeeId,
Datediff(tbl.OutTime, tbl.InTime) as DD,
-- Change outTime to end of the day if shift is overnight
DATE_FORMAT(tbl.InTime, "%Y-%m-%d %H:%i:%s") as InTime,
CASE WHEN Datediff(tbl.OutTime, tbl.InTime) = 1
THEN DATE_FORMAT(tbl.OutTime, "%Y-%m-%d 00:00:00")
ELSE DATE_FORMAT(tbl.OutTime, "%Y-%m-%d %H:%i:%s")
END AS OutTime
FROM tbl
UNION DISTINCT
SELECT
tbl.EmployeeId,
Datediff(tbl.OutTime,tbl.InTime) as DD,
-- Change inTime to beginning of the next day if shift is overnight
CASE WHEN Datediff(tbl.OutTime,tbl.InTime) = 1
THEN DATE_FORMAT(tbl.OutTime, "%Y-%m-%d 00:00:00")
ELSE DATE_FORMAT(tbl.InTime, "%Y-%m-%d %H:%i:%s")
END AS InTime,
DATE_FORMAT(tbl.OutTime, "%Y-%m-%d %H:%i:%s") as OutTime
FROM tbl
order by EmployeeId
Try this, because you can do an insert off of a select and inside your select you can set the values to use to be different days.
For Adding the new row:
insert into table ("EMPLOYEE_ID","INTIME","OUTTIME") values
SELECT EMPLOYEE_ID,date(INTIME),OUTTIME
FROM table
where date(intime) < date(outtime)
Updating the original row:
update table
set outtime =date(outtime)
where date(intime)= date(outtime)

How to display roundof time

Using SQL Server 2005
Table1
ID Intime Outtime
001 00.21.00 00.48.00
002 08.23.00 13.45.00
003 00.34.00 00.18.00
I need to display the time time like 30 minutes or 1 Hours, it should display a roundoff time
Expected Output
ID Intime Outtime
001 00.30.00 01.00.00
002 08.30.00 14.00.00
003 01.00.00 00.30.00
How to make a query for the roundoff time.
You can round the current date to 30 minutes like:
select dateadd(mi, datediff(mi,0,getdate())/30*30, 0)
Explanation: this takes the number of minutes since the 0-date:
datediff(mi,0,getdate())
Then it rounds that to a multiple of 30 by dividing and multiplying by 30:
datediff(mi,0,getdate())/30*30
The result is added back to the 0-date to find the last 30 minute block
dateadd(mi, datediff(mi,0,getdate())/30*30, 0)
This can be adjusted easily for 60 minutes. :)
By checking the range
select ID,
DateAdd(mi, DateDiff(mi, 0, Intime +
case when InMi >= 15 then 30 - InMi else - InMi end), 0) as Intime,
DateAdd(mi, DateDiff(mi, 0, Outtime +
case when OutMi >= 15 then 30 - OutMi else - OutMi end), 0) as Outtime
FROM
(
select ID, Intime, Outtime,
datepart(mi, InTime) % 30 InMi,
datepart(mi, Outtime) % 30 OutMi
from tbl
) X
or by using the classical trick equivalent to Int(x+0.5)..
select ID,
dateadd(mi, ((datediff(mi, 0, Intime)+15)/30)*30, 0) Intime,
dateadd(mi, ((datediff(mi, 0, Outtime)+15)/30)*30, 0) Outtime
from tbl
IF you want to ROUNDUP instead
(you have a value going from 00.34.00 to 01.00.00) Then you need this
select ID,
dateadd(mi, ((datediff(mi, 0, Intime)+29)/30)*30, 0) Intime,
dateadd(mi, ((datediff(mi, 0, Outtime)+29)/30)*30, 0) Outtime
from tbl
Take a look at the DATEDIFF, DATEADD and DATEPART. You should be able to do what you want with that.
http://msdn.microsoft.com/en-us/library/ms189794.aspx
http://msdn.microsoft.com/en-us/library/ms186819.aspx
http://msdn.microsoft.com/en-us/library/ms174420.aspx
Here is kind of a step-by-step routine. I'm sure you can do something shorter and even more efficient. It would also simplify a lot if you used a datetime data type instead of a string.
declare #T table (id char(3), intime char(8), outtime char(8))
insert into #T values ('001', '00.21.00', '00.48.00')
insert into #T values ('002', '08.23.00', '13.45.00')
insert into #T values ('003', '00.34.00', '00.18.00')
;with
cteTime(id, intime, outtime)
as
( -- Convert to datetime
select
id,
cast(replace(intime, '.', ':') as datetime),
cast(replace(outtime, '.', ':') as datetime)
from #T
),
cteMinute(id, intime, outtime)
as
( -- Get the minute part
select
id,
datepart(mi, intime),
datepart(mi, outtime)
from cteTime
),
cteMinuteDiff(id, intime, outtime)
as
( -- Calcualte the desired diff
select
id,
case when intime > 30 then (60 - intime) else (30 - intime) end,
case when outtime > 30 then (60 - outtime) else (30 - outtime) end
from cteMinute
),
cteRoundTime(id, intime, outtime)
as
( -- Get the rounded time
select
cteTime.id,
dateadd(mi, cteMinuteDiff.intime, cteTime.intime),
dateadd(mi, cteMinuteDiff.outtime, cteTime.outtime)
from cteMinuteDiff
inner join cteTime
on cteMinuteDiff.id = cteTime.id
),
cteRoundedTimeParts(id, inHour, inMinute, outHour, outMinute)
as
( -- Split the time into parts
select
id,
cast(datepart(hh, intime) as varchar(2)) as inHour,
cast(datepart(mi, intime) as varchar(2)) as inMinute,
cast(datepart(hh, outtime) as varchar(2)) as outHour,
cast(datepart(mi, outtime) as varchar(2)) as outMinute
from cteRoundTime
),
cteRoundedTime(id, intime, outtime)
as
( -- Build the time string representation
select
id,
right('00'+inHour, 2)+'.'+right('00'+inMinute, 2)+'.00',
right('00'+outHour, 2)+'.'+right('00'+outMinute, 2)+'.00'
from cteRoundedTimeParts
)
select *
from cteRoundedTime