找到相反方向错误连接的成对电线

问题描述 投票:0回答:2

让我们有一个电线的数据库模型(下面的

-----
asciiart),其端接在插头(
*
字符)上。该插头可以是“最多两根电线的端点,形成任意长度的线性路径”或“它是单根电线的端点,这意味着它是路径的端子插头。多个线性路径(也称为“通道”)可以绑定在一起形成电路板。来自同一层平行车道的相应电线用 parallel_id 标识。棋盘上的泳道数量可能是 1 到无界。 示例:
board1
        P1  W1   P2  W2   P3  W3   P4
         *--------*--------*--------*
            p1       p2       p3
         *--------*--------*--------*
        P5  W4   P6  W5   P7  W6   P8

board2
        P9  W7   P10 W8   P11 W9   P16
         *--------*--------*--------*
            p4       p5       p6
         *--------*--------*--------*
        P13 W10  P14 W11  P15 W12  P12

board3
        P17 W13  P18 W14  P19
         *--------*--------*
            p7       p8

给出了数据库表示(当然是根据实际情况简化的):

with wire(board_id, parallel_id, wire_id, plug_l, plug_r) as (
  select 1, 1,  1,  1,  2 from dual union all
  select 1, 2,  2,  2,  3 from dual union all
  select 1, 3,  3,  3,  4 from dual union all
  select 1, 1,  4,  5,  6 from dual union all
  select 1, 2,  5,  6,  7 from dual union all
  select 1, 3,  6,  7,  8 from dual union all
  select 2, 4,  7,  9, 10 from dual union all
  select 2, 5,  8, 10, 11 from dual union all
  select 2, 6,  9, 11, 16 from dual union all
  select 2, 4, 10, 13, 14 from dual union all
  select 2, 5, 11, 14, 15 from dual union all
  select 2, 6, 12, 15, 12 from dual union all
  select 3, 7, 13, 17, 18 from dual union all
  select 3, 8, 14, 18, 19 from dual
)
select * from wire order by board_id, parallel_id, wire_id;

任务是找到所有情况,其中电路板恰好有 2 个通道,有两条具有相同

parallel_id
 的电线,并且它们的插头 id 在某个方向上的顺序与下一对插头 id 在同一方向上的顺序不同。在上面的示例中,它是从 
parallel_id=5

向右的方向,因为插头 11,15 强制对通道进行自上而下的排序,而插头 12,16 强制对通道进行自下而上的排序。

(真实案例基于网络库存环境,其中要比较的插头 ID 实际上是结构化布线中使用的一些业务编号 - 为了简单起见,我重复使用了 ID。我们还可以安全地假设 
wire.plug_%
列是

not null

并且没有插头涉及三根或更多根电线。)

预期输出是:

错误消息从 (15,11) 到 (12,16) 的连接无效
可能的解决方案之一是为
pivot
sql oracle
2个回答
0
投票
unpivot

SQL 子句编写有趣的编码方式。

SQL 查询将逐步构建,我们假设之前的 CTE 成立。

首先,让我们测量通道数并引入一些鉴别器列来区分同一

parallel_id

内不同通道的电线。

这只是辅助CTE。

,
paths as (
  select w.board_id, w.parallel_id, w.wire_id, w.plug_l, w.plug_r
       , count(*) over (partition by w.board_id, w.parallel_id) as cnt
       , row_number() over (partition by w.board_id, w.parallel_id order by null) as lane
  from wire w
)
select * from paths order by board_id, parallel_id, lane;
+--------+-----------+-------+------+------+---+----+ |BOARD_ID|PARALLEL_ID|WIRE_ID|PLUG_L|PLUG_R|CNT|LANE| +--------+-----------+-------+------+------+---+----+ |1 |1 |1 |1 |2 |2 |1 | |1 |1 |4 |5 |6 |2 |2 | |1 |2 |2 |2 |3 |2 |1 | |1 |2 |5 |6 |7 |2 |2 | |1 |3 |3 |3 |4 |2 |1 | |1 |3 |6 |7 |8 |2 |2 | |2 |4 |7 |9 |10 |2 |1 | |2 |4 |10 |13 |14 |2 |2 | |2 |5 |11 |14 |15 |2 |1 | |2 |5 |8 |10 |11 |2 |2 | |2 |6 |9 |11 |16 |2 |1 | |2 |6 |12 |15 |12 |2 |2 | |3 |7 |13 |17 |18 |1 |1 | |3 |8 |14 |18 |19 |1 |1 | +--------+-----------+-------+------+------+---+----+

给定数据表示的第一个问题是我们存储数据
每条线
,而我们需要处理

特定线对的每个方向数据。因此首先我们必须摆脱plug_lplug_r列。这些列成为具有单列

plug_id
和方向鉴别器列的行。
删除多个通常类似命名的列,以支持
less
列和鉴别器
unpivot子句的合适用例: , transposed as ( select * from paths unpivot (plug_id for direction in (plug_l as 'L', plug_r as 'R')) ) select * from transposed order by board_id, parallel_id, direction;
+--------+-----------+-------+---+----+---------+-------+
|BOARD_ID|PARALLEL_ID|WIRE_ID|CNT|LANE|DIRECTION|PLUG_ID|
+--------+-----------+-------+---+----+---------+-------+
|1       |1          |1      |2  |1   |L        |1      |
|1       |1          |4      |2  |2   |L        |5      |
|1       |1          |1      |2  |1   |R        |2      |
|1       |1          |4      |2  |2   |R        |6      |
|1       |2          |2      |2  |1   |L        |2      |
|1       |2          |5      |2  |2   |L        |6      |
|1       |2          |5      |2  |2   |R        |7      |
|1       |2          |2      |2  |1   |R        |3      |
|1       |3          |3      |2  |1   |L        |3      |
|1       |3          |6      |2  |2   |L        |7      |
|1       |3          |3      |2  |1   |R        |4      |
|1       |3          |6      |2  |2   |R        |8      |
|2       |4          |10     |2  |2   |L        |13     |
|2       |4          |7      |2  |1   |L        |9      |
|2       |4          |7      |2  |1   |R        |10     |
|2       |4          |10     |2  |2   |R        |14     |
|2       |5          |11     |2  |1   |L        |14     |
|2       |5          |8      |2  |2   |L        |10     |
|2       |5          |11     |2  |1   |R        |15     |
|2       |5          |8      |2  |2   |R        |11     |
|2       |6          |9      |2  |1   |L        |11     |
|2       |6          |12     |2  |2   |L        |15     |
|2       |6          |9      |2  |1   |R        |16     |
|2       |6          |12     |2  |2   |R        |12     |
|3       |7          |13     |1  |1   |L        |17     |
|3       |7          |13     |1  |1   |R        |18     |
|3       |8          |14     |1  |1   |L        |18     |
|3       |8          |14     |1  |1   |R        |19     |
+--------+-----------+-------+---+----+---------+-------+

现在我们必须将未缠结的数据编织回来。
我们想要为相同的
board_id

parallel_id

direction
取两行。显然,它们的不同之处在于
lane
wire_id
plug_id
。让我们为每个列创建两个 id 列,用
lane
进行区分。
制作
更多
列并将鉴别器列值融合为新列的名称
是与上一段中的过程相反的过程,因此pivot子句很方便(注意它甚至可以是同一SQL语句的一部分,而不需要额外的 CTE): -- caution: this paragraph rewrites (rather than appends) the CTE from last paragraph , transposed as ( select * from paths unpivot (plug_id for direction in (plug_l as 'L', plug_r as 'R')) pivot (max(wire_id) as wire_id, max(plug_id) as plug_id for lane in (1 as l1, 2 as l2)) where cnt = 2 ) select * from transposed order by board_id, parallel_id, direction;
+--------+-----------+---+---------+----------+----------+----------+----------+
|BOARD_ID|PARALLEL_ID|CNT|DIRECTION|L1_WIRE_ID|L1_PLUG_ID|L2_WIRE_ID|L2_PLUG_ID|
+--------+-----------+---+---------+----------+----------+----------+----------+
|1       |1          |2  |L        |1         |1         |4         |5         |
|1       |1          |2  |R        |1         |2         |4         |6         |
|1       |2          |2  |L        |2         |2         |5         |6         |
|1       |2          |2  |R        |2         |3         |5         |7         |
|1       |3          |2  |L        |3         |3         |6         |7         |
|1       |3          |2  |R        |3         |4         |6         |8         |
|2       |4          |2  |L        |7         |9         |10        |13        |
|2       |4          |2  |R        |7         |10        |10        |14        |
|2       |5          |2  |L        |11        |14        |8         |10        |
|2       |5          |2  |R        |11        |15        |8         |11        |
|2       |6          |2  |L        |9         |11        |12        |15        |
|2       |6          |2  |R        |9         |16        |12        |12        |
+--------+-----------+---+---------+----------+----------+----------+----------+

(关于宽度为 2 的过滤板的旁注:
 条件很简单 - 我们只想从 
cnt=2

CTE 中取出带有 paths 的行,并隐藏该列,因为此后就不再需要了。有几种可能性:


在以下之间插入特殊 CTE:
... filtered as ( select board_id, parallel_id, wire_id, plug_l, plug_r, lane from paths where cnt != 2 ) , transposed as (... from filtered ...) ...

(不幸的是,该条件不能出现在
    where
  • CTE 的
    paths
    子句中,因为
    cnt
    是在窗口函数中计算的,而 Oracle 不支持
    qualify
     子句
    。)
    transposed
    CTE 中将其过滤掉,如示例中所示。隐藏列稍后会完成。
  • 为了与常量进行比较,也可以使用以下技巧:
    pivot ... for (lane,cnt) in ((1,2) as l1, (2,2) as l2))
    
    这利用了
  • for
  • 子句中的列自动隐藏的事实。然而,
    cnt != 2
    的行仍然存在
    null
    值,因此额外的
    where l1_wire_id is not null
    条件无论如何都是必要的。
    )
    
    利润!
  • 现在我们有了正确格式的数据 - 每行代表一对电线的一侧。有了这些数据,就可以很容易地组成查询来解决指定的任务。整个SQL如下:

with wire(board_id, parallel_id, wire_id, plug_l, plug_r) as ( select 1, 1, 1, 1, 2 from dual union all select 1, 2, 2, 2, 3 from dual union all select 1, 3, 3, 3, 4 from dual union all select 1, 1, 4, 5, 6 from dual union all select 1, 2, 5, 6, 7 from dual union all select 1, 3, 6, 7, 8 from dual union all select 2, 4, 7, 9, 10 from dual union all select 2, 5, 8, 10, 11 from dual union all select 2, 6, 9, 11, 16 from dual union all select 2, 4, 10, 13, 14 from dual union all select 2, 5, 11, 14, 15 from dual union all select 2, 6, 12, 15, 12 from dual union all select 3, 7, 13, 17, 18 from dual union all select 3, 8, 14, 18, 19 from dual union all select null,null,null,null,null from dual where 0=1 ) --select * from wire order by board_id, parallel_id, wire_id; , paths as ( select w.board_id, w.parallel_id, w.wire_id, w.plug_l, w.plug_r , count(*) over (partition by w.board_id, w.parallel_id) as cnt , row_number() over (partition by w.board_id, w.parallel_id order by null) as lane from wire w ) --select * from paths order by board_id, parallel_id, lane; , transposed as ( select * from paths unpivot (plug_id for direction in (plug_l as 'L', plug_r as 'R'))-- order by board_id, parallel_id, lane; pivot (max(wire_id) as wire_id, max(plug_id) as plug_id for lane in (1 as l1, 2 as l2)) where cnt = 2 ) --select * from transposed order by board_id, parallel_id, direction; , nextplug as ( select t.l1_wire_id as start_wire_1, t.l1_plug_id as start_plug_1, w1.wire_id as next_wire_1, case t.l1_plug_id when w1.plug_l then w1.plug_r else w1.plug_l end as next_plug_1 , t.l2_wire_id as start_wire_2, t.l2_plug_id as start_plug_2, w2.wire_id as next_wire_2, case t.l2_plug_id when w2.plug_l then w2.plug_r else w2.plug_l end as next_plug_2 from transposed t join wire w1 on t.l1_plug_id in (w1.plug_l, w1.plug_r) and t.l1_wire_id != w1.wire_id join wire w2 on t.l2_plug_id in (w2.plug_l, w2.plug_r) and t.l2_wire_id != w2.wire_id ) select 'Invalid connection from (' || start_plug_1 || ',' || start_plug_2 || ') to (' || next_plug_1 || ',' || next_plug_2 || ')' as error_message from nextplug where sign(start_plug_1 - start_plug_2) != sign(next_plug_1 - next_plug_2); +------------------------------------------+ |ERROR_MESSAGE | +------------------------------------------+ |Invalid connection from (15,11) to (12,16)| +------------------------------------------+


从 Oracle 12 开始,您可以使用 
MATCH_RECOGNIZE

0
投票

SELECT 'Invalid connection' ||' from ('||plug_l1||','||plug_l2||')' ||' to ('||plug_r1||','||plug_r2||')' AS error_message FROM wire MATCH_RECOGNIZE( PARTITION BY board_id, parallel_id ORDER BY wire_id MEASURES wire1.plug_l AS plug_l1, wire1.plug_r AS plug_r1, wire2.plug_l AS plug_l2, wire2.plug_r AS plug_r2 PATTERN ( ^ wire1 wire2 $ ) DEFINE wire2 AS (plug_l < PREV(plug_l) AND plug_r > PREV(plug_r)) OR (plug_l > PREV(plug_l) AND plug_r < PREV(plug_r)) );

或者,在早期版本中:

SELECT 'Invalid connection'
       ||' from ('||plug_l1||','||plug_l2||')'
       ||' to ('||plug_r1||','||plug_r2||')'
         AS error_message
FROM   (
  SELECT board_id,
         parallel_id,
         wire_id,
         LAG(plug_l) OVER (PARTITION BY board_id, parallel_id ORDER BY wire_id)
          AS plug_l1,
         LAG(plug_r) OVER (PARTITION BY board_id, parallel_id ORDER BY wire_id)
          AS plug_r1,
         plug_l AS plug_l2,
         plug_r AS plug_r2,
         COUNT(*) OVER (PARTITION BY board_id, parallel_id)
          AS num_wire
  FROM   wire
)
WHERE  num_wire = 2
AND    (  (plug_l1 < plug_l2 AND plug_r1 > plug_r2)
       OR (plug_l1 > plug_l2 AND plug_r1 < plug_r2)
       );

对于样本数据:

CREATE TABLE wire(board_id, parallel_id, wire_id, plug_l, plug_r) as
  select 1, 1,  1,  1,  2 from dual union all
  select 1, 2,  2,  2,  3 from dual union all
  select 1, 3,  3,  3,  4 from dual union all
  select 1, 1,  4,  5,  6 from dual union all
  select 1, 2,  5,  6,  7 from dual union all
  select 1, 3,  6,  7,  8 from dual union all
  select 2, 4,  7,  9, 10 from dual union all
  select 2, 5,  8, 10, 11 from dual union all
  select 2, 6,  9, 11, 16 from dual union all
  select 2, 4, 10, 13, 14 from dual union all
  select 2, 5, 11, 14, 15 from dual union all
  select 2, 6, 12, 15, 12 from dual union all
  select 3, 7, 13, 17, 18 from dual union all
  select 3, 8, 14, 18, 19 from dual

两个输出:


错误消息从 (11,15) 到 (16,12) 的连接无效小提琴

© www.soinside.com 2019 - 2024. All rights reserved.