分割时间,给定2个间隔 | Oracle SQL

huangapple go评论62阅读模式
英文:

Splitting times given 2 intevals | Oracle SQL

问题

以下是您要翻译的内容:

我在一个组织工作,用户有时按“分割”时间表工作,数据结构如下所示:

用户 开始时间 结束时间 类型
用户1 3/29/23 8:00 AM 3/29/23 8:00 PM 开放
用户1 3/29/23 12:00 PM 3/29/23 4:00 PM 关闭
用户2 3/29/23 10:00 AM 3/29/23 10:00 PM 开放
用户2 3/29/23 2:00 PM 3/29/23 6:00 PM 关闭

问题是我需要知道他们的工作时间,不包括他们的“关闭”时间 - 我该如何使最终结果看起来像这样:

用户 开始时间 结束时间
用户1 3/29/23 8:00 AM 3/29/23 12:00 PM
用户1 3/29/23 4:00 PM 3/29/23 8:00 PM
用户2 3/29/23 10:00 AM 3/29/23 2:00 PM
用户2 3/29/23 6:00 PM 3/29/23 10:00 PM
英文:

I'm working for an organization where users sometimes work on a "split" schedule, and the data is structured like this as an example:

User Time Start Time End Type
User1 3/29/23 8:00 AM 3/29/23 8:00 PM Open
User1 3/29/23 12:00 PM 3/29/23 4:00 PM Closed
User2 3/29/23 10:00 AM 3/29/23 10:00 PM Open
User2 3/29/23 2:00 PM 3/29/23 6:00 PM Closed

Problem is I'll need to know their working hours without their "closed" hours -- how do I make it so that the end result would look something like this:

User Time Start Time End
User1 3/29/23 8:00 AM 3/29/23 12:00 PM
User1 3/29/23 4:00 PM 3/29/23 8:00 PM
User2 3/29/23 10:00 AM 3/29/23 2:00 PM
User2 3/29/23 6:00 PM 3/29/23 10:00 PM

I've tried using PARTITION BY but for the love of me can't get the result I wanted... probably am doing it wrong

答案1

得分: 1

以下是您要翻译的内容的翻译部分:

这个查询依赖于相当严格的假设:

  • 所有班次都在同一天内;
  • 每个开放的班次包含的关闭班次不超过一个;
  • 关闭的班次完全包含在相应的开放班次内;
  • 没有没有对应的开放班次的关闭班次存在。

此查询联合了三个部分:

  1. 仅包含开放班次
  2. 班次的第一部分(从开放部分的开始到关闭部分的开始)
  3. 班次的第二部分(从关闭部分的结束到开放部分的结束)
英文:

Here is query that could be used.

It relies on quite heavy assumptions:

  • all shifts contained within same day,
  • every open shift contains not more than one closed shift,
  • closed shift contained fully inside of open counterpart,
  • no closed shifts exists without corresponding open one.
With
    pairs as (
        select
            jc.user,
            jo.timestart o_start,
            jc.timestart c_start,
            jc.timeend c_end,
            jo.timeend o_end
        from
            journal jo
            join journal jc on (
                j1.user = j2.user
                and trunc (j1.timestart) = trunc (j2.timestart)
                and jc.type = 'Closed'
            )
        Where
            jo.type = 'Open'
    ),
    open_only as (
        --Only open ones
        select
            j1.user,
            j1.timestart,
            jc.timeend
        from
            journal j1
        where
            type = 'Open' not exists (
                select
                    1
                from
                    journal j2
                where
                    j1.user = j2.user
                    and trunc (j1.timestart) = trunc (j2.timestart)
                    and j2.type = 'Closed'
            )
    )
select
    user,
    timestart,
    timeend
from
    open_only
UNION ALL -- First part of shift
select
    user,
    o_start,
    c_start
from
    pairs
UNION ALL -- First part of shift
select
    user,
    c_end,
    o_end
from
    pairs

This query unions three parts:

  1. Only open shifts
  2. First part of shift (from beginning of open part, to beginning of closed part)
  3. Second part of shift (from ending of closed part to ending of open part)

答案2

得分: 1

以下是翻译的内容:

您可以使用UNPIVOT对时间进行处理,然后使用分析函数来计算是否有更多的开始时间和结束时间,分别对应"Open"和"Closed",并筛选出那些属于开放范围而不属于关闭范围的时间范围。

(注意:这将处理:开放范围内的多个关闭范围;相同类型的范围之间的重叠范围;跨越多天的范围;没有包含开放范围的关闭范围。)

对于示例数据:

CREATE TABLE table_name (user_name, Time_Start, Time_End, Type) AS
SELECT 'User1', DATE '2023-03-29' + INTERVAL '08:00:00' HOUR TO SECOND, DATE '2023-03-29' + INTERVAL '20:00:00' HOUR TO SECOND, 'Open' FROM DUAL UNION ALL
SELECT 'User1', DATE '2023-03-29' + INTERVAL '12:00:00' HOUR TO SECOND, DATE '2023-03-29' + INTERVAL '16:00:00' HOUR TO SECOND, 'Closed' FROM DUAL UNION ALL
SELECT 'User2', DATE '2023-03-29' + INTERVAL '10:00:00' HOUR TO SECOND, DATE '2023-03-29' + INTERVAL '22:00:00' HOUR TO SECOND, 'Open' FROM DUAL UNION ALL
SELECT 'User2', DATE '2023-03-29' + INTERVAL '14:00:00' HOUR TO SECOND, DATE '2023-03-29' + INTERVAL '18:00:00' HOUR TO SECOND, 'Closed' FROM DUAL UNION ALL
SELECT 'User3', DATE '2023-03-29' + INTERVAL '10:00:00' HOUR TO SECOND, DATE '2023-03-29' + INTERVAL '22:00:00' HOUR TO SECOND, 'Open' FROM DUAL UNION ALL
SELECT 'User3', DATE '2023-03-29' + INTERVAL '11:00:00' HOUR TO SECOND, DATE '2023-03-29' + INTERVAL '12:00:00' HOUR TO SECOND, 'Closed' FROM DUAL UNION ALL
SELECT 'User3', DATE '2023-03-29' + INTERVAL '13:00:00' HOUR TO SECOND, DATE '2023-03-29' + INTERVAL '14:00:00' HOUR TO SECOND, 'Closed' FROM DUAL;

输出:

USER_NAME TIME_START TIME_END
User1 2023-03-29 08:00:00 2023-03-29 12:00:00
User1 2023-03-29 16:00:00 2023-03-29 20:00:00
User2 2023-03-29 10:00:00 2023-03-29 14:00:00
User2 2023-03-29 18:00:00 2023-03-29 22:00:00
User3 2023-03-29 10:00:00 2023-03-29 11:00:00
User3 2023-03-29 12:00:00 2023-03-29 13:00:00
User3 2023-03-29 14:00:00 2023-03-29 22:00:00

fiddle

英文:

You can UNPIVOT the times and then use analytic functions to count whether you have more start than end times for both open and closed and filter to only have those ranges that are part of an open range and not part of a closed range.

(Note: this will handle: multiple closed ranges within an open range; overlapping ranges of the same type; ranges that span days; and closed ranges without a containing open range.)

SELECT user_name,
       time_start,
       time_end
FROM   (
  SELECT user_name,
         dt AS time_start,
         LEAD(dt) OVER (PARTITION BY user_name ORDER BY dt) AS time_end,
         SUM(CASE type WHEN 'Open' THEN is_open END )
           OVER (PARTITION BY user_name ORDER BY dt) AS is_open,
         SUM(CASE type WHEN 'Closed' THEN is_open ELSE 0 END )
           OVER (PARTITION BY user_name ORDER BY dt) AS is_closed
  FROM   table_name
  UNPIVOT ( dt FOR is_open IN ( time_start AS 1, time_end AS -1 ) )
)
WHERE  is_open > 0
AND    is_closed = 0

Which, for the sample data:

CREATE TABLE table_name (user_name,	Time_Start, Time_End, Type) AS
SELECT 'User1', DATE '2023-03-29' + INTERVAL '08:00:00' HOUR TO SECOND, DATE '2023-03-29' + INTERVAL '20:00:00' HOUR TO SECOND,	'Open'   FROM DUAL UNION ALL
SELECT 'User1', DATE '2023-03-29' + INTERVAL '12:00:00' HOUR TO SECOND, DATE '2023-03-29' + INTERVAL '16:00:00' HOUR TO SECOND,	'Closed' FROM DUAL UNION ALL
SELECT 'User2', DATE '2023-03-29' + INTERVAL '10:00:00' HOUR TO SECOND, DATE '2023-03-29' + INTERVAL '22:00:00' HOUR TO SECOND,	'Open'   FROM DUAL UNION ALL
SELECT 'User2', DATE '2023-03-29' + INTERVAL '14:00:00' HOUR TO SECOND, DATE '2023-03-29' + INTERVAL '18:00:00' HOUR TO SECOND,	'Closed' FROM DUAL UNION ALL
SELECT 'User3', DATE '2023-03-29' + INTERVAL '10:00:00' HOUR TO SECOND, DATE '2023-03-29' + INTERVAL '22:00:00' HOUR TO SECOND,	'Open'   FROM DUAL UNION ALL
SELECT 'User3', DATE '2023-03-29' + INTERVAL '11:00:00' HOUR TO SECOND, DATE '2023-03-29' + INTERVAL '12:00:00' HOUR TO SECOND,	'Closed' FROM DUAL UNION ALL
SELECT 'User3', DATE '2023-03-29' + INTERVAL '13:00:00' HOUR TO SECOND, DATE '2023-03-29' + INTERVAL '14:00:00' HOUR TO SECOND,	'Closed' FROM DUAL;

Outputs:

USER_NAME TIME_START TIME_END
User1 2023-03-29 08:00:00 2023-03-29 12:00:00
User1 2023-03-29 16:00:00 2023-03-29 20:00:00
User2 2023-03-29 10:00:00 2023-03-29 14:00:00
User2 2023-03-29 18:00:00 2023-03-29 22:00:00
User3 2023-03-29 10:00:00 2023-03-29 11:00:00
User3 2023-03-29 12:00:00 2023-03-29 13:00:00
User3 2023-03-29 14:00:00 2023-03-29 22:00:00

fiddle

答案3

得分: 0

你可以使用下面的解决方案来解决你的问题。它使用了分析函数LAGLEAD来实现所需的逻辑。
我假设在一个开放范围内不会有多个封闭范围。

SELECT User#
, CASE WHEN Time_Start < LAG(Time_End, 1)OVER(PARTITION BY User# ORDER BY Time_Start, Time_End) 
            THEN Time_End 
       ELSE Time_Start
  END Time_Start
, CASE WHEN Time_End < LAG(Time_End, 1)OVER(PARTITION BY User# ORDER BY Time_Start, Time_End) 
        AND Time_Start < LAG(Time_End, 1)OVER(PARTITION BY User# ORDER BY Time_Start, Time_End)
            THEN LAG(Time_End, 1)OVER(PARTITION BY User# ORDER BY Time_Start, Time_End)
       ELSE LEAD(Time_Start, 1, Time_End)OVER(PARTITION BY User# ORDER BY Time_Start, Time_End)
  END Time_End
from sample_data_tab t
ORDER BY User#, Time_Start, Time_End

【演示】:https://dbfiddle.uk/RtmvtZxz

英文:

You can use the solution below to solve your problem. it uses the analytic functions LAG and LEAD to implement the required logic.
I assume you don't have more than one closed ranges within an open range.

SELECT User#
, CASE WHEN Time_Start &lt; LAG(Time_End, 1)OVER(PARTITION BY User# ORDER BY Time_Start, Time_End) 
            THEN Time_End 
       ELSE Time_Start
  END Time_Start
, CASE WHEN Time_End &lt; LAG(Time_End, 1)OVER(PARTITION BY User# ORDER BY Time_Start, Time_End) 
        AND Time_Start &lt; LAG(Time_End, 1)OVER(PARTITION BY User# ORDER BY Time_Start, Time_End)
            THEN LAG(Time_End, 1)OVER(PARTITION BY User# ORDER BY Time_Start, Time_End)
       ELSE LEAD(Time_Start, 1, Time_End)OVER(PARTITION BY User# ORDER BY Time_Start, Time_End)
  END Time_End
from sample_data_tab t
ORDER BY User#, Time_Start, Time_End

demo

答案4

得分: 0

以下是您提供的代码的中文翻译部分:

-- 主要 SQL 查询
SELECT  USER_NAME, 
        SHIFT_1, SHIFT_1_ENDS, BREAK_1, BREAK_1_UNTIL,
        SHIFT_2, SHIFT_2_ENDS, BREAK_2, BREAK_2_UNTIL,
        SHIFT_3, SHIFT_3_ENDS
FROM    (   Select      b.BREAK_NO, w.USER_NAME "USER_NAME", TRUNC(w.STARTS) "工作日期",
                        To_Char(w.STARTS, 'hh24:mi') "班次1", CASE WHEN b.BREAK_NO = 1 THEN To_Char(b.STARTS, 'hh24:mi') END "班次1结束",
                        To_Char(b.STARTS, 'hh24:mi') "休息1", CASE WHEN b.BREAK_NO = 1 THEN To_Char(b.ENDS, 'hh24:mi') END "休息1结束",
                        --
                        To_Char(b.ENDS, 'hh24:mi') "班次2",
                        CASE  WHEN b.TOTAL_BREAKS = 1 And b.BREAK_NO = 1 
                              THEN To_Char(w.ENDS, 'hh24:mi') 
                        ELSE  CASE WHEN b.TOTAL_BREAKS = 2 And b.BREAK_NO = 1 THEN  LEAD(To_Char(b.STARTS, 'hh24:mi')) OVER(Partition By b.USER_NAME, TRUNC(b.STARTS) Order By b.USER_NAME, b.STARTS) END 
                        END "班次2结束",
                        --
                        CASE WHEN b.TOTAL_BREAKS = 2 And b.BREAK_NO = 1 THEN  LEAD(To_Char(b.STARTS, 'hh24:mi')) OVER(Partition By b.USER_NAME, TRUNC(b.STARTS) Order By b.USER_NAME, b.STARTS)  END "休息2",
                        CASE WHEN b.TOTAL_BREAKS = 2 And b.BREAK_NO = 1 THEN  LEAD(To_Char(b.ENDS, 'hh24:mi')) OVER(Partition By b.USER_NAME, TRUNC(b.STARTS) Order By b.USER_NAME, b.STARTS)  END "休息2结束",
                        --
                        CASE WHEN b.TOTAL_BREAKS = 2 And b.BREAK_NO = 1 THEN  LEAD(To_Char(b.ENDS, 'hh24:mi')) OVER(Partition By b.USER_NAME, TRUNC(b.STARTS) Order By b.USER_NAME, b.STARTS)  END "班次3",
                        CASE WHEN b.TOTAL_BREAKS = 2 And b.BREAK_NO = 1 THEN  To_Char(w.ENDS, 'hh24:mi')  END "班次3结束"
            From        tbl w
            Inner Join  breaks b ON(b.USER_NAME = w.USER_NAME And TRUNC(b.STARTS) = TRUNC(w.STARTS) And w.STATUS = 'Open')
        )
Where   BREAK_NO = 1

如果您只想获取工作小时,只需在主要 SQL 查询的选择列表中选择它们:

SELECT  USER_NAME, 
        SHIFT_1, SHIFT_1_ENDS, --BREAK_1, BREAK_1_UNTIL,   休息时间被排除
        SHIFT_2, SHIFT_2_ENDS, --BREAK_2, BREAK_2_UNTIL,
        SHIFT_3, SHIFT_3_ENDS
FROM ...  ...  ...  ...
英文:

Maybe you'd like to have shifts and breaks of a user in a single row. It could be done using a combination of analyitic functions LEAD() & LAG() with Case expressions. The code is adjusted for sample data below and it's limited to max of two breaks in a working day (should be enough, though)

WITH    --  Sample Data
	tbl (USER_NAME, STARTS, ENDS, STATUS) AS
		( Select &#39;User1&#39;, To_Date(&#39;2023-03-29 08:00:00&#39;, &#39;yyyy-mm-dd hh24:mi:ss&#39;), To_Date(&#39;2023-03-29 20:00:00&#39;, &#39;yyyy-mm-dd hh24:mi:ss&#39;), &#39;Open&#39;   From Dual Union All
			Select &#39;User1&#39;, To_Date(&#39;2023-03-29 12:00:00&#39;, &#39;yyyy-mm-dd hh24:mi:ss&#39;), To_Date(&#39;2023-03-29 16:00:00&#39;, &#39;yyyy-mm-dd hh24:mi:ss&#39;), &#39;Closed&#39; From Dual Union All
			Select &#39;User2&#39;, To_Date(&#39;2023-03-29 10:00:00&#39;, &#39;yyyy-mm-dd hh24:mi:ss&#39;), To_Date(&#39;2023-03-29 22:00:00&#39;, &#39;yyyy-mm-dd hh24:mi:ss&#39;), &#39;Open&#39;   From Dual Union All
			Select &#39;User2&#39;, To_Date(&#39;2023-03-29 14:00:00&#39;, &#39;yyyy-mm-dd hh24:mi:ss&#39;), To_Date(&#39;2023-03-29 18:00:00&#39;, &#39;yyyy-mm-dd hh24:mi:ss&#39;), &#39;Closed&#39; From Dual Union All
			Select &#39;User3&#39;, To_Date(&#39;2023-03-29 10:00:00&#39;, &#39;yyyy-mm-dd hh24:mi:ss&#39;), To_Date(&#39;2023-03-29 22:00:00&#39;, &#39;yyyy-mm-dd hh24:mi:ss&#39;), &#39;Open&#39;   From Dual Union All
			Select &#39;User3&#39;, To_Date(&#39;2023-03-29 11:00:00&#39;, &#39;yyyy-mm-dd hh24:mi:ss&#39;), To_Date(&#39;2023-03-29 12:00:00&#39;, &#39;yyyy-mm-dd hh24:mi:ss&#39;), &#39;Closed&#39; From Dual Union All
			Select &#39;User3&#39;, To_Date(&#39;2023-03-29 13:00:00&#39;, &#39;yyyy-mm-dd hh24:mi:ss&#39;), To_Date(&#39;2023-03-29 14:00:00&#39;, &#39;yyyy-mm-dd hh24:mi:ss&#39;), &#39;Closed&#39; FROM Dual
		),
  breaks AS   -- cte breaks
    ( Select  ROW_NUMBER() OVER(Partition By USER_NAME, TRUNC(STARTS) Order By USER_NAME, STARTS) &quot;BREAK_NO&quot;,
              COUNT(*) OVER(Partition By USER_NAME, TRUNC(STARTS)) &quot;TOTAL_BREAKS&quot;,
              USER_NAME, STARTS, ENDS
      From tbl Where STATUS = &#39;Closed&#39;
      Order By USER_NAME, STARTS
    )
--  M a i n   S Q L
SELECT  USER_NAME, 
        SHIFT_1, SHIFT_1_ENDS, BREAK_1, BREAK_1_UNTIL,
        SHIFT_2, SHIFT_2_ENDS, BREAK_2, BREAK_2_UNTIL,
        SHIFT_3, SHIFT_3_ENDS
FROM    (   Select      b.BREAK_NO, w.USER_NAME &quot;USER_NAME&quot;, TRUNC(w.STARTS) &quot;WORK_DATE&quot;,
                        To_Char(w.STARTS, &#39;hh24:mi&#39;) &quot;SHIFT_1&quot;, CASE WHEN b.BREAK_NO = 1 THEN To_Char(b.STARTS, &#39;hh24:mi&#39;) END &quot;SHIFT_1_ENDS&quot;,
                        To_Char(b.STARTS, &#39;hh24:mi&#39;) &quot;BREAK_1&quot;, CASE WHEN b.BREAK_NO = 1 THEN To_Char(b.ENDS, &#39;hh24:mi&#39;) END &quot;BREAK_1_UNTIL&quot;,
                        --
                        To_Char(b.ENDS, &#39;hh24:mi&#39;) &quot;SHIFT_2&quot;,
                        CASE  WHEN b.TOTAL_BREAKS = 1 And b.BREAK_NO = 1 
                              THEN To_Char(w.ENDS, &#39;hh24:mi&#39;) 
                        ELSE  CASE WHEN b.TOTAL_BREAKS = 2 And b.BREAK_NO = 1 THEN  LEAD(To_Char(b.STARTS, &#39;hh24:mi&#39;)) OVER(Partition By b.USER_NAME, TRUNC(b.STARTS) Order By b.USER_NAME, b.STARTS) END 
                        END &quot;SHIFT_2_ENDS&quot;,
                        --
                        CASE WHEN b.TOTAL_BREAKS = 2 And b.BREAK_NO = 1 THEN  LEAD(To_Char(b.STARTS, &#39;hh24:mi&#39;)) OVER(Partition By b.USER_NAME, TRUNC(b.STARTS) Order By b.USER_NAME, b.STARTS)  END &quot;BREAK_2&quot;,
                        CASE WHEN b.TOTAL_BREAKS = 2 And b.BREAK_NO = 1 THEN  LEAD(To_Char(b.ENDS, &#39;hh24:mi&#39;)) OVER(Partition By b.USER_NAME, TRUNC(b.STARTS) Order By b.USER_NAME, b.STARTS)  END &quot;BREAK_2_UNTIL&quot;,
                        --
                        CASE WHEN b.TOTAL_BREAKS = 2 And b.BREAK_NO = 1 THEN  LEAD(To_Char(b.ENDS, &#39;hh24:mi&#39;)) OVER(Partition By b.USER_NAME, TRUNC(b.STARTS) Order By b.USER_NAME, b.STARTS)  END &quot;SHIFT_3&quot;,
                        CASE WHEN b.TOTAL_BREAKS = 2 And b.BREAK_NO = 1 THEN  To_Char(w.ENDS, &#39;hh24:mi&#39;)  END &quot;SHIFT_3_ENDS&quot;
            From        tbl w
            Inner Join  breaks b ON(b.USER_NAME = w.USER_NAME And TRUNC(b.STARTS) = TRUNC(w.STARTS) And w.STATUS = &#39;Open&#39;)
        )
Where   BREAK_NO = 1
--
--  R e s u l t :
USER_NAME SHIFT_1 SHIFT_1_ENDS BREAK_1 BREAK_1_UNTIL SHIFT_2 SHIFT_2_ENDS BREAK_2 BREAK_2_UNTIL SHIFT_3 SHIFT_3_ENDS
--------- ------- ------------ ------- ------------- ------- ------------ ------- ------------- ------- ------------
User1     08:00   12:00        12:00   16:00         16:00   20:00                                                   
User2     10:00   14:00        14:00   18:00         18:00   22:00                                                   
User3     10:00   11:00        11:00   12:00         12:00   13:00        13:00   14:00         14:00   22:00      

If you want just working hours - all you should do is select just them in the Select list of a main SQL:

SELECT  USER_NAME, 
        SHIFT_1, SHIFT_1_ENDS, --BREAK_1, BREAK_1_UNTIL,   breaks excluded
        SHIFT_2, SHIFT_2_ENDS, --BREAK_2, BREAK_2_UNTIL,
        SHIFT_3, SHIFT_3_ENDS
FROM ...  ...  ...  ...

--
--  R e s u l t :
USER_NAME SHIFT_1 SHIFT_1_ENDS SHIFT_2 SHIFT_2_ENDS SHIFT_3 SHIFT_3_ENDS
--------- ------- ------------ ------- ------------ ------- ------------
User1     08:00   12:00        16:00   20:00                             
User2     10:00   14:00        18:00   22:00                             
User3     10:00   11:00        12:00   13:00        14:00   22:00      

huangapple
  • 本文由 发表于 2023年3月31日 04:35:18
  • 转载请务必保留本文链接:https://go.coder-hub.com/75892780.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定