3

我的表有类似于下面的信息

Emp  Date       START_TIME            END_TIME             Code     Minutes
---  --------   -------------------   -------------------  ----     -------
 E1  11/1/2012  11/1/2012 6:55:00 AM  11/1/2012 7:01:00 AM  C1       6
 E1  11/1/2012  11/1/2012 6:57:00 AM  11/1/2012 8:01:00 AM  C2       64
 E2  11/1/2012  11/1/2012 6:57:00 AM  11/1/2012 8:00:00 AM  C2       63
 E1  11/2/2012  11/2/2012 7:35:00 AM  11/2/2012 8:01:00 AM  C1       26

预期输出是

Date       Code  Range                        Minutes
---------  ----  -----------------------      ------- 
11/1/2012   C1   6:30:00 AM-7:00:00 AM           5
11/1/2012   C1   7:00:00 AM-7:30:00 AM           1
11/1/2012   C2   6:30:00 AM-7:00:00 AM           6   
11/1/2012   C2   7:00:00 AM-7:30:00 AM           60
11/1/2012   C2   7:30:00 AM-8:00:00 AM           60
11/1/2012   C2   8:00:00 AM-8:30:00 AM           1
11/2/2012   C1   7:30:00 AM-8:00:00 AM           25
11/2/2012   C1   8:00:00 AM-8:30:00 AM           1

省略 Emp 字段,我想按日期和代码分组,每个代码的总时间为 30 分钟。我的限制是使用选择语句来实现这一点,即只能通过 SQL 查询,因为不允许使用 PL/SQL。提前致谢!

4

5 回答 5

2

涉及示范条款的解决方案。

首先让我们计算每个条目需要的 30 分钟块的数量。

SQL> select emp, start_time, end_time, code,
  2                      trunc(start_time, 'mi')
  3                      - (mod(to_char(trunc(start_time, 'mi'), 'mi'), 30) / 1440) start_block,
  4                      ceil(2*24*(end_time-(trunc(start_time, 'mi')
  5                      - (mod(to_char(trunc(start_time, 'mi'), 'mi'), 30) / 1440)))) blocks
  6                 from tab f
  7  /

EM START_TIME             END_TIME               CO START_BLOCK                BLOCKS
-- ---------------------- ---------------------- -- ---------------------- ----------
E1 11/01/2012 06:55:00 am 11/01/2012 07:01:00 am C1 11/01/2012 06:30:00 am          2
E1 11/01/2012 06:57:00 am 11/01/2012 08:01:00 am C2 11/01/2012 06:30:00 am          4
E2 11/01/2012 06:57:00 am 11/01/2012 08:00:00 am C2 11/01/2012 06:30:00 am          3
E1 11/02/2012 07:35:00 am 11/02/2012 08:01:00 am C1 11/02/2012 07:30:00 am          2

现在,我们使用模型子句生成行以将其分成 30 分钟的时间段。

SQL> with foo as (select rownum id, emp, start_time, end_time, code,
  2                      trunc(start_time, 'mi')
  3                      - (mod(to_char(trunc(start_time, 'mi'), 'mi'), 30) / 1440) start_block,
  4                      ceil(2*24*(end_time-(trunc(start_time, 'mi')
  5                      - (mod(to_char(trunc(start_time, 'mi'), 'mi'), 30) / 1440)))) blocks
  6                 from tab f)
  7  select trunc(start_time) thedate, code, emp, range, minutes
  8    from foo
  9   model partition by(id)
 10         dimension by(0 as f)
 11         measures(code, emp, start_time, end_time, start_block, blocks,
 12                  sysdate as start_range,
 13                  sysdate as end_range,
 14                  cast(0 as number) minutes,
 15                  cast('' as varchar2(50)) range)
 16         rules (start_range [for f from 0 to blocks[0]-1  increment 1]  = start_block[0] + (30*cv(f)/1440),
 17                end_range[any]  =  start_range[cv()] + (30/1440),
 18                code[any]  =  code[0],
 19                emp[any]   =  emp[0],
 20                start_time[any]  =  start_time[0],
 21                end_time[any]  =  end_time[0],
 22                range [any] = to_char(start_range[cv()], 'dd/mm/yyyy hh:mi:ss am') || ' - ' || to_char(end_range[cv()], 'dd/mm/yyyy hh24:mi:ss am'),
 23                minutes [any]   = case
 24                                    when start_time[0] between start_range[cv()] and end_range[cv()]
 25                                    then 1440 *(end_range[cv()] - start_time[0])
 26                                    when end_time[0] between start_range[cv()] and end_range[cv()]
 27                                    then 1440 *(end_time[0] - start_range[cv()])
 28                                    else 1440 * (end_range[cv()] - start_range[cv()])
 29                                  end );

CO EM RANGE                                                 MINUTES
-- -- -------------------------------------------------- ----------
C2 E2 11/01/2012 06:30:00 am - 11/01/2012 07:00:00 am             3
C2 E2 11/01/2012 07:00:00 am - 11/01/2012 07:30:00 am            30
C2 E2 11/01/2012 07:30:00 am - 11/01/2012 08:00:00 am            30
C1 E1 11/01/2012 06:30:00 am - 11/01/2012 07:00:00 am             5
C1 E1 11/01/2012 07:00:00 am - 11/01/2012 07:30:00 am             1
C1 E1 11/02/2012 07:30:00 am - 11/02/2012 08:00:00 am            25
C1 E1 11/02/2012 08:00:00 am - 11/02/2012 08:30:00 am             1
C2 E1 11/01/2012 06:30:00 am - 11/01/2012 07:00:00 am             3
C2 E1 11/01/2012 07:00:00 am - 11/01/2012 07:30:00 am            30
C2 E1 11/01/2012 07:30:00 am - 11/01/2012 08:00:00 am            30
C2 E1 11/01/2012 08:00:00 am - 11/01/2012 08:30:00 am             1

11 rows selected.

所以我们按以下方式进行分区:

partition by(id)

即通过一个独特的参考。然后我们将使用我们的维度生成行

dimension by(0 as f)

结合部分规则:

for f from 0 to blocks[0]-1  increment 1

所以 start_range 列是用 start_range [for f from 0 to blocks[0]-1 increment 1] = start_block[0] + (30*cv(f)/1440) 生成的,

start_block[0] 在第一个查询中,例如:

EM START_TIME             END_TIME               CO START_BLOCK                BLOCKS
-- ---------------------- ---------------------- -- ---------------------- ----------
E1 11/01/2012 06:55:00 am 11/01/2012 07:01:00 am C1 11/01/2012 06:30:00 am          2

所以对于这一行,它的计算结果为

start_range[0 to 1] = 11/01/2012 06:30:00 am + (30minutes * the value of f)

IE

start_range[0] = 11/01/2012 06:30:00 am + (30min*0) = 11/01/2012 06:30:00 am
start_range[1] = 11/01/2012 06:30:00 am + (30min*1) = 11/01/2012 07:00:00 am

其余的很简单:

end_range[any]  =  start_range[cv()] + (30/1440),

意味着对于end-range当前行,我们需要start_range并添加 30 分钟。

range列是 start_range 和 end_range 的串联:

range [any] = to_char(start_range[cv()], 'dd/mm/yyyy hh:mi:ss am') || ' - ' || to_char(end_range[cv()], 'dd/mm/yyyy hh24:mi:ss am'),

最后为了计算该范围内的分钟数:

minutes [any]   = case
                    when start_time[0] between start_range[cv()] and end_range[cv()]
                    then 1440 *(end_range[cv()] - start_time[0])
                    when end_time[0] between start_range[cv()] and end_range[cv()]
                    then 1440 *(end_time[0] - start_range[cv()])
                    else 1440 * (end_range[cv()] - start_range[cv()])
                  end );
  1. 如果 start_time 在范围内,取范围的结尾 - 开始时间
  2. 如果 end_time 在范围内,取 end_time - 范围的开始
  3. 否则它的 end_range - start_range。

1440 只是以分钟的形式得到答案。

现在我们可以将它们全部组合起来:

SQL> with foo as (select rownum id, emp, start_time, end_time, code,
  2                      trunc(start_time, 'mi')
  3                      - (mod(to_char(trunc(start_time, 'mi'), 'mi'), 30) / 1440) start_block,
  4                      ceil(2*24*(end_time-(trunc(start_time, 'mi')
  5                      - (mod(to_char(trunc(start_time, 'mi'), 'mi'), 30) / 1440)))) blocks
  6                 from tab f)
  7  select thedate, code, range, sum(minutes) minutes
  8    from (select trunc(start_time) thedate, code, emp, range, minutes
  9            from foo
 10           model partition by(id)
 11                 dimension by(0 as f)
 12                 measures(code, emp, start_time, end_time, start_block, blocks,
 13                          sysdate as start_range,
 14                          sysdate as end_range,
 15                          cast(0 as number) minutes,
 16                          cast('' as varchar2(50)) range)
 17                 rules (start_range [for f from 0 to blocks[0]-1  increment 1]  = start_block[0] + (30*cv(f)/1440),
 18                        code[any]  =  code[0],
 19                        emp[any]  =  emp[0],
 20                        end_range[any]  =  start_range[cv()] + (30/1440),
 21                        start_time[any]  =  start_time[0],
 22                        end_time[any]  =  end_time[0],
 23                        range [any] = to_char(start_range[cv()], 'dd/mm/yyyy hh:mi:ss am') || ' - ' || to_char(end_range[cv()], 'dd/mm/yyyy hh24:mi:ss am'),
 24                        minutes [any]   = case
 25                                            when start_time[0] between start_range[cv()] and end_range[cv()]
 26                                            then 1440 *(end_range[cv()] - start_time[0])
 27                                            when end_time[0] between start_range[cv()] and end_range[cv()]
 28                                            then 1440 *(end_time[0] - start_range[cv()])
 29                                            else 1440 * (end_range[cv()] - start_range[cv()])
 30                                          end ))
 31   group by thedate, code, range
 32   order by thedate, code, range;

THEDATE    CO RANGE                                                 MINUTES
---------- -- -------------------------------------------------- ----------
11/01/2012 C1 11/01/2012 06:30:00 am - 11/01/2012 07:00:00 am             5
11/01/2012 C1 11/01/2012 07:00:00 am - 11/01/2012 07:30:00 am             1
11/01/2012 C2 11/01/2012 06:30:00 am - 11/01/2012 07:00:00 am             6
11/01/2012 C2 11/01/2012 07:00:00 am - 11/01/2012 07:30:00 am            60
11/01/2012 C2 11/01/2012 07:30:00 am - 11/01/2012 08:00:00 am            60
11/01/2012 C2 11/01/2012 08:00:00 am - 11/01/2012 08:30:00 am             1
11/02/2012 C1 11/02/2012 07:30:00 am - 11/02/2012 08:00:00 am            25
11/02/2012 C1 11/02/2012 08:00:00 am - 11/02/2012 08:30:00 am             1
于 2013-01-07T15:07:10.477 回答
1

我很确定这可以清理干净,并且变得更清晰和更高效,因为 Oracle 不是我的强项之一,但它可以工作并且应该给出如何完成任务的想法。

这里的关键是加入一个数字列表,将您的记录分成半小时的时间段。

SELECT  "Date",
        "Code",
        "RangeStart" + ((r - 1) / 48.0) AS "RangeStart",
        "RangeStart" + (r / 48.0) AS "RangeEnd",
        SUM(CASE WHEN r = 1 THEN "StartMinutes"
                WHEN "END_TIME" >= "RangeStart" + ((r - 1) / 48.0) AND "END_TIME" < "RangeStart" + (r / 48.0) THEN "EndMinutes"
                ELSE 30 
            END) AS "TotalMinutes"
FROM    (   SELECT  "Emp",
                    "Date",
                    "START_TIME",
                    "END_TIME",
                    "Code",
                    CASE WHEN EXTRACT(MINUTE from "START_TIME") > 30 THEN 60 ELSE 30 END - EXTRACT(MINUTE from "START_TIME") AS "StartMinutes",
                    EXTRACT(MINUTE from END_TIME) - CASE WHEN EXTRACT(MINUTE from "END_TIME") > 30 THEN 30 ELSE 0 END AS "EndMinutes",
                    "START_TIME" - (EXTRACT(MINUTE from "START_TIME") - CASE WHEN EXTRACT(MINUTE from "START_TIME") > 30 THEN 30 ELSE 0 END) / (60 * 24.0) AS "RangeStart"
            FROM    T
        ) T
        INNER JOIN
        (   SELECT  Rownum r
            FROM    dual
            CONNECT BY Rownum <= 100
        ) r
            ON "END_TIME" > ("RangeStart" + ((r - 1) / 48.0))
GROUP BY "Date", "Code", "RangeStart" + ((r - 1) / 48.0), "RangeStart" + (r / 48.0)
ORDER BY "Code", "Date", "RangeStart";

SQL FDDLE 示例

于 2013-01-07T14:39:30.860 回答
1

这是另一种解决方案(它不是很优雅,并且使用硬编码的日期文字来获取存储桶的边界 - 可能应该由子查询替换以获取它们):

  with v_data as (
    select 1 pk, 'E1' emp, to_date('2012-11-01', 'YYYY-MM-DD') as date1, to_date('2012-11-01 06:55:00', 'YYYY-MM-DD hh24:mi:ss') as start_time, to_date('2012-11-01 07:01:00', 'YYYY-MM-DD hh24:mi:ss') as end_time, 'C1' as code, 6 as minutes from dual union all 
    select 2 pk, 'E1' emp, to_date('2012-11-01', 'YYYY-MM-DD') as date1, to_date('2012-11-01 06:57:00', 'YYYY-MM-DD hh24:mi:ss') as start_time, to_date('2012-11-01 08:01:00', 'YYYY-MM-DD hh24:mi:ss') as end_time, 'C2' as code, 64 as minutes from dual union all 
    select 3 pk, 'E2' emp, to_date('2012-11-01', 'YYYY-MM-DD') as date1, to_date('2012-11-01 06:57:00', 'YYYY-MM-DD hh24:mi:ss') as start_time, to_date('2012-11-01 08:00:00', 'YYYY-MM-DD hh24:mi:ss') as end_time, 'C2' as code, 63 as minutes from dual union all 
    select 4 pk, 'E1' emp, to_date('2012-11-02', 'YYYY-MM-DD') as date1, to_date('2012-11-02 07:35:00', 'YYYY-MM-DD hh24:mi:ss') as start_time, to_date('2012-11-02 08:01:00', 'YYYY-MM-DD hh24:mi:ss') as end_time, 'C1' as code, 26 as minutes from dual), 
v_buckets as (          
  select 
    to_date('2012-11-01', 'YYYY-MM-DD') + (rownum-1)/48 as bucket_start,
    to_date('2012-11-01', 'YYYY-MM-DD') + rownum/48 as bucket_end  
    from dual
  connect by rownum <96
)
select v3.date1, v3.bucket_start, v3.bucket_end, v3.code, sum(v3.time_spent_in_bucket) as minutes 
from (
select v2.*, (least(end_time, bucket_end) - greatest(start_time, bucket_start))*1440 as time_spent_in_bucket from 
(
select buck.*,
       v1.*
  from v_buckets buck
  join v_data v1 
    on (
       -- time slot completely contained in one bucket
        (v1.start_time >= buck.bucket_start and v1.start_time < buck.bucket_end and
        v1.end_time >= buck.bucket_start and v1.end_time < buck.bucket_end)
       -- time slot starts in bucket, expands to next bucket
        or (v1.start_time >= buck.bucket_start and v1.start_time < buck.bucket_end and
        v1.end_time >= buck.bucket_end)
       -- time slot started in previous bucket, ends in this bucket)
        or (v1.start_time < buck.bucket_start and v1.end_time > buck.bucket_start and
        v1.end_time <= buck.bucket_end)
       -- time slot began in previous bucket, expands to next bucket
       or (v1.start_time < buck.bucket_start and v1.end_time >= buck.bucket_end) 
        )
) v2
) v3 
where start_time is not null
group by date1, bucket_start, bucket_end, code
order by bucket_start, code
于 2013-01-07T15:19:39.453 回答
1

这是我的尝试:

select trunc(trunc_start) as datetime, code, range , sum(duration) minutes
from (
select code, end_time, start_time, TRUNC_START , 
  to_char(trunc_start,'hh:mi:ss AM')||'-'||to_char(trunc_start+1/48,'hh:mi:ss AM') as range,
  case 
    when end_time-trunc_start between 0 and 1/48 then (end_time-trunc_start)*1440 
    when start_time-trunc_start between 0 and 1/48 then (trunc_start-start_time)*1440+30
    else 30 
  end as duration
from(
  select  s.*, n , 
  trunc(start_time) + trunc((start_time-trunc(start_time))*48)/48 + (n-1)/48 as trunc_start
  from s
  join (select level n from dual connect by level <=48) a
  on n-2 <= (end_time-start_time)*100
  )b
)
where trunc_start < end_time --eliminating fake intervals
group by code, trunc(trunc_start), range
order by 1, 3
;

对不起where:)

SQLFIDDLE

于 2013-01-07T15:55:45.053 回答
-1

以下是一些适用于您的范围的一般示例:

SELECT job
  , sum(decode(greatest(sal,2999), least(sal,6000), 1, 0)) "Range 3000-6000"
  , sum(decode(greatest(sal,1000), least(sal,2999), 1, 0)) "Range 1000-3000"
  , sum(decode(greatest(sal,0),    least(sal,999), 1, 0))  "Range 0-1000"
  FROM scott.emp
GROUP BY  job
/
于 2013-01-07T13:35:11.450 回答