Зберігання маршрутів автобусів у базі даних


16

Я провів деякі дослідження і виявив, що я повинен зберігати маршрут як послідовність зупинок. Щось на зразок:

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

Я створив три таблиці:

  • Маршрути
  • Зупиняється
  • RouteStops

... де RouteStops - перехідна таблиця.

У мене є щось на кшталт:

Маршрути

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

Станції

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

Маршрути

+-------------+---------------+
| 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-е правила, як ви помітили, передбачають багато-багато стосунків, тому ви правильно зробили висновок про створення routeStations.

Третє правило - цікаве. Це означає, що потрібен додатковий стовпець, щоб відповідати вимозі. Куди воно має піти? Ми можемо бачити, що ця властивість залежить від маршруту та станції. Тому він повинен бути розташований у routeStations.

Я додав би стовпець до таблиці routeStation під назвою "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 у RouteStation на своєму прикладі. Ви використовуєте StationName як ідентифікатор.
  2. Якщо ви не використовуєте назву маршруту, тоді навіть немає необхідності в routeId, оскільки ви можете отримати це від routeStations
  3. Навіть якщо ви посилаєтесь на таблицю маршрутів, оптимізатор вашої бази даних помітить, що це додаткове посилання не потребує та просто видалить зайві кроки.

Для розробки в примітці 3 я створив випадок використання:

Це Oracle 12c Enterprise.

Зауважте, що в плані виконання нижче цієї таблиці маршрути взагалі не використовуються. Оптимізатор бази витрат (CBO) знає, що він може отримати routeId безпосередньо з первинного ключа routeStation (крок 5, INDEX RANGE SCAN на 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) і захоплює стовпець "route.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

Я не бачив, щоб хто-небудь нічого з цього приводу говорив, тож я подумав, що додав би за вашу оцінку. Я також розміщую некластеризований унікальний індекс (залежно від вашого RDBMS) у таблиці RouteStation / RouteStops у всіх трьох стовпцях. Таким чином, ви не зможете помилитися, і автобус поїде на дві наступні станції. Це ускладнить оновлення, але я думаю, що все-таки слід вважати частиною хорошого дизайну.


-1

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

Навіть не думайте робити маршрутизацію чи розклад графіків із запитами до бази даних (або в збереженій програмі), це ніколи не буде досить швидким. ( Якщо тільки це не проблема "домашнього завдання". )

Навіть для програми, яка обробляє дані в пам'яті, завантаження даних із бази даних ніколи не буде швидким, якщо всі дані не завантажуються при запуску або дані зберігаються в деморалізованому вигляді. Після деморалізації даних мало сенсу використовувати реляційну базу даних.

Тому я вважаю, що база даних є "головною" копією даних і погоджуюсь, що мені також доведеться зберігати їх заздалегідь обробленою в пам'яті додатків або на сервері готівки, як мембрана.

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


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