按顺序获取特定系列值
Get specific series values in order
我有一个table包含每个员工的访问时间和移动信息。
有些员工两天之间轮班,我有列判断这个访问时间是属于前一天还是当天。
我需要收集属于一天的访问时间,然后获取当前访问时间与下一个访问时间之间的差异,并确定移动的检查状态(第一次或最后一次或白天移动)。
如果员工在同一天有轮班工作时间,我会得到上述结果,但如果他的工作时间介于两天之间,我就无法得到真实结果。
示例:
1 号员工的换班方法:17:00 -> 01:00
2 号员工有换班方法:08:30 -> 16:30
总工时:08:00
CREATE TABLE My_Table (
EMP_ID NUMBER(4) ,
Timeinout date ,
flag number(1)
);
INSERT INTO My_Table VALUES (1,to_date('03-07-2018 16:39:44','dd-mm-yyyy hh24:mi:ss'),0);
INSERT INTO My_Table VALUES (1,to_date('04-07-2018 01:14:40','dd-mm-yyyy hh24:mi:ss'),1);
INSERT INTO My_Table VALUES (1,to_date('04-07-2018 01:14:44 ','dd-mm-yyyy hh24:mi:ss'),1);
INSERT INTO My_Table VALUES (1,to_date('04-07-2018 16:14:52','dd-mm-yyyy hh24:mi:ss'),0);
INSERT INTO My_Table VALUES (1,to_date('05-07-2018 01:07:40','dd-mm-yyyy hh24:mi:ss'),1);
INSERT INTO My_Table VALUES (1,to_date('05-07-2018 01:07:44 ','dd-mm-yyyy hh24:mi:ss'),1);
INSERT INTO My_Table VALUES (1,to_date('05-07-2018 16:31:08','dd-mm-yyyy hh24:mi:ss'),0);
INSERT INTO My_Table VALUES (1,to_date('06-07-2018 01:01:48 ','dd-mm-yyyy hh24:mi:ss'),1);
INSERT INTO My_Table VALUES (1,to_date('06-07-2018 01:01:52','dd-mm-yyyy hh24:mi:ss'),1);
INSERT INTO My_Table VALUES (2,to_date('03-07-2018 08:37:40','dd-mm-yyyy hh24:mi:ss'),0);
INSERT INTO My_Table VALUES (2,to_date('03-07-2018 16:27:36','dd-mm-yyyy hh24:mi:ss'),0);
INSERT INTO My_Table VALUES (2,to_date('04-07-2018 08:37:04 ','dd-mm-yyyy hh24:mi:ss'),0);
INSERT INTO My_Table VALUES (2,to_date('04-07-2018 12:58:36','dd-mm-yyyy hh24:mi:ss'),0);
INSERT INTO My_Table VALUES (2,to_date('04-07-2018 13:09:48 ','dd-mm-yyyy hh24:mi:ss'),0);
INSERT INTO My_Table VALUES (2,to_date('04-07-2018 17:15:32 ','dd-mm-yyyy hh24:mi:ss'),0);
COMMIT;
我使用的查询是针对 employee number2 return 真实结果:
select X.*,
CASE
WHEN Previous_Movment IS NULL AND Next_Movment IS NOT NULL THEN
'First Check'
WHEN Previous_Movment IS NOT NULL AND Next_Movment IS NULL THEN
'Last Check'
when Previous_Movment IS NULL AND Next_Movment IS NULL THEN
'Do Not have access in this day'
ELSE
'During the Day'
end CHECK_sTATUS
from (select trunc(a.timeinout) as date_,
a.timeinout current_movment,
LEAD(TIMEINOUT) OVER(partition by trunc(TIMEINOUT), Emp_ID ORDER BY TIMEINOUT, Emp_ID) Next_Movment,
LAG(TIMEINOUT) OVER(partition by trunc(TIMEINOUT), Emp_ID ORDER BY TIMEINOUT, Emp_ID) Previous_Movment,
trunc(24 *
mod(LEAD(TIMEINOUT) OVER(partition by trunc(TIMEINOUT),
Emp_ID ORDER BY TIMEINOUT,
Emp_ID) - TIMEINOUT,
1)) as diff_hours,
trunc(mod(mod(LEAD(TIMEINOUT)
OVER(partition by trunc(TIMEINOUT),
Emp_ID ORDER BY TIMEINOUT,
Emp_ID) - TIMEINOUT,
1) * 24,
1) * 60) as diff_minus,
trunc(mod(mod(mod(LEAD(TIMEINOUT)
OVER(partition by trunc(TIMEINOUT),
Emp_ID ORDER BY TIMEINOUT,
Emp_ID) - TIMEINOUT,
1) * 24,
1) * 60,
1) * 60) as diff_sec,
Emp_ID,
FLAG Return_Previous_day_or_not
from My_table a
WHERE trunc(a.timeinout) between
to_Date('03-07-2018', 'dd-mm-yyyy') and
to_Date('07-07-2018', 'dd-mm-yyyy')
and a.Emp_ID = 2
) X
并得到以下员工编号 2 的结果:
但是当我将查询切换到员工编号 1 时,我得到了错误的结果,因为两天之间的变动:
注:
我在员工的 LEAD & lAG 函数中使用了 Trunc(Timeinout)谁有同一天的工作时间。
从 Oracle 12 开始,您可以使用 MATCH_RECOGNIZE
。
假设您希望轮班持续不超过 12 小时(您的轮班似乎是 8 小时,人们早到晚退)那么:
SELECT emp_id,
timeinout AS current_movement,
flag,
CASE
WHEN cls IN ('FIRST_CHECK', 'DURING_DAY')
THEN next_timeinout
END AS next_movement,
CASE
WHEN cls IN ('DURING_DAY', 'LAST_CHECK')
THEN prev_timeinout
END AS previous_movement,
CASE
WHEN cls IN ('FIRST_CHECK', 'DURING_DAY')
THEN (next_timeinout - timeinout) DAY TO SECOND
END AS diff,
cls AS check_status
FROM my_table
MATCH_RECOGNIZE(
PARTITION BY emp_id
ORDER BY timeinout
MEASURES
PREV(timeinout) AS prev_timeinout,
NEXT(timeinout) AS next_timeinout,
LAST(last_check.timeinout) AS last_timeinout,
CLASSIFIER() AS cls
ALL ROWS PER MATCH
PATTERN (first_check (during_day* last_check)?)
DEFINE
during_day AS timeinout < first_check.timeinout + INTERVAL '12' HOUR,
last_check AS timeinout < first_check.timeinout + INTERVAL '12' HOUR
) m
对于您的示例数据,输出:
EMP_ID
CURRENT_MOVEMENT
FLAG
NEXT_MOVEMENT
PREVIOUS_MOVEMENT
DIFF
CHECK_STATUS
1
2018-07-03T16:39:44
0
2018-07-04T01:14:40
+00 08:34:56.000000
FIRST_CHECK
1
2018-07-04T01:14:40
1
2018-07-04T01:14:44
2018-07-03T16:39:44
+00 00:00:04.000000
DURING_DAY
1
2018-07-04T01:14:44
1
2018-07-04T01:14:40
LAST_CHECK
1
2018-07-04T16:14:52
0
2018-07-05T01:07:40
+00 08:52:48.000000
FIRST_CHECK
1
2018-07-05T01:07:40
1
2018-07-05T01:07:44
2018-07-04T16:14:52
+00 00:00:04.000000
DURING_DAY
1
2018-07-05T01:07:44
1
2018-07-05T01:07:40
LAST_CHECK
1
2018-07-05T16:31:08
0
2018-07-06T01:01:48
+00 08:30:40.000000
FIRST_CHECK
1
2018-07-06T01:01:48
1
2018-07-06T01:01:52
2018-07-05T16:31:08
+00 00:00:04.000000
DURING_DAY
1
2018-07-06T01:01:52
1
2018-07-06T01:01:48
LAST_CHECK
2
2018-07-03T08:37:40
0
2018-07-03T16:27:36
+00 07:49:56.000000
FIRST_CHECK
2
2018-07-03T16:27:36
0
2018-07-03T08:37:40
LAST_CHECK
2
2018-07-04T08:37:04
0
2018-07-04T12:58:36
+00 04:21:32.000000
FIRST_CHECK
2
2018-07-04T12:58:36
0
2018-07-04T13:09:48
2018-07-04T08:37:04
+00 00:11:12.000000
DURING_DAY
2
2018-07-04T13:09:48
0
2018-07-04T17:15:32
2018-07-04T12:58:36
+00 04:05:44.000000
DURING_DAY
2
2018-07-04T17:15:32
0
2018-07-04T13:09:48
LAST_CHECK
db<>fiddle here
从 Oracle 11gR2 开始,您可以使用递归子查询:
WITH ordered_data AS (
SELECT m.*,
ROW_NUMBER() OVER (PARTITION BY emp_id ORDER BY timeinout) AS rn,
LAG(timeinout) OVER (PARTITION BY emp_id ORDER BY timeinout)
AS previous_movement,
LEAD(timeinout) OVER (PARTITION BY emp_id ORDER BY timeinout)
AS next_movement
FROM my_table m
),
grouped_data (
emp_id,
current_movement,
flag,
rn,
previous_movement,
next_movement,
grp,
check_status,
first_movement
) AS (
SELECT emp_id,
timeinout,
flag,
rn,
NULL,
next_movement,
1,
'FIRST_CHECK',
timeinout
FROM ordered_data
WHERE rn = 1
UNION ALL
SELECT o.emp_id,
o.timeinout AS current_movement,
o.flag,
o.rn,
CASE
WHEN o.timeinout < g.first_movement + INTERVAL '12' HOUR
THEN o.previous_movement
ELSE NULL
END,
CASE
WHEN o.timeinout < g.first_movement + INTERVAL '12' HOUR
AND ( o.next_movement >= g.first_movement + INTERVAL '12' HOUR
OR o.next_movement IS NULL)
THEN NULL
ELSE o.next_movement
END,
CASE
WHEN o.timeinout < g.first_movement + INTERVAL '12' HOUR
THEN g.grp
ELSE g.grp + 1
END,
CASE
WHEN o.timeinout < g.first_movement + INTERVAL '12' HOUR
AND ( o.next_movement >= g.first_movement + INTERVAL '12' HOUR
OR o.next_movement IS NULL)
THEN 'LAST_CHECK'
WHEN o.timeinout < g.first_movement + INTERVAL '12' HOUR
THEN 'DURING_DAY'
ELSE 'FIRST_CHECK'
END,
CASE
WHEN o.timeinout < g.first_movement + INTERVAL '12' HOUR
THEN g.first_movement
ELSE o.timeinout
END
FROM ordered_data o
INNER JOIN grouped_data g
ON (o.emp_id = g.emp_id AND o.rn = g.rn + 1)
)
SELECT emp_id,
current_movement,
flag,
previous_movement,
next_movement,
(next_movement - current_movement) DAY TO SECOND AS diff,
check_status
FROM grouped_data
ORDER BY emp_id, current_movement
db<>fiddle here
您可以使用 PIPELINED
函数:
CREATE TYPE my_table_type AS OBJECT (
emp_id NUMBER,
current_movement DATE,
flag NUMBER,
previous_movement DATE,
next_movement DATE,
diff INTERVAL DAY TO SECOND,
check_status VARCHAR2(15)
);
CREATE TYPE my_table_tbltype AS TABLE OF my_table_type;
然后:
CREATE FUNCTION parse_my_table RETURN my_table_tbltype PIPELINED
AS
c_shift_length CONSTANT INTERVAL DAY TO SECOND := INTERVAL '12' HOUR;
v_emp_id MY_TABLE.EMP_ID%TYPE;
v_first_movement MY_TABLE.TIMEINOUT%TYPE;
v_previous_movement MY_TABLE.TIMEINOUT%TYPE;
v_next_movement MY_TABLE.TIMEINOUT%TYPE;
v_check_status VARCHAR2(15);
BEGIN
FOR row IN (
SELECT m.*,
LAG(timeinout) OVER (PARTITION BY emp_id ORDER BY timeinout)
AS previous_movement,
LEAD(timeinout) OVER (PARTITION BY emp_id ORDER BY timeinout)
AS next_movement
FROM my_table m
ORDER BY m.emp_id, m.timeinout
)
LOOP
IF v_emp_id IS NULL
OR v_emp_id != row.emp_id
OR row.timeinout >= v_first_movement + c_shift_length THEN
v_emp_id := row.emp_id;
v_first_movement := row.timeinout;
v_previous_movement := NULL;
v_next_movement := row.next_movement;
v_check_status := 'FIRST_CHECK';
ELSIF row.next_movement >= v_first_movement + c_shift_length
OR row.next_movement IS NULL THEN
v_previous_movement := row.previous_movement;
v_next_movement := NULL;
v_check_status := 'LAST_CHECK';
ELSE
v_previous_movement := row.previous_movement;
v_next_movement := row.next_movement;
v_check_status := 'DURING_DAY';
END IF;
PIPE ROW (
my_table_type(
row.emp_id,
row.timeinout,
row.flag,
v_previous_movement,
v_next_movement,
(v_next_movement - row.timeinout) DAY TO SECOND,
v_check_status
)
);
END LOOP;
END;
/
然后:
SELECT *
FROM TABLE(parse_my_table());
输出:
EMP_ID
CURRENT_MOVEMENT
FLAG
PREVIOUS_MOVEMENT
NEXT_MOVEMENT
DIFF
CHECK_STATUS
1
2018-07-03T16:39:44
0
2018-07-04T01:14:40
+00 08:34:56.000000
FIRST_CHECK
1
2018-07-04T01:14:40
1
2018-07-03T16:39:44
2018-07-04T01:14:44
+00 00:00:04.000000
DURING_DAY
1
2018-07-04T01:14:44
1
2018-07-04T01:14:40
LAST_CHECK
1
2018-07-04T16:14:52
0
2018-07-05T01:07:40
+00 08:52:48.000000
FIRST_CHECK
1
2018-07-05T01:07:40
1
2018-07-04T16:14:52
2018-07-05T01:07:44
+00 00:00:04.000000
DURING_DAY
1
2018-07-05T01:07:44
1
2018-07-05T01:07:40
LAST_CHECK
1
2018-07-05T16:31:08
0
2018-07-06T01:01:48
+00 08:30:40.000000
FIRST_CHECK
1
2018-07-06T01:01:48
1
2018-07-05T16:31:08
2018-07-06T01:01:52
+00 00:00:04.000000
DURING_DAY
1
2018-07-06T01:01:52
1
2018-07-06T01:01:48
LAST_CHECK
2
2018-07-03T08:37:40
0
2018-07-03T16:27:36
+00 07:49:56.000000
FIRST_CHECK
2
2018-07-03T16:27:36
0
2018-07-03T08:37:40
LAST_CHECK
2
2018-07-04T08:37:04
0
2018-07-04T12:58:36
+00 04:21:32.000000
FIRST_CHECK
2
2018-07-04T12:58:36
0
2018-07-04T08:37:04
2018-07-04T13:09:48
+00 00:11:12.000000
DURING_DAY
2
2018-07-04T13:09:48
0
2018-07-04T12:58:36
2018-07-04T17:15:32
+00 04:05:44.000000
DURING_DAY
2
2018-07-04T17:15:32
0
2018-07-04T13:09:48
LAST_CHECK
db<>fiddle here
您可以使用 MODEL
子句:
SELECT emp_id,
current_movement,
flag,
previous_movement,
next_movement,
(next_movement - current_movement) DAY TO SECOND AS diff,
check_status
FROM (
SELECT m.*,
ROW_NUMBER() OVER (PARTITION BY emp_id ORDER BY timeinout) AS rn
FROM my_table m
)
MODEL
PARTITION BY (emp_id)
DIMENSION BY (rn)
MEASURES (
timeinout AS current_movement,
flag,
CAST(NULL AS DATE) AS first_movement,
CAST(NULL AS DATE) AS previous_movement,
CAST(NULL AS DATE) AS next_movement,
CAST(NULL AS VARCHAR2(11)) AS check_status
)
RULES (
first_movement[1] = current_movement[1],
first_movement[rn>1] = CASE
WHEN current_movement[cv(rn)] < first_movement[cv(rn)-1] + INTERVAL '12' HOUR
THEN first_movement[cv(rn)-1]
ELSE current_movement[cv(rn)]
END,
previous_movement[1] = NULL,
previous_movement[rn>1] = CASE
WHEN current_movement[cv(rn)] < first_movement[cv(rn)-1] + INTERVAL '12' HOUR
THEN current_movement[cv(rn)-1]
ELSE NULL
END,
next_movement[1] = current_movement[cv(rn)+1],
next_movement[rn>1] = CASE
WHEN current_movement[cv(rn)+1] < first_movement[cv(rn)] + INTERVAL '12' HOUR
THEN current_movement[cv(rn)+1]
ELSE NULL
END,
check_status[1] = 'FIRST_CHECK',
check_status[rn>1] = CASE
WHEN first_movement[cv(rn)-1] != first_movement[cv(rn)]
THEN 'FIRST_CHECK'
WHEN current_movement[cv(rn)+1] < first_movement[cv(rn)] + INTERVAL '12' HOUR
THEN 'DURING_DAY'
ELSE 'LAST_CHECK'
END
)
输出:
EMP_ID
CURRENT_MOVEMENT
FLAG
PREVIOUS_MOVEMENT
NEXT_MOVEMENT
DIFF
CHECK_STATUS
1
2018-07-03T16:39:44
0
2018-07-04T01:14:40
+00 08:34:56.000000
FIRST_CHECK
1
2018-07-04T01:14:40
1
2018-07-03T16:39:44
2018-07-04T01:14:44
+00 00:00:04.000000
DURING_DAY
1
2018-07-04T01:14:44
1
2018-07-04T01:14:40
LAST_CHECK
1
2018-07-04T16:14:52
0
2018-07-05T01:07:40
+00 08:52:48.000000
FIRST_CHECK
1
2018-07-05T01:07:40
1
2018-07-04T16:14:52
2018-07-05T01:07:44
+00 00:00:04.000000
DURING_DAY
1
2018-07-05T01:07:44
1
2018-07-05T01:07:40
LAST_CHECK
1
2018-07-05T16:31:08
0
2018-07-06T01:01:48
+00 08:30:40.000000
FIRST_CHECK
1
2018-07-06T01:01:48
1
2018-07-05T16:31:08
2018-07-06T01:01:52
+00 00:00:04.000000
DURING_DAY
1
2018-07-06T01:01:52
1
2018-07-06T01:01:48
LAST_CHECK
2
2018-07-03T08:37:40
0
2018-07-03T16:27:36
+00 07:49:56.000000
FIRST_CHECK
2
2018-07-03T16:27:36
0
2018-07-03T08:37:40
LAST_CHECK
2
2018-07-04T08:37:04
0
2018-07-04T12:58:36
+00 04:21:32.000000
FIRST_CHECK
2
2018-07-04T12:58:36
0
2018-07-04T08:37:04
2018-07-04T13:09:48
+00 00:11:12.000000
DURING_DAY
2
2018-07-04T13:09:48
0
2018-07-04T12:58:36
2018-07-04T17:15:32
+00 04:05:44.000000
DURING_DAY
2
2018-07-04T17:15:32
0
2018-07-04T13:09:48
LAST_CHECK
db<>fiddle here
我有一个table包含每个员工的访问时间和移动信息。
有些员工两天之间轮班,我有列判断这个访问时间是属于前一天还是当天。
我需要收集属于一天的访问时间,然后获取当前访问时间与下一个访问时间之间的差异,并确定移动的检查状态(第一次或最后一次或白天移动)。
如果员工在同一天有轮班工作时间,我会得到上述结果,但如果他的工作时间介于两天之间,我就无法得到真实结果。
示例:
1 号员工的换班方法:17:00 -> 01:00
2 号员工有换班方法:08:30 -> 16:30
总工时:08:00
CREATE TABLE My_Table (
EMP_ID NUMBER(4) ,
Timeinout date ,
flag number(1)
);
INSERT INTO My_Table VALUES (1,to_date('03-07-2018 16:39:44','dd-mm-yyyy hh24:mi:ss'),0);
INSERT INTO My_Table VALUES (1,to_date('04-07-2018 01:14:40','dd-mm-yyyy hh24:mi:ss'),1);
INSERT INTO My_Table VALUES (1,to_date('04-07-2018 01:14:44 ','dd-mm-yyyy hh24:mi:ss'),1);
INSERT INTO My_Table VALUES (1,to_date('04-07-2018 16:14:52','dd-mm-yyyy hh24:mi:ss'),0);
INSERT INTO My_Table VALUES (1,to_date('05-07-2018 01:07:40','dd-mm-yyyy hh24:mi:ss'),1);
INSERT INTO My_Table VALUES (1,to_date('05-07-2018 01:07:44 ','dd-mm-yyyy hh24:mi:ss'),1);
INSERT INTO My_Table VALUES (1,to_date('05-07-2018 16:31:08','dd-mm-yyyy hh24:mi:ss'),0);
INSERT INTO My_Table VALUES (1,to_date('06-07-2018 01:01:48 ','dd-mm-yyyy hh24:mi:ss'),1);
INSERT INTO My_Table VALUES (1,to_date('06-07-2018 01:01:52','dd-mm-yyyy hh24:mi:ss'),1);
INSERT INTO My_Table VALUES (2,to_date('03-07-2018 08:37:40','dd-mm-yyyy hh24:mi:ss'),0);
INSERT INTO My_Table VALUES (2,to_date('03-07-2018 16:27:36','dd-mm-yyyy hh24:mi:ss'),0);
INSERT INTO My_Table VALUES (2,to_date('04-07-2018 08:37:04 ','dd-mm-yyyy hh24:mi:ss'),0);
INSERT INTO My_Table VALUES (2,to_date('04-07-2018 12:58:36','dd-mm-yyyy hh24:mi:ss'),0);
INSERT INTO My_Table VALUES (2,to_date('04-07-2018 13:09:48 ','dd-mm-yyyy hh24:mi:ss'),0);
INSERT INTO My_Table VALUES (2,to_date('04-07-2018 17:15:32 ','dd-mm-yyyy hh24:mi:ss'),0);
COMMIT;
我使用的查询是针对 employee number2 return 真实结果:
select X.*,
CASE
WHEN Previous_Movment IS NULL AND Next_Movment IS NOT NULL THEN
'First Check'
WHEN Previous_Movment IS NOT NULL AND Next_Movment IS NULL THEN
'Last Check'
when Previous_Movment IS NULL AND Next_Movment IS NULL THEN
'Do Not have access in this day'
ELSE
'During the Day'
end CHECK_sTATUS
from (select trunc(a.timeinout) as date_,
a.timeinout current_movment,
LEAD(TIMEINOUT) OVER(partition by trunc(TIMEINOUT), Emp_ID ORDER BY TIMEINOUT, Emp_ID) Next_Movment,
LAG(TIMEINOUT) OVER(partition by trunc(TIMEINOUT), Emp_ID ORDER BY TIMEINOUT, Emp_ID) Previous_Movment,
trunc(24 *
mod(LEAD(TIMEINOUT) OVER(partition by trunc(TIMEINOUT),
Emp_ID ORDER BY TIMEINOUT,
Emp_ID) - TIMEINOUT,
1)) as diff_hours,
trunc(mod(mod(LEAD(TIMEINOUT)
OVER(partition by trunc(TIMEINOUT),
Emp_ID ORDER BY TIMEINOUT,
Emp_ID) - TIMEINOUT,
1) * 24,
1) * 60) as diff_minus,
trunc(mod(mod(mod(LEAD(TIMEINOUT)
OVER(partition by trunc(TIMEINOUT),
Emp_ID ORDER BY TIMEINOUT,
Emp_ID) - TIMEINOUT,
1) * 24,
1) * 60,
1) * 60) as diff_sec,
Emp_ID,
FLAG Return_Previous_day_or_not
from My_table a
WHERE trunc(a.timeinout) between
to_Date('03-07-2018', 'dd-mm-yyyy') and
to_Date('07-07-2018', 'dd-mm-yyyy')
and a.Emp_ID = 2
) X
并得到以下员工编号 2 的结果:
但是当我将查询切换到员工编号 1 时,我得到了错误的结果,因为两天之间的变动:
我在员工的 LEAD & lAG 函数中使用了 Trunc(Timeinout)谁有同一天的工作时间。
从 Oracle 12 开始,您可以使用 MATCH_RECOGNIZE
。
假设您希望轮班持续不超过 12 小时(您的轮班似乎是 8 小时,人们早到晚退)那么:
SELECT emp_id,
timeinout AS current_movement,
flag,
CASE
WHEN cls IN ('FIRST_CHECK', 'DURING_DAY')
THEN next_timeinout
END AS next_movement,
CASE
WHEN cls IN ('DURING_DAY', 'LAST_CHECK')
THEN prev_timeinout
END AS previous_movement,
CASE
WHEN cls IN ('FIRST_CHECK', 'DURING_DAY')
THEN (next_timeinout - timeinout) DAY TO SECOND
END AS diff,
cls AS check_status
FROM my_table
MATCH_RECOGNIZE(
PARTITION BY emp_id
ORDER BY timeinout
MEASURES
PREV(timeinout) AS prev_timeinout,
NEXT(timeinout) AS next_timeinout,
LAST(last_check.timeinout) AS last_timeinout,
CLASSIFIER() AS cls
ALL ROWS PER MATCH
PATTERN (first_check (during_day* last_check)?)
DEFINE
during_day AS timeinout < first_check.timeinout + INTERVAL '12' HOUR,
last_check AS timeinout < first_check.timeinout + INTERVAL '12' HOUR
) m
对于您的示例数据,输出:
EMP_ID CURRENT_MOVEMENT FLAG NEXT_MOVEMENT PREVIOUS_MOVEMENT DIFF CHECK_STATUS 1 2018-07-03T16:39:44 0 2018-07-04T01:14:40 +00 08:34:56.000000 FIRST_CHECK 1 2018-07-04T01:14:40 1 2018-07-04T01:14:44 2018-07-03T16:39:44 +00 00:00:04.000000 DURING_DAY 1 2018-07-04T01:14:44 1 2018-07-04T01:14:40 LAST_CHECK 1 2018-07-04T16:14:52 0 2018-07-05T01:07:40 +00 08:52:48.000000 FIRST_CHECK 1 2018-07-05T01:07:40 1 2018-07-05T01:07:44 2018-07-04T16:14:52 +00 00:00:04.000000 DURING_DAY 1 2018-07-05T01:07:44 1 2018-07-05T01:07:40 LAST_CHECK 1 2018-07-05T16:31:08 0 2018-07-06T01:01:48 +00 08:30:40.000000 FIRST_CHECK 1 2018-07-06T01:01:48 1 2018-07-06T01:01:52 2018-07-05T16:31:08 +00 00:00:04.000000 DURING_DAY 1 2018-07-06T01:01:52 1 2018-07-06T01:01:48 LAST_CHECK 2 2018-07-03T08:37:40 0 2018-07-03T16:27:36 +00 07:49:56.000000 FIRST_CHECK 2 2018-07-03T16:27:36 0 2018-07-03T08:37:40 LAST_CHECK 2 2018-07-04T08:37:04 0 2018-07-04T12:58:36 +00 04:21:32.000000 FIRST_CHECK 2 2018-07-04T12:58:36 0 2018-07-04T13:09:48 2018-07-04T08:37:04 +00 00:11:12.000000 DURING_DAY 2 2018-07-04T13:09:48 0 2018-07-04T17:15:32 2018-07-04T12:58:36 +00 04:05:44.000000 DURING_DAY 2 2018-07-04T17:15:32 0 2018-07-04T13:09:48 LAST_CHECK
db<>fiddle here
从 Oracle 11gR2 开始,您可以使用递归子查询:
WITH ordered_data AS (
SELECT m.*,
ROW_NUMBER() OVER (PARTITION BY emp_id ORDER BY timeinout) AS rn,
LAG(timeinout) OVER (PARTITION BY emp_id ORDER BY timeinout)
AS previous_movement,
LEAD(timeinout) OVER (PARTITION BY emp_id ORDER BY timeinout)
AS next_movement
FROM my_table m
),
grouped_data (
emp_id,
current_movement,
flag,
rn,
previous_movement,
next_movement,
grp,
check_status,
first_movement
) AS (
SELECT emp_id,
timeinout,
flag,
rn,
NULL,
next_movement,
1,
'FIRST_CHECK',
timeinout
FROM ordered_data
WHERE rn = 1
UNION ALL
SELECT o.emp_id,
o.timeinout AS current_movement,
o.flag,
o.rn,
CASE
WHEN o.timeinout < g.first_movement + INTERVAL '12' HOUR
THEN o.previous_movement
ELSE NULL
END,
CASE
WHEN o.timeinout < g.first_movement + INTERVAL '12' HOUR
AND ( o.next_movement >= g.first_movement + INTERVAL '12' HOUR
OR o.next_movement IS NULL)
THEN NULL
ELSE o.next_movement
END,
CASE
WHEN o.timeinout < g.first_movement + INTERVAL '12' HOUR
THEN g.grp
ELSE g.grp + 1
END,
CASE
WHEN o.timeinout < g.first_movement + INTERVAL '12' HOUR
AND ( o.next_movement >= g.first_movement + INTERVAL '12' HOUR
OR o.next_movement IS NULL)
THEN 'LAST_CHECK'
WHEN o.timeinout < g.first_movement + INTERVAL '12' HOUR
THEN 'DURING_DAY'
ELSE 'FIRST_CHECK'
END,
CASE
WHEN o.timeinout < g.first_movement + INTERVAL '12' HOUR
THEN g.first_movement
ELSE o.timeinout
END
FROM ordered_data o
INNER JOIN grouped_data g
ON (o.emp_id = g.emp_id AND o.rn = g.rn + 1)
)
SELECT emp_id,
current_movement,
flag,
previous_movement,
next_movement,
(next_movement - current_movement) DAY TO SECOND AS diff,
check_status
FROM grouped_data
ORDER BY emp_id, current_movement
db<>fiddle here
您可以使用 PIPELINED
函数:
CREATE TYPE my_table_type AS OBJECT (
emp_id NUMBER,
current_movement DATE,
flag NUMBER,
previous_movement DATE,
next_movement DATE,
diff INTERVAL DAY TO SECOND,
check_status VARCHAR2(15)
);
CREATE TYPE my_table_tbltype AS TABLE OF my_table_type;
然后:
CREATE FUNCTION parse_my_table RETURN my_table_tbltype PIPELINED
AS
c_shift_length CONSTANT INTERVAL DAY TO SECOND := INTERVAL '12' HOUR;
v_emp_id MY_TABLE.EMP_ID%TYPE;
v_first_movement MY_TABLE.TIMEINOUT%TYPE;
v_previous_movement MY_TABLE.TIMEINOUT%TYPE;
v_next_movement MY_TABLE.TIMEINOUT%TYPE;
v_check_status VARCHAR2(15);
BEGIN
FOR row IN (
SELECT m.*,
LAG(timeinout) OVER (PARTITION BY emp_id ORDER BY timeinout)
AS previous_movement,
LEAD(timeinout) OVER (PARTITION BY emp_id ORDER BY timeinout)
AS next_movement
FROM my_table m
ORDER BY m.emp_id, m.timeinout
)
LOOP
IF v_emp_id IS NULL
OR v_emp_id != row.emp_id
OR row.timeinout >= v_first_movement + c_shift_length THEN
v_emp_id := row.emp_id;
v_first_movement := row.timeinout;
v_previous_movement := NULL;
v_next_movement := row.next_movement;
v_check_status := 'FIRST_CHECK';
ELSIF row.next_movement >= v_first_movement + c_shift_length
OR row.next_movement IS NULL THEN
v_previous_movement := row.previous_movement;
v_next_movement := NULL;
v_check_status := 'LAST_CHECK';
ELSE
v_previous_movement := row.previous_movement;
v_next_movement := row.next_movement;
v_check_status := 'DURING_DAY';
END IF;
PIPE ROW (
my_table_type(
row.emp_id,
row.timeinout,
row.flag,
v_previous_movement,
v_next_movement,
(v_next_movement - row.timeinout) DAY TO SECOND,
v_check_status
)
);
END LOOP;
END;
/
然后:
SELECT *
FROM TABLE(parse_my_table());
输出:
EMP_ID CURRENT_MOVEMENT FLAG PREVIOUS_MOVEMENT NEXT_MOVEMENT DIFF CHECK_STATUS 1 2018-07-03T16:39:44 0 2018-07-04T01:14:40 +00 08:34:56.000000 FIRST_CHECK 1 2018-07-04T01:14:40 1 2018-07-03T16:39:44 2018-07-04T01:14:44 +00 00:00:04.000000 DURING_DAY 1 2018-07-04T01:14:44 1 2018-07-04T01:14:40 LAST_CHECK 1 2018-07-04T16:14:52 0 2018-07-05T01:07:40 +00 08:52:48.000000 FIRST_CHECK 1 2018-07-05T01:07:40 1 2018-07-04T16:14:52 2018-07-05T01:07:44 +00 00:00:04.000000 DURING_DAY 1 2018-07-05T01:07:44 1 2018-07-05T01:07:40 LAST_CHECK 1 2018-07-05T16:31:08 0 2018-07-06T01:01:48 +00 08:30:40.000000 FIRST_CHECK 1 2018-07-06T01:01:48 1 2018-07-05T16:31:08 2018-07-06T01:01:52 +00 00:00:04.000000 DURING_DAY 1 2018-07-06T01:01:52 1 2018-07-06T01:01:48 LAST_CHECK 2 2018-07-03T08:37:40 0 2018-07-03T16:27:36 +00 07:49:56.000000 FIRST_CHECK 2 2018-07-03T16:27:36 0 2018-07-03T08:37:40 LAST_CHECK 2 2018-07-04T08:37:04 0 2018-07-04T12:58:36 +00 04:21:32.000000 FIRST_CHECK 2 2018-07-04T12:58:36 0 2018-07-04T08:37:04 2018-07-04T13:09:48 +00 00:11:12.000000 DURING_DAY 2 2018-07-04T13:09:48 0 2018-07-04T12:58:36 2018-07-04T17:15:32 +00 04:05:44.000000 DURING_DAY 2 2018-07-04T17:15:32 0 2018-07-04T13:09:48 LAST_CHECK
db<>fiddle here
您可以使用 MODEL
子句:
SELECT emp_id,
current_movement,
flag,
previous_movement,
next_movement,
(next_movement - current_movement) DAY TO SECOND AS diff,
check_status
FROM (
SELECT m.*,
ROW_NUMBER() OVER (PARTITION BY emp_id ORDER BY timeinout) AS rn
FROM my_table m
)
MODEL
PARTITION BY (emp_id)
DIMENSION BY (rn)
MEASURES (
timeinout AS current_movement,
flag,
CAST(NULL AS DATE) AS first_movement,
CAST(NULL AS DATE) AS previous_movement,
CAST(NULL AS DATE) AS next_movement,
CAST(NULL AS VARCHAR2(11)) AS check_status
)
RULES (
first_movement[1] = current_movement[1],
first_movement[rn>1] = CASE
WHEN current_movement[cv(rn)] < first_movement[cv(rn)-1] + INTERVAL '12' HOUR
THEN first_movement[cv(rn)-1]
ELSE current_movement[cv(rn)]
END,
previous_movement[1] = NULL,
previous_movement[rn>1] = CASE
WHEN current_movement[cv(rn)] < first_movement[cv(rn)-1] + INTERVAL '12' HOUR
THEN current_movement[cv(rn)-1]
ELSE NULL
END,
next_movement[1] = current_movement[cv(rn)+1],
next_movement[rn>1] = CASE
WHEN current_movement[cv(rn)+1] < first_movement[cv(rn)] + INTERVAL '12' HOUR
THEN current_movement[cv(rn)+1]
ELSE NULL
END,
check_status[1] = 'FIRST_CHECK',
check_status[rn>1] = CASE
WHEN first_movement[cv(rn)-1] != first_movement[cv(rn)]
THEN 'FIRST_CHECK'
WHEN current_movement[cv(rn)+1] < first_movement[cv(rn)] + INTERVAL '12' HOUR
THEN 'DURING_DAY'
ELSE 'LAST_CHECK'
END
)
输出:
EMP_ID CURRENT_MOVEMENT FLAG PREVIOUS_MOVEMENT NEXT_MOVEMENT DIFF CHECK_STATUS 1 2018-07-03T16:39:44 0 2018-07-04T01:14:40 +00 08:34:56.000000 FIRST_CHECK 1 2018-07-04T01:14:40 1 2018-07-03T16:39:44 2018-07-04T01:14:44 +00 00:00:04.000000 DURING_DAY 1 2018-07-04T01:14:44 1 2018-07-04T01:14:40 LAST_CHECK 1 2018-07-04T16:14:52 0 2018-07-05T01:07:40 +00 08:52:48.000000 FIRST_CHECK 1 2018-07-05T01:07:40 1 2018-07-04T16:14:52 2018-07-05T01:07:44 +00 00:00:04.000000 DURING_DAY 1 2018-07-05T01:07:44 1 2018-07-05T01:07:40 LAST_CHECK 1 2018-07-05T16:31:08 0 2018-07-06T01:01:48 +00 08:30:40.000000 FIRST_CHECK 1 2018-07-06T01:01:48 1 2018-07-05T16:31:08 2018-07-06T01:01:52 +00 00:00:04.000000 DURING_DAY 1 2018-07-06T01:01:52 1 2018-07-06T01:01:48 LAST_CHECK 2 2018-07-03T08:37:40 0 2018-07-03T16:27:36 +00 07:49:56.000000 FIRST_CHECK 2 2018-07-03T16:27:36 0 2018-07-03T08:37:40 LAST_CHECK 2 2018-07-04T08:37:04 0 2018-07-04T12:58:36 +00 04:21:32.000000 FIRST_CHECK 2 2018-07-04T12:58:36 0 2018-07-04T08:37:04 2018-07-04T13:09:48 +00 00:11:12.000000 DURING_DAY 2 2018-07-04T13:09:48 0 2018-07-04T12:58:36 2018-07-04T17:15:32 +00 04:05:44.000000 DURING_DAY 2 2018-07-04T17:15:32 0 2018-07-04T13:09:48 LAST_CHECK
db<>fiddle here