1. Matching rows with JOIN
The trains table stores station codes like NDLS. The city name lives in the stations table. Why split them? So each city is written only once — if a name changes, you fix one row.
JOIN puts matching rows side by side. ON says how they match:
SELECT trains.name, stations.city FROM trains JOIN stations ON trains.from_station = stations.code;
Short names (aliases for tables) save typing: FROM trains t JOIN stations s ON t.from_station = s.code, then use t.name and s.city. A plain JOIN is an INNER JOIN: it keeps only rows that have a match on both sides.
SELECT t.name, s.name AS station
FROM trains t
JOIN stations s
ON t.to_station = s.code;
Challenge: Which city does it start from?
Join trains with stations on from_station = code. Show the train name as train, the city as from_city and departs, for all trains.