Хранение автобусных маршрутов в базе данных


16

Я провел некоторое исследование и обнаружил, что должен хранить маршрут как последовательность остановок. Что-то вроде:

Start -> Stop A -> Stop B -> Stop C -> End

Я создал три таблицы:

  • Маршруты
  • Остановки
  • RouteStops

... где RouteStops - это соединительная таблица.

У меня есть что-то вроде:

Маршруты

+---------+
| routeId |
+---------+
|    1    |
+---------+
|    2    |
+---------+

станций

+-----------+------+
| stationId | Name |
+-----------+------+
|     1     |   A  |
+-----------+------+
|     2     |   B  |
+-----------+------+
|     3     |   C  |
+-----------+------+
|     4     |   D  |
+-----------+------+

RouteStations

+-------------+---------------+
| routeId(fk) | stationId(fk) |
+-------------+---------------+
|     1       |       A       |
+-------------+---------------+
|     1       |       C       |
+-------------+---------------+
|     1       |       D       |
+-------------+---------------+
|     2       |       A       |
+-------------+---------------+
|     2       |       D       |
+-------------+---------------+

Маршрут 1 проходит

Station A -> Station C -> Station D

Маршрут 2 проходит

Station A -> Station D

Это хороший способ хранить маршруты?

Согласно Википедии :

[...] система баз данных не гарантирует какого-либо упорядочения строк, если не указано ORDER BYусловие [...]

Могу ли я положиться на такую ​​схему базы данных или, может быть, это нужно сделать по-другому?

На самом деле это мой университетский проект, поэтому мне просто интересно, можно ли считать такую ​​схему правильной. В этом случае я бы, вероятно, сохранил только несколько маршрутов (около 3-5) и станций (около 10-15), каждый маршрут будет состоять из около 5 станций. Я также был бы рад услышать, как это должно выглядеть в случае реальной и большой автобусной компании.


Возможно, вы захотите взглянуть на общую спецификацию транзитной подачи ; в то время как каналы GTFS указываются для обмена в виде файлов CSV, приложения часто хранят и управляют GTFS в реляционной базе данных.
Курт Рашке

3
Ваш вопрос переключается между терминами «Стоп» и «Станция». Вы, вероятно, должны уточнить свой словарь домена ( то есть выбрать одно имя и остаться с ним).
Терсосаврос

@ monoh_.i у меня также похожий вопрос dba.stackexchange.com/questions/194223/… если у вас есть идея, которой вы можете поделиться
видение

Ответы:


19

Для всего бизнес-анализа, ведущего к архитектуре базы данных, я рекомендую написать правила:

  • Маршрут имеет 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       |
+-------------+---------------+

Примечания:

  1. Я исправил StationId в RouteStations в моем примере. Вы используете StationName в качестве идентификатора.
  2. Если вы не используете имя маршрута, тогда даже не нужен routeId, поскольку вы можете получить его из routeStations
  3. Даже если бы вы ссылались на таблицу маршрутов, ваш оптимизатор базы данных заметил бы, что ему не нужна эта дополнительная ссылка, и просто удалил лишние шаги.

Для разработки на ноте 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")      

@ Nicolas.i у меня также есть похожие вопросы, можете ли вы мне помочь dba.stackexchange.com/questions/194223/…
видение

3

Вы правы, в реляционной таблице нет внутреннего порядка записей. Это означает, что вам нужно предоставить какой-то явный способ заказа станций внутри каждого маршрута.

В зависимости от того, как вы планируете получить доступ к данным, вы можете

  1. Добавьте sequenceNumberстолбец, чтобы RouteStationsсохранить последовательность каждой станции на каждом маршруте.
  2. Добавьте nextStationIdстолбец для хранения «указателя» на следующую станцию ​​в каждом маршруте.

У @ mustaccio.i также есть похожие вопросы, можете ли вы мне помочь dba.stackexchange.com/questions/194223/…
видение

0

Я не видел, чтобы кто-то что-то говорил об этом, поэтому я решил добавить к вашей оценке. Я бы также поместил некластеризованный уникальный индекс (в зависимости от вашей СУБД) в таблицу RouteStations / RouteStops во всех трех столбцах. Таким образом, вы не сможете совершать ошибки, и автобус будет ехать до двух следующих станций. Это усложнит обновление, но я думаю, что все равно это следует рассматривать как часть хорошего дизайна.


-1

Я говорю как прикладной программист :

Даже не думайте о маршрутизации или расписании запросов к базе данных (или в хранимых процессах), это никогда не будет достаточно быстрым. ( Если только это не проблема «домашней работы». )

Даже для приложения, которое обрабатывает данные в памяти, загрузка данных из базы данных никогда не будет быстрой, если все данные не загружаются при запуске или данные хранятся в деморализованной форме. Как только данные деморализованы, нет смысла использовать реляционную базу данных.

Поэтому я считаю, что база данных является «основной» копией данных, и я согласен с тем, что мне также придется хранить ее предварительно обработанной в памяти приложения или на сервере кэширования, таком как мембрана.

Ответ ndefontenay дает хороший дизайн таблицы в качестве отправной точки, но вы должны учитывать, что маршруты имеют различное время в зависимости от времени дня и часто имеют разные остановки в зависимости от времени, дня недели или даже школьных каникул.


5
Нигде он не упоминает, что он хочет делать маршрутизацию или расписание; он спрашивает, как хранить маршруты в БД. Кроме того, хотя программист может быть деморализован, я уверен, что в какой-то момент данные будут (де) нормализованы . :)
AnoE
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.