【问题标题】:How to Get the Shorthest Path in a Flightroutes-Table如何获得飞行路线表中的最短路径
【发布时间】:2017-04-19 11:22:36
【问题描述】:

我无法获得一份声明以获取航班上所有中途停留的信息。

我有如下的航线表,其中有一个源机场和一个目的地机场。 现在我想获得从机场 A 到机场 B 的最短航线(中途停留最少),没有从 A 到 B 的直达航线,所以我必须将多条航线连接在一起。

例如,如果我想从 18 到 1403,我想获取路线

(18 > 24 | 24 > 87 | 87 > 1403) 

而不是

(18 > 24 | 24 > 87 | 87 > 99| 99 > 1403)

这是一些测试数据

src_apid | dst_apid
---------+----------
18       | 24
24       | 87
87       | 99
87       | 1403
99       | 18
99       | 1403

我的尝试如下所示:

WITH rejkabrest (
src_apid,
dst_apid
) AS (
SELECT
    src_apid,
    dst_apid
FROM
    routes
WHERE
    src_apid = 18  
UNION ALL 
SELECT
    a.src_apid,
    a.dst_apid
FROM
    routes a
    INNER JOIN rejkabrest b ON a.src_apid = b.dst_apid
    WHERE b.dst_apid = 1403
) SELECT
src_apid, dst_apid
FROM
rejkabrest;

但是这种方式我只能得到从源机场 18 开始的所有路线。如果我尝试另一种方式,我会遇到循环问题。

希望你们能帮助我。非常感谢!

【问题讨论】:

标签: sql oracle shortest-path


【解决方案1】:

这是递归构建路径的一种方法。使用CYCLE 子句来避免循环异常。您可以使用 Oracle 的 KEEP FIRST 从找到的路径中获得最短路径。

with cte (dst_apid, path, stops) as
(
  select dst_apid, src_apid || ' > ' || dst_apid as path, 0 as stops
  from routes
  where src_apid = 18  
  union all 
  select r.dst_apid, cte.path || ' > ' || r.dst_apid, cte.stops + 1
  from cte 
  join routes r on r.src_apid = cte.dst_apid
  where cte.dst_apid <> 1403
)
cycle dst_apid set cycle to 1 default 0
select max(path) keep (dense_rank first order by stops)
from cte
where cte.dst_apid = 1403;

除了KEEP FIRST,这是标准 SQL。您可以改用SELECT path FROM cte WHERE dst_apid = 1403 FETCH FIRST 1 ROW ONLY 来使此标准兼容。 Oracle 从 12c 开始支持这种语法。

【讨论】:

  • 您好,谢谢您的回答。您可能希望将第 7 行中的 stop 更改为 cte.stops 以供将来的读者使用(否则它将不起作用,至少对我而言)。我真的无法执行查询,因为我的表有数千行。是否可以在航班之间添加最大停靠次数以提高性能?等了 30 分钟后,我仍然没有得到任何结果:-/
  • 好吧,它在没有限定符的情况下对我有用,但我会添加它,因为这显然取决于 Oracle 版本(或者您在路由表中有一个名为 stop 的列)。当然,您可以限制停靠点的数量,例如:cte.dst_apid &lt;&gt; 1403 and cte.stops &lt; 4 在 cte 中的位置。 (cte.stops &lt; 4 限制为 4 站;使用您想要的任何数字。)
【解决方案2】:

使用connect by nocycle 和函数rank()

select path
  from (
    select r.*, rank() over (order by lvl) rnk
      from (select routes.*, level lvl, 
                   sys_connect_by_path(src_apid, '->')||'->'||dst_apid path
              from routes 
              connect by nocycle src_apid = prior dst_apid and src_apid <> 1403
              start with src_apid = 18) r
      where dst_apid = 1403 )
  where rnk = 1

演示:

with routes (src_apid, dst_apid ) as (
    select 18,   24 from dual union all
    select 24,   87 from dual union all
    select 87,   99 from dual union all
    select 87, 1403 from dual union all
    select 99,   18 from dual union all
    select 99, 1403 from dual )
select path
  from (
    select r.*, rank() over (order by lvl) rnk
      from (select routes.*, level lvl, 
                   sys_connect_by_path(src_apid, '->')||'->'||dst_apid path
              from routes 
              connect by nocycle src_apid = prior dst_apid and src_apid <> 1403
              start with src_apid = 18) r
      where dst_apid = 1403 )
  where rnk = 1

PATH
--------------------
->18->24->87->1403

【讨论】:

    【解决方案3】:

    如果您希望每个航班都有一行,那么想到的唯一解决方案是两个递归查询。第一个构建编号为 1、1.1、1.2、1.1.1 等的航线;秒收集属于同一路线的航班。相当复杂:

    with cte1 (routepart, pos, src_apid, dst_apid) as
    (
        select to_char(rownum) as routepart, 1 as pos, src_apid, dst_apid
        from routes
        where src_apid = 18  
      union all 
      select cte1.routepart || '-' || rownum, pos + 1, r.src_apid, r.dst_apid
      from cte1 
      join routes r on r.src_apid = cte1.dst_apid
      where cte1.dst_apid <> 1403
    )
    cycle src_apid set cycle to 1 default 0
    , cte2 (route, routepart, pos, src_apid, dst_apid) as
    (
      select routepart as route, routepart, pos, src_apid, dst_apid
      from cte1
      where dst_apid = 1403
      union all
      select cte2.route, cte1.routepart, cte1.pos, cte1.src_apid, cte1.dst_apid
      from cte1
      join cte2 on cte2.routepart like cte1.routepart || '%'
                and nvl(length(regexp_replace(cte2.routepart, '[[:digit:]]', '')), 0) =
                    nvl(length(regexp_replace(cte1.routepart, '[[:digit:]]', '')), 0) + 1
    )
    cycle src_apid set cycle to 1 default 0
    select pos, src_apid, dst_apid
    from
    (
      select
        cte2.*, 
        rank() over (order by length(regexp_replace(route, '[[:digit:]]', ''))) as rn
      from cte2
    )
    where rn = 1
    order by route, pos;
    

    如果您不想打领带,请使用 ROW_NUMBER 而不是 RANK

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-09-27
      • 1970-01-01
      • 2011-12-19
      • 1970-01-01
      • 2017-11-17
      • 1970-01-01
      • 2017-09-12
      相关资源
      最近更新 更多