I have a dataset that can be represented as below network diagram where cable segments form the hierarchy:
The first segment is the input cable for a box and is tagged 'I', and the same box (addressId
) will have out cable (tagged 'O') that would go and connect to another box at a different location. The out cable is termed as In cable at the second box.
I have SQL that can give me segment-1 data, and if I add another layer with subquery I get segment-2 and union of these will give me segment-1 and segment-2 cable information. But if there are N number of segments in the network then my query gets complicated too fast. I wanted to find out if there is a way to rewrite these below queries such a way that it can retrieve all the segments regardless of the segment size and without having to write unions for each segments.
-- 1 Segment list with the subject cable and pair range as Incount
select region,cable,lowRange,highRange,address,addressId
from schema.TableName
where upper(region) = 'COLORADO' and
upper(cable) = 'ABCD' and
to_number(lowRange) >= 1 and
to_number(highRange) <= 600 and
side = 'i';
-- 2 Segment list with 1 Segment cable and pair range Outcount as Incount
select c.region,c.cable,c.lowRange,c.highRange,c.address,c.addressId
from schema.TableName c,
--1 Segment list which have Outcount of their own
(select a.region,a.address as xbox,a.cable,a.lowRange,a.highRange
from schema.TableName a,
--1 Segment list with the subject cable and pair range as Incount
(select region,addressId
from schema.TableName
where upper(region) = 'COLORADO' and
upper(cable) = 'ABCD' and
to_number(lowRange) >= 1 and
to_number(highRange) <= 600 and
side = 'i') b
where a.region = b.region and
a.addressId = b.addressId and
a.side = 'o') d
where c.region = d.region and
c.ca = d.ca and
c.side = 'i' and
((c.lowRange between d.lowRange and d.highRange) or
(c.highRange between d.lopr and d.highRange));
My knowledge on hierarchical joins is limited, I came up with below but of course it doesn't work as intended. And I suspect it might be due to CONNECT BY PRIOR
cannot account and not account box location (addressId
) selectively (cable that is input to a box and output from the same box will have same addressId
whereas the same cable tagged as input at different box will have different addressid
) or maybe this needs recursive query logic.
Much appreciated if anyone can provide me pointers.
SELECT DISTINCT
t.region, t.cable, t.lowRange, t.highRange, t.address, t.addressId,
LEVEL AS segment_level
FROM
schema.TableName t
WHERE
t.side = 'i'
CONNECT BY
PRIOR t.region = t.region
AND PRIOR t.cable = t.cable
AND PRIOR t.side = 'o'
AND t.addressId <> PRIOR t.addressId
AND (
(t.lowRange BETWEEN PRIOR t.lowRange AND PRIOR t.highRange)
OR
(t.highRange BETWEEN PRIOR t.lowRange AND PRIOR t.highRange)
)
START WITH
UPPER(t.region) = 'COLORADO'
AND UPPER(t.cable) = 'ABCD'
AND TO_NUMBER(t.lowRange) >= 1
AND TO_NUMBER(t.highRange) <= 600
AND t.side = 'i';
========================
Update: Please find the DB fiddle below. The expected result should have all the segments available on the table of side 'i'. I have included 'segmentlevel' as an additional column just to help with the identification here, but in reality this column wont exist on the table.
Also the query I have with the unions provide upto segment '2' only because I have added in just 2 levels of union.
https://dbfiddle.uk/WpmHfcpk
I'd say that recursion cannot alternate o
s and i
s, you have to chain rows of the same model (one o
+ one i
). But our "model" doesn't have to be a table: it can be the JOIN
of two tables.
So instead of recursing over an o
, then an i
, then an o
, then an i
, let's group those two by two and recurse over DATA_SAMPLE o JOIN DATA_SAMPLE i ON o.side = 'o' and i.side = 'i'
and /* everything else needed to get a real match */
, until there's no more of those i/o
link to add to the chain.
I switched from Oracle's proprietary CONNECT BY
to the more standard SQL recursive CTE, that I find easier to read and also write.
a quick mapping from CONNECT BY
to recursive CTE is:
with recursive_alias(col1, col2, col3) as
(
select … from source_table -- This corresponds to START WITH
union all
select …
from recursive_alias -- Refering to the alias means PRIOR
join source_table on source_table.… = recursive_alias.… -- Pick up a new row.
-- As we are on a regular from and join, we can continue joining tables:
join another_source_table on … -- In our case, as we said that our model was two tables, we will join to another occurrence of DATA_SAMPLE.
)
select * from r;
With this in mind, and reusing the a
, b
and c
table aliases of your fiddle, I got:
with
chain(region,cable,lowRange,highRange,address,addressId ,side, segmentlevel) as
(
select region,cable,lowRange,highRange,address,addressId ,side, segmentlevel
from DATA_SAMPLE
where region = 'colorado' and
upper(cable) = '6' and
lowRange >= 500 and
highRange <= 600 and
side = 'i'
union all -- After that union all comes the recursive part: the chain table alias (that we realias as b) holds last iteration's rows
-- (that is, for iteration 1: the rows coming from iteration 0 above)
select c.region,c.cable,c.lowRange,c.highRange,c.address,c.addressId,c.side, c.segmentlevel
from chain b, DATA_SAMPLE a, DATA_SAMPLE c
where a.region = b.region and
a.addressId = b.addressId and
a.side = 'o'
and c.region = a.region and
c.cable = a.cable and
c.side = 'i' and
((c.lowRange between a.lowRange and a.highRange) or
(c.highRange between a.lowRange and a.highRange))
)
select * from chain
order by segmentlevel, side, lowrange, highrange, address;
(I kept the table1, table2 where table1.… = table2.…
structure to make it easily mappable to your original query, but switching to a table1 join table2 on table1.… = table2.…
model would avoid the risk of forgetting a join condition, as the on
keyword is mandatory)
Thanks to your fiddle, I could make it run on your data and got exactly the same result as your 2 levels… plus segmentlevel
3 of course (I did a diff -u
to make sure of it):
REGION | CABLE | LOWRANGE | HIGHRANGE | ADDRESS | ADDRESSID | SIDE | SEGMENTLEVEL |
---|---|---|---|---|---|---|---|
colorado | 6 | 501 | 600 | address14 | 1453 | i | 1 |
colorado | cp111s6 | 1 | 50 | address194 | 1475 | i | 2 |
… | |||||||
colorado | cp111s6 | 276 | 350 | address254 | 535 | i | 2 |
colorado | cp111s6 | 276 | 350 | address334 | 479 | i | 2 |
colorado | cprs301 | 1 | 5 | address684 | 233 | i | 3 |
colorado | cprs301 | 1 | 5 | address694 | 234 | i | 3 |
… | |||||||
colorado | 211s6th | 56 | 71 | address504 | 112 | i | 3 |
colorado | 313s6th | 61 | 75 | address434 | 203 | i | 3 |
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With