Armazenando rotas de ônibus em um banco de dados

16

Eu fiz algumas pesquisas e descobri que eu deveria armazenar uma rota como uma sequência de paradas. Algo como:

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

Eu criei três tabelas:

  • Rotas
  • Pára
  • RouteStops

... em que RouteStops é uma tabela de junção.

Eu tenho algo como:

Rotas

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

Estações

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

A rota 1 passa

Station A -> Station C -> Station D

A rota 2 passa

Station A -> Station D

Essa é uma boa maneira de armazenar rotas?

De acordo com a Wikipedia :

[...] o sistema de banco de dados não garante nenhuma ordem das linhas, a menos que uma ORDER BYcláusula seja especificada [...]

Posso confiar nesse esquema de banco de dados ou talvez isso deva ser feito de maneira diferente?

Este é realmente o meu projeto universitário, por isso estou me perguntando se esse esquema pode ser considerado correto. Nesse caso, eu provavelmente armazenaria apenas várias rotas (aproximadamente 3-5) e estações (aproximadamente 10 a 15), cada rota consistirá em cerca de 5 estações. Eu também ficaria feliz em saber como isso deve ser, no caso de empresas de ônibus reais e grandes.

monoh_
fonte
Você pode consultar a Especificação geral de feed de trânsito ; enquanto os feeds GTFS são especificados para serem trocados como arquivos CSV, os aplicativos geralmente armazenam e manipulam o GTFS em um banco de dados relacional.
Kurt Raschke
3
Sua pergunta alterna entre os termos 'Parar' e 'Estação'. Você provavelmente deve esclarecer o seu vocabulário de domínio ( isto é, escolher um nome e ficar com ele).
Tersosauros 11/03/16
@ monoh_.i tem tipo também semelhante de questão dba.stackexchange.com/questions/194223/... .se você tem idéia de que você pode compartilhar
visão

Respostas:

19

Para todas as análises de negócios que levam à arquitetura do banco de dados, recomendo escrever regras:

  • Uma rota tem 2 ou mais estações
  • Uma estação pode ser usada por várias rotas
  • As estações em uma rota vêm em uma ordem específica

As 1ª e 2ª regras, como você notou, implicam em um relacionamento de muitos para muitos; portanto, você concluiu com razão a criação de routeStations.

A 3ª regra é interessante. Isso implica que uma coluna extra é necessária para atender ao requisito. Para onde deveria ir? Podemos ver que essa propriedade depende da rota e estação. Portanto, ele deve estar localizado em routeStations.

Eu adicionaria uma coluna à tabela routeStations chamada "stationOrder".

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

Então, a consulta se torna fácil:

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. Corrigi o StationId no RouteStations no meu exemplo. Você está usando o StationName como o ID.
  2. Se você não usar um nome de rota, não haverá necessidade de routeId, pois você pode obtê-lo em routeStations
  3. Mesmo se você vincular à tabela de rotas, o otimizador de banco de dados perceberá que não precisa desse link extra e simplesmente removerá as etapas extras.

Para desenvolver na nota 3, criei o caso de uso:

Este é o Oracle 12c Enterprise.

Observe que, no plano de execução abaixo, as rotas da tabela não são usadas. o CBO (Cost Base Optimizer) sabe que pode obter o routeId diretamente da chave primária do routeStations (etapa 5, VERIFICAÇÃO DE INTERVALO DO ÍNDICE em ROUTESTATIONS_PK, Informações do Predicado 5 - acesso ("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")

Agora a parte divertida, vamos adicionar um nome de coluna à tabela de rotas. Agora há uma coluna que realmente precisamos em "rotas". O CBO usa o índice para encontrar o ID da linha da rota 1, acessa a tabela (acesso à tabela pelo índice rowid) e pega a coluna "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
fonte
@ Nicolas.i tem tipo também semelhante de pergunta que você pode me ajudar a dba.stackexchange.com/questions/194223/...
visão
3

Você está certo, não há uma ordem inerente de registros em uma tabela relacional. Isso significa que você precisa fornecer uma maneira explícita de solicitar estações em cada rota.

Dependendo de como você planeja acessar os dados, pode

  1. Adicione a sequenceNumbercoluna para RouteStationsarmazenar, obviamente, a sequência de cada estação em cada rota.
  2. Adicione a nextStationIdcoluna para armazenar um "ponteiro" para a próxima estação em cada rota.
mustaccio
fonte
@ mustaccio.i tem tipo também semelhante de pergunta que você pode me ajudar a dba.stackexchange.com/questions/194223/...
visão
0

Eu não vi ninguém dizer nada sobre isso, então achei que acrescentaria a sua nota. Também colocaria um índice exclusivo sem cluster (dependendo do RDBMS) na tabela RouteStations / RouteStops nas três colunas. Dessa forma, você não será capaz de cometer erros e terá o ônibus indo para 2 próximas estações. Isso tornará mais difícil as atualizações, mas acho que ainda deve ser considerado como parte de um bom design.

Josh Simar
fonte
-1

Estou falando como programador de aplicativos :

Nem pense em fazer roteamento ou cronograma com consultas no banco de dados (ou em um processo armazenado), nunca será rápido o suficiente. ( A menos que seja apenas um problema de "lição de casa" ) .

Mesmo para um aplicativo que processa os dados na memória que carregam os dados do banco de dados, nunca será rápido, a menos que todos os dados sejam carregados na inicialização ou os dados sejam armazenados de forma desmoralizada. Depois que os dados são desmoralizados, pouco adianta usar um banco de dados relacional.

Portanto, eu pensaria no banco de dados como sendo a cópia "principal" dos dados e aceitaria que também terei que armazená-lo pré-processado na memória do aplicativo ou em um servidor de pagamento como o membase.

A resposta de ndefontenay fornece um bom design de tabela como ponto de partida, mas você deve considerar que as rotas têm horários diferentes, dependendo da hora do dia, e geralmente têm paradas diferentes, dependendo da hora, dia da semana ou mesmo férias escolares.

Ian Ringrose
fonte
5
Em nenhum lugar ele menciona que deseja fazer roteamento ou cronograma; ele pergunta como armazenar rotas em um banco de dados. Além disso, embora um programador possa estar desmoralizado, espero que os dados sejam (des) normalizados em algum momento. :)
AnoE