SQL 中更复杂的孤岛和缺口问题
A more complex Island & Gaps problem in SQL
首先,如果已经讨论过这个问题,我想道歉,但经过 4 小时的搜索,我找不到任何可以帮助我解决问题的东西。
情况是这样的:有一些塔,不同的客户可以在塔上的不同高度安装不同的设备(就像电信中的塔)。
这些是事实:
塔有 4 个面,设备只能安装在相对的 2 个面(对于相同的高度)。所以我们不能将设备安装在塔的 3 个不同的侧面(在相同的高度),而不是在靠近的 2 个侧面。我希望我的解释没问题。
每个客户都有自己的塔总高度范围。
要求是确定该塔中每个客户端的可用性。如果客户的设备只安装在一侧,则视为部分可用;如果根本没有设备,它将被视为可用。如果客户将设备安装在塔的底部,则该高度将被视为不可用。
这些将是我们正在处理的数据:
create table tower_test
(Tower_Number VARCHAR2(12),
Tower_Side VARCHAR2(1),
Tower_Height NUMBER,
Tower_Height_Um VARCHAR2(1) default 'm',
Client VARCHAR2(25),
Client_Start_Height NUMBER,
Range_From NUMBER,
Range_To NUMBER);
-- No Client
-- Side A
-- Client 1
INSERT INTO tower_test (Tower_Number, Tower_Side, Tower_Height, Client, Client_Start_Height, Range_From, Range_To) VALUES ('123456_TWR1', 'A', 50, 'Client_1', 12, 17, 18);
INSERT INTO tower_test (Tower_Number, Tower_Side, Tower_Height, Client, Client_Start_Height, Range_From, Range_To) VALUES ('123456_TWR1', 'A', 50, 'Client_1', 12, 22, 23);
-- Client 2
INSERT INTO tower_test (Tower_Number, Tower_Side, Tower_Height, Client, Client_Start_Height, Range_From, Range_To) VALUES ('123456_TWR1', 'A', 50, 'Client_2', 24, 35, 36);
-- Client 3
INSERT INTO tower_test (Tower_Number, Tower_Side, Tower_Height, Client, Client_Start_Height, Range_From, Range_To) VALUES ('123456_TWR1', 'A', 50, 'Client_3', 40, 40, 41);
INSERT INTO tower_test (Tower_Number, Tower_Side, Tower_Height, Client, Client_Start_Height, Range_From, Range_To) VALUES ('123456_TWR1', 'A', 50, 'Client_3', 40, 47, 48);
-- Side B
-- Client 1
INSERT INTO tower_test (Tower_Number, Tower_Side, Tower_Height, Client, Client_Start_Height, Range_From, Range_To) VALUES ('123456_TWR1', 'B', 50, 'Client_1', 12, 13, 14);
INSERT INTO tower_test (Tower_Number, Tower_Side, Tower_Height, Client, Client_Start_Height, Range_From, Range_To) VALUES ('123456_TWR1', 'B', 50, 'Client_1', 12, 19, 20);
-- Client 2
INSERT INTO tower_test (Tower_Number, Tower_Side, Tower_Height, Client, Client_Start_Height, Range_From, Range_To) VALUES ('123456_TWR1', 'B', 50, 'Client_2', 24, 31, 32);
INSERT INTO tower_test (Tower_Number, Tower_Side, Tower_Height, Client, Client_Start_Height, Range_From, Range_To) VALUES ('123456_TWR1', 'B', 50, 'Client_2', 24, 37, 38);
-- Client 3
INSERT INTO tower_test (Tower_Number, Tower_Side, Tower_Height, Client, Client_Start_Height, Range_From, Range_To) VALUES ('123456_TWR1', 'B', 50, 'Client_3', 40, 43, 44);
INSERT INTO tower_test (Tower_Number, Tower_Side, Tower_Height, Client, Client_Start_Height, Range_From, Range_To) VALUES ('123456_TWR1', 'B', 50, 'Client_3', 40, 46, 47);
-- Side C
-- Client 1
INSERT INTO tower_test (Tower_Number, Tower_Side, Tower_Height, Client, Client_Start_Height, Range_From, Range_To) VALUES ('123456_TWR1', 'C', 50, 'Client_1', 12, 17, 18);
INSERT INTO tower_test (Tower_Number, Tower_Side, Tower_Height, Client, Client_Start_Height, Range_From, Range_To) VALUES ('123456_TWR1', 'C', 50, 'Client_1', 12, 22, 23);
-- Client 2
INSERT INTO tower_test (Tower_Number, Tower_Side, Tower_Height, Client, Client_Start_Height, Range_From, Range_To) VALUES ('123456_TWR1', 'C', 50, 'Client_2', 24, 28, 29);
-- Client 3
INSERT INTO tower_test (Tower_Number, Tower_Side, Tower_Height, Client, Client_Start_Height, Range_From, Range_To) VALUES ('123456_TWR1', 'C', 50, 'Client_3', 40, 40, 41);
INSERT INTO tower_test (Tower_Number, Tower_Side, Tower_Height, Client, Client_Start_Height, Range_From, Range_To) VALUES ('123456_TWR1', 'C', 50, 'Client_3', 40, 47, 48);
-- Side D
-- Client 1
INSERT INTO tower_test (Tower_Number, Tower_Side, Tower_Height, Client, Client_Start_Height, Range_From, Range_To) VALUES ('123456_TWR1', 'D', 50, 'Client_1', 12, 19, 20);
-- Client 2
INSERT INTO tower_test (Tower_Number, Tower_Side, Tower_Height, Client, Client_Start_Height, Range_From, Range_To) VALUES ('123456_TWR1', 'D', 50, 'Client_2', 24, 31, 32);
INSERT INTO tower_test (Tower_Number, Tower_Side, Tower_Height, Client, Client_Start_Height, Range_From, Range_To) VALUES ('123456_TWR1', 'D', 50, 'Client_2', 24, 37, 38);
-- Client 3
INSERT INTO tower_test (Tower_Number, Tower_Side, Tower_Height, Client, Client_Start_Height, Range_From, Range_To) VALUES ('123456_TWR1', 'D', 50, 'Client_3', 40, 46, 47);
COMMIT;
SELECT * FROM tower_test;
所以,从这组数据开始:
我需要这种格式:
有没有办法在 Oracle SQL 中使用 Gap & Island 方法获得它?如果是的话,你能解释一下如何实现吗?
谢谢!
这是一个填补范围空白的解决方案。
与预期的结果不完全一样。
F.e。空隙获得可用的 'A' 代码。
但它会是你开始的东西。
第一个 CTE 将数据展平,并获取塔侧面的计数。
第二个 CTE 增加了差距。
WITH CTE1_FLATTEND AS
(
SELECT
Tower_Number
, Client
, Client_Start_Height
, Range_From
, Range_To
, COUNT(DISTINCT
CASE
WHEN Tower_Side IN ('A','C')
THEN Tower_Side
END) AS Total_AC
, COUNT(DISTINCT
CASE
WHEN Tower_Side IN ('B','D')
THEN Tower_Side
END) AS Total_BD
, COUNT(DISTINCT Tower_Side) AS Total_ABCD
, MAX(Tower_Height) AS Tower_Height
, MAX(Tower_Height_Um) AS Tower_Height_Um
FROM tower_test
GROUP BY
Tower_Number
, Client
, Client_Start_Height
, Range_From
, Range_To
)
, CTE2_GAPS_ADDED AS
(
SELECT
Tower_Number
, Client
, Client_Start_Height
, Client_Start_Height AS Next_Client_Start_Height
, COALESCE(LAG(Range_To)
OVER (PARTITION BY Tower_Number, Client_Start_Height
ORDER BY Range_From)+1, Client_Start_Height) AS Range_From
, Range_From-1 AS Range_To
, 'A' AS Avlblt_Type -- Available Gap
, Tower_Height
, Tower_Height_Um
FROM CTE1_FLATTEND
UNION
SELECT
Tower_Number
, Client
, Client_Start_Height
, COALESCE(LEAD(Client_Start_Height)
OVER (PARTITION BY Tower_Number
ORDER BY Range_From), Client_Start_Height) AS Next_Client_Start_Height
, Range_To+1 AS Range_From
, COALESCE(LEAD(Range_From)
OVER (PARTITION BY Tower_Number
ORDER BY Range_From)-1, Tower_Height) AS Range_To
, 'A' AS Avlblt_Type -- Available Gap
, Tower_Height
, Tower_Height_Um
FROM CTE1_FLATTEND
UNION ALL
SELECT
Tower_Number
, Client
, Client_Start_Height
, Client_Start_Height AS Next_Client_Start_Height
, Range_From
, Range_To
, CASE
WHEN Total_ABCD = 1 THEN 'P' -- Partial
WHEN Total_ABCD > 2 THEN 'F' -- Fault
WHEN Total_AC = 2 THEN 'T' -- Taken A C
WHEN Total_BD = 2 THEN 'T' -- Taken B D
ELSE 'E' -- Exception
END AS Avlblt_Type
, Tower_Height
, Tower_Height_Um
FROM CTE1_FLATTEND
UNION ALL
SELECT
Tower_Number
, 'No Client' AS Client
, 1 AS Client_Start_Height
, 0 AS Next_Client_Start_Height
, 0 AS Range_From
, MIN(Client_Start_Height)-1 AS Range_To
, 'T' AS Avlblt_Type
, Tower_Height
, Tower_Height_Um
FROM CTE1_FLATTEND
GROUP BY
Tower_Number
, Tower_Height
, Tower_Height_Um
)
SELECT
Tower_Number
, Tower_Height
, Tower_Height_Um
, Client
, Avlblt_Type
, Range_From
, Range_To
--, Client_Start_Height
--, Next_Client_Start_Height
FROM CTE2_GAPS_ADDED
WHERE Range_From <= Range_To
AND NOT (Client_Start_Height < Next_Client_Start_Height
AND Range_From >= Next_Client_Start_Height)
ORDER BY Tower_Number, Range_From;
TOWER_NUMBER | TOWER_HEIGHT | TOWER_HEIGHT_UM | CLIENT | AVLBLT_TYPE | RANGE_FROM | RANGE_TO
:----------- | -----------: | :-------------- | :-------- | :---------- | ---------: | -------:
123456_TWR1 | 50 | m | No Client | T | 0 | 11
123456_TWR1 | 50 | m | Client_1 | A | 12 | 12
123456_TWR1 | 50 | m | Client_1 | P | 13 | 14
123456_TWR1 | 50 | m | Client_1 | A | 15 | 16
123456_TWR1 | 50 | m | Client_1 | T | 17 | 18
123456_TWR1 | 50 | m | Client_1 | T | 19 | 20
123456_TWR1 | 50 | m | Client_1 | A | 21 | 21
123456_TWR1 | 50 | m | Client_1 | T | 22 | 23
123456_TWR1 | 50 | m | Client_2 | A | 24 | 27
123456_TWR1 | 50 | m | Client_2 | P | 28 | 29
123456_TWR1 | 50 | m | Client_2 | A | 30 | 30
123456_TWR1 | 50 | m | Client_2 | T | 31 | 32
123456_TWR1 | 50 | m | Client_2 | A | 33 | 34
123456_TWR1 | 50 | m | Client_2 | P | 35 | 36
123456_TWR1 | 50 | m | Client_2 | T | 37 | 38
123456_TWR1 | 50 | m | Client_2 | A | 39 | 39
123456_TWR1 | 50 | m | Client_3 | T | 40 | 41
123456_TWR1 | 50 | m | Client_3 | A | 42 | 42
123456_TWR1 | 50 | m | Client_3 | P | 43 | 44
123456_TWR1 | 50 | m | Client_3 | A | 45 | 45
123456_TWR1 | 50 | m | Client_3 | T | 46 | 47
123456_TWR1 | 50 | m | Client_3 | T | 47 | 48
123456_TWR1 | 50 | m | Client_3 | A | 49 | 50
演示 db<>fiddle here
第二个 CTE 大量使用第一个 CTE。
为了提高性能,您可以从一开始就用查询填充临时 table。
然后将 CTE 中的温度 table 与联合一起使用。
如果您的数据为第三范式且数据为四 tables:
,这可能会更容易
tower (tower_number, tower_height, tower_height_um)
tower_sides (tower_number, tower_side)
tower_client (tower_number, client, client_start_height, client_end_height)
tower_equipment(tower, tower_side, client, range_from, range_to)
但是,可以将其拆分为 table 中的那些组件并使用:
SELECT tower_number,
tower_height,
tower_height_um,
client,
CASE availability
WHEN 0 THEN 'T'
WHEN 1 THEN 'P'
END AS availability,
range_from,
range_to
FROM (
SELECT h.tower_number,
MAX(h.tower_height) AS tower_height,
MAX(h.tower_height_um) AS tower_height_um,
h.height,
MAX(ch.client) AS client,
COUNT(t.tower_side) AS availability
FROM (
-- Generate all the heights and sides
WITH heights (tower_number, tower_side, height, tower_height, tower_height_um) AS (
SELECT tower_number,
tower_side,
0,
MAX(tower_height),
MAX(tower_height_um) KEEP (DENSE_RANK LAST ORDER BY tower_height)
FROM tower_test
GROUP BY tower_number, tower_side
UNION ALL
SELECT tower_number,
tower_side,
height + 1,
tower_height,
tower_height_um
FROM heights
WHERE height < tower_height
)
SELECT * FROM heights
) h
LEFT OUTER JOIN (
-- Include the client heights
SELECT *
FROM (
SELECT DISTINCT
tower_number,
client,
client_start_height AS start_height,
LEAD(client_start_height - 1, 1, tower_height) OVER (
PARTITION BY tower_number
ORDER BY client_start_height, range_to
) AS end_height
FROM tower_test
)
WHERE start_height < end_height
) ch
ON ( h.tower_number = ch.tower_number
AND h.height BETWEEN ch.start_height AND ch.end_height)
-- Include the equipment ranges
LEFT OUTER JOIN tower_test t
ON ( t.tower_number = h.tower_number
AND t.tower_side = h.tower_side
AND h.height BETWEEN t.range_from AND t.range_to)
GROUP BY
h.tower_number,
h.height
)
MATCH_RECOGNIZE(
PARTITION BY tower_number
ORDER BY height
MEASURES
FIRST(tower_height) AS tower_height,
FIRST(tower_height_um) AS tower_height_um,
FIRST(client) AS client,
FIRST(availability) AS availability,
FIRST(height) AS range_from,
LAST(height) AS range_to
ONE ROW PER MATCH
PATTERN (client_available+)
DEFINE
client_available
AS (FIRST(client) = client OR FIRST(client) IS NULL AND client IS NULL)
AND FIRST(availability) = availability
)
WHERE availability < 2
对于您的示例数据,输出:
TOWER_NUMBER
TOWER_HEIGHT
TOWER_HEIGHT_UM
CLIENT
AVAILABILITY
RANGE_FROM
RANGE_TO
123456_TWR1
50
m
null
T
0
11
123456_TWR1
50
m
Client_1
T
12
12
123456_TWR1
50
m
Client_1
P
13
14
123456_TWR1
50
m
Client_1
T
15
16
123456_TWR1
50
m
Client_1
T
21
21
123456_TWR1
50
m
Client_2
T
24
27
123456_TWR1
50
m
Client_2
P
28
29
123456_TWR1
50
m
Client_2
T
30
30
123456_TWR1
50
m
Client_2
T
33
34
123456_TWR1
50
m
Client_2
P
35
36
123456_TWR1
50
m
Client_2
T
39
39
123456_TWR1
50
m
Client_3
T
42
42
123456_TWR1
50
m
Client_3
P
43
44
123456_TWR1
50
m
Client_3
T
45
45
123456_TWR1
50
m
Client_3
T
49
50
db<>fiddle here
首先,如果已经讨论过这个问题,我想道歉,但经过 4 小时的搜索,我找不到任何可以帮助我解决问题的东西。
情况是这样的:有一些塔,不同的客户可以在塔上的不同高度安装不同的设备(就像电信中的塔)。
这些是事实:
塔有 4 个面,设备只能安装在相对的 2 个面(对于相同的高度)。所以我们不能将设备安装在塔的 3 个不同的侧面(在相同的高度),而不是在靠近的 2 个侧面。我希望我的解释没问题。
每个客户都有自己的塔总高度范围。
要求是确定该塔中每个客户端的可用性。如果客户的设备只安装在一侧,则视为部分可用;如果根本没有设备,它将被视为可用。如果客户将设备安装在塔的底部,则该高度将被视为不可用。
这些将是我们正在处理的数据:
create table tower_test
(Tower_Number VARCHAR2(12),
Tower_Side VARCHAR2(1),
Tower_Height NUMBER,
Tower_Height_Um VARCHAR2(1) default 'm',
Client VARCHAR2(25),
Client_Start_Height NUMBER,
Range_From NUMBER,
Range_To NUMBER);
-- No Client
-- Side A
-- Client 1
INSERT INTO tower_test (Tower_Number, Tower_Side, Tower_Height, Client, Client_Start_Height, Range_From, Range_To) VALUES ('123456_TWR1', 'A', 50, 'Client_1', 12, 17, 18);
INSERT INTO tower_test (Tower_Number, Tower_Side, Tower_Height, Client, Client_Start_Height, Range_From, Range_To) VALUES ('123456_TWR1', 'A', 50, 'Client_1', 12, 22, 23);
-- Client 2
INSERT INTO tower_test (Tower_Number, Tower_Side, Tower_Height, Client, Client_Start_Height, Range_From, Range_To) VALUES ('123456_TWR1', 'A', 50, 'Client_2', 24, 35, 36);
-- Client 3
INSERT INTO tower_test (Tower_Number, Tower_Side, Tower_Height, Client, Client_Start_Height, Range_From, Range_To) VALUES ('123456_TWR1', 'A', 50, 'Client_3', 40, 40, 41);
INSERT INTO tower_test (Tower_Number, Tower_Side, Tower_Height, Client, Client_Start_Height, Range_From, Range_To) VALUES ('123456_TWR1', 'A', 50, 'Client_3', 40, 47, 48);
-- Side B
-- Client 1
INSERT INTO tower_test (Tower_Number, Tower_Side, Tower_Height, Client, Client_Start_Height, Range_From, Range_To) VALUES ('123456_TWR1', 'B', 50, 'Client_1', 12, 13, 14);
INSERT INTO tower_test (Tower_Number, Tower_Side, Tower_Height, Client, Client_Start_Height, Range_From, Range_To) VALUES ('123456_TWR1', 'B', 50, 'Client_1', 12, 19, 20);
-- Client 2
INSERT INTO tower_test (Tower_Number, Tower_Side, Tower_Height, Client, Client_Start_Height, Range_From, Range_To) VALUES ('123456_TWR1', 'B', 50, 'Client_2', 24, 31, 32);
INSERT INTO tower_test (Tower_Number, Tower_Side, Tower_Height, Client, Client_Start_Height, Range_From, Range_To) VALUES ('123456_TWR1', 'B', 50, 'Client_2', 24, 37, 38);
-- Client 3
INSERT INTO tower_test (Tower_Number, Tower_Side, Tower_Height, Client, Client_Start_Height, Range_From, Range_To) VALUES ('123456_TWR1', 'B', 50, 'Client_3', 40, 43, 44);
INSERT INTO tower_test (Tower_Number, Tower_Side, Tower_Height, Client, Client_Start_Height, Range_From, Range_To) VALUES ('123456_TWR1', 'B', 50, 'Client_3', 40, 46, 47);
-- Side C
-- Client 1
INSERT INTO tower_test (Tower_Number, Tower_Side, Tower_Height, Client, Client_Start_Height, Range_From, Range_To) VALUES ('123456_TWR1', 'C', 50, 'Client_1', 12, 17, 18);
INSERT INTO tower_test (Tower_Number, Tower_Side, Tower_Height, Client, Client_Start_Height, Range_From, Range_To) VALUES ('123456_TWR1', 'C', 50, 'Client_1', 12, 22, 23);
-- Client 2
INSERT INTO tower_test (Tower_Number, Tower_Side, Tower_Height, Client, Client_Start_Height, Range_From, Range_To) VALUES ('123456_TWR1', 'C', 50, 'Client_2', 24, 28, 29);
-- Client 3
INSERT INTO tower_test (Tower_Number, Tower_Side, Tower_Height, Client, Client_Start_Height, Range_From, Range_To) VALUES ('123456_TWR1', 'C', 50, 'Client_3', 40, 40, 41);
INSERT INTO tower_test (Tower_Number, Tower_Side, Tower_Height, Client, Client_Start_Height, Range_From, Range_To) VALUES ('123456_TWR1', 'C', 50, 'Client_3', 40, 47, 48);
-- Side D
-- Client 1
INSERT INTO tower_test (Tower_Number, Tower_Side, Tower_Height, Client, Client_Start_Height, Range_From, Range_To) VALUES ('123456_TWR1', 'D', 50, 'Client_1', 12, 19, 20);
-- Client 2
INSERT INTO tower_test (Tower_Number, Tower_Side, Tower_Height, Client, Client_Start_Height, Range_From, Range_To) VALUES ('123456_TWR1', 'D', 50, 'Client_2', 24, 31, 32);
INSERT INTO tower_test (Tower_Number, Tower_Side, Tower_Height, Client, Client_Start_Height, Range_From, Range_To) VALUES ('123456_TWR1', 'D', 50, 'Client_2', 24, 37, 38);
-- Client 3
INSERT INTO tower_test (Tower_Number, Tower_Side, Tower_Height, Client, Client_Start_Height, Range_From, Range_To) VALUES ('123456_TWR1', 'D', 50, 'Client_3', 40, 46, 47);
COMMIT;
SELECT * FROM tower_test;
所以,从这组数据开始:
我需要这种格式:
有没有办法在 Oracle SQL 中使用 Gap & Island 方法获得它?如果是的话,你能解释一下如何实现吗?
谢谢!
这是一个填补范围空白的解决方案。
与预期的结果不完全一样。
F.e。空隙获得可用的 'A' 代码。
但它会是你开始的东西。
第一个 CTE 将数据展平,并获取塔侧面的计数。
第二个 CTE 增加了差距。
WITH CTE1_FLATTEND AS ( SELECT Tower_Number , Client , Client_Start_Height , Range_From , Range_To , COUNT(DISTINCT CASE WHEN Tower_Side IN ('A','C') THEN Tower_Side END) AS Total_AC , COUNT(DISTINCT CASE WHEN Tower_Side IN ('B','D') THEN Tower_Side END) AS Total_BD , COUNT(DISTINCT Tower_Side) AS Total_ABCD , MAX(Tower_Height) AS Tower_Height , MAX(Tower_Height_Um) AS Tower_Height_Um FROM tower_test GROUP BY Tower_Number , Client , Client_Start_Height , Range_From , Range_To ) , CTE2_GAPS_ADDED AS ( SELECT Tower_Number , Client , Client_Start_Height , Client_Start_Height AS Next_Client_Start_Height , COALESCE(LAG(Range_To) OVER (PARTITION BY Tower_Number, Client_Start_Height ORDER BY Range_From)+1, Client_Start_Height) AS Range_From , Range_From-1 AS Range_To , 'A' AS Avlblt_Type -- Available Gap , Tower_Height , Tower_Height_Um FROM CTE1_FLATTEND UNION SELECT Tower_Number , Client , Client_Start_Height , COALESCE(LEAD(Client_Start_Height) OVER (PARTITION BY Tower_Number ORDER BY Range_From), Client_Start_Height) AS Next_Client_Start_Height , Range_To+1 AS Range_From , COALESCE(LEAD(Range_From) OVER (PARTITION BY Tower_Number ORDER BY Range_From)-1, Tower_Height) AS Range_To , 'A' AS Avlblt_Type -- Available Gap , Tower_Height , Tower_Height_Um FROM CTE1_FLATTEND UNION ALL SELECT Tower_Number , Client , Client_Start_Height , Client_Start_Height AS Next_Client_Start_Height , Range_From , Range_To , CASE WHEN Total_ABCD = 1 THEN 'P' -- Partial WHEN Total_ABCD > 2 THEN 'F' -- Fault WHEN Total_AC = 2 THEN 'T' -- Taken A C WHEN Total_BD = 2 THEN 'T' -- Taken B D ELSE 'E' -- Exception END AS Avlblt_Type , Tower_Height , Tower_Height_Um FROM CTE1_FLATTEND UNION ALL SELECT Tower_Number , 'No Client' AS Client , 1 AS Client_Start_Height , 0 AS Next_Client_Start_Height , 0 AS Range_From , MIN(Client_Start_Height)-1 AS Range_To , 'T' AS Avlblt_Type , Tower_Height , Tower_Height_Um FROM CTE1_FLATTEND GROUP BY Tower_Number , Tower_Height , Tower_Height_Um ) SELECT Tower_Number , Tower_Height , Tower_Height_Um , Client , Avlblt_Type , Range_From , Range_To --, Client_Start_Height --, Next_Client_Start_Height FROM CTE2_GAPS_ADDED WHERE Range_From <= Range_To AND NOT (Client_Start_Height < Next_Client_Start_Height AND Range_From >= Next_Client_Start_Height) ORDER BY Tower_Number, Range_From;
TOWER_NUMBER | TOWER_HEIGHT | TOWER_HEIGHT_UM | CLIENT | AVLBLT_TYPE | RANGE_FROM | RANGE_TO :----------- | -----------: | :-------------- | :-------- | :---------- | ---------: | -------: 123456_TWR1 | 50 | m | No Client | T | 0 | 11 123456_TWR1 | 50 | m | Client_1 | A | 12 | 12 123456_TWR1 | 50 | m | Client_1 | P | 13 | 14 123456_TWR1 | 50 | m | Client_1 | A | 15 | 16 123456_TWR1 | 50 | m | Client_1 | T | 17 | 18 123456_TWR1 | 50 | m | Client_1 | T | 19 | 20 123456_TWR1 | 50 | m | Client_1 | A | 21 | 21 123456_TWR1 | 50 | m | Client_1 | T | 22 | 23 123456_TWR1 | 50 | m | Client_2 | A | 24 | 27 123456_TWR1 | 50 | m | Client_2 | P | 28 | 29 123456_TWR1 | 50 | m | Client_2 | A | 30 | 30 123456_TWR1 | 50 | m | Client_2 | T | 31 | 32 123456_TWR1 | 50 | m | Client_2 | A | 33 | 34 123456_TWR1 | 50 | m | Client_2 | P | 35 | 36 123456_TWR1 | 50 | m | Client_2 | T | 37 | 38 123456_TWR1 | 50 | m | Client_2 | A | 39 | 39 123456_TWR1 | 50 | m | Client_3 | T | 40 | 41 123456_TWR1 | 50 | m | Client_3 | A | 42 | 42 123456_TWR1 | 50 | m | Client_3 | P | 43 | 44 123456_TWR1 | 50 | m | Client_3 | A | 45 | 45 123456_TWR1 | 50 | m | Client_3 | T | 46 | 47 123456_TWR1 | 50 | m | Client_3 | T | 47 | 48 123456_TWR1 | 50 | m | Client_3 | A | 49 | 50
演示 db<>fiddle here
第二个 CTE 大量使用第一个 CTE。
为了提高性能,您可以从一开始就用查询填充临时 table。
然后将 CTE 中的温度 table 与联合一起使用。
如果您的数据为第三范式且数据为四 tables:
,这可能会更容易tower (tower_number, tower_height, tower_height_um)
tower_sides (tower_number, tower_side)
tower_client (tower_number, client, client_start_height, client_end_height)
tower_equipment(tower, tower_side, client, range_from, range_to)
但是,可以将其拆分为 table 中的那些组件并使用:
SELECT tower_number,
tower_height,
tower_height_um,
client,
CASE availability
WHEN 0 THEN 'T'
WHEN 1 THEN 'P'
END AS availability,
range_from,
range_to
FROM (
SELECT h.tower_number,
MAX(h.tower_height) AS tower_height,
MAX(h.tower_height_um) AS tower_height_um,
h.height,
MAX(ch.client) AS client,
COUNT(t.tower_side) AS availability
FROM (
-- Generate all the heights and sides
WITH heights (tower_number, tower_side, height, tower_height, tower_height_um) AS (
SELECT tower_number,
tower_side,
0,
MAX(tower_height),
MAX(tower_height_um) KEEP (DENSE_RANK LAST ORDER BY tower_height)
FROM tower_test
GROUP BY tower_number, tower_side
UNION ALL
SELECT tower_number,
tower_side,
height + 1,
tower_height,
tower_height_um
FROM heights
WHERE height < tower_height
)
SELECT * FROM heights
) h
LEFT OUTER JOIN (
-- Include the client heights
SELECT *
FROM (
SELECT DISTINCT
tower_number,
client,
client_start_height AS start_height,
LEAD(client_start_height - 1, 1, tower_height) OVER (
PARTITION BY tower_number
ORDER BY client_start_height, range_to
) AS end_height
FROM tower_test
)
WHERE start_height < end_height
) ch
ON ( h.tower_number = ch.tower_number
AND h.height BETWEEN ch.start_height AND ch.end_height)
-- Include the equipment ranges
LEFT OUTER JOIN tower_test t
ON ( t.tower_number = h.tower_number
AND t.tower_side = h.tower_side
AND h.height BETWEEN t.range_from AND t.range_to)
GROUP BY
h.tower_number,
h.height
)
MATCH_RECOGNIZE(
PARTITION BY tower_number
ORDER BY height
MEASURES
FIRST(tower_height) AS tower_height,
FIRST(tower_height_um) AS tower_height_um,
FIRST(client) AS client,
FIRST(availability) AS availability,
FIRST(height) AS range_from,
LAST(height) AS range_to
ONE ROW PER MATCH
PATTERN (client_available+)
DEFINE
client_available
AS (FIRST(client) = client OR FIRST(client) IS NULL AND client IS NULL)
AND FIRST(availability) = availability
)
WHERE availability < 2
对于您的示例数据,输出:
TOWER_NUMBER TOWER_HEIGHT TOWER_HEIGHT_UM CLIENT AVAILABILITY RANGE_FROM RANGE_TO 123456_TWR1 50 m null T 0 11 123456_TWR1 50 m Client_1 T 12 12 123456_TWR1 50 m Client_1 P 13 14 123456_TWR1 50 m Client_1 T 15 16 123456_TWR1 50 m Client_1 T 21 21 123456_TWR1 50 m Client_2 T 24 27 123456_TWR1 50 m Client_2 P 28 29 123456_TWR1 50 m Client_2 T 30 30 123456_TWR1 50 m Client_2 T 33 34 123456_TWR1 50 m Client_2 P 35 36 123456_TWR1 50 m Client_2 T 39 39 123456_TWR1 50 m Client_3 T 42 42 123456_TWR1 50 m Client_3 P 43 44 123456_TWR1 50 m Client_3 T 45 45 123456_TWR1 50 m Client_3 T 49 50
db<>fiddle here