【问题标题】:Aggregate Overlapping Segments to Measure Effective Length聚合重叠段以测量有效长度
【发布时间】:2019-02-04 11:32:00
【问题描述】:

我有一个road_events 表:

create table road_events (
    event_id number(4,0),
    road_id number(4,0),
    year number(4,0),
    from_meas number(10,2),
    to_meas number(10,2),
    total_road_length number(10,2)
    );

insert into road_events (event_id, road_id, year, from_meas, to_meas, total_road_length) values (1,1,2020,25,50,100);
insert into road_events (event_id, road_id, year, from_meas, to_meas, total_road_length) values (2,1,2000,25,50,100);
insert into road_events (event_id, road_id, year, from_meas, to_meas, total_road_length) values (3,1,1980,0,25,100);
insert into road_events (event_id, road_id, year, from_meas, to_meas, total_road_length) values (4,1,1960,75,100,100);
insert into road_events (event_id, road_id, year, from_meas, to_meas, total_road_length) values (5,1,1940,1,100,100);

insert into road_events (event_id, road_id, year, from_meas, to_meas, total_road_length) values (6,2,2000,10,30,100);
insert into road_events (event_id, road_id, year, from_meas, to_meas, total_road_length) values (7,2,1975,30,60,100);
insert into road_events (event_id, road_id, year, from_meas, to_meas, total_road_length) values (8,2,1950,50,90,100);

insert into road_events (event_id, road_id, year, from_meas, to_meas, total_road_length) values (9,3,2050,40,90,100);

insert into road_events (event_id, road_id, year, from_meas, to_meas, total_road_length) values (10,4,2040,0,200,200);
insert into road_events (event_id, road_id, year, from_meas, to_meas, total_road_length) values (11,4,2013,0,199,200);
insert into road_events (event_id, road_id, year, from_meas, to_meas, total_road_length) values (12,4,2001,0,200,200);

insert into road_events (event_id, road_id, year, from_meas, to_meas, total_road_length) values (13,5,1985,50,70,300);
insert into road_events (event_id, road_id, year, from_meas, to_meas, total_road_length) values (14,5,1985,10,50,300);
insert into road_events (event_id, road_id, year, from_meas, to_meas, total_road_length) values (15,5,1965,1,301,300);
commit;

select * from road_events;

  EVENT_ID    ROAD_ID       YEAR  FROM_MEAS    TO_MEAS TOTAL_ROAD_LENGTH
---------- ---------- ---------- ---------- ---------- -----------------
         1          1       2020         25         50               100
         2          1       2000         25         50               100
         3          1       1980          0         25               100
         4          1       1960         75        100               100
         5          1       1940          1        100               100

         6          2       2000         10         30               100
         7          2       1975         30         60               100
         8          2       1950         50         90               100

         9          3       2050         40         90               100

        10          4       2040          0        200               200
        11          4       2013          0        199               200
        12          4       2001          0        200               200

        13          5       1985         50         70               300
        14          5       1985         10         50               300
        15          5       1965          1        301               300

我想选择代表每条道路上最近工作的事件。

这是一个棘手的操作,因为事件通常只涉及道路的一部分。这意味着我不能简单地选择每条道路的最新事件;我只需要选择不重叠的最近的事件里程


可能的逻辑(按顺序):

我不愿意猜测如何解决这个问题,因为它最终可能带来的伤害大于帮助(有点像XY Problem)。另一方面,它可能会提供对问题本质的洞察,所以这里是这样的:

  1. 为每条道路选择最近的事件。我们将调用最近的事件:event A
  2. 如果event A>= total_road_length,那么这就是我所需要的。算法到此结束。
  3. 否则,获取与event A 不具有相同范围的下一个按时间顺序排列的事件 (event B)。
  4. 如果event B 的范围与event A 的范围重叠,则只获取event B 不重叠的部分。
  5. 重复步骤 3 和 4,直到总事件长度为 = total_road_length。或者在这条路没有更多活动时停下来。

问题:

我知道这是一项艰巨的任务,但是要做到这一点需要什么?

这是一个经典的线性引用问题。如果我可以将线性引用操作作为查询的一部分进行,那将非常有帮助。

结果是:

  EVENT_ID    ROAD_ID       YEAR  TOTAL_ROAD_LENGTH   EVENT_LENGTH
---------- ---------- ----------  -----------------   ------------
         1          1       2020                100             25
         3          1       1980                100             25
         4          1       1960                100             25
         5          1       1940                100             25

         6          2       2000                100             20
         7          2       1975                100             30
         8          2       1950                100             30

         9          3       2050                100             50

        10          4       2040                200            200

        13          5       1985                300             20
        14          5       1985                300             40
        15          5       1965                300            240

相关问题:Select where number range does not overlap

【问题讨论】:

  • 如果一个事件来自 2010 年 10-100 年和另一个 20-40 年 2020 年,三行 10-20/2010、20-40/2020、40-100/2010,应该返回什么?
  • @dnoeth 不错。我没有想到这一点。 Option 1 (dnoeth's option): 返回 3 行就可以了;这是最明确的选择。 Option 2: 但是,事后看来,我认为没有必要将 from_measto_meas 作为查询的一部分返回。但是有必要知道返回的事件的长度。因此,我们可以只返回一个event_length 列,它是10-20/201040-100/2010 的聚合。当然,20-40/2020 是一行。
  • 我认为这可以通过分析功能和窗口化来完成。你有很多标准。棘手的部分将是处理重叠。

标签: sql oracle select oracle12c asset-management


【解决方案1】:

我的主要 DBMS 是 Teradata,但这也可以在 Oracle 中按原样工作。

WITH all_meas AS
 ( -- get a distinct list of all from/to points
   SELECT road_id, from_meas AS meas
   FROM road_events
   UNION
   SELECT road_id, to_meas
   FROM road_events
 )
-- select * from all_meas order by 1,2
 , all_ranges AS
 ( -- create from/to ranges
   SELECT road_id, meas AS from_meas 
     ,Lead(meas)
      Over (PARTITION BY road_id
            ORDER BY meas) AS to_meas
   FROM all_meas
  )
 -- SELECT * from all_ranges order by 1,2
, all_event_ranges AS
 ( -- now match the ranges to the event ranges
   SELECT 
      ar.*
     ,re.event_id
     ,re.year
     ,re.total_road_length
     ,ar.to_meas - ar.from_meas AS event_length
     -- used to filter the latest event as multiple events might cover the same range 
     ,Row_Number()
      Over (PARTITION BY ar.road_id, ar.from_meas
            ORDER BY year DESC) AS rn
   FROM all_ranges ar
   JOIN road_events re
     ON ar.road_id = re.road_id
    AND ar.from_meas < re.to_meas
    AND ar.to_meas > re.from_meas
   WHERE ar.to_meas IS NOT NULL
 )
SELECT event_id, road_id, year, total_road_length, Sum(event_length)
FROM all_event_ranges
WHERE rn = 1 -- latest year only
GROUP BY event_id, road_id, year, total_road_length
ORDER BY road_id, year DESC;

如果您需要返回实际覆盖的from/to_meas(如您在编辑前的问题中),它可能会更复杂。第一部分是相同的,但没有聚合,查询可以返回具有相同 event_id 的相邻行(例如,对于事件 3:0-1 和 1-25):

SELECT * FROM all_event_ranges
WHERE rn = 1
ORDER BY road_id, from_meas;

如果您想合并相邻的行,您还需要两个步骤(使用标准方法,标记组的第一行并计算组号):

WITH all_meas AS
 (
   SELECT road_id, from_meas AS meas
   FROM road_events
   UNION
   SELECT road_id, to_meas
   FROM road_events
 )
-- select * from all_meas order by 1,2
 , all_ranges AS
 ( 
   SELECT road_id, meas AS from_meas 
     ,Lead(meas)
      Over (PARTITION BY road_id
            ORDER BY meas) AS to_meas
   FROM all_meas
  )
-- SELECT * from all_ranges order by 1,2
, all_event_ranges AS
 (
   SELECT 
      ar.*
     ,re.event_id
     ,re.year
     ,re.total_road_length
     ,ar.to_meas - ar.from_meas AS event_length
     ,Row_Number()
      Over (PARTITION BY ar.road_id, ar.from_meas
            ORDER BY year DESC) AS rn
   FROM all_ranges ar
   JOIN road_events  re
     ON ar.road_id = re.road_id
    AND ar.from_meas < re.to_meas
    AND ar.to_meas > re.from_meas
   WHERE ar.to_meas IS NOT NULL
 )
-- SELECT * FROM all_event_ranges WHERE rn = 1 ORDER BY road_id, from_meas
, adjacent_events AS 
 ( -- assign 1 to the 1st row of an event
   SELECT t.*
     ,CASE WHEN Lag(event_id)
                Over(PARTITION BY road_id
                     ORDER BY from_meas) = event_id
           THEN 0 
           ELSE 1 
      END AS flag
   FROM all_event_ranges t
   WHERE rn = 1
 )
-- SELECT * FROM adjacent_events ORDER BY road_id, from_meas 
, grouped_events AS
 ( -- assign a groupnumber to adjacent rows using a Cumulative Sum over 0/1
   SELECT t.*
     ,Sum(flag)
      Over (PARTITION BY road_id
            ORDER BY from_meas
            ROWS Unbounded Preceding) AS grp
   FROM adjacent_events t
)
-- SELECT * FROM grouped_events ORDER BY  road_id, from_meas
SELECT event_id, road_id, year, Min(from_meas), Max(to_meas), total_road_length, Sum(event_length)
FROM grouped_events
GROUP BY event_id, road_id, grp, year, total_road_length
ORDER BY 2, Min(from_meas);

编辑:

Ups,我刚刚发现一个博客 Overlapping ranges with priority 使用一些简化的 Oracle 语法做同样的事情。事实上,我将我的查询从 Teradata 中的其他一些简化语法转换为标准/Oracle SQL :-)

【讨论】:

  • 根据您的查询经验,您对要在表中索引哪些列有什么建议吗?
  • Teradata 中不需要很多索引,但 all_event_ranges 中的连接占用了大部分资源,因此索引 road_id ,myabe 添加 from_measto_meas...跨度>
【解决方案2】:

还有另一种计算方法,使用 from 和 to 值:

with 
  part_begin_point as (
    Select distinct road_id, from_meas point
    from road_events be
    union 
    Select distinct road_id, to_meas point
    from road_events ee
  )
, newest_part as (
  select e.event_id
  , e.road_id
  , e.year
  , e.total_road_length
  , p.point
  , LAG(e.event_id) over (partition by p.road_id order by p.point) prev_event
  , e.to_meas event_to_meas
  from part_begin_point p
  join road_events e
   on p.road_id = e.road_id
   and p.point >= e.from_meas and  p.point < e.to_meas
   and not exists(
        select 1 from road_events ne 
        where e.road_id = ne.road_id
        and p.point >= ne.from_meas and p.point < ne.to_meas
        and (e.year < ne.year or e.year = ne.year and e.event_id < ne.event_id))
  )
select event_id, road_id, year
, point from_meas
, LEAD(point, 1, event_to_meas) over (partition by road_id order by point) to_meas
, total_road_length
, LEAD(point, 1, event_to_meas) over (partition by road_id order by point) - point EVENT_LENGTH
from newest_part
where 1=1
and event_id <> prev_event or prev_event is null
order by event_id, point

SQL Fiddle

【讨论】:

    【解决方案3】:

    今天想太多了,但我现在有些东西忽略了 +/- 10 米。

    首先制作了一个函数,它将对/从对作为字符串接收并返回字符串中对所覆盖的距离。例如 '10:20;35:45' 返回 20。

    CREATE
        OR replace FUNCTION get_distance_range_str (strRangeStr VARCHAR2)
    
    RETURN NUMBER IS intRetNum NUMBER;
    
    BEGIN
        --split input string
        WITH cte_1
        AS (
            SELECT regexp_substr(strRangeStr, '[^;]+', 1, LEVEL) AS TO_FROM_STRING
            FROM dual connect BY regexp_substr(strRangeStr, '[^;]+', 1, LEVEL) IS NOT NULL
            )
            --split From/To pairs
            ,cte_2
        AS (
            SELECT cte_1.TO_FROM_STRING
                ,to_number(substr(cte_1.TO_FROM_STRING, 1, instr(cte_1.TO_FROM_STRING, ':') - 1)) AS FROM_MEAS
                ,to_number(substr(cte_1.TO_FROM_STRING, instr(cte_1.TO_FROM_STRING, ':') + 1, length(cte_1.TO_FROM_STRING) - instr(cte_1.TO_FROM_STRING, ':'))) AS TO_MEAS
            FROM cte_1
            )
            --merge ranges
            ,cte_merge_ranges
        AS (
            SELECT s1.FROM_MEAS
                ,
                --t1.TO_MEAS 
                MIN(t1.TO_MEAS) AS TO_MEAS
            FROM cte_2 s1
            INNER JOIN cte_2 t1 ON s1.FROM_MEAS <= t1.TO_MEAS
                AND NOT EXISTS (
                    SELECT *
                    FROM cte_2 t2
                    WHERE t1.TO_MEAS >= t2.FROM_MEAS
                        AND t1.TO_MEAS < t2.TO_MEAS
                    )
            WHERE NOT EXISTS (
                    SELECT *
                    FROM cte_2 s2
                    WHERE s1.FROM_MEAS > s2.FROM_MEAS
                        AND s1.FROM_MEAS <= s2.TO_MEAS
                    )
            GROUP BY s1.FROM_MEAS
            )
        SELECT sum(TO_MEAS - FROM_MEAS) AS DISTANCE_COVERED
        INTO intRetNum
        FROM cte_merge_ranges;
    
        RETURN intRetNum;
    END;
    

    然后编写了这个查询,该查询为该函数在适当的先验范围内构建了一个字符串。无法通过 list_agg 使用窗口化,但能够通过相关子查询实现相同的效果。

    --use list agg to create list of to/from pairs for rows before current row in the ordering
    WITH cte_2
    AS (
        SELECT T1.*
            ,(
                SELECT LISTAGG(FROM_MEAS || ':' || TO_MEAS || ';') WITHIN
                GROUP (
                        ORDER BY ORDER BY YEAR DESC, EVENT_ID DESC
                        )
                FROM road_events T2
                WHERE T1.YEAR || lpad(T1.EVENT_ID, 10,'0') < 
                    T2.YEAR || lpad(T2.EVENT_ID, 10,'0')
                    AND T1.ROAD_ID = T2.ROAD_ID
                GROUP BY road_id
                ) AS PRIOR_RANGES_STR
        FROM road_events T1
        )
        --get distance for prior range string - distance ignoring current row
        --get distance including current row
        ,cte_3
    AS (
        SELECT cte_2.*
            ,coalesce(get_distance_range_str(PRIOR_RANGES_STR), 0) AS DIST_PRIOR
            ,get_distance_range_str(PRIOR_RANGES_STR || FROM_MEAS || ':' || TO_MEAS || ';') AS DIST_NOW
        FROM cte_2 cte_2
        )
        --distance including current row less distance ignoring current row is distance added to the range this row
        ,cte_4
    AS (
        SELECT cte_3.*
            ,DIST_NOW - DIST_PRIOR AS DIST_ADDED_THIS_ROW
        FROM cte_3
        )
    SELECT *
    FROM cte_4
    --filter out any rows with distance added as 0
    WHERE DIST_ADDED_THIS_ROW > 0
    ORDER BY ROAD_ID, YEAR DESC, EVENT_ID DESC
    

    这里的sqlfiddle:http://sqlfiddle.com/#!4/81331/36

    在我看来,结果与您的相符。我在最终查询中留下了额外的列以尝试说明每个步骤。

    在测试用例上工作 - 可能需要一些工作来处理更大数据集中的所有可能性,但我认为这是一个开始和完善的好地方。

    重叠范围合并的信用是这里的第一个答案:Merge overlapping date intervals

    带有窗口的 list_agg 的信用是这里的第一个答案: LISTAGG equivalent with windowing clause

    【讨论】:

    • 我认为消除
    • 重读这个问题,我构建它并不是为了严格满足您的标准——在字里行间进行读取并解决了“每英里的道路最后一次施工是什么时候?”。跨度>
    • 酷 - 添加了一点以应用 YEAR DESC、EVENT_ID DESC 的顺序,以更好地处理同一行同一年的多个事件的情况。
    • Select events that represent the most recent work on roadswhen was each mile of road last worked on?有什么区别?
    • 没什么——我指的是建议的逻辑部分。有些规则读起来就像您只比较 2 行。我也对表明总和可能大于道路长度的公差感到困惑,所以我想我可能误解了其中一些规则。猜猜我没有在字里行间阅读,而是阅读了问题,查看了建议的逻辑,然后忽略了帖子中建议的逻辑部分。
    【解决方案4】:

    我对你的“道路事件”有疑问,因为你没有描述什么是 1st meas,我认为它是 0 到 1 之间的期间,没有 1。

    所以,你可以用一个查询来计算这个:

    with newest_MEAS as (
    select ROAD_ID, MEAS.m, max(year) y
    from road_events
    join (select rownum -1 m 
          from dual 
          connect by rownum -1 <= (select max(TOTAL_ROAD_LENGTH) from road_events) ) MEAS
      on MEAS.m between FROM_MEAS and TO_MEAS
    group by ROAD_ID, MEAS.m )
    select re.event_id, nm.ROAD_ID, re.total_road_length, nm.y, count(nm.m) EVENT_LENGTH
    from newest_MEAS nm
    join road_events re 
      on nm.ROAD_ID = re.ROAD_ID
      and nm.m between re.from_meas and re.to_meas -1
      and nm.y = re.year
    group by re.event_id, nm.ROAD_ID, re.total_road_length, nm.y
    order by event_id
    

    SQL Fiddle

    【讨论】:

      【解决方案5】:

      解决方案:

      SELECT RE.road_id, RE.event_id, RE.year, RE.from_meas, RE.to_meas, RE.road_length, RE.event_length, RE.used_length, RE.leftover_length
        FROM
        (
          SELECT RE.C_road_id[road_id], RE.C_event_id[event_id], RE.C_year[year], RE.C_from_meas[from_meas], RE.C_to_meas[to_meas], RE.C_road_length[road_length],
                 RE.event_length, RE.used_length, (RE.event_length - (CASE WHEN RE.HasOverlap = 1 THEN RE.used_length ELSE 0 END))[leftover_length]
            FROM
            (
              SELECT RE.C_road_id, RE.C_event_id, RE.C_year, RE.C_from_meas, RE.C_to_meas, RE.C_road_length,
                     (CASE WHEN MAX(RE.A_event_id) IS NOT NULL THEN 1 ELSE 0 END)[HasOverlap],
                     (RE.C_to_meas - RE.C_from_meas)[event_length],
                     SUM(   (CASE WHEN RE.O_to_meas <= RE.C_to_meas THEN RE.O_to_meas ELSE RE.C_to_meas END)
                          - (CASE WHEN RE.O_from_meas >= RE.C_from_meas THEN RE.O_from_meas ELSE RE.C_from_meas END)
                        )[used_length]--This is the length that is already being counted towards later years.
                FROM
                (
                  SELECT RE.C_road_id, RE.C_event_id, RE.C_year, RE.C_from_meas, RE.C_to_meas, RE.C_road_length,
                         RE.A_event_id, MIN(RE.O_from_meas)[O_from_meas], MAX(RE.O_to_meas)[O_to_meas]
                    FROM
                    (
                      SELECT RE_C.road_id[C_road_id], RE_C.event_id[C_event_id], RE_C.year[C_year], RE_C.from_meas[C_from_meas], RE_C.to_meas[C_to_meas], RE_C.total_road_length[C_road_length],
                             RE_A.road_id[A_road_id], RE_A.event_id[A_event_id], RE_A.year[A_year], RE_A.from_meas[A_from_meas], RE_A.to_meas[A_to_meas], RE_A.total_road_length[A_road_length],
                             RE_O.road_id[O_road_id], RE_O.event_id[O_event_id], RE_O.year[O_year], RE_O.from_meas[O_from_meas], RE_O.to_meas[O_to_meas], RE_O.total_road_length[O_road_length],
                             (ROW_NUMBER() OVER (PARTITION BY RE_C.road_id, RE_C.event_id, RE_O.event_id ORDER BY RE_S.Overlap DESC, RE_A.event_id))[RowNum]--Use to Group Overlaps into Swaths.
                        FROM road_events as RE_C--Current.
                        LEFT JOIN road_events as RE_A--After.  --Use a Left-Join to capture when there is only 1 Event (or it is the Last-Event in the list).
                          ON RE_A.road_id   = RE_C.road_id
                         AND RE_A.event_id != RE_C.event_id--Not the same EventID.
                         AND RE_A.year     >= RE_C.year--Occured on or After the Current Event.
                         AND (    (RE_A.from_meas >= RE_C.from_meas AND RE_A.from_meas <= RE_C.to_meas)--There is Overlap.
                               OR (RE_A.to_meas   >= RE_C.from_meas AND RE_A.to_meas   <= RE_C.to_meas)--There is Overlap.
                               OR (RE_A.to_meas    = RE_C.to_meas   AND RE_A.from_meas  = RE_C.from_meas)--They are Equal.
                             )
                        LEFT JOIN road_events as RE_O--Overlapped/Linked.
                          ON RE_O.road_id   = RE_C.road_id
                         AND RE_O.event_id != RE_C.event_id--Not the same EventID.
                         AND RE_O.year     >= RE_C.year--Occured on or After the Current Event.
                         AND (    (RE_O.from_meas >= RE_A.from_meas AND RE_O.from_meas <= RE_A.to_meas)--There is Overlap.
                               OR (RE_O.to_meas   >= RE_A.from_meas AND RE_O.to_meas   <= RE_A.to_meas)--There is Overlap.
                               OR (RE_O.to_meas    = RE_A.to_meas   AND RE_O.from_meas  = RE_A.from_meas)--They are Equal.
                             )
                        OUTER APPLY
                        (
                          SELECT COUNT(*)[Overlap]
                            FROM road_events as RE_O--Overlapped/Linked.
                           WHERE RE_O.road_id   = RE_C.road_id
                             AND RE_O.event_id != RE_C.event_id--Not the same EventID.
                             AND RE_O.year     >= RE_C.year--Occured on or After the Current Event.
                             AND (    (RE_O.from_meas >= RE_A.from_meas AND RE_O.from_meas <= RE_A.to_meas)--There is Overlap.
                                   OR (RE_O.to_meas   >= RE_A.from_meas AND RE_O.to_meas   <= RE_A.to_meas)--There is Overlap.
                                   OR (RE_O.to_meas    = RE_A.to_meas   AND RE_O.from_meas  = RE_A.from_meas)--They are Equal.
                                 )
                        ) AS RE_S--Swath of Overlaps.
                    ) AS RE
                   WHERE RE.RowNum = 1--Remove Duplicates and Select those that are in the biggest Swaths.
                   GROUP BY RE.C_road_id, RE.C_event_id, RE.C_year, RE.C_from_meas, RE.C_to_meas, RE.C_road_length,
                            RE.A_event_id
                ) AS RE
               GROUP BY RE.C_road_id, RE.C_event_id, RE.C_year, RE.C_from_meas, RE.C_to_meas, RE.C_road_length
            ) AS RE
        ) AS RE
       WHERE RE.leftover_length > 0--Filter out Events that had their entire Segments overlapped by a Later Event(s).
       ORDER BY RE.road_id, RE.year DESC, RE.event_id
      

      SQL 小提琴:
      http://sqlfiddle.com/#!18/2880b/1

      添加的规则/假设/说明:
      1.) 允许 event_idroad_id 可能是 Guid 的或乱序创建的,
      所以不要在脚本中假设更高或更低的值给记录的关系带来意义。
      例如:
      ID 为 1 和 ID 为 2 并不能保证 ID 为 2 是最新的(反之亦然)。
      这样解决方案将更加通用,并且不那么“hacky”。​​
      2.) 过滤掉其整个段与后来的事件重叠的事件。
      例如:
      如果 2008 年 20-50 有工作,2009 年 10-60 有工作,
      那么 2008 年的 Event 将被过滤掉,因为它的整个 Segment 在 2009 年被重新散列。

      其他测试数据:
      为确保解决方案不仅仅针对给定的数据集量身定制,
      我在原始数据集中添加了一个 6road_id,以便解决更多的边缘情况。

      INSERT INTO road_events (event_id, road_id, year, from_meas, to_meas, total_road_length) VALUES (16,6,2012,0,100,100);
      INSERT INTO road_events (event_id, road_id, year, from_meas, to_meas, total_road_length) VALUES (17,6,2013,68,69,100);
      INSERT INTO road_events (event_id, road_id, year, from_meas, to_meas, total_road_length) VALUES (18,6,2014,65,66,100);
      INSERT INTO road_events (event_id, road_id, year, from_meas, to_meas, total_road_length) VALUES (19,6,2015,62,63,100);
      INSERT INTO road_events (event_id, road_id, year, from_meas, to_meas, total_road_length) VALUES (20,6,2016,50,60,100);
      INSERT INTO road_events (event_id, road_id, year, from_meas, to_meas, total_road_length) VALUES (21,6,2017,30,40,100);
      INSERT INTO road_events (event_id, road_id, year, from_meas, to_meas, total_road_length) VALUES (22,6,2017,20,55,100);
      INSERT INTO road_events (event_id, road_id, year, from_meas, to_meas, total_road_length) VALUES (23,6,2018,0,25,100);
      

      结果:我在 Green 中添加了 8 附加记录)

      数据库版本:
      此解决方案与 Oracle 和 SQL-Server 无关:
      它应该适用于 SS2008+ 和 Oracle 12c+。

      这个问题用 Oracle 12c 标记,但是没有我可以在没有注册的情况下使用的在线小提琴,
      所以我在 SQL-Server 中对其进行了测试 - 但相同的语法应该适用于两者。
      我的大部分查询都依赖 Cross-ApplyOuter-Apply
      Oracle 在 12c 中引入了这些“联接”:
      https://oracle-base.com/articles/12c/lateral-inline-views-cross-apply-and-outer-apply-joins-12cr1

      简单而高效:
      这使用:
      • 无相关子查询。
      • 无递归。
      • 没有 CTE。
      • 没有工会。
      • 没有用户功能。

      索引:
      我在您的一个 cmets 中读到了您询问的有关索引的问题。
      我将为您将要搜索和分组的每个主要字段添加 1 列索引:
      road_idevent_idyear
      你可以看看这个索引是否对你有帮助(我不知道你打算如何使用这些数据):
      关键字段: road_idevent_idyear
      包括: from_measto_meas

      标题:
      您可能需要考虑将此问题的标题重命名为更易于搜索的名称,例如:
      聚合重叠段以测量有效长度”。
      这将使解决方案更容易找到,以帮助有类似问题的其他人。

      其他想法:
      这样的事情在计算花费在某事上的总时间时很有用
      具有重叠的开始和停止时间戳。

      【讨论】:

        【解决方案6】:

        这个发现扩展了表格,为每条道路的每英里生成一行,并简单地采用 MAX 年份。然后我们可以通过COUNT 的行数来生成 event_length。

        它完全按照您上面指定的方式生成表格。

        注意:我对 SQL Server 运行了此查询。我认为您可以在 Oracle 中使用 LEAST 而不是 SELECT MIN(event_length) FROM (VALUES...)

        WITH NumberRange(result) AS 
        (
            SELECT 0
            UNION ALL
            SELECT result + 1
            FROM   NumberRange 
            WHERE  result < 301 --Max length of any road
        ),
        CurrentRoadEventLength(road_id, [year], event_length) AS
        (
            SELECT road_id, [year], COUNT(*) AS event_length
            FROM   (
                    SELECT re.road_id, n.result, MAX(re.[year]) as [year]
                    FROM   road_events re INNER JOIN NumberRange n 
                           ON (    re.from_meas <= n.result 
                               AND re.to_meas > n.result
                              )
                    GROUP BY re.road_id, n.result
                   ) events_per_mile
            GROUP BY road_id, [year]
        )
        SELECT re.event_id, re.road_id, re.[year], re.total_road_length, 
               (SELECT MIN(event_length) FROM (VALUES (re.to_meas - re.from_meas), (cre.event_length)) AS EventLengths(event_length))
        FROM   road_events re INNER JOIN CurrentRoadEventLength cre
               ON (    re.road_id = cre.road_id
                   AND re.[year] = cre.[year]
                  )
        ORDER BY re.event_id, re.road_id
        OPTION (MAXRECURSION 301) --Max length of any road
        

        【讨论】:

          猜你喜欢
          • 2021-07-28
          • 2019-04-13
          • 2015-05-20
          • 2018-05-09
          • 2020-08-17
          • 1970-01-01
          • 2019-09-21
          • 2018-04-26
          • 2021-06-12
          相关资源
          最近更新 更多