将数据重新排序为“从”和“到”列

时间:2018-08-21 23:28:25

标签: sql oracle

此餐桌之旅在汽车行驶时对其进行跟踪。

Journey   Chkpt1 Chkpt2
    51    AAA    BBB
    51    BBB    CCC
    51    DDD    CCC
    51    EEE    DDD
    51    EEE    FFF

从段AAA-BBB到段BBB-CCC,然后到DDD-CCC,依此类推。

但是列Chkpt1和Chkpt2的顺序不正确。 我想生成一个重新排序的表,在同一旅程中,其中一列始终表示“发件人”,另一列始终表示“发件人”。因此我们可以很容易地看到这辆车正在从AAA到BBB,从BBB到CCC,从CCC到DDD(而不是从DDD到CCC)。

所需的结果将是:

Reordered Chkpt1 Chkpt2
    51    AAA    BBB
    51    BBB    CCC
    51    CCC    DDD    *
    51    DDD    EEE    *
    51    EEE    FFF

(星号仅用于标记已更改的行。)

在下一个示例中,汽车开始从Chkpt2移动到Chkpt1。

示例2:

Journey Chkpt1 Chkpt2
    52    NNN    MMM
    52    OOO    NNN
    52    PPP    OOO
    52    PPP    QQQ
    52    RRR    QQQ

从Chkpt2到Chkpt1,下面所有行应以相同的方式重新排序。

Reordered Chkpt1 Chkpt2
    52    NNN    MMM
    52    OOO    NNN
    52    PPP    OOO
    52    QQQ    PPP    *
    52    RRR    QQQ

SQL是怎么做到的?

注意:以上结果对我来说足够了。 但是,或者,结果始终可以显示在“从”和“到”两列中。

Reordered2 From  To
    51    AAA    BBB
    51    BBB    CCC
    51    CCC    DDD    *
    51    DDD    EEE    *
    51    EEE    FFF
    52    NNN    MMM
    52    OOO    NNN
    52    PPP    OOO
    52    QQQ    PPP    *
    52    RRR    QQQ

预先感谢,
艾默生

2 个答案:

答案 0 :(得分:2)

这里是执行此操作的一种方法-使用递归查询(因此需要Oracle 11.2或更高版本)。对于较早的版本,使用分层查询(CONNECT BY)重写它应该不会太困难。

该查询标识每个旅程的两个端点,并假定“起点”是第一个终点(按字母顺序);因此,如果将端点标识为AAA和FFF,则查询将选择AAA作为起点,并选择FFF作为旅程的终点​​。可以在origins分解子查询(CTE)中轻松更改。

输出按行程排序,然后按航段号(第一个,第二个等)排序。保留CHKPT列,最后两列显示相应航段的FROM和TO端点。

with
  simulated_data (journey, chkpt1, chkpt2) as (
    select 51, 'AAA', 'BBB' from dual union all
    select 51, 'BBB', 'CCC' from dual union all
    select 51, 'DDD', 'CCC' from dual union all
    select 51, 'EEE', 'DDD' from dual union all
    select 51, 'EEE', 'FFF' from dual union all
    select 52, 'NNN', 'MMM' from dual union all
    select 52, 'OOO', 'NNN' from dual union all
    select 52, 'PPP', 'OOO' from dual union all
    select 52, 'PPP', 'QQQ' from dual union all
    select 52, 'RRR', 'QQQ' from dual
  ) -- select * from simulated_data; /*
, endpoints (journey, endpoint) as (
    select   journey, chkpt
    from     (
               select  journey, chkpt
               from    simulated_data
               unpivot (chkpt for col in (chkpt1, chkpt2))
             )
    group by journey, chkpt
    having   count(*) = 1
  )  -- select * from endpoints; /*
, origins (journey, orig) as (
    select   journey, min(endpoint)
    from     endpoints
    group by journey
  ) -- select * from origins; /*
, rec (journey, leg, chkpt1, chkpt2, leg_start, leg_end) as (
    select o.journey, 1, s.chkpt1, s.chkpt2, o.orig, 
           case o.orig when s.chkpt1 then chkpt2 else chkpt1 end
      from origins o join simulated_data s
                     on  o.journey = s.journey
                     and o.orig in (s.chkpt1, chkpt2)
    union all
    select r.journey, leg + 1, s.chkpt1, s.chkpt2, r.leg_end,
           case r.leg_end when s.chkpt1 then s.chkpt2 else s.chkpt1 end
      from rec r join simulated_data s
                 on   r.journey = s.journey
                 and  r.leg_end       in (s.chkpt1, s.chkpt2)
                 and  r.leg_start not in (s.chkpt1, s.chkpt2)
  )
select   journey, leg, chkpt1, chkpt2, leg_start, leg_end
from     rec
order by journey, leg
;

输出:

   JOURNEY        LEG  CHKPT1     CHKPT2     LEG_START  LEG_END   
---------- ----------  ---------- ---------- ---------- ----------
        51          1  AAA        BBB        AAA        BBB       
        51          2  BBB        CCC        BBB        CCC       
        51          3  DDD        CCC        CCC        DDD       
        51          4  EEE        DDD        DDD        EEE       
        51          5  EEE        FFF        EEE        FFF       
        52          1  NNN        MMM        MMM        NNN       
        52          2  OOO        NNN        NNN        OOO       
        52          3  PPP        OOO        OOO        PPP       
        52          4  PPP        QQQ        PPP        QQQ       
        52          5  RRR        QQQ        QQQ        RRR

答案 1 :(得分:2)

您将需要一列来定义顺序。我只是“发明”了一个名为LEG的人。将其更改为您的时间戳记或任何内容。

然后,您可以使用按旅程划分的LEAD()来检查下一行的值。如果chkpt1等于下一行的任何检查点,则必须将其交换为chkpt2。如果chkpt2等于以下两个值中的任何一个,则检查点的顺序已经正确。

如有必要,将其放在CASE ... END中以交换列。

但是,如果没有下一行,则LEAD()将返回NULL,并且所有相等性检查都将不匹配。但是有可能旅程的最后一行中的列也必须互换。要处理这种情况,请添加检查,并使用LAG()和模拟逻辑将检查点与上一行的检查点进行比较。

SELECT JOURNEY,
       CASE
         WHEN CHKPT1 IN (LEAD(CHKPT1, 1) OVER (PARTITION BY JOURNEY
                                               ORDER BY LEG),
                         LEAD(CHKPT2, 1) OVER (PARTITION BY JOURNEY
                                               ORDER BY LEG)) THEN
           CHKPT2
         WHEN CHKPT2 IN (LEAD(CHKPT1, 1) OVER (PARTITION BY JOURNEY
                                               ORDER BY LEG),
                         LEAD(CHKPT2, 1) OVER (PARTITION BY JOURNEY
                                               ORDER BY LEG)) THEN
           CHKPT1
         WHEN CHKPT1 IN (LAG(CHKPT1, 1) OVER (PARTITION BY JOURNEY
                                              ORDER BY LEG),
                         LAG(CHKPT2, 1) OVER (PARTITION BY JOURNEY
                                              ORDER BY LEG)) THEN
           CHKPT1
         WHEN CHKPT2 IN (LAG(CHKPT1, 1) OVER (PARTITION BY JOURNEY
                                              ORDER BY LEG),
                         LAG(CHKPT2, 1) OVER (PARTITION BY JOURNEY
                                              ORDER BY LEG)) THEN
           CHKPT2
       END CHKPT1,
       CASE
         WHEN CHKPT1 IN (LEAD(CHKPT1, 1) OVER (PARTITION BY JOURNEY
                                               ORDER BY LEG),
                         LEAD(CHKPT2, 1) OVER (PARTITION BY JOURNEY
                                               ORDER BY LEG)) THEN
           CHKPT1
         WHEN CHKPT2 IN (LEAD(CHKPT1, 1) OVER (PARTITION BY JOURNEY
                                               ORDER BY LEG),
                         LEAD(CHKPT2, 1) OVER (PARTITION BY JOURNEY
                                               ORDER BY LEG)) THEN
           CHKPT2
         WHEN CHKPT1 IN (LAG(CHKPT1, 1) OVER (PARTITION BY JOURNEY
                                              ORDER BY LEG),
                         LAG(CHKPT2, 1) OVER (PARTITION BY JOURNEY
                                              ORDER BY LEG)) THEN
           CHKPT2
         WHEN CHKPT2 IN (LAG(CHKPT1, 1) OVER (PARTITION BY JOURNEY
                                              ORDER BY LEG),
                         LAG(CHKPT2, 1) OVER (PARTITION BY JOURNEY
                                              ORDER BY LEG)) THEN
           CHKPT1
       END CHKPT2
       FROM JOURNEY;

db<>fiddle

注意:

仅在两个检查点之间的周期内失败。但是,我认为,跨越两个以上检查点的周期应该起作用。

通常可能只需要在两个检查点之间进行额外的检查就可以处理一个循环,但是在这种情况下无法推断出方向,因此必须随机采取其中的一个,不一定是正确的。

对于仅跨越两个检查点(表中仅一行)的旅程也将失败,因为LEAD()LAG()都将返回NULL。这可能需要额外检查。但是,就像只有两个检查点的循环一样,无法确定顺序。该表的数据可能是正确的,也可能是错误的,仅靠手头的给定数据是不可能的。