Przechowywanie tras autobusów w bazie danych

16

Przeprowadziłem pewne badania i odkryłem, że powinienem zapisać trasę jako sekwencję postojów. Coś jak:

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

Utworzyłem trzy tabele:

  • Trasy
  • Przystanki
  • RouteStops

... gdzie RouteStops jest tabelą połączeń.

Mam coś takiego:

Trasy

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

Stacje

+-----------+------+
| 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       |
+-------------+---------------+

Trasa 1 przechodzi

Station A -> Station C -> Station D

Trasa 2 przechodzi

Station A -> Station D

Czy to dobry sposób na przechowywanie tras?

Według Wikipedii :

[...] system bazy danych nie gwarantuje uporządkowania wierszy, chyba że określono ORDER BYklauzulę [...]

Czy mogę polegać na takim schemacie bazy danych, czy może należy to zrobić inaczej?

To właściwie mój projekt uniwersytecki, więc zastanawiam się tylko, czy taki schemat można uznać za poprawny. W tym przypadku prawdopodobnie zapisałbym tylko kilka tras (około 3-5) i stacje (około 10-15), każda trasa będzie składać się z około 5 stacji. Z przyjemnością usłyszę, jak to powinno wyglądać w przypadku prawdziwej i dużej firmy autobusowej.

monoh_
źródło
Warto zapoznać się z ogólną specyfikacją pliku danych tranzytu ; podczas gdy kanały GTFS mają być wymieniane jako pliki CSV, aplikacje często przechowują i manipulują GTFS w relacyjnej bazie danych.
Kurt Raschke,
3
Twoje pytanie przełącza się między terminami „Stop” i „Stacja”. Prawdopodobnie powinieneś wyjaśnić swoje słownictwo domenowe ( tj . Wybierz jedną nazwę i pozostań przy niej).
Tersosauros,
@ monoh_.i mam również podobne pytanie dba.stackexchange.com/questions/194223/ .... jeśli masz pomysł, możesz się podzielić
wizja

Odpowiedzi:

19

Do wszystkich analiz biznesowych prowadzących do architektury bazy danych zalecam pisanie reguł:

  • Trasa ma 2 lub więcej stacji
  • Ze stacji można korzystać na wielu trasach
  • Stacje na trasie są w określonej kolejności

Pierwsza i druga reguła, jak zauważyłeś, implikuje relację wiele do wielu, więc słusznie postanowiłeś utworzyć trasy.

Interesująca jest trzecia zasada. Oznacza to, że potrzebna jest dodatkowa kolumna, aby spełnić wymagania. Gdzie to powinno iść? Widzimy, że ta właściwość zależy od trasy ORAZ stacji. Dlatego powinien znajdować się na stacjach Route.

Dodałbym do tabeli kolumnę routeStations o nazwie „stationOrder”.

+-------------+---------------+---------------
| routeId(fk) | stationId(fk) | StationOrder |
+-------------+---------------+---------------
|     1       |       1       |       3      |
+-------------+---------------+---------------
|     1       |       3       |       1      |
+-------------+---------------+---------------
|     1       |       4       |       2      |
+-------------+---------------+---------------
|     2       |       1       |       1      |
+-------------+---------------+---------------
|     2       |       4       |       2      |
+-------------+---------------+---------------

Następnie zapytania stają się łatwe:

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       |
+-------------+---------------+

Uwagi:

  1. Naprawiłem StationId w RouteStations w moim przykładzie. Używasz nazwy stacji jako identyfikatora.
  2. Jeśli nie użyjesz nazwy trasy, to nie ma nawet potrzeby routeId, ponieważ możesz uzyskać ją z RouteStations
  3. Nawet jeśli podłączysz link do tabeli tras, optymalizator bazy danych zauważy, że nie potrzebuje tego dodatkowego łącza i po prostu usunie dodatkowe kroki.

Aby rozwinąć notatkę 3, zbudowałem przypadek użycia:

To jest Oracle 12c Enterprise.

Zauważ, że w poniższym planie wykonania trasy w ogóle nie są używane. Optymalizator podstawy kosztów (CBO) wie, że może uzyskać routeId bezpośrednio z klucza podstawowego routeStations (krok 5, SKANOWANIE ZAKRESU INDEKSU na ROUTESTATIONS_PK, Informacje o predykacie 5 - dostęp („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")

Teraz zabawna część, dodajmy nazwę kolumny do tabeli tras. Teraz jest kolumna, której tak naprawdę potrzebujemy w „trasach”. CBO używa indeksu do znalezienia rowID dla trasy 1, a następnie uzyskuje dostęp do tabeli (dostęp do tabeli przez indeks rowid) i chwyta kolumnę „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 de Fontenay
źródło
@ Nicolas.i mam również podobne pytanie, czy możesz mi pomóc dba.stackexchange.com/questions/194223/...
wizja
3

Masz rację, nie ma właściwej kolejności rekordów w tabeli relacyjnej. Oznacza to, że musisz podać konkretny sposób zamawiania stacji w obrębie każdej trasy.

W zależności od tego, jak planujesz uzyskać dostęp do danych, które możesz

  1. Dodaj sequenceNumberkolumnę doRouteStations oczywiście zapisać sekwencję każdej stacji na każdej trasie.
  2. Dodaj nextStationIdkolumnę, aby zapisać „wskaźnik” do następnej stacji na każdej trasie.
mustaccio
źródło
@ mustaccio.i mam również podobne pytanie, czy możesz mi pomóc dba.stackexchange.com/questions/194223/...
wizja
0

Nie widziałem, żeby ktokolwiek o tym mówił, więc pomyślałem, że dodam do twojej oceny. Umieściłbym również nieklastrowany indeks Unique (w zależności od RDBMS) w tabeli RouteStations / RouteStops we wszystkich trzech kolumnach. W ten sposób nie będziesz mógł popełniać błędów i autobus będzie jechał na 2 kolejne stacje. Utrudni to aktualizacje, ale myślę, że nadal powinien być uważany za część dobrego projektu.

Josh Simar
źródło
-1

Mówię jako programista aplikacji :

Nawet nie myśl o rutowaniu lub harmonogramowaniu zapytań do bazy danych (lub w przechowywanym proc), nigdy nie będzie wystarczająco szybki. ( Chyba że jest to tylko problem „pracy domowej”. ).

Nawet w przypadku aplikacji przetwarzającej dane w pamięci ładowanie danych z bazy danych nigdy nie będzie szybkie, chyba że wszystkie dane zostaną załadowane podczas uruchamiania lub dane będą przechowywane w formie zdemoralizowanej. Po zdemoralizowaniu danych nie ma sensu korzystanie z relacyjnej bazy danych.

Dlatego pomyślałbym, że baza danych jest „główną” kopią danych i zgadzam się, że będę musiał przechowywać ją wstępnie przetworzoną w pamięci aplikacji lub na serwerze kasowym, takim jak membase.

Odpowiedź ndefontenay daje dobry projekt tabeli jako punktu wyjścia, ale musisz wziąć pod uwagę, że trasy mają różne czasy w zależności od pory dnia i często mają różne przystanki w zależności od pory dnia, dnia tygodnia, a nawet wakacji szkolnych.

Ian Ringrose
źródło
5
Nigdzie nie wspomina, że ​​chce robić trasy lub rozkład jazdy; pyta, jak przechowywać trasy w bazie danych. Poza tym, chociaż programista może zostać zdemoralizowany, mam nadzieję, że dane zostaną (de) znormalizowane w pewnym momencie. :)
AnoE