I spent the last 40mins looking on here and other websites for an answer. Background to the problem: my ERP system records a date and time when a labour booking is made. It records the booking date as datetime and the booking time as decimal(4)
. I'm trying to convert the time (decimal to datetime) so the date and time are one value.
Sample data:
Job Op Date Time
----------------------------------
1 10 06/01/2015 254
1 20 06/01/2015 254
1 20 06/01/2015 542
1 20 06/01/2015 1347
1 20 07/01/2015 1340
1 30 07/01/2015 1408
1 30 07/01/2015 1340
1 30 08/01/2015 1037
1 40 06/01/2015 543
1 40 06/01/2015 1348
1 40 08/01/2015 1038
1 50 07/01/2015 1219
1 50 08/01/2015 1039
1 60 07/01/2015 1220
1 60 10/01/2015 1054
1 60 12/01/2015 859
There can be multiple bookings in a day for the same operation. My ultimate aim is to find the earliest booking and latest booking for a operation on a job number.
Job Op Start End StartTime FinishTime
-------------------------------------------------------------
1 10 06/01/2015 06/01/2015 254 254
1 20 06/01/2015 07/01/2015 254 1340
1 30 07/01/2015 08/01/2015 1340 1037
1 40 06/01/2015 08/01/2015 543 1038
1 50 07/01/2015 08/01/2015 1219 1039
1 60 07/01/2015 12/01/2015 1220 859
You can do this with row_number()
and conditional aggregation:
select s.job, s.op,
max(case when seqnum = 1 then date end) as start,
max(case when seqnum = cnt then date end) as end,
max(case when seqnum = 1 then time end) as start_time,
max(case when seqnum = cnt then time end) as end_time
from (select s.*,
row_number() over (partition by job, op order by date, time) as seqnum,
count(*) over (partition by job, op) as cnt
from sample s
) s
group by job, op;
As a note: the title of your question seems to have nothing to do with what you want to accomplish.
You conversion from decimal(4, 0) to time is:
DATEADD(MINUTE, (FLOOR([Time] / 100) * 60) + ([Time] % 100), '00:00')
Which is just adding your total minutes to 00:00
to get a time, where your total minutes is calculated as (hours * 60) + minutes
where
Hours = FLOOR([Time] / 100)
Minutes = ([Time] % 100)
HOWEVER if it is not too late you should scrap this approach completely.
In SQL Server there is rarely a need to store date and time columns separately, the best approach is to have a single DATETIME2
column, and if for any reason you need separate columns (such as indexing) use computed columns eg
ALTER TABLE T ADD StartDate AS CAST(StartDateTime AS DATE);
If you must have the two columns stored separately at the very least you should use the TIME
data type to store time, not DECIMAL(4, 0)
. The best approach to your situation is to fix the actual problem, which is incorrect data types, rather than the question you have asked. If you were to do this you would have data like:
Job Op JobDateTime
----------------------------------
1 10 06/01/2015 02:54
1 20 06/01/2015 02:54
1 20 06/01/2015 05:42
1 20 06/01/2015 13:47
1 20 07/01/2015 13:40
1 30 07/01/2015 14:08
1 30 07/01/2015 13:40
1 30 08/01/2015 10:37
1 40 06/01/2015 05:43
etc.
Then your query is as simple as
SELECT JOb, Op, FirstJob = MIN(JobDateTime), LastJob = MAX(JobDateTime)
FROM T
GROUP BY JOb, Op;
The code to do this correction would be:
ALTER TABLE [YourTable] ADD JobDateTime DATETIME2 NOT NULL;
UPDATE [YourTable]
SET JobDateTime = DATEADD(MINUTE, (FLOOR([Time] / 100) * 60) + ([Time] % 100), CAST([Date] AS DATETIME)));
ALTER TABLE [YourTable] DROP COLUMN [Date];
ALTER TABLE [YourTable] DROP COLUMN [Time];
If you already have lots of existing code that uses these columns, you could use a computed column to do the calculation for you:
ALTER TABLE YourTable
ADD JobDateTime AS DATEADD(MINUTE, (FLOOR([Time] / 100) * 60) + ([Time] % 100), CAST([Date] AS DATETIME));
eg
CREATE TABLE #T (Job INT, Op INT, [Date] DATE, [Time] DECIMAL(4, 0));
INSERT #T (Job, Op, [Date], [Time])
VALUES
(1, 10, '2015-06-01', 254),
(1, 20, '2015-06-01', 254),
(1, 20, '2015-06-01', 542),
(1, 20, '2015-06-01', 1347),
(1, 20, '2015-07-01', 1340),
(1, 30, '2015-07-01', 1408),
(1, 30, '2015-07-01', 1340),
(1, 30, '2015-08-01', 1037),
(1, 40, '2015-06-01', 543),
(1, 40, '2015-06-01', 1348),
(1, 40, '2015-08-01', 1038),
(1, 50, '2015-07-01', 1219),
(1, 50, '2015-08-01', 1039),
(1, 60, '2015-07-01', 1220),
(1, 60, '2015-10-01', 1054),
(1, 60, '2015-12-01', 859);
ALTER TABLE #T
ADD JobDateTime AS DATEADD(MINUTE, (FLOOR([Time] / 100) * 60) + ([Time] % 100), CAST([Date] AS DATETIME));
SELECT Job,
Op,
FirstJob = MIN(JobDateTime),
LastJob = MAX(JobDateTime)
FROM #T
GROUP BY Job, Op;
As the above implies, I'd recommend just returning the date and time as a single column, but if you do want them as separate columns, then you should at least use the TIME
type:
Either way I would recommend returning a full date time, or at the very least using date and time columns, eg
SELECT Job,
Op,
[Start] = CAST(MIN(JobDateTime) AS DATE),
[End] = CAST(MAX(JobDateTime) AS DATE),
StartTime = CAST(MIN(JobDateTime) AS TIME),
EndTime = CAST(MAX(JobDateTime) AS TIME)
FROM #T
GROUP BY Job, Op;
If you can't make any schema changes you can just include the formula in your actual query (after you have been to punch your dba for using decimal to store time):
SELECT Job,
Op,
FirstJob = MIN(DATEADD(MINUTE, (FLOOR([Time] / 100) * 60) + ([Time] % 100), CAST([Date] AS DATETIME))),
LastJob = MAX(DATEADD(MINUTE, (FLOOR([Time] / 100) * 60) + ([Time] % 100), CAST([Date] AS DATETIME)))
FROM #T
GROUP BY Job, Op;
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.