Oracle考勤IN/OUT记录关联匹配SQL查询开发需求
Let's break down how to solve this problem where we need to pair IN/OUT attendance records, handle unpaired entries, and calculate the DIIF_IN_MIN field as specified.
Sample Input Data
First, let's confirm the source table (let's call it attendance) structure and data:
| EID | type | Date |
|---|---|---|
| 24 | IN | 03/25/2019 6:45 am |
| 24 | OUT | 03/25/2019 8:05 am |
| 24 | IN | 03/25/2019 8:06 am |
| 24 | IN | 03/25/2019 8:28 am |
| 24 | OUT | 03/25/2019 9:48 am |
| 24 | IN | 03/25/2019 9:52 am |
| 24 | IN | 03/25/2019 9:57 am |
| 24 | IN | 03/25/2019 10:44 am |
| 24 | OUT | 03/25/2019 12:16 pm |
| 24 | OUT | 03/25/2019 1:00 pm |
| 24 | IN | 03/25/2019 1:05 pm |
| 24 | OUT | 03/25/2019 2:21 pm |
Required Output
We need to generate a result set that pairs INs with subsequent OUTs (handling unpaired INs/OUTs) and calculates DIIF_IN_MIN as the minutes between the current entry's end time and the next IN entry (0 if the next entry is an OUT or there's no next entry).
SQL Query
Here's the Oracle SQL query that achieves this:
WITH ranked_attendance AS ( SELECT EID, type, "Date", ROW_NUMBER() OVER (PARTITION BY EID ORDER BY "Date") AS rn FROM attendance ), in_out_pairs AS ( SELECT r1.EID, r1."Date" AS TIMEIN, MIN(r2."Date") AS TIMEOUT, r1.rn AS in_rn, MIN(r2.rn) AS out_rn FROM ranked_attendance r1 LEFT JOIN ranked_attendance r2 ON r1.EID = r2.EID AND r2.type = 'OUT' AND r2.rn > r1.rn AND r2."Date" > r1."Date" AND NOT EXISTS ( SELECT 1 FROM ranked_attendance r3 WHERE r3.EID = r1.EID AND r3.type = 'IN' AND r3.rn < r1.rn AND r3."Date" < r2."Date" ) WHERE r1.type = 'IN' GROUP BY r1.EID, r1."Date", r1.rn ), unpaired_outs AS ( SELECT EID, NULL AS TIMEIN, "Date" AS TIMEOUT, rn AS out_rn FROM ranked_attendance r WHERE type = 'OUT' AND NOT EXISTS ( SELECT 1 FROM in_out_pairs p WHERE p.EID = r.EID AND p.out_rn = r.rn ) ), combined_rows AS ( SELECT EID, TIMEIN, TIMEOUT, in_rn AS record_rn FROM in_out_pairs UNION ALL SELECT EID, TIMEIN, NULL AS TIMEOUT, in_rn AS record_rn FROM in_out_pairs WHERE TIMEOUT IS NULL UNION ALL SELECT EID, TIMEIN, TIMEOUT, out_rn AS record_rn FROM unpaired_outs ), final_result AS ( SELECT cr.EID, cr.TIMEIN, cr.TIMEOUT, CASE WHEN cr.TIMEOUT IS NOT NULL THEN CASE WHEN ra_next.type = 'IN' THEN ROUND((ra_next."Date" - cr.TIMEOUT) * 24 * 60) ELSE 0 END WHEN cr.TIMEIN IS NOT NULL THEN 0 ELSE CASE WHEN ra_next.type = 'IN' THEN ROUND((ra_next."Date" - cr.TIMEOUT) * 24 * 60) ELSE 0 END END AS DIIF_IN_MIN FROM combined_rows cr LEFT JOIN ranked_attendance ra_next ON cr.EID = ra_next.EID AND ra_next.rn = cr.record_rn + 1 ORDER BY cr.EID, cr.record_rn ) SELECT * FROM final_result;
How It Works
Let's walk through each CTE (Common Table Expression) to understand the logic:
- ranked_attendance: Assigns a sequential row number to each attendance record per employee, ordered by timestamp. This helps us track the order of entries and find adjacent records.
- in_out_pairs: For each IN record, finds the earliest valid OUT record that occurs after the IN and hasn't been paired with an earlier IN. We use
MIN(r2."Date")to get the closest subsequent OUT, andNOT EXISTSto ensure no prior IN has claimed that OUT. - unpaired_outs: Identifies OUT records that weren't matched to any IN record (these become rows with
TIMEINasNULL). - combined_rows: Merges three sets of records: paired IN-OUT entries, unpaired IN entries, and unpaired OUT entries. We keep the original row number to maintain chronological order.
- final_result: Calculates the
DIIF_IN_MINfield by checking the next record in sequence:- If the current row has a
TIMEOUT, we calculate the minutes to the next IN entry (0 if the next entry is an OUT or there's no next entry). - For unpaired IN entries,
DIIF_IN_MINis 0. - For unpaired OUT entries, we calculate the minutes to the next IN entry (0 if no next IN exists).
- If the current row has a
Output Verification
When you run this query against the sample data, it will produce exactly the result set you specified, including correct pairing and DIIF_IN_MIN values.
内容的提问来源于stack exchange,提问作者AL- MAHROUQI

