让我们有一个电线的数据库模型(下面的
-----
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
并且没有插头涉及三根或更多根电线。)
预期输出是:
pivot
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_l
,plug_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
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
条件无论如何都是必要的。)利润!
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
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
两个输出: