我需要Oracle sql在给定示例输入的情况下显示以下输出。 基本上,员工安排9小时轮班。 我需要在白天将活动分开来分开记录。 特别是现金的一般活动。我需要创建新记录。
Activity start time end time
Shift 2010-01-01 8:00:00 2010-01-01 17:00:00
Open 2010-01-01 8:00:00 2010-01-01 9:00:00
Cash 2010-01-01 9:00:00 2010-01-01 16:00:00
Break 2010-01-01 10:00:00 2010-01-01 10:15:00
Lunch 2010-01-01 12:00:00 2010-01-01 13:00:00
Break 2010-01-01 14:30:00 2010-01-01 14:45:00
Close 2010-01-01 16:00:00 2010-01-01 17:00:00
输出:
Activity start time end time
Open 2010-01-01 8:00:00 2010-01-01 9:00:00
Cash 2010-01-01 9:00:00 2010-01-01 10:00:00
Break 2010-01-01 10:00:00 2010-01-01 10:15:00
Cash 2010-01-01 10:15:00 2010-01-01 12:00:00
Lunch 2010-01-01 12:00:00 2010-01-01 13:00:00
Cash 2010-01-01 13:00:00 2010-01-01 14:30:00
Break 2010-01-01 14:30:00 2010-01-01 14:45:00
Cash 2010-01-01 14:45:00 2010-01-01 16:00:00
Close 2010-01-01 16:00:00 2010-01-01 17:00:00
非常感谢任何帮助。
答案 0 :(得分:1)
这是一种间隙和岛屿问题。假设总是有一个'开放'记录,其开头与'Shift'开始匹配,而'Close'记录的结尾与'Shift'结束;并且一般活动始终为“现金”,其开头与“开放”结束相匹配,其结束与“结束”开始相匹配;填补空白时,其中一些记录是多余的。
您可以使用超前和滞后功能生成虚拟“现金”记录,这些记录位于所有其他活动之间,向前看和向后看:
select activity orig_activity, start_time orig_start, end_time orig_end,
'Cash' as activity, lag(end_time) over (order by end_time) as start_time, start_time as end_time
from table1
where activity not in ('Shift', 'Cash')
union all
select activity orig_activity, start_time orig_start, end_time orig_end,
'Cash' as activity, end_time as start_time, lead(start_time) over (order by start_time) as end_time
from table1
where activity not in ('Shift', 'Cash')
order by orig_start;
ORIG_ ORIG_START ORIG_END ACTI START_TIME END_TIME
----- ------------------- ------------------- ---- ------------------- -------------------
Open 2010-01-01 08:00:00 2010-01-01 09:00:00 Cash 2010-01-01 08:00:00
Open 2010-01-01 08:00:00 2010-01-01 09:00:00 Cash 2010-01-01 09:00:00 2010-01-01 10:00:00
Break 2010-01-01 10:00:00 2010-01-01 10:15:00 Cash 2010-01-01 09:00:00 2010-01-01 10:00:00
Break 2010-01-01 10:00:00 2010-01-01 10:15:00 Cash 2010-01-01 10:15:00 2010-01-01 12:00:00
Lunch 2010-01-01 12:00:00 2010-01-01 13:00:00 Cash 2010-01-01 10:15:00 2010-01-01 12:00:00
Lunch 2010-01-01 12:00:00 2010-01-01 13:00:00 Cash 2010-01-01 13:00:00 2010-01-01 14:30:00
Break 2010-01-01 14:30:00 2010-01-01 14:45:00 Cash 2010-01-01 14:45:00 2010-01-01 16:00:00
Break 2010-01-01 14:30:00 2010-01-01 14:45:00 Cash 2010-01-01 13:00:00 2010-01-01 14:30:00
Close 2010-01-01 16:00:00 2010-01-01 17:00:00 Cash 2010-01-01 17:00:00
Close 2010-01-01 16:00:00 2010-01-01 17:00:00 Cash 2010-01-01 14:45:00 2010-01-01 16:00:00
例如,在休息之后和午餐之前,可以看到相同的差距。通过忽略原始值,您可以删除distinct
或union
而不是union all
的值。您还可以排除任何生成的行具有空的开始或结束时间,以及任何与其他记录重叠的行 - 如果其他两个活动是连续的,则可能会发生这种情况:
select activity, start_time, end_time from (
select 'Cash' as activity,
lag(end_time) over (order by end_time) as start_time,
start_time as end_time
from table1
where activity not in ('Shift', 'Cash')
union
select 'Cash' as activity,
end_time as start_time,
lead(start_time) over (order by start_time) as end_time
from table1
where activity not in ('Shift', 'Cash')
) tmp
where start_time is not null
and end_time is not null
and not exists (
select null from table1 where activity not in ('Shift', 'Cash') and (start_time = tmp.start_time or end_time = tmp.end_time)
)
order by start_time;
ACTI START_TIME END_TIME
---- ------------------- -------------------
Cash 2010-01-01 09:00:00 2010-01-01 10:00:00
Cash 2010-01-01 10:15:00 2010-01-01 12:00:00
Cash 2010-01-01 13:00:00 2010-01-01 14:30:00
Cash 2010-01-01 14:45:00 2010-01-01 16:00:00
然后,您可以将其与所有原始表行合并,但“现金”记录除外:
...
union all
select activity, start_time, end_time
from table1
where activity not in ('Shift', 'Cash')
order by start_time;
ACTIV START_TIME END_TIME
----- ------------------- -------------------
Open 2010-01-01 08:00:00 2010-01-01 09:00:00
Cash 2010-01-01 09:00:00 2010-01-01 10:00:00
Break 2010-01-01 10:00:00 2010-01-01 10:15:00
Cash 2010-01-01 10:15:00 2010-01-01 12:00:00
Lunch 2010-01-01 12:00:00 2010-01-01 13:00:00
Cash 2010-01-01 13:00:00 2010-01-01 14:30:00
Break 2010-01-01 14:30:00 2010-01-01 14:45:00
Cash 2010-01-01 14:45:00 2010-01-01 16:00:00
Close 2010-01-01 16:00:00 2010-01-01 17:00:00
这也假设活动从不重叠,但非'''活动可能是相邻的。
可能还有其他的差距和岛屿方法也可行。
答案 1 :(得分:1)
我将第二个亚历克斯的回答。但是,只是为了完全不同的东西,你可以找出班次中的不同秒数,找出每个人每秒做的事情,然后将这些分组到你的结果范围内。
我认为这比Alex的方法效率低,但可能更灵活:它没有考虑输入数据的外观。
with shift_data ( activity, start_time, end_time ) AS
-- This is just test data that would be in your database table
(
SELECT 'Shift',to_date('2010-01-01 8:00:00','YYYY-MM-DD HH24:MI:SS'),to_date('2010-01-01 17:00:00','YYYY-MM-DD HH24:MI:SS') FROM DUAL UNION ALL
SELECT 'Open',to_date('2010-01-01 8:00:00','YYYY-MM-DD HH24:MI:SS'),to_date('2010-01-01 9:00:00','YYYY-MM-DD HH24:MI:SS') FROM DUAL UNION ALL
SELECT 'Cash',to_date('2010-01-01 9:00:00','YYYY-MM-DD HH24:MI:SS'),to_date('2010-01-01 16:00:00','YYYY-MM-DD HH24:MI:SS') FROM DUAL UNION ALL
SELECT 'Break',to_date('2010-01-01 10:00:00','YYYY-MM-DD HH24:MI:SS'),to_date('2010-01-01 10:15:00','YYYY-MM-DD HH24:MI:SS') FROM DUAL UNION ALL
SELECT 'Lunch',to_date('2010-01-01 12:00:00','YYYY-MM-DD HH24:MI:SS'),to_date('2010-01-01 13:00:00','YYYY-MM-DD HH24:MI:SS') FROM DUAL UNION ALL
SELECT 'Break',to_date('2010-01-01 14:30:00','YYYY-MM-DD HH24:MI:SS'),to_date('2010-01-01 14:45:00','YYYY-MM-DD HH24:MI:SS') FROM DUAL UNION ALL
SELECT 'Close',to_date('2010-01-01 16:00:00','YYYY-MM-DD HH24:MI:SS'),to_date('2010-01-01 17:00:00','YYYY-MM-DD HH24:MI:SS') FROM DUAL
),
seconds_in_shift as (
-- Step 1: get a list of every second that falls in the shift
SELECT start_time + (ROWNUM - 1) / 86400 second
FROM shift_data
WHERE activity = 'Shift'
CONNECT BY ROWNUM <= ( (end_time - start_time) * 86400) + 1),
activity_each_second as (
-- Step 2: figure out what the person was doing every second. If multiple
-- activities overlap, choose whichever one had the shortest duration
-- Also, mark which seconds represent a transition from one activity to
-- another ("marker" column)
SELECT second,
MAX (activity) KEEP (DENSE_RANK FIRST ORDER BY end_time - start_time) activity,
CASE WHEN MAX (activity) KEEP (DENSE_RANK FIRST ORDER BY end_time - start_time)
!= NVL(LAG(MAX (activity) KEEP (DENSE_RANK FIRST ORDER BY end_time - start_time))
OVER ( PARTITION BY NULL ORDER BY SECOND),'#NULL#') THEN 'Y' ELSE NULL END marker
FROM seconds_in_shift ss
INNER JOIN shift_data sd ON ss.second BETWEEN sd.start_time AND sd.end_time
GROUP BY second),
ranges as (
-- Step 3: count the number of marker columns from the beginning of the shift
-- to the current second. Call this "activity_number".
select aes.*,
count(marker) OVER ( PARTITION BY NULL ORDER BY second) activity_number
from activity_each_second aes )
-- Finally, show the activity, start, and end time for each activity_number
SELECT activity,
round(min(second),'MI') start_time,
round(max(second),'MI') end_time
FROM ranges
GROUP BY activity, activity_number
ORDER BY activity_number;
结果:
Open 1/1/2010 8:00:00 A 1/1/2010 9:00:00 AM
Cash 1/1/2010 9:00:00 A 1/1/2010 10:00:00 AM
Break 1/1/2010 10:00:00 1/1/2010 10:15:00 AM
Cash 1/1/2010 10:15:00 1/1/2010 12:00:00 PM
Lunch 1/1/2010 12:00:00 1/1/2010 1:00:00 PM
Cash 1/1/2010 1:00:00 P 1/1/2010 2:30:00 PM
Break 1/1/2010 2:30:00 P 1/1/2010 2:45:00 PM
Cash 1/1/2010 2:45:00 P 1/1/2010 4:00:00 PM
Close 1/1/2010 4:00:00 P 1/1/2010 5:00:00 PM
注意:我通过将时间四舍五入到最近的分钟来作弊。如果没有四舍五入,则范围会有重叠。例如,4点在线点将是“现金”或“关闭”,它不会是两者。
答案 2 :(得分:1)
假设:所有活动间隔都在'Shift'
间隔内,两个活动间隔最多可能有一个共同的端点(它们可能相邻 - 但它们不能以任何方式重叠)。
此外,我假设您的表中可能有多名员工(因此必须解决),并且计算必须在每个日历日单独完成。您将在输入数据中看到这一点,并在查询中处理。
以下是仅使用分析lag()
函数获得所需结果的方法。它首先只收集与'Shift'
和'Cash'
不同的活动,然后填充'Cash'
的空白(包括'Shift'
的开头和/或结尾,如果没有特定活动(如'Open'
或'Close'
)在'Shift'
的开头或结尾处开始或结束。特别是'Shift'
区间,如输入中所示,在此解决方案中并不特别有用;你会看到我在下面的prep
调用的CTE中如何处理。
所以我不需要在任何地方输入nls_date_format
,我首先运行
alter session set nls_date_format = 'yyyy-mm-dd hh24:mi:ss'
然后:
with
table1 ( empno, activity, start_time, end_time ) as (
select 101, 'Shift', to_date('2010-01-01 8:00:00') , to_date('2010-01-01 17:00:00') from dual union all
select 101, 'Open' , to_date('2010-01-01 8:00:00') , to_date('2010-01-01 9:00:00') from dual union all
select 101, 'Cash' , to_date('2010-01-01 9:00:00') , to_date('2010-01-01 16:00:00') from dual union all
select 101, 'Break', to_date('2010-01-01 10:00:00'), to_date('2010-01-01 10:15:00') from dual union all
select 101, 'Lunch', to_date('2010-01-01 12:00:00'), to_date('2010-01-01 13:00:00') from dual union all
select 101, 'Break', to_date('2010-01-01 14:30:00'), to_date('2010-01-01 14:45:00') from dual union all
select 101, 'Close', to_date('2010-01-01 16:00:00'), to_date('2010-01-01 17:00:00') from dual
),
prep ( empno, activity, start_time, end_time, flag ) as (
select empno, activity, start_time, end_time, 1
from table1
where activity not in ('Shift', 'Cash')
union all select empno, 'Shift', start_time, start_time, 0
from table1
where activity = 'Shift'
union all select empno, 'Shift', end_time, end_time, 2
from table1
where activity = 'Shift'
),
with_cash_intervals ( empno, activity, start_time, end_time ) as (
select empno, activity, start_time, end_time
from prep
where activity != 'Shift'
union all
select empno, 'Cash', lag(end_time) over (partition by empno, trunc(start_time)
order by flag, start_time), start_time
from prep
)
select empno, activity, start_time, end_time
from with_cash_intervals
where start_time < end_time
order by empno, start_time -- if needed
输出:
EMPNO ACTIVITY START_TIME END_TIME
----- -------- ------------------- -------------------
101 Open 2010-01-01 08:00:00 2010-01-01 09:00:00
101 Cash 2010-01-01 09:00:00 2010-01-01 10:00:00
101 Break 2010-01-01 10:00:00 2010-01-01 10:15:00
101 Cash 2010-01-01 10:15:00 2010-01-01 12:00:00
101 Lunch 2010-01-01 12:00:00 2010-01-01 13:00:00
101 Cash 2010-01-01 13:00:00 2010-01-01 14:30:00
101 Break 2010-01-01 14:30:00 2010-01-01 14:45:00
101 Cash 2010-01-01 14:45:00 2010-01-01 16:00:00
101 Close 2010-01-01 16:00:00 2010-01-01 17:00:00
9 rows selected.