Almacenar rutas de autobús en una base de datos

16

Investigué un poco y descubrí que debería almacenar una ruta como una secuencia de paradas. Algo como:

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

He creado tres tablas:

  • Rutas
  • Paradas
  • RouteStops

... donde RouteStops es una tabla de unión.

Tengo algo como:

Rutas

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

Estaciones

+-----------+------+
| 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 ruta 1 pasa por

Station A -> Station C -> Station D

La ruta 2 pasa por

Station A -> Station D

¿Es esta una buena forma de almacenar rutas?

De acuerdo con Wikipedia :

[...] el sistema de base de datos no garantiza ningún orden de las filas a menos ORDER BYque se especifique una cláusula [...]

¿Puedo confiar en tal esquema de base de datos o tal vez esto debería hacerse de manera diferente?

Este es en realidad mi proyecto universitario, por lo que me pregunto si ese esquema puede considerarse correcto. Para este caso, probablemente almacenaría solo varias rutas (aproximadamente 3-5) y estaciones (aproximadamente 10-15), cada ruta consistirá en aproximadamente 5 estaciones. También me alegraría saber cómo debería ser esto en caso de una compañía de autobuses real y grande.

monoh_
fuente
Es posible que desee ver la Especificación general de alimentación de tránsito ; mientras que las fuentes de GTFS se especifican para intercambiarse como archivos CSV, las aplicaciones a menudo almacenan y manipulan GTFS en una base de datos relacional.
Kurt Raschke
3
Su pregunta cambia entre los términos 'Stop' y 'Station'. Probablemente debería aclarar el vocabulario de su dominio ( es decir, elegir un nombre y quedarse con él).
Tersosauros
@ monoh_.i también tengo un tipo de pregunta similar dba.stackexchange.com/questions/194223/…. si tienes una idea, ¿puedes compartirla
Visión

Respuestas:

19

Para todos los análisis comerciales que conducen a la arquitectura de la base de datos, recomiendo escribir reglas:

  • Una ruta tiene 2 o más estaciones
  • Una estación puede ser utilizada por muchas rutas
  • Las estaciones en una ruta vienen en un orden específico

La primera y la segunda reglas, como notó, implican una relación de muchos a muchos, por lo que concluyó legítimamente para crear routeStations.

La tercera regla es la interesante. Implica que se necesita una columna adicional para cumplir con el requisito. ¿A dónde debería ir? Podemos ver que esta propiedad depende de la ruta y la estación. Por lo tanto, debe ubicarse en routeStations.

Agregaría una columna a la tabla routeStations llamada "stationOrder".

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

Entonces las consultas se vuelven fáciles:

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

Notas:

  1. Arreglé el StationId en RouteStations en mi ejemplo. Estás utilizando StationName como Id.
  2. Si no utiliza un nombre de ruta, entonces ni siquiera es necesario routeId ya que puede obtenerlo de routeStations
  3. Incluso si se vinculara a la tabla de rutas, su optimizador de base de datos notaría que no necesita ese enlace adicional y simplemente eliminaría los pasos adicionales.

Para desarrollar en la nota 3, he construido el caso de uso:

Este es Oracle 12c Enterprise.

Tenga en cuenta que en el plan de ejecución a continuación, esa tabla de rutas no se utiliza en absoluto. el Optimizador de la base de costos (CBO) sabe que puede obtener el ID de ruta directamente de la clave principal de routeStations (paso 5, ESCANEO DE RANGO DE ÍNDICE en ROUTESTATIONS_PK, Información de predicado 5 - acceso ("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")

Ahora la parte divertida, agreguemos un nombre de columna a la tabla de rutas. Ahora hay una columna que realmente necesitamos en "rutas". La CBO usa el índice para encontrar el rowID para la ruta 1, luego accede a la tabla (acceso a la tabla por el índice rowid) y toma la columna "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
fuente
@ Nicolas.i también tengo un tipo de pregunta similar, ¿pueden ayudarme? Dba.stackexchange.com/questions/194223/…
visión
3

Tiene razón, no hay un orden inherente de registros en una tabla relacional. Esto significa que debe proporcionar alguna forma explícita de ordenar estaciones dentro de cada ruta.

Dependiendo de cómo esté planeando acceder a los datos, podría

  1. Agregue la sequenceNumbercolumna para RouteStationsalmacenar, obviamente, la secuencia de cada estación en cada ruta.
  2. Agregue la nextStationIdcolumna para almacenar un "puntero" a la siguiente estación en cada ruta.
mustaccio
fuente
@ mustaccio.i también tengo un tipo de pregunta similar, ¿pueden ayudarme? dba.stackexchange.com/questions/194223/…
visión
0

No vi a nadie decir nada sobre esto, así que pensé que agregaría para su calificación. También colocaría un índice exclusivo no agrupado (dependiendo de su RDBMS) en la tabla RouteStations / RouteStops en las tres columnas. De esta manera, no podrá cometer errores y el autobús irá a las 2 estaciones siguientes. Esto dificultará las actualizaciones, pero creo que aún debe considerarse como parte de un buen diseño.

Josh Simar
fuente
-1

Estoy hablando como programador de aplicaciones :

Ni siquiera piense en hacer un enrutamiento o un calendario con consultas en la base de datos (o en un proceso almacenado), nunca será lo suficientemente rápido. ( A menos que esto sea solo un problema de "tarea" ) .

Incluso para una aplicación que procesa los datos en la memoria, cargar los datos de la base de datos nunca será rápido a menos que todos los datos se carguen al inicio, o los datos se almacenen en forma desmoralizada. Una vez que los datos están desmoralizados, no tiene mucho sentido usar una base de datos relacional.

Por lo tanto, pensaría que la base de datos es la copia "maestra" de los datos y aceptaría que también tendré que almacenarlos preprocesados ​​en la memoria de la aplicación o en un servidor de cobro como membase.

La respuesta de ndefontenay ofrece un buen diseño de tabla como punto de partida, pero debe tener en cuenta que las rutas tienen diferentes horarios según la hora del día y, a menudo, tienen diferentes paradas según la hora, el día de la semana o incluso las vacaciones escolares.

Ian Ringrose
fuente
55
En ninguna parte menciona que quiere hacer rutas o horarios; pregunta cómo almacenar rutas en un DB. Además, si bien un programador podría estar desmoralizado, espero que los datos se (des) normalicen en algún momento. :)
AnoE