Setup:
CREATE TABLE STATION (
id NUMBER PRIMARY KEY,
name VARCHAR2(20),
city VARCHAR2(20)
);
CREATE TABLE SERVICE (
train_number NUMBER PRIMARY KEY,
train_name VARCHAR2(20)
);
CREATE TABLE ROUTE (
id NUMBER PRIMARY KEY,
train_number NUMBER REFERENCES SERVICE( train_number ),
station_id NUMBER REFERENCES STATION( id ),
arrival_time NUMBER,
departure_time NUMBER
);
INSERT INTO STATION VALUES ( 1, 'stationA', 'city1' );
INSERT INTO STATION VALUES ( 2, 'stationB', 'city1' );
INSERT INTO STATION VALUES ( 3, 'stationC', 'city2' );
INSERT INTO STATION VALUES ( 4, 'stationD', 'city3' );
INSERT INTO SERVICE VALUES ( 1337, 'train1' );
INSERT INTO SERVICE VALUES ( 1338, 'train2' );
INSERT INTO SERVICE VALUES ( 1339, 'train3' );
INSERT INTO ROUTE VALUES (1, 1337, 1, null, 1800);
INSERT INTO ROUTE VALUES (2, 1337, 2, 1900, 2000);
INSERT INTO ROUTE VALUES (3, 1337, 3, 2020, 2100);
INSERT INTO ROUTE VALUES (4, 1337, 4, 2120, 2200);
INSERT INTO ROUTE VALUES (5, 1338, 1, null, 1800);
INSERT INTO ROUTE VALUES (6, 1338, 4, 1900, 2000);
INSERT INTO ROUTE VALUES (7, 1338, 3, 2020, 2100);
INSERT INTO ROUTE VALUES (8, 1338, 2, 2120, 2200);
Query:
WITH INDEXED_ROUTES AS (
SELECT train_number,
s.name,
ROW_NUMBER() OVER( PARTITION BY train_number ORDER BY COALESCE( arrival_time, 0 ), COALESCE( departure_time, 2400 ) ) AS IDX
FROM ROUTE r
INNER JOIN
STATION s
ON ( r.station_id = s.id )
)
SELECT train_number,
SUBSTR( SYS_CONNECT_BY_PATH( NAME, ' -> ' ), 5 ) AS route
FROM indexed_routes
WHERE CONNECT_BY_ISLEAF = 1
START WITH IDX = 1
CONNECT BY PRIOR IDX + 1 = IDX
AND PRIOR train_number = train_number;
or alternatively:
SELECT train_number,
LISTAGG( s.name, ' -> ' ) WITHIN GROUP ( ORDER BY arrival_time ASC NULLS FIRST ) AS route
FROM ROUTE r
INNER JOIN
STATION s
ON ( r.station_id = s.id )
GROUP BY train_number;
Either will output:
TRAIN_NUMBER ROUTE
------------ --------------------------------------------
1337 stationA -> stationB -> stationC -> stationD
1338 stationA -> stationD -> stationC -> stationB
If you just want pairs of stations then use the LAG
or LEAD
analytic functions:
SELECT train_number,
LAG( s.name ) OVER( PARTITION BY train_number ORDER BY COALESCE( arrival_time, 0 ), COALESCE( departure_time, 2400 ) ) AS prev_station_name,
s.name AS station_name
FROM ROUTE r
INNER JOIN
STATION s
ON ( r.station_id = s.id );
Output:
TRAIN_NUMBER PREV_STATION_NAME STATION_NAME
------------ -------------------- --------------------
1337 (null) stationA
1337 stationA stationB
1337 stationB stationC
1337 stationC stationD
1338 (null) stationA
1338 stationA stationD
1338 stationD stationC
1338 stationC stationB