Memorizzare le rotte del bus in un database


16

Ho fatto alcune ricerche e ho scoperto che avrei dovuto memorizzare un percorso come una sequenza di fermate. Qualcosa di simile a:

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

Ho creato tre tabelle:

  • Itinerari
  • Fermate
  • RouteStops

... dove RouteStops è una tabella di giunzione.

Ho qualcosa del tipo:

Itinerari

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

Stazioni

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

Il percorso 1 passa attraverso

Station A -> Station C -> Station D

Il percorso 2 passa attraverso

Station A -> Station D

È un buon modo per memorizzare le rotte?

Secondo Wikipedia :

[...] il sistema di database non garantisce alcun ordinamento delle righe se ORDER BYnon viene specificata una clausola [...]

Posso fare affidamento su un tale schema di database o forse questo dovrebbe essere fatto diversamente?

Questo è in realtà il mio progetto universitario, quindi mi chiedo solo se tale schema possa essere considerato corretto. In questo caso, probabilmente memorizzerei solo diversi percorsi (circa 3-5) e stazioni (circa 10-15), ogni percorso sarà composto da circa 5 stazioni. Sarei anche felice di sapere come dovrebbe apparire nel caso di una vera e propria compagnia di autobus.


Potresti voler guardare le specifiche generali del feed di transito ; mentre i feed GTFS sono specificati per essere scambiati come file CSV, le applicazioni spesso archiviano e manipolano GTFS in un database relazionale.
Kurt Raschke,

3
La tua domanda passa tra i termini "Stop" e "Stazione". Probabilmente dovresti chiarire il vocabolario del tuo dominio (ad esempio, scegli un nome e rimani con esso).
Tersosauros,

@ monoh_.i ho anche domande simili su dba.stackexchange.com/questions/194223/…. se hai idea puoi condividere
visione

Risposte:


19

Per tutte le analisi di business che portano all'architettura del database, consiglio di scrivere regole:

  • Un percorso ha 2 o più stazioni
  • Una stazione può essere utilizzata da molti percorsi
  • Le stazioni su un percorso arrivano in un ordine specifico

La prima e la seconda regola, come hai notato, implicano una relazione da molte a molte, quindi hai giustamente concluso di creare routeStation.

La terza regola è quella interessante. Implica che è necessaria una colonna aggiuntiva per soddisfare il requisito. Dove dovrebbe andare? Possiamo vedere che questa proprietà dipende da Route AND Station. Pertanto, dovrebbe trovarsi nelle routeStation.

Vorrei aggiungere una colonna alla tabella routeStations chiamata "stationOrder".

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

Quindi l'interrogazione diventa semplice:

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

Appunti:

  1. Nel mio esempio ho corretto StationId in RouteStations. Si sta utilizzando StationName come ID.
  2. Se non usi un nome di percorso, non è nemmeno necessario routeId poiché puoi ottenerlo da routeStations
  3. Anche se si collegasse alla tabella di instradamento, l'ottimizzatore del database noterebbe che non ha bisogno di quel collegamento aggiuntivo e rimuove semplicemente i passaggi aggiuntivi.

Per sviluppare sulla nota 3, ho creato il caso d'uso:

Questa è Oracle 12c Enterprise.

Si noti che nel piano di esecuzione di seguito non vengono utilizzati affatto i percorsi di tabella. lo Cost Base Optimizer (CBO) sa che può ottenere routeId direttamente dalla chiave primaria di routeStations (passaggio 5, INDICE RANGE SCAN su ROUTESTATIONS_PK, informazioni di predicato 5 - accesso ("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")

Ora la parte divertente, aggiungiamo un nome di colonna alla tabella del percorso. Ora c'è una colonna di cui abbiamo effettivamente bisogno in "route". Il CBO utilizza l'indice per trovare il rowID per la route 1, quindi accede alla tabella (accesso alla tabella tramite il rowid dell'indice) e prende la colonna "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 ho anche un tipo di domanda simile, mi puoi aiutare dba.stackexchange.com/questions/194223/…
vision

3

Hai ragione, non esiste un ordine intrinseco di record in una tabella relazionale. Ciò significa che è necessario fornire un modo esplicito per ordinare le stazioni all'interno di ciascun percorso.

A seconda di come prevedi di accedere ai dati che potresti

  1. Aggiungi la sequenceNumbercolonna per RouteStationsmemorizzare, ovviamente, la sequenza di ogni stazione in ogni percorso.
  2. Aggiungi la nextStationIdcolonna per memorizzare un "puntatore" alla stazione successiva in ogni percorso.

@ Mustaccio.i ho anche domande simili: mi puoi aiutare dba.stackexchange.com/questions/194223/…
vision

0

Non ho visto nessuno dichiarare nulla al riguardo, quindi ho pensato di aggiungere per il tuo voto. Posizionerei anche un indice Unico non cluster (a seconda del tuo RDBMS) sulla tabella RouteStations / RouteStops su tutte e tre le colonne. In questo modo non sarà possibile commettere errori e fare in modo che l'autobus vada a 2 stazioni successive. Ciò renderà più difficile gli aggiornamenti, ma penso che dovrebbe essere ancora considerato come parte di un buon design.


-1

Sto parlando come programmatore dell'applicazione :

Non pensare nemmeno di eseguire il routing o la pianificazione con query sul database (o in un proc memorizzato) che non sarà mai abbastanza veloce. ( A meno che questo non sia solo un problema di "compiti a casa". )

Anche per un'applicazione che elabora i dati in memoria il caricamento dei dati dal database non sarà mai veloce a meno che tutti i dati non vengano caricati all'avvio o i dati vengano archiviati in una forma demoralizzata. Una volta che i dati sono demoralizzati, non ha molto senso utilizzare un database relazionale.

Pertanto, considererei il database come la copia "master" dei dati e accetterei che dovrò anche memorizzarlo pre-elaborato nella memoria dell'applicazione o in un server di incasso come membase.

La risposta di ndefontenay fornisce un buon design da tavolo come punto di partenza, ma devi considerare che i percorsi hanno tempi diversi a seconda dell'ora del giorno e spesso hanno fermate diverse a seconda dell'ora, del giorno della settimana o persino delle vacanze scolastiche.


5
Da nessuna parte dice che vuole fare il routing o l'orario; chiede come memorizzare le rotte in un DB. Inoltre, mentre un programmatore potrebbe essere demoralizzato, spero sicuramente che i dati saranno (de-) normalizzati ad un certo punto. :)
AnoE
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.