Для всего бизнес-анализа, ведущего к архитектуре базы данных, я рекомендую написать правила:
- Маршрут имеет 2 или более станций
- Станция может использоваться многими маршрутами
- Станции на маршруте приходят в определенном порядке
1-е и 2-е правила, как вы заметили, подразумевают взаимосвязь «многие ко многим», поэтому вы правильно сделали, что создали routeStation.
Третье правило интересное. Это означает, что для соответствия требованиям необходим дополнительный столбец. Куда это должно идти? Мы видим, что это свойство зависит от маршрута и станции. Поэтому он должен быть расположен в routeStations.
Я бы добавил столбец в таблицу routeStations под названием "stationOrder".
+-------------+---------------+---------------
| routeId(fk) | stationId(fk) | StationOrder |
+-------------+---------------+---------------
| 1 | 1 | 3 |
+-------------+---------------+---------------
| 1 | 3 | 1 |
+-------------+---------------+---------------
| 1 | 4 | 2 |
+-------------+---------------+---------------
| 2 | 1 | 1 |
+-------------+---------------+---------------
| 2 | 4 | 2 |
+-------------+---------------+---------------
Тогда запрос становится простым:
select rs.routeID,s.Name
from routeStations rs
join
Stations s
on rs.stationId=s.StationId
where rs.routeId=1
order by rs.StationOrder;
+-------------+---------------+
| routeId(fk) | stationId(fk) |
+-------------+---------------+
| 1 | C |
+-------------+---------------+
| 1 | D |
+-------------+---------------+
| 1 | A |
+-------------+---------------+
Примечания:
- Я исправил StationId в RouteStations в моем примере. Вы используете StationName в качестве идентификатора.
- Если вы не используете имя маршрута, тогда даже не нужен routeId, поскольку вы можете получить его из routeStations
- Даже если бы вы ссылались на таблицу маршрутов, ваш оптимизатор базы данных заметил бы, что ему не нужна эта дополнительная ссылка, и просто удалил лишние шаги.
Для разработки на ноте 3 я создал вариант использования:
Это Oracle 12c Enterprise.
Обратите внимание, что в плане выполнения ниже эта таблица маршрутов вообще не используется. Оптимизатор базовых затрат (CBO) знает, что он может получить routeId непосредственно из первичного ключа routeStations (шаг 5, СКАНДИРОВАНИЕ ДИАПАЗОНА ИНДЕКСА на ROUTESTATIONS_PK, Информация о предикате 5 - доступ («RS». «ROUTEID» = 1))
--Table ROUTES
create sequence routeId_Seq start with 1 increment by 1 maxvalue 9999999999999 cache 1000;
CREATE TABLE routes
(
routeId INTEGER NOT NULL
);
ALTER TABLE routes ADD (
CONSTRAINT routes_PK
PRIMARY KEY
(routeId)
ENABLE VALIDATE);
insert into routes values (routeId_Seq.nextval);
insert into routes values (routeId_Seq.nextval);
commit;
--TABLE STATIONS
create sequence stationId_seq start with 1 increment by 1 maxvalue 9999999999999 cache 1000;
create table stations(
stationID INTEGER NOT NULL,
name varchar(50) NOT NULL
);
ALTER TABLE stations ADD (
CONSTRAINT stations_PK
PRIMARY KEY
(stationId)
ENABLE VALIDATE);
insert into stations values (stationId_seq.nextval,'A');
insert into stations values (stationId_seq.nextval,'B');
insert into stations values (stationId_seq.nextval,'C');
insert into stations values (stationId_seq.nextval,'D');
commit;
--
--Table ROUTESTATIONS
CREATE TABLE routeStations
(
routeId INTEGER NOT NULL,
stationId INTEGER NOT NULL,
stationOrder INTEGER NOT NULL
);
ALTER TABLE routeStations ADD (
CONSTRAINT routeStations_PK
PRIMARY KEY
(routeId, stationId)
ENABLE VALIDATE);
ALTER TABLE routeStations ADD (
FOREIGN KEY (routeId)
REFERENCES ROUTES (ROUTEID)
ENABLE VALIDATE,
FOREIGN KEY (stationId)
REFERENCES STATIONS (stationId)
ENABLE VALIDATE);
insert into routeStations values (1,1,3);
insert into routeStations values (1,3,1);
insert into routeStations values (1,4,2);
insert into routeStations values (2,1,1);
insert into routeStations values (2,4,2);
commit;
explain plan for select rs.routeID,s.Name
from ndefontenay.routeStations rs
join
ndefontenay.routes r
on r.routeId=rs.routeId
join ndefontenay.stations s
on rs.stationId=s.stationId
where rs.routeId=1
order by rs.StationOrder;
set linesize 1000
set pages 500
select * from table (dbms_xplan.display);
PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------
Plan hash value: 2617709240
---------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 79 | 1 (100)| 00:00:01 |
| 1 | SORT ORDER BY | | 1 | 79 | 1 (100)| 00:00:01 |
| 2 | NESTED LOOPS | | | | | |
| 3 | NESTED LOOPS | | 1 | 79 | 0 (0)| 00:00:01 |
| 4 | TABLE ACCESS BY INDEX ROWID| ROUTESTATIONS | 1 | 39 | 0 (0)| 00:00:01 |
|* 5 | INDEX RANGE SCAN | ROUTESTATIONS_PK | 1 | | 0 (0)| 00:00:01 |
|* 6 | INDEX UNIQUE SCAN | STATIONS_PK | 1 | | 0 (0)| 00:00:01 |
| 7 | TABLE ACCESS BY INDEX ROWID | STATIONS | 1 | 40 | 0 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
5 - access("RS"."ROUTEID"=1)
6 - access("RS"."STATIONID"="S"."STATIONID")
Теперь самое интересное, давайте добавим имя столбца в таблицу маршрутов. Теперь есть столбец, который нам действительно нужен в «маршрутах». CBO использует индекс, чтобы найти rowID для маршрута 1, затем обращается к таблице (доступ к таблице по индексу rowid) и захватывает столбец "rout.name".
ALTER TABLE ROUTES
ADD (name VARCHAR2(50));
update routes set name='Old Town' where routeId=1;
update routes set name='North County' where routeId=2;
commit;
explain plan for select r.name as routeName,s.Name as stationName
from routeStations rs
join
routes r
on r.routeId=rs.routeId
join stations s
on rs.stationId=s.stationId
where rs.routeId=1
order by rs.StationOrder;
set linesize 500
set pages 500
select * from table (dbms_xplan.display);
PLAN_TABLE_OUTPUT
---------------------------------------------------------------------------------------------------
Plan hash value: 3368128430
----------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 119 | 1 (100)| 00:00:01 |
| 1 | SORT ORDER BY | | 1 | 119 | 1 (100)| 00:00:01 |
| 2 | NESTED LOOPS | | | | | |
| 3 | NESTED LOOPS | | 1 | 119 | 0 (0)| 00:00:01 |
| 4 | NESTED LOOPS | | 1 | 79 | 0 (0)| 00:00:01 |
| 5 | TABLE ACCESS BY INDEX ROWID| ROUTES | 1 | 40 | 0 (0)| 00:00:01 |
|* 6 | INDEX UNIQUE SCAN | ROUTES_PK | 1 | | 0 (0)| 00:00:01 |
| 7 | TABLE ACCESS BY INDEX ROWID| ROUTESTATIONS | 1 | 39 | 0 (0)| 00:00:01 |
|* 8 | INDEX RANGE SCAN | ROUTESTATIONS_PK | 1 | | 0 (0)| 00:00:01 |
|* 9 | INDEX UNIQUE SCAN | STATIONS_PK | 1 | | 0 (0)| 00:00:01 |
| 10 | TABLE ACCESS BY INDEX ROWID | STATIONS | 1 | 40 | 0 (0)| 00:00:01 |
----------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
6 - access("R"."ROUTEID"=1)
8 - access("RS"."ROUTEID"=1)
9 - access("RS"."STATIONID"="S"."STATIONID")