Speichern von Busrouten in einer Datenbank

16

Ich habe einige Nachforschungen angestellt und festgestellt, dass ich eine Route als eine Folge von Stopps speichern sollte. Etwas wie:

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

Ich habe drei Tabellen erstellt:

  • Routen
  • Stoppt
  • RouteStops

... wo RouteStops eine Kreuzungstabelle ist.

Ich habe so etwas wie:

Routen

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

Stationen

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

Route 1 geht durch

Station A -> Station C -> Station D

Route 2 geht durch

Station A -> Station D

Ist dies eine gute Möglichkeit, Routen zu speichern?

Laut Wikipedia :

[...] das Datenbanksystem garantiert keine Reihenfolge der Zeilen, es sei denn, es wird eine ORDER BYKlausel angegeben [...]

Kann ich mich auf ein solches Datenbankschema verlassen oder sollte dies anders erfolgen?

Dies ist eigentlich mein Universitätsprojekt, daher frage ich mich nur, ob ein solches Schema als korrekt angesehen werden kann. In diesem Fall würde ich wahrscheinlich nur mehrere Routen (ca. 3-5) und Stationen (ca. 10-15) speichern, jede Route wird aus ca. 5 Stationen bestehen. Ich würde mich auch freuen zu hören, wie das bei echten und großen Busunternehmen aussehen soll.

monoh_
quelle
Möglicherweise möchten Sie sich die allgemeinen Spezifikationen für Transit-Feeds ansehen . Während GTFS-Feeds als CSV-Dateien ausgetauscht werden sollen, speichern und bearbeiten Anwendungen GTFS häufig in einer relationalen Datenbank.
Kurt Raschke
3
Ihre Frage wechselt zwischen den Begriffen 'Stop' und 'Station'. Sie sollten wahrscheinlich Ihr Domain-Vokabular klären ( dh einen Namen auswählen und dabei bleiben).
Tersosauros
@ Monoh_.Ich habe auch ähnliche Art von Frage dba.stackexchange.com/questions/194223/… .Wenn Sie Idee haben, können Sie teilen
Vision

Antworten:

19

Für alle Geschäftsanalysen, die zur Datenbankarchitektur führen, empfehle ich, Regeln zu schreiben:

  • Eine Route hat 2 oder mehr Stationen
  • Eine Station kann auf vielen Wegen benutzt werden
  • Stationen auf einer Route kommen in einer bestimmten Reihenfolge

Die 1. und 2. Regel implizieren, wie Sie bemerkt haben, viele-zu-viele-Beziehungen, sodass Sie zu Recht den Schluss gezogen haben, routeStations zu erstellen.

Die 3. Regel ist die interessante. Dies impliziert, dass eine zusätzliche Spalte erforderlich ist, um die Anforderung zu erfüllen. Wohin soll es gehen? Wir können sehen, dass diese Eigenschaft von Route UND Station abhängt. Daher sollte es sich in routeStations befinden.

Ich würde der Tabelle routeStations eine Spalte mit dem Namen "stationOrder" hinzufügen.

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

Dann wird das Abfragen einfach:

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

Anmerkungen:

  1. In meinem Beispiel habe ich die StationId in RouteStations korrigiert. Sie verwenden den Stationsnamen als ID.
  2. Wenn Sie keinen Routennamen verwenden, wird routeId nicht einmal benötigt, da dies von routeStations bezogen werden kann
  3. Selbst wenn Sie eine Verknüpfung zur Routentabelle herstellen würden, würde Ihr Datenbankoptimierer feststellen, dass diese zusätzliche Verknüpfung nicht erforderlich ist, und die zusätzlichen Schritte einfach entfernen.

Um auf Note 3 zu entwickeln, habe ich den Use Case erstellt:

Dies ist Oracle 12c Enterprise.

Beachten Sie, dass im unten stehenden Ausführungsplan die Tabellenrouten überhaupt nicht verwendet werden. Der Cost Base Optimizer (CBO) weiß, dass er die routeId direkt vom Primärschlüssel der routeStations abrufen kann (Schritt 5, INDEX RANGE SCAN auf ROUTESTATIONS_PK, Predicate Information 5 - Zugriff ("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")

Fügen Sie der Routentabelle jetzt einen Spaltennamen hinzu. Jetzt gibt es eine Spalte, die wir tatsächlich in "Routen" benötigen. Der CBO verwendet den Index, um die Zeilen-ID für Route 1 zu ermitteln, greift dann auf die Tabelle zu (Tabellenzugriff über die Index-Zeilen-ID) und greift auf die Spalte "routes.name" zu.

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
quelle
@ Nicolas.Ich habe auch ähnliche Art von Frage können Sie mir helfen dba.stackexchange.com/questions/194223/…
Vision
3

Sie haben recht, es gibt keine inhärente Reihenfolge von Datensätzen in einer relationalen Tabelle. Dies bedeutet, dass Sie für jede Route eine explizite Möglichkeit zum Bestellen von Stationen angeben müssen.

Je nachdem, wie Sie auf die Daten zugreifen möchten, können Sie dies tun

  1. Fügen Sie die sequenceNumberSpalte hinzu, RouteStationsum die Sequenz jeder Station in jeder Route zu speichern.
  2. Fügen Sie die nextStationIdSpalte hinzu, um einen "Zeiger" auf die nächste Station in jeder Route zu speichern.
mustaccio
quelle
@ mustaccio.i haben auch ähnliche Art von Frage können Sie mir helfen dba.stackexchange.com/questions/194223/…
Vision
0

Ich habe niemanden gesehen, der irgendetwas darüber gesagt hat, also dachte ich mir, ich würde es für deine Note hinzufügen. Ich würde auch einen nicht gruppierten eindeutigen Index (abhängig von Ihrem RDBMS) in der RouteStations / RouteStops-Tabelle über alle drei Spalten platzieren. Auf diese Weise können Sie keine Fehler machen und der Bus fährt zu 2 nächsten Stationen. Dies wird es schwieriger für Updates, aber ich denke, sollte immer noch als Teil eines guten Designs betrachtet werden.

Josh Simar
quelle
-1

Ich spreche als Anwendungsprogrammierer :

Denken Sie nicht einmal daran, Routing oder Zeitplanung mit Abfragen für die Datenbank durchzuführen (oder in einem gespeicherten Prozess), es wird niemals schnell genug sein. ( Es sei denn, dies ist nur ein "Hausaufgaben" -Problem. )

Selbst für eine Anwendung, die die Daten im Speicher verarbeitet, ist das Laden der Daten aus der Datenbank niemals schnell, es sei denn, alle Daten werden beim Start geladen oder die Daten werden in einer demoralisierten Form gespeichert. Sobald die Daten demoralisiert sind, macht es wenig Sinn, eine relationale Datenbank zu verwenden.

Daher würde ich die Datenbank als "Master" -Kopie der Daten betrachten und akzeptieren, dass ich sie auch vorverarbeitet im Anwendungsspeicher oder auf einem Cashing-Server wie membase speichern muss.

Die Antwort von ndefontenay gibt ein gutes Tabellendesign als Ausgangspunkt an, Sie müssen jedoch berücksichtigen, dass die Routen je nach Tageszeit ein anderes Timing haben und je nach Zeit, Wochentag oder sogar Schulferien häufig unterschiedliche Stopps haben.

Ian Ringrose
quelle
5
Nirgendwo erwähnt er, dass er Routing oder Stundenplanung machen möchte; Er fragt, wie Routen in einer DB gespeichert werden sollen. Obwohl ein Programmierer demoralisiert sein könnte, hoffe ich, dass die Daten irgendwann (de) normalisiert werden. :)
AnoE