Stockage des itinéraires de bus dans une base de données

16

J'ai fait quelques recherches et j'ai découvert que je devais enregistrer un itinéraire comme une séquence d'arrêts. Quelque chose comme:

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

J'ai créé trois tableaux:

  • Itinéraires
  • Arrête
  • RouteStops

... où RouteStops est une table de jonction.

J'ai quelque chose comme:

Itinéraires

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

Gares

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

La route 1 passe

Station A -> Station C -> Station D

La route 2 passe

Station A -> Station D

Est-ce un bon moyen de stocker des itinéraires?

Selon Wikipedia :

[...] le système de base de données ne garantit aucun ordre des lignes sauf si une ORDER BYclause est spécifiée [...]

Puis-je compter sur un tel schéma de base de données ou peut-être que cela devrait être fait différemment?

C'est en fait mon projet universitaire, donc je me demande simplement si un tel schéma peut être considéré comme correct. Dans ce cas, je ne stockerais probablement que plusieurs itinéraires (environ 3-5) et stations (environ 10-15), chaque itinéraire sera composé d'environ 5 stations. Je serais également heureux d'entendre à quoi cela devrait ressembler dans le cas d'une véritable et grande compagnie de bus.

monoh_
la source
Vous voudrez peut-être consulter les spécifications générales du flux de transport en commun ; alors que les flux GTFS sont spécifiés pour être échangés en tant que fichiers CSV, les applications stockent et manipulent souvent GTFS dans une base de données relationnelle.
Kurt Raschke
3
Votre question bascule entre les termes «Stop» et «Station». Vous devriez probablement clarifier le vocabulaire de votre domaine ( c'est-à-dire choisir un nom et le conserver).
Tersosauros
@ monoh_.i a également un genre de question similaire dba.stackexchange.com/questions/194223/… .si vous avez une idée, pouvez-vous la partager
vision

Réponses:

19

Pour toute analyse commerciale menant à l'architecture de base de données, je recommande d'écrire des règles:

  • Un itinéraire comporte 2 stations ou plus
  • Une station peut être utilisée par de nombreux itinéraires
  • Les stations sur un itinéraire viennent dans un ordre spécifique

Les 1re et 2e règles, comme vous l'avez remarqué, impliquent une relation plusieurs à plusieurs, vous avez donc conclu à juste titre à créer routeStations.

La troisième règle est intéressante. Cela implique qu'une colonne supplémentaire est nécessaire pour répondre à l'exigence. Où cela devrait-il aller? Nous pouvons voir que cette propriété dépend de la route ET de la station. Par conséquent, il doit être situé dans routeStations.

J'ajouterais une colonne au tableau routeStations appelée "stationOrder".

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

L'interrogation devient alors facile:

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

Remarques:

  1. J'ai corrigé le StationId dans RouteStations dans mon exemple. Vous utilisez le StationName comme ID.
  2. Si vous n'utilisez pas de nom de route, il n'y a même pas besoin de routeId puisque vous pouvez l'obtenir de routeStations
  3. Même si vous établissez un lien vers la table de routage, votre optimiseur de base de données remarquerait qu'il n'a pas besoin de ce lien supplémentaire et supprimera simplement les étapes supplémentaires.

Pour développer sur la note 3, j'ai construit le cas d'utilisation:

Il s'agit d'Oracle 12c Enterprise.

Notez que dans le plan d'exécution ci-dessous, les routes de table ne sont pas utilisées du tout. l'Optimiseur de base de coûts (CBO) sait qu'il peut obtenir le routeId directement à partir de la clé primaire de routeStations (étape 5, INDEX RANGE SCAN sur ROUTESTATIONS_PK, Predicate Information 5 - access ("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")

Maintenant, la partie amusante, ajoutons un nom de colonne à la table de routage. Il y a maintenant une colonne dont nous avons réellement besoin dans "routes". Le CBO utilise l'index pour trouver le rowID de la route 1, puis accède à la table (accès à la table par index rowid) et saisit la colonne "routes.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
la source
@ Nicolas.i j'ai aussi un genre de question similaire, pouvez-vous m'aider dba.stackexchange.com/questions/194223/…
vision
3

Vous avez raison, il n'y a pas d'ordre d'enregistrement inhérent dans une table relationnelle. Cela signifie que vous devez fournir un moyen explicite de commander des stations sur chaque itinéraire.

Selon la façon dont vous prévoyez d'accéder aux données que vous pourriez

  1. Ajoutez la sequenceNumbercolonne àRouteStations pour stocker, évidemment, la séquence de chaque station dans chaque itinéraire.
  2. Ajoutez la nextStationIdcolonne pour stocker un "pointeur" vers la station suivante dans chaque itinéraire.
mustaccio
la source
@ mustaccio.i a également un genre de question similaire, pouvez-vous m'aider dba.stackexchange.com/questions/194223/…
vision
0

Je n'ai vu personne dire quoi que ce soit à ce sujet, alors j'ai pensé que j'ajouterais pour votre note. Je placerais également un index Unique non clusterisé (en fonction de votre SGBDR) sur la table RouteStations / RouteStops sur les trois colonnes. De cette façon, vous ne pourrez pas faire d'erreurs et le bus se rendra aux 2 stations suivantes. Cela rendra les mises à jour plus difficiles, mais je pense que cela devrait toujours être considéré comme faisant partie d'un bon design.

Josh Simar
la source
-1

Je parle en tant que programmeur d'applications :

Ne pensez même pas à faire du routage ou des horaires avec des requêtes sur la base de données (ou dans un proc stocké), cela ne sera jamais assez rapide. ( À moins que ce ne soit qu'un problème de «devoirs». )

Même pour une application qui traite les données en mémoire, le chargement des données de la base de données ne sera jamais rapide à moins que toutes les données soient chargées au démarrage ou que les données soient stockées sous une forme démoralisée. Une fois les données démoralisées, il est inutile d'utiliser une base de données relationnelle.

Par conséquent, je penserais que la base de données est la copie «principale» des données et accepterais que je devrai également la stocker prétraitée dans la mémoire de l'application ou sur un serveur d'encaissement comme membase.

La réponse de ndefontenay donne un bon design de table comme point de départ, mais vous devez considérer que les itinéraires ont des horaires différents en fonction de l'heure de la journée et ont souvent des arrêts différents en fonction de l'heure, du jour de la semaine ou même des vacances scolaires.

Ian Ringrose
la source
5
Nulle part il ne mentionne qu'il veut faire du routage ou des horaires; il demande comment stocker les itinéraires dans une base de données. De plus, alors qu'un programmeur pourrait être démoralisé, j'espère bien que les données seront (dé-) normalisées à un moment donné. :)
AnoE