Let’s build a simple journey planner using SQL (part 2 of probably more than 2)
Last month we started our humble journey on a path towards becoming an ass-hat developer who builds journey planners using SQL. In the previous blog I described the data model for our journey planner and introduced a simple yet ludicrously inefficient pathfinding algorithm in SQL that generates a list of possible paths from one station to another.
Today, we’re continuing the idiocy by writing SQL code that generates travel plans so that you know where you need to go and when.
Let’s start with a short recap of some of the things we discussed last time. Feel free to skip this section if you still remember how everything works.
We have a MySQL database that contains a node
table, which lists every train
station that is directly reachable from the Netherlands.
id | name |
---|---|
21392 | Nijmegen |
21598 | Nijmegen Dukenburg |
21601 | Nijmegen Heyendaal |
21603 | Nijmegen Lent |
21803 | Nijmegen Goffert |
Trains don’t just stop at train stations – they stop at specific platforms at
those train stations. Those platforms are defined in the stop
table.
id | name | latitude | longitude | platform | node_id |
---|---|---|---|---|---|
35025 | Nijmegen | 51.843352716672 | 5.8527283567859 | 1 | 21392 |
35026 | Nijmegen | 51.8440139658 | 5.85304677486 | 1a | 21392 |
35027 | Nijmegen | 51.842415589576 | 5.8522424471402 | 1b | 21392 |
35028 | Nijmegen | 51.843418123199 | 5.8525284242761 | 3 | 21392 |
35030 | Nijmegen | 51.8440338507 | 5.85280001163 | 3a | 21392 |
35031 | Nijmegen | 51.842471977321 | 5.8520218494775 | 3b | 21392 |
35032 | Nijmegen | 51.843382598179 | 5.8523051845771 | 4 | 21392 |
35033 | Nijmegen | 51.8440537357 | 5.85269272327 | 4a | 21392 |
35034 | Nijmegen | 51.842489994433 | 5.851883317404 | 4b | 21392 |
35029 | Nijmegen | 51.842214820937 | 5.8522482783764 | 35 | 21392 |
35035 | Nijmegen | 51.843845719325 | 5.8534115553016 | 21392 | |
35036 | Nijmegen | 51.842718896418 | 5.852064148637 | 21392 | |
47601 | Nijmegen | 51.8432285026 | 5.85303068161 | 21392 | |
35045 | Nijmegen Heyendaal | 51.8268250995 | 5.86774528027 | 1 | 21601 |
35046 | Nijmegen Heyendaal | 51.826879804 | 5.86788743734 | 2 | 21601 |
35047 | Nijmegen Heyendaal | 51.8266261734 | 5.86765944958 | 21601 | |
35048 | Nijmegen Heyendaal | 51.8266261734 | 5.86765944958 | 21601 |
Trains are scheduled to arrive at and depart from specific platforms at specific
times. Once a train departs, it continues its trip towards the next stop. All
this information can be found in the stop_time
table.
id | trip_id | stop_id | stop_sequence | arrival_time | departure_time | shape_dist_traveled |
---|---|---|---|---|---|---|
25541 | 1807 | 35028 | 1 | 2023-06-01 15:28:00 | 2023-06-01 15:28:00 | 1 |
4395 | 1807 | 33353 | 5 | 2023-06-01 15:40:00 | 2023-06-01 15:45:00 | 18394 |
4396 | 1807 | 34041 | 8 | 2023-06-01 15:55:00 | 2023-06-01 15:55:00 | 34741 |
39696 | 1807 | 34705 | 9 | 2023-06-01 16:01:00 | 2023-06-01 16:01:00 | 41835 |
54327 | 1807 | 35518 | 14 | 2023-06-01 16:19:00 | 2023-06-01 16:26:00 | 75049 |
54328 | 1807 | 33511 | 20 | 2023-06-01 16:42:00 | 2023-06-01 16:44:00 | 104460 |
39697 | 1807 | 33569 | 22 | 2023-06-01 16:49:00 | 2023-06-01 16:51:00 | 110602 |
4397 | 1807 | 35346 | 23 | 2023-06-01 16:57:00 | 2023-06-01 16:57:00 | 119499 |
Passengers may need to change trains to reach their destination. Changing trains
takes a non-zero amount of time. The transfer
table tells us .
id | from_stop_id | to_stop_id | min_transfer_time |
---|---|---|---|
8612 | 35025 | 35036 | 240 |
8613 | 35025 | 35026 | 240 |
8614 | 35025 | 35027 | 240 |
4959 | 35026 | 35027 | 180 |
The final building block that we need for our journey planner is the result we
get from querying from possible_path
, which lists the best ways to travel from
one station to another through the Dutch train network.
path | transfers | stops | distance |
---|---|---|---|
21601 – 21392 – 21680 – 21473 | 2 | 9 | 121928 |
21601 – 21392 – 21671 – 21473 | 2 | 11 | 131837 |
21601 – 21392 – 21678 – 21618 – 21473 | 3 | 8 | 114842 |
21601 – 21392 – 21251 – 21473 | 2 | 13 | 143748 |
21601 – 21392 – 21680 – 21618 – 21473 | 3 | 9 | 121927 |
As you may recall from the previous blog, we wrote a query that helps us navigate the train network from Nijmegen Heyendaal station to Amsterdam Science Park station. Now it’s time for us to pick up where we left off and generate some travel plans!
Before we start, let’s take a look at the website of the Dutch Railways. When we use its travel planner to generate plans for this route, we get the following result:
If we assume that our time of departure is 08:00, our best option is to take the 08:03 train from Nijmegen Heyendaal. After a few transfers on Nijmegen and Amsterdam Centraal, we should arrive at Amsterdam Science Park at 09:49.
Let’s try to replicate this advice using SQL by following the corresponding
possible_path
(21601 – 21392 – 21680 – 21473
).
I’ll first show how we can do this manually, step by step. The first step we need to take is to determine which trains depart from Nijmegen Heyendaal around 08:00. For simplicity’s sake, let’s assume that people are willing to spend at most 30 minutes waiting for a train.
We can use the following trip retrieval query to retrieve all departures from Nijmegen Heyendaal between 08:00 and 08:30:
Executing the query gives us eight results. Trains depart from this station every 15 minutes in both directions.
trip_id | stop_sequence | departure_time | name | platform | stop_id |
---|---|---|---|---|---|
5010 | 8 | 2023-06-01 08:03:00 | Nijmegen Heyendaal | 2 | 35046 |
2455 | 2 | 2023-06-01 08:11:00 | Nijmegen Heyendaal | 1 | 35045 |
4639 | 12 | 2023-06-01 08:18:00 | Nijmegen Heyendaal | 2 | 35046 |
684 | 2 | 2023-06-01 08:26:00 | Nijmegen Heyendaal | 1 | 35045 |
1389 | 6 | 2023-06-01 08:33:00 | Nijmegen Heyendaal | 2 | 35046 |
4641 | 2 | 2023-06-01 08:41:00 | Nijmegen Heyendaal | 1 | 35045 |
3218 | 12 | 2023-06-01 08:48:00 | Nijmegen Heyendaal | 2 | 35046 |
685 | 2 | 2023-06-01 08:56:00 | Nijmegen Heyendaal | 1 | 35045 |
The second station in our path was 21392
(Nijmegen), so let’s query the
database to find out which trips from the previous search results can take us to
Nijmegen:
As expected, only four out of those eight trips allow us to reach Nijmegen:
trip_id | stop_sequence | arrival_time | name | platform | stop_id |
---|---|---|---|---|---|
5010 | 9 | 2023-06-01 08:07:00 | Nijmegen | 35 | 35029 |
4639 | 13 | 2023-06-01 08:22:00 | Nijmegen | 1b | 35027 |
1389 | 7 | 2023-06-01 08:37:00 | Nijmegen | 35 | 35029 |
3218 | 13 | 2023-06-01 08:52:00 | Nijmegen | 1b | 35027 |
Once we are in Nijmegen, it’s time to change trains. We will want to retrieve all departures from this station again. Sadly, we can’t simply execute a modified version of the query that we just used for Nijmegen Heyendaal. Our journey no longer starts at “a station”, but at a specific platform (35 or 1b). Walking from one platform to another may take considerable time, especially at larger stations.
The trip retrieval query below retrieves all departures within the next 30 minutes that are realistically possible if one starts their journey from platforms 35 or 1b:
Executing this query gives us the following results:
trip_id | stop_sequence | departure_time | name | platform | stop_id | node_id |
---|---|---|---|---|---|---|
3985 | 1 | 2023-06-01 08:13:00 | Nijmegen | 3 | 35028 | 21392 |
4017 | 19 | 2023-06-01 08:16:00 | Nijmegen | 1a | 35026 | 21392 |
3984 | 40 | 2023-06-01 08:17:00 | Nijmegen | 3 | 35028 | 21392 |
1122 | 5 | 2023-06-01 08:18:00 | Nijmegen | 4b | 35034 | 21392 |
4639 | 13 | 2023-06-01 08:22:00 | Nijmegen | 1b | 35027 | 21392 |
684 | 1 | 2023-06-01 08:23:00 | Nijmegen | 35 | 35029 | 21392 |
2634 | 17 | 2023-06-01 08:24:00 | Nijmegen | 1a | 35026 | 21392 |
3258 | 1 | 2023-06-01 08:28:00 | Nijmegen | 3 | 35028 | 21392 |
2628 | 12 | 2023-06-01 08:30:00 | Nijmegen | 4b | 35034 | 21392 |
1837 | 4 | 2023-06-01 08:32:00 | Nijmegen | 1a | 35026 | 21392 |
1802 | 23 | 2023-06-01 08:32:00 | Nijmegen | 3 | 35028 | 21392 |
1389 | 7 | 2023-06-01 08:37:00 | Nijmegen | 35 | 35029 | 21392 |
Planning the remainder of the trip should be a walk in the park. We can
repeatedly modify and execute the last two trip retrieval queries with the
remaining node_id
s until we reach our final destination.
You should now have a rough idea of how we can generate a travel plan by iteratively executing queries to retrieve departures and arrivals at each of the nodes in our path. If we were building an application using a normal programming language that’s probably exactly what we would implement in the final version, but that’s not what I’m going to do.
Instead, I’m going to write a stored procedure. A stored procedure is in a normal programming language. And since my manager seems to be very excited about this “big query thing, I am going to write one big-ass stored procedure that does everything. To hell with functional decomposition.
We’ll start with the following template:
Here we define a procedure GET_PATH_ADVICE
, which given a path
(as computed
using the possible_path
CTE) and a time of departure, will generate several
travel plans in a WHILE
loop, using modified versions of the three trip
retrieval queries we saw earlier.
It doesn’t look very chonky yet, but that’s because it still contains a lot of TODOs that we need to process. In the remainder of this section, we’ll slowly feed it lines of SQL code until it’s morbidly obese.
Declaring variables
We begin by declaring some variables at the top of our procedure:
-
We use
node
andstep
to keep track of the current state.node
keeps track of the station we are currently at, whilestep
is a counter that we can use to determine which of the three trip retrieval queries we need to execute, among other things. -
A
dest
variable makes it a bit easier for the algorithm to determine when it has created a travel plan for the complete journey (in this case from Nijmegen Heyendaal to Amsterdam Science Park). -
We also declare an
earliest_arrival_time
datetime. I’ll explain this one later, once we actually get to use it.
We also need a place to store intermediate and final query results. Although lists and maps (dictionaries) are not available in SQL, we can create a temporary table to store our data.
dest
is used to determine whether a travel plan is “complete”. We set its
value to the last node_id
in the path
string that is passed to the
GET_PATH_ADVICE
procedure.
The node
variable is set in the first and every other iteration of the WHILE
loop, i.e. when a traveller starts their journey at the first station or reaches
a new station by train. When this happens, we also remove the current node from
the path
.
Departure from first station
The query that retrieves all departures from the origin station is based on the
first of our three trip retrieval queries. I’ve made a few modifications to it.
For starters, node
and departure_time
are no longer hardcoded, but determined
dynamically.
We also fetch different fields, which we insert into the tmp_path_advice
table:
-
stop_times
stores references to relevantstop_time
records, which contain all the information we need to generate human-friendly travel advice. -
cur_step
stores the number of the current iteration. This allows the algorithm to properly appendstop_time.id
s to thestop_times
field. -
cur_stop_id
is used to determine whichtrip
s can be taken from a transfer station (like Nijmegen). -
cur_trip_id
andcur_stop_sequence
can be used to determine whichtrip
s can be taken to arrive at the nextnode
. -
first_datetime
is when someone boards a train for the first time. This value will stay the same in subsequent iterations. -
cur_datetime
is either the current departure time or arrival time. This is used to determine which transfers are possible, among other things. -
is_complete
is a boolean value that tells us whether the computedstop_times
is complete. It’s always false here, because we haven’t even left the first station at this point.
After the first iteration, the tmp_path_advice
table looks roughly like this:
stop_times | cur_step | cur_stop_id | cur_trip_id | cur_stop_sequence | first_datetime | cur_datetime | is_complete |
---|---|---|---|---|---|---|---|
7352 | 0 | 35046 | 5010 | 8 | 2023-06-01 08:03:00 | 2023-06-01 08:03:00 | 0 |
29055 | 0 | 35045 | 2455 | 2 | 2023-06-01 08:11:00 | 2023-06-01 08:11:00 | 0 |
36810 | 0 | 35046 | 4639 | 12 | 2023-06-01 08:18:00 | 2023-06-01 08:18:00 | 0 |
21877 | 0 | 35045 | 684 | 2 | 2023-06-01 08:26:00 | 2023-06-01 08:26:00 | 0 |
Arrivals at subsequent stations
In the next iteration of the while loop, the procedure will attempt to find all
stations that can be reached with the trip
s from the iteration directly before
it. It then inserts a record for each of the reachable stops:
The tmp_path_advice
table now has 18 new rows, with each row representing a
stop that can be reached via one of the four trips at a specific time:
stop_times | cur_step | cur_stop_id | cur_trip_id | cur_stop_sequence | first_datetime | cur_datetime | is_complete |
---|---|---|---|---|---|---|---|
7352 | 0 | 35046 | 5010 | 8 | 2023-06-01 08:03:00 | 2023-06-01 08:03:00 | 0 |
29055 | 0 | 35045 | 2455 | 2 | 2023-06-01 08:11:00 | 2023-06-01 08:11:00 | 0 |
36810 | 0 | 35046 | 4639 | 12 | 2023-06-01 08:18:00 | 2023-06-01 08:18:00 | 0 |
21877 | 0 | 35045 | 684 | 2 | 2023-06-01 08:26:00 | 2023-06-01 08:26:00 | 0 |
7352,42562 | 1 | 35029 | 5010 | 9 | 2023-06-01 08:03:00 | 2023-06-01 08:07:00 | 0 |
29055,15802 | 1 | 34949 | 2455 | 3 | 2023-06-01 08:11:00 | 2023-06-01 08:16:00 | 0 |
29055,36136 | 1 | 33838 | 2455 | 4 | 2023-06-01 08:11:00 | 2023-06-01 08:22:00 | 0 |
36810,16415 | 1 | 35027 | 4639 | 13 | 2023-06-01 08:18:00 | 2023-06-01 08:22:00 | 0 |
29055,8394 | 1 | 33732 | 2455 | 5 | 2023-06-01 08:11:00 | 2023-06-01 08:29:00 | 0 |
21877,35446 | 1 | 34949 | 684 | 3 | 2023-06-01 08:26:00 | 2023-06-01 08:32:00 | 0 |
21877,15203 | 1 | 33838 | 684 | 4 | 2023-06-01 08:26:00 | 2023-06-01 08:36:00 | 0 |
29055,36137 | 1 | 35626 | 2455 | 6 | 2023-06-01 08:11:00 | 2023-06-01 08:37:00 | 0 |
29055,50913 | 1 | 35643 | 2455 | 7 | 2023-06-01 08:11:00 | 2023-06-01 08:43:00 | 0 |
21877,42934 | 1 | 33732 | 684 | 5 | 2023-06-01 08:26:00 | 2023-06-01 08:44:00 | 0 |
21877,15204 | 1 | 35626 | 684 | 6 | 2023-06-01 08:26:00 | 2023-06-01 08:51:00 | 0 |
29055,8395 | 1 | 33768 | 2455 | 8 | 2023-06-01 08:11:00 | 2023-06-01 08:57:00 | 0 |
21877,105 | 1 | 35645 | 684 | 7 | 2023-06-01 08:26:00 | 2023-06-01 08:57:00 | 0 |
29055,29056 | 1 | 35621 | 2455 | 9 | 2023-06-01 08:11:00 | 2023-06-01 09:01:00 | 0 |
29055,29057 | 1 | 35456 | 2455 | 10 | 2023-06-01 08:11:00 | 2023-06-01 09:07:00 | 0 |
29055,36138 | 1 | 35293 | 2455 | 11 | 2023-06-01 08:11:00 | 2023-06-01 09:14:00 | 0 |
29055,773 | 1 | 35359 | 2455 | 12 | 2023-06-01 08:11:00 | 2023-06-01 09:20:00 | 0 |
29055,50914 | 1 | 35213 | 2455 | 13 | 2023-06-01 08:11:00 | 2023-06-01 09:25:00 | 0 |
Departures from subsequent stations
The third trip retrieval query, which retrieves departures for subsequent stations on a journey, is similarly structured:
This query inserts the following records:
stop_times | cur_step | cur_stop_id | cur_trip_id | cur_stop_sequence | first_datetime | cur_datetime | is_complete |
---|---|---|---|---|---|---|---|
7352,42562,53344 | 2 | 35028 | 3985 | 1 | 2023-06-01 08:03:00 | 2023-06-01 08:13:00 | 0 |
7352,42562,53354 | 2 | 35026 | 4017 | 19 | 2023-06-01 08:03:00 | 2023-06-01 08:16:00 | 0 |
7352,42562,29492 | 2 | 35028 | 3984 | 40 | 2023-06-01 08:03:00 | 2023-06-01 08:17:00 | 0 |
7352,42562,32231 | 2 | 35034 | 1122 | 5 | 2023-06-01 08:03:00 | 2023-06-01 08:18:00 | 0 |
7352,42562,16415 | 2 | 35027 | 4639 | 13 | 2023-06-01 08:03:00 | 2023-06-01 08:22:00 | 0 |
7352,42562,35445 | 2 | 35029 | 684 | 1 | 2023-06-01 08:03:00 | 2023-06-01 08:23:00 | 0 |
7352,42562,17518 | 2 | 35026 | 2634 | 17 | 2023-06-01 08:03:00 | 2023-06-01 08:24:00 | 0 |
7352,42562,26028 | 2 | 35028 | 3258 | 1 | 2023-06-01 08:03:00 | 2023-06-01 08:28:00 | 0 |
36810,16415,26028 | 2 | 35028 | 3258 | 1 | 2023-06-01 08:18:00 | 2023-06-01 08:28:00 | 0 |
7352,42562,51057 | 2 | 35034 | 2628 | 12 | 2023-06-01 08:03:00 | 2023-06-01 08:30:00 | 0 |
36810,16415,51057 | 2 | 35034 | 2628 | 12 | 2023-06-01 08:18:00 | 2023-06-01 08:30:00 | 0 |
7352,42562,30742 | 2 | 35026 | 1837 | 4 | 2023-06-01 08:03:00 | 2023-06-01 08:32:00 | 0 |
7352,42562,10068 | 2 | 35028 | 1802 | 23 | 2023-06-01 08:03:00 | 2023-06-01 08:32:00 | 0 |
36810,16415,10068 | 2 | 35028 | 1802 | 23 | 2023-06-01 08:18:00 | 2023-06-01 08:32:00 | 0 |
36810,16415,30742 | 2 | 35026 | 1837 | 4 | 2023-06-01 08:18:00 | 2023-06-01 08:32:00 | 0 |
7352,42562,15424 | 2 | 35029 | 1389 | 7 | 2023-06-01 08:03:00 | 2023-06-01 08:37:00 | 0 |
36810,16415,15424 | 2 | 35029 | 1389 | 7 | 2023-06-01 08:18:00 | 2023-06-01 08:37:00 | 0 |
36810,16415,19736 | 2 | 35027 | 4641 | 1 | 2023-06-01 08:18:00 | 2023-06-01 08:38:00 | 0 |
36810,16415,41152 | 2 | 35033 | 379 | 16 | 2023-06-01 08:18:00 | 2023-06-01 08:39:00 | 0 |
36810,16415,26193 | 2 | 35028 | 3865 | 1 | 2023-06-01 08:18:00 | 2023-06-01 08:43:00 | 0 |
36810,16415,4978 | 2 | 35026 | 3289 | 19 | 2023-06-01 08:18:00 | 2023-06-01 08:46:00 | 0 |
36810,16415,32927 | 2 | 35028 | 3247 | 40 | 2023-06-01 08:18:00 | 2023-06-01 08:47:00 | 0 |
36810,16415,56419 | 2 | 35034 | 2544 | 5 | 2023-06-01 08:18:00 | 2023-06-01 08:48:00 | 0 |
36810,16415,21007 | 2 | 35027 | 3218 | 13 | 2023-06-01 08:18:00 | 2023-06-01 08:52:00 | 0 |
These two INSERT
queries are repeated until the procedure has reached the end
of the path
. When this happens, the tmp_path_advice
table !
Data cleanup
Once the while loop terminates, all that remains is to generate output that can be displayed to a user. But first we need to clean up some data.
Let’s start by removing all records from tmp_path_advice
that we don’t need:
We then retrieve the earliest possible time of arrival and store it in the
earliest_arrival_time
variable. The best travel plans are those that have a
cur_datetime
equal to earliest_arrival_time
.
A user may also be interested in alternative travel plans, e.g. plans that
suggest taking a later train (keeping the overall travel time the same) or
adding a bit more slack during transfers (increasing the overall travel time).
However, what we would like to avoid are plans where a traveller would do this
at every station along the path. Therefore, we only consider travel plans where
the arrival time at the final destination is at most thirty minutes later than
the earliest_arrival_time
.
Generating advice
Now we can generate our travel advice. The tmp_path_advice
table now only
contains records that look a lot like this one here:
stop_times | cur_step | cur_stop_id | cur_trip_id | cur_stop_sequence | first_datetime | cur_datetime | is_complete |
---|---|---|---|---|---|---|---|
7352,42562,53344,31435,43651,850 | 5 | 33602 | 2209 | 7 | 2023-06-01 08:03:00 | 2023-06-01 09:49:00 | 1 |
The stop_times
fields contains stop_time.id
s that can be used to look up
almost all the information that we want to show to users. Unfortunately this
field is a comma-separated list, so we can’t JOIN
on this field directly.
However, what we can do is convert stop_times
values to a JSON list, parse it,
and then CROSS JOIN
with the resulting table. This allows us to simply
SELECT
the information we need from the joined result:
Now that everything is in place, it’s time to show the complete version of the
GET_PATH_ADVICE
procedure. The text continues below this snippet. Click
here if you don’t feel like scrolling until your finger falls off.
We can call this stored procedure as follows:
The table below displays the results of the procedure call. I have excluded the last two columns to ensure that the table fits on the page. I have also given each travel plan a unique colour to make it easier to see where each journey starts and ends.
journey | type | time | station | platform | train_type | headsign |
---|---|---|---|---|---|---|
8eee2c55 | Departure | 08:03:00 | Nijmegen Heyendaal | 2 | Arriva Stoptrein | Nijmegen |
8eee2c55 | Arrival | 08:07:00 | Nijmegen | 35 | Arriva Stoptrein | Nijmegen |
8eee2c55 | Departure | 08:13:00 | Nijmegen | 3 | NS Intercity | Den Helder |
8eee2c55 | Arrival | 09:35:00 | Amsterdam Centraal | 8 | NS Intercity | Den Helder |
8eee2c55 | Departure | 09:41:00 | Amsterdam Centraal | 13a | NS Sprinter | Amersfoort Vathorst |
8eee2c55 | Arrival | 09:49:00 | Amsterdam Science Park | 1 | NS Sprinter | Amersfoort Vathorst |
ab329c3d | Departure | 08:03:00 | Nijmegen Heyendaal | 2 | Arriva Stoptrein | Nijmegen |
ab329c3d | Arrival | 08:07:00 | Nijmegen | 35 | Arriva Stoptrein | Nijmegen |
ab329c3d | Departure | 08:13:00 | Nijmegen | 3 | NS Intercity | Den Helder |
ab329c3d | Arrival | 09:35:00 | Amsterdam Centraal | 8 | NS Intercity | Den Helder |
ab329c3d | Departure | 09:53:00 | Amsterdam Centraal | 10a | NS Sprinter | Zwolle |
ab329c3d | Arrival | 10:01:00 | Amsterdam Science Park | 1 | NS Sprinter | Zwolle |
0ece0c64 | Departure | 08:18:00 | Nijmegen Heyendaal | 2 | Arriva Stoptrein | Nijmegen |
0ece0c64 | Arrival | 08:22:00 | Nijmegen | 1b | Arriva Stoptrein | Nijmegen |
0ece0c64 | Departure | 08:43:00 | Nijmegen | 3 | NS Intercity | Den Helder |
0ece0c64 | Arrival | 10:05:00 | Amsterdam Centraal | 8 | NS Intercity | Den Helder |
0ece0c64 | Departure | 10:11:00 | Amsterdam Centraal | 13a | NS Sprinter | Amersfoort Vathorst |
0ece0c64 | Arrival | 10:19:00 | Amsterdam Science Park | 1 | NS Sprinter | Amersfoort Vathorst |
We can’t pat ourselves on the back yet: our GET_PATH_ADVICE()
only works for
one path, while the possible_path
common table expression gave us five
different paths to explore. What we want is a single procedure that is almost as
easy to use as the official Dutch Railways travel planner. We’ll have to combine
the possible_path
query with GET_PATH_ADVICE()
into one gargantuan procedure
that does everything.
The resulting procedure is listed below. I’ve added a few explanatory comments here and there. Needless to say, it’s long, so feel free to click here to skip to the end.
We can call the PLAN_JOURNEY
procedure as follows:
This gives us the following results:
journey | type | time | station | platform | train_type | headsign | duration |
---|---|---|---|---|---|---|---|
8eee2c55 | Departure | 08:03:00 | Nijmegen Heyendaal | 2 | Arriva Stoptrein | Nijmegen | 01:46 |
8eee2c55 | Arrival | 08:07:00 | Nijmegen | 35 | Arriva Stoptrein | Nijmegen | 01:46 |
8eee2c55 | Departure | 08:13:00 | Nijmegen | 3 | NS Intercity | Den Helder | 01:46 |
8eee2c55 | Arrival | 09:35:00 | Amsterdam Centraal | 8 | NS Intercity | Den Helder | 01:46 |
8eee2c55 | Departure | 09:41:00 | Amsterdam Centraal | 13a | NS Sprinter | Amersfoort Vathorst | 01:46 |
8eee2c55 | Arrival | 09:49:00 | Amsterdam Science Park | 1 | NS Sprinter | Amersfoort Vathorst | 01:46 |
598f1521 | Departure | 08:03:00 | Nijmegen Heyendaal | 2 | Arriva Stoptrein | Nijmegen | 01:46 |
598f1521 | Arrival | 08:07:00 | Nijmegen | 35 | Arriva Stoptrein | Nijmegen | 01:46 |
598f1521 | Departure | 08:13:00 | Nijmegen | 3 | NS Intercity | Den Helder | 01:46 |
598f1521 | Arrival | 09:27:00 | Amsterdam Amstel | 1 | NS Intercity | Den Helder | 01:46 |
598f1521 | Departure | 09:32:00 | Amsterdam Amstel | 1 | NS Sprinter | Uitgeest | 01:46 |
598f1521 | Arrival | 09:35:00 | Amsterdam Muiderpoort | 8 | NS Sprinter | Uitgeest | 01:46 |
598f1521 | Departure | 09:46:00 | Amsterdam Muiderpoort | 2 | NS Sprinter | Amersfoort Vathorst | 01:46 |
598f1521 | Arrival | 09:49:00 | Amsterdam Science Park | 1 | NS Sprinter | Amersfoort Vathorst | 01:46 |
ab329c3d | Departure | 08:03:00 | Nijmegen Heyendaal | 2 | Arriva Stoptrein | Nijmegen | 01:58 |
ab329c3d | Arrival | 08:07:00 | Nijmegen | 35 | Arriva Stoptrein | Nijmegen | 01:58 |
ab329c3d | Departure | 08:13:00 | Nijmegen | 3 | NS Intercity | Den Helder | 01:58 |
ab329c3d | Arrival | 09:35:00 | Amsterdam Centraal | 8 | NS Intercity | Den Helder | 01:58 |
ab329c3d | Departure | 09:53:00 | Amsterdam Centraal | 10a | NS Sprinter | Zwolle | 01:58 |
ab329c3d | Arrival | 10:01:00 | Amsterdam Science Park | 1 | NS Sprinter | Zwolle | 01:58 |
1fc35e08 | Departure | 08:03:00 | Nijmegen Heyendaal | 2 | Arriva Stoptrein | Nijmegen | 01:58 |
1fc35e08 | Arrival | 08:07:00 | Nijmegen | 35 | Arriva Stoptrein | Nijmegen | 01:58 |
1fc35e08 | Departure | 08:13:00 | Nijmegen | 3 | NS Intercity | Den Helder | 01:58 |
1fc35e08 | Arrival | 09:35:00 | Amsterdam Centraal | 8 | NS Intercity | Den Helder | 01:58 |
1fc35e08 | Departure | 09:39:00 | Amsterdam Centraal | 5b | NS Sprinter | Utrecht Centraal | 01:58 |
1fc35e08 | Arrival | 09:44:00 | Amsterdam Muiderpoort | 9 | NS Sprinter | Utrecht Centraal | 01:58 |
1fc35e08 | Departure | 09:58:00 | Amsterdam Muiderpoort | 2 | NS Sprinter | Zwolle | 01:58 |
1fc35e08 | Arrival | 10:01:00 | Amsterdam Science Park | 1 | NS Sprinter | Zwolle | 01:58 |
45e9a348 | Departure | 08:03:00 | Nijmegen Heyendaal | 2 | Arriva Stoptrein | Nijmegen | 01:58 |
45e9a348 | Arrival | 08:07:00 | Nijmegen | 35 | Arriva Stoptrein | Nijmegen | 01:58 |
45e9a348 | Departure | 08:13:00 | Nijmegen | 3 | NS Intercity | Den Helder | 01:58 |
45e9a348 | Arrival | 09:35:00 | Amsterdam Centraal | 8 | NS Intercity | Den Helder | 01:58 |
45e9a348 | Departure | 09:49:00 | Amsterdam Centraal | 5 | NS Sprinter | Rotterdam Centraal | 01:58 |
45e9a348 | Arrival | 09:54:00 | Amsterdam Muiderpoort | 9 | NS Sprinter | Rotterdam Centraal | 01:58 |
45e9a348 | Departure | 09:58:00 | Amsterdam Muiderpoort | 2 | NS Sprinter | Zwolle | 01:58 |
45e9a348 | Arrival | 10:01:00 | Amsterdam Science Park | 1 | NS Sprinter | Zwolle | 01:58 |
c0369efc | Departure | 08:03:00 | Nijmegen Heyendaal | 2 | Arriva Stoptrein | Nijmegen | 01:58 |
c0369efc | Arrival | 08:07:00 | Nijmegen | 35 | Arriva Stoptrein | Nijmegen | 01:58 |
c0369efc | Departure | 08:13:00 | Nijmegen | 3 | NS Intercity | Den Helder | 01:58 |
c0369efc | Arrival | 09:27:00 | Amsterdam Amstel | 1 | NS Intercity | Den Helder | 01:58 |
c0369efc | Departure | 09:32:00 | Amsterdam Amstel | 1 | NS Sprinter | Uitgeest | 01:58 |
c0369efc | Arrival | 09:35:00 | Amsterdam Muiderpoort | 8 | NS Sprinter | Uitgeest | 01:58 |
c0369efc | Departure | 09:58:00 | Amsterdam Muiderpoort | 2 | NS Sprinter | Zwolle | 01:58 |
c0369efc | Arrival | 10:01:00 | Amsterdam Science Park | 1 | NS Sprinter | Zwolle | 01:58 |
f5ed9dac | Departure | 08:03:00 | Nijmegen Heyendaal | 2 | Arriva Stoptrein | Nijmegen | 01:58 |
f5ed9dac | Arrival | 08:07:00 | Nijmegen | 35 | Arriva Stoptrein | Nijmegen | 01:58 |
f5ed9dac | Departure | 08:13:00 | Nijmegen | 3 | NS Intercity | Den Helder | 01:58 |
f5ed9dac | Arrival | 09:35:00 | Amsterdam Centraal | 8 | NS Intercity | Den Helder | 01:58 |
f5ed9dac | Departure | 09:41:00 | Amsterdam Centraal | 13a | NS Sprinter | Amersfoort Vathorst | 01:58 |
f5ed9dac | Arrival | 09:46:00 | Amsterdam Muiderpoort | 2 | NS Sprinter | Amersfoort Vathorst | 01:58 |
f5ed9dac | Departure | 09:58:00 | Amsterdam Muiderpoort | 2 | NS Sprinter | Zwolle | 01:58 |
f5ed9dac | Arrival | 10:01:00 | Amsterdam Science Park | 1 | NS Sprinter | Zwolle | 01:58 |
0ece0c64 | Departure | 08:18:00 | Nijmegen Heyendaal | 2 | Arriva Stoptrein | Nijmegen | 02:01 |
0ece0c64 | Arrival | 08:22:00 | Nijmegen | 1b | Arriva Stoptrein | Nijmegen | 02:01 |
0ece0c64 | Departure | 08:43:00 | Nijmegen | 3 | NS Intercity | Den Helder | 02:01 |
0ece0c64 | Arrival | 10:05:00 | Amsterdam Centraal | 8 | NS Intercity | Den Helder | 02:01 |
0ece0c64 | Departure | 10:11:00 | Amsterdam Centraal | 13a | NS Sprinter | Amersfoort Vathorst | 02:01 |
0ece0c64 | Arrival | 10:19:00 | Amsterdam Science Park | 1 | NS Sprinter | Amersfoort Vathorst | 02:01 |
33b7969d | Departure | 08:03:00 | Nijmegen Heyendaal | 2 | Arriva Stoptrein | Nijmegen | 02:16 |
33b7969d | Arrival | 08:07:00 | Nijmegen | 35 | Arriva Stoptrein | Nijmegen | 02:16 |
33b7969d | Departure | 08:13:00 | Nijmegen | 3 | NS Intercity | Den Helder | 02:16 |
33b7969d | Arrival | 09:45:00 | Amsterdam Sloterdijk | 3 | NS Intercity | Den Helder | 02:16 |
33b7969d | Departure | 09:53:00 | Amsterdam Sloterdijk | 12 | NS Sprinter | Amersfoort Vathorst | 02:16 |
33b7969d | Arrival | 10:19:00 | Amsterdam Science Park | 1 | NS Sprinter | Amersfoort Vathorst | 02:16 |
ad5ea63c | Departure | 08:18:00 | Nijmegen Heyendaal | 2 | Arriva Stoptrein | Nijmegen | 02:01 |
ad5ea63c | Arrival | 08:22:00 | Nijmegen | 1b | Arriva Stoptrein | Nijmegen | 02:01 |
ad5ea63c | Departure | 08:43:00 | Nijmegen | 3 | NS Intercity | Den Helder | 02:01 |
ad5ea63c | Arrival | 09:57:00 | Amsterdam Amstel | 1 | NS Intercity | Den Helder | 02:01 |
ad5ea63c | Departure | 10:02:00 | Amsterdam Amstel | 1 | NS Sprinter | Uitgeest | 02:01 |
ad5ea63c | Arrival | 10:05:00 | Amsterdam Muiderpoort | 8 | NS Sprinter | Uitgeest | 02:01 |
ad5ea63c | Departure | 10:16:00 | Amsterdam Muiderpoort | 2 | NS Sprinter | Amersfoort Vathorst | 02:01 |
ad5ea63c | Arrival | 10:19:00 | Amsterdam Science Park | 1 | NS Sprinter | Amersfoort Vathorst | 02:01 |
267d6590 | Departure | 08:03:00 | Nijmegen Heyendaal | 2 | Arriva Stoptrein | Nijmegen | 02:16 |
267d6590 | Arrival | 08:07:00 | Nijmegen | 35 | Arriva Stoptrein | Nijmegen | 02:16 |
267d6590 | Departure | 08:13:00 | Nijmegen | 3 | NS Intercity | Den Helder | 02:16 |
267d6590 | Arrival | 09:35:00 | Amsterdam Centraal | 8 | NS Intercity | Den Helder | 02:16 |
267d6590 | Departure | 09:41:00 | Amsterdam Centraal | 13a | NS Sprinter | Amersfoort Vathorst | 02:16 |
267d6590 | Arrival | 09:46:00 | Amsterdam Muiderpoort | 2 | NS Sprinter | Amersfoort Vathorst | 02:16 |
267d6590 | Departure | 10:16:00 | Amsterdam Muiderpoort | 2 | NS Sprinter | Amersfoort Vathorst | 02:16 |
267d6590 | Arrival | 10:19:00 | Amsterdam Science Park | 1 | NS Sprinter | Amersfoort Vathorst | 02:16 |
7354f15a | Departure | 08:03:00 | Nijmegen Heyendaal | 2 | Arriva Stoptrein | Nijmegen | 02:16 |
7354f15a | Arrival | 08:07:00 | Nijmegen | 35 | Arriva Stoptrein | Nijmegen | 02:16 |
7354f15a | Departure | 08:13:00 | Nijmegen | 3 | NS Intercity | Den Helder | 02:16 |
7354f15a | Arrival | 09:35:00 | Amsterdam Centraal | 8 | NS Intercity | Den Helder | 02:16 |
7354f15a | Departure | 09:53:00 | Amsterdam Centraal | 10a | NS Sprinter | Zwolle | 02:16 |
7354f15a | Arrival | 09:58:00 | Amsterdam Muiderpoort | 2 | NS Sprinter | Zwolle | 02:16 |
7354f15a | Departure | 10:16:00 | Amsterdam Muiderpoort | 2 | NS Sprinter | Amersfoort Vathorst | 02:16 |
7354f15a | Arrival | 10:19:00 | Amsterdam Science Park | 1 | NS Sprinter | Amersfoort Vathorst | 02:16 |
b89059e7 | Departure | 08:03:00 | Nijmegen Heyendaal | 2 | Arriva Stoptrein | Nijmegen | 02:16 |
b89059e7 | Arrival | 08:07:00 | Nijmegen | 35 | Arriva Stoptrein | Nijmegen | 02:16 |
b89059e7 | Departure | 08:13:00 | Nijmegen | 3 | NS Intercity | Den Helder | 02:16 |
b89059e7 | Arrival | 09:35:00 | Amsterdam Centraal | 8 | NS Intercity | Den Helder | 02:16 |
b89059e7 | Departure | 09:49:00 | Amsterdam Centraal | 5 | NS Sprinter | Rotterdam Centraal | 02:16 |
b89059e7 | Arrival | 09:54:00 | Amsterdam Muiderpoort | 9 | NS Sprinter | Rotterdam Centraal | 02:16 |
b89059e7 | Departure | 10:16:00 | Amsterdam Muiderpoort | 2 | NS Sprinter | Amersfoort Vathorst | 02:16 |
b89059e7 | Arrival | 10:19:00 | Amsterdam Science Park | 1 | NS Sprinter | Amersfoort Vathorst | 02:16 |
That’s it, we’re almost done! There’s just one more thing we need to do, but I’ll leave that one for the next post…