Sql查询将src转换为用于连接航班的dest路径

时间:2017-10-06 02:23:47

标签: sql postgresql

我想解决一个问题,我希望找到src到目的地的飞行路径,包括连接航班,如果可能的话,需要时间排序。

我可以使用像listagg这样的东西以某种方式将中间航班聚合为字符串。

我们可以限制连接航班的数量和所需的时间。

我现在把它作为一个开始,让我连接航班

with  cap as (select 30 time_cap , 5 connect_cap), 
 connecting as 
    (select f1.src src1
          , f1.dest dest1
          , f1.stt st1
          , f1.endt end1
          , f2.src src2
          , f2.dest dest2
          , f2.stt st2
          , f2.endt end2
          , (f2.endt - f1.stt) as time 
     from flight f1 
     inner join flight f2 on f1.dest = f2.src 
     where f1.endt < f2.stt)

我的表现在看起来像这样

\d+ flight
                                  Table "public.flight"
 Column |            Type             | Modifiers | Storage  | Stats target | Description 
--------+-----------------------------+-----------+----------+--------------+-------------
 src    | character varying(20)       |           | extended |              | 
 dest   | character varying(20)       |           | extended |              | 
 stt    | timestamp without time zone |           | plain    |              | 
 endt   | timestamp without time zone |           | plain    |              | 

这是一个已经结束的面试问题。

可以在sql查询中解决图表bfs类解决方案吗?

即使是非工作查询(伪代码 - 如果尝试过也可以工作)或方法都可以。

在下面的查询中, 我想在string_agg中找到一种方法,我可以检查最后一个目的地是否是我想去的目的地。

with flight as (select f1.src||'-'||f1.dest||','||f2.src||'-'||f2.dest route
                     , f1.src src1
                     , f1.dest dest1
                     , f1.stt st1
                     , f1.endt end1
                     , f2.src src2
                     , f2.dest dest2
                     , f2.stt st2
                     , f2.endt end2
                     , (f2.endt - f1.stt) as time 
                from flight f1 
                inner join flight f2 on f1.dest = f2.src 
                where f1.endt < f2.stt) 

select string_agg(route,',') from flight ;

查询航班的输出

  route  | src1 | dest1 |         st1         |        end1         | src2 | dest2 |         st2         |        end2         |   time   
---------+------+-------+---------------------+---------------------+------+-------+---------------------+---------------------+----------
 a-b,b-c | a    | b     | 2017-05-17 09:31:56 | 2017-05-17 14:31:56 | b    | c     | 2017-05-17 15:31:56 | 2017-05-17 16:31:56 | 07:00:00

1 个答案:

答案 0 :(得分:1)

SQL中的这些类型的树遍历问题可以使用特殊语法WITH RECURSIVE来解决。

要测试解决方案,我们使用虚拟数据创建下表:

CREATE TABLE flight (
  src TEXT
, dest TEXT
, stt TIMESTAMP
, endt TIMESTAMP);

INSERT INTO flight VALUES 
('SIN', 'DAC', '2016-12-31 22:45:00', '2017-01-01 01:45:00'),
('DAC', 'SIN', '2017-01-01 16:30:00', '2017-01-01 21:30:00'),
('SIN', 'DAC', '2017-01-01 22:45:00', '2017-01-02 01:45:00'),
('DAC', 'DEL', '2017-01-01 10:00:00', '2017-01-01 11:30:00'),
('DEL', 'DAC', '2017-01-01 12:30:00', '2017-01-01 14:00:00'),
('DAC', 'CCU', '2017-01-01 10:30:00', '2017-01-01 11:15:00'),
('CCU', 'DAC', '2017-01-01 11:45:00', '2017-01-01 12:30:00'),
('SIN', 'DEL', '2017-01-01 11:00:00', '2017-01-01 15:00:00'),
('DEL', 'SIN', '2017-01-01 16:30:00', '2017-01-01 20:30:00'),
('CCU', 'DEL', '2017-01-01 12:00:00', '2017-01-01 12:45:00'),
('DEL', 'CCU', '2017-01-01 13:15:00', '2017-01-01 14:00:00');

递归CTE很难理解,所以我将逐块构建逻辑。

在递归CTE中有两部分。一个锚子查询&amp;递归子查询。首先执行锚子查询,然后执行递归子查询,直到返回空结果集。棘手的部分(至少对我而言)是构建将从1个节点到下一个节点执行遍历的连接。

使用UNION ALL(或有时UNION)合并锚点和递归子查询并返回。

由于我们对航线路线感兴趣,因此开始的简单锚点子查询将是:

SELECT src, ARRAY[src] path, dest FROM flight

以上查询有3列用于start,path&amp;结束,在第二列中,我们将src字段从TEXT转换为TEXT[]。虽然这不是严格需要的,但它将大大简化剩余的步骤,因为数组很容易附加。

使用上面的锚点查询,我们现在可以定义我们的递归cte。

WITH RECURSIVE flight_paths (src, path, dest) AS (
SELECT
  src
, ARRAY[src]
, dest 
FROM flight
UNION ALL
SELECT
  fp.src
, fp.path || f.src -- appends another 'flight source'
, f.dest -- updates the dest to the new dest
FROM flight f
JOIN flight_paths fp ON f.src = fp.dest 
-- this is the join that links the tree nodes
WHERE NOT f.src = ANY(fp.path) 
-- this condition prevents infinite recursion by not visiting nodes that have already been visited.
) SELECT * FROM flight_paths
-- finally, we can select from the flight_paths recursive cte

上面的测试数据返回136行。前15行显示如下:

src path        dest
SIN {SIN}       DAC
DAC {DAC}       SIN
SIN {SIN}       DAC
DAC {DAC}       DEL
DEL {DEL}       DAC
DAC {DAC}       CCU
CCU {CCU}       DAC
SIN {SIN}       DEL
DEL {DEL}       SIN
CCU {CCU}       DEL
DEL {DEL}       CCU
DEL {DEL,CCU}   DAC
DAC {DAC,CCU}   DAC
DEL {DEL,CCU}   DEL
DAC {DAC,CCU}   DEL
DEL {DEL,CCU}   DEL
DAC {DAC,CCU}   DEL

这部分设置树遍历,是编写递归cte最难的部分。现在,设置了遍历,我们可以修改上面的内容,以便:

  1. 我们从源头开始在目的地结束
  2. 到达目的地时停止迭代
  3. 仅考虑转机航班A-B&amp; B-C在到达时有效(A-B)&lt;离开(B-C)
  4. 对于此示例,请src := SIN&amp; dest := CCU

    WITH RECURSIVE flight_paths (src, path, dest, stt, endt) AS (
    SELECT
      src
    , ARRAY[src]
    , dest 
    , stt
    , endt
    FROM flight
    UNION ALL
    SELECT
      fp.src
    , fp.path || f.src
    , f.dest 
    , fp.stt
    , f.endt -- update endt to the new endt
    FROM flight f
    JOIN flight_paths fp ON f.src = fp.dest 
    WHERE NOT f.src = ANY(fp.path) 
      AND NOT 'CCU' = ANY(fp.path) 
      -- (2) this new predicate stop iteration when the destination is reached
      AND f.stt > fp.endt
      -- (3) this new predicate only proceeds the iteration if the connecting flights are valid
    ) 
    SELECT * 
    FROM flight_paths
    WHERE src = 'SIN' AND dest = 'CCU'
    -- (1) specify the start & end
    

    这提供了2条可能的路线,第一个航班的出发时间为stt列&amp;最后一班航班的到达时间为endt

    src path            dest    stt                 endt
    SIN {SIN,DAC}       CCU     2016-12-31 22:45:00 2017-01-01 11:15:00
    SIN {SIN,DAC,DEL}   CCU     2016-12-31 22:45:00 2017-01-01 14:00:00
    

    现在可以非常轻松地优化结果集。例如,最终查询可以修改为仅返回在中午之前到达目的地的航班:

    SELECT * 
    FROM flight_paths
    WHERE src = 'SIN' AND dest = 'CCU' 
      AND endt::time < '12:00:00'::time
    

    从这一点开始,添加计算飞行时间和列的列也相当容易。递归cte中的连接时间,然后过滤适合这两次谓词的航班。我希望我已经给出了足够的细节来生成这两种变体。

    您还可以通过过滤path数组上的长度来限制连接数,尽管在达到最大连接数后停止递归cte中的迭代可能会更有效。

    最后一点说明:为了使事情变得简单,我使用了除最终目的地之外的路径,例如: {SIN, DAC, DEL}代替航班序列{SIN-DAC, DAC-DEL, DEL-CCU}或停留{DAC, DEL} ),但我们可以非常轻松地获得这些陈述,如下所示:

    WITH RECURSIVE flight_paths (src, flights, path, dest, stt, endt) AS (
    SELECT
      src
    , ARRAY[src || '-' || dest]
    , ARRAY[src]
    , dest 
    , stt
    , endt
    FROM flight
    UNION ALL
    SELECT
      fp.src
    , fp.flights || (f.src || '-' || f.dest)
    , fp.path || f.src
    , f.dest
    , fp.stt
    , f.endt
    FROM flight f
    JOIN flight_paths fp ON f.src = fp.dest 
    WHERE NOT f.src = ANY(fp.path) 
      AND NOT 'CCU' = ANY(fp.path) 
      AND f.stt > fp.endt
    ) 
    SELECT flights, stt, endt, path[2:] stopovers
    FROM flight_paths
    WHERE src = 'SIN' AND dest = 'CCU'