Sandbox Schema & Relationships
A map of every table and column in the sandbox database β and how they connect. Read this before writing JOINs so you know exactly which column links to which table.
What am I looking at?
The sandbox is a PostgreSQL database with five pre-loaded datasets β e-commerce, finance, a chatbot log, maps, and health. Every lesson, the Playground, and the DBA tool all run queries against these exact tables. This page is your map: it shows every table, every column, and the relationships (foreign keys) that let you JOIN them together.
When you write a JOIN, you need to tell the database which column on table A matches which column on table B. Foreign keys are those columns. For example, orders.customer_id points to customers.id β so you join on that pair.
Legend
- π PKPrimary key β the unique identifier for each row in the table. Always
id(orsymbolin securities). - π FKForeign key β this column βjoins toβ another tableβs primary key. The badge shows the target (e.g.
customers.id). - π softText-match / soft link β a logical relationship (e.g.
products.categorymatchescategories.name) that is not an enforced database constraint. You can still join on it, but no referential integrity is guaranteed.
E-commerce
publicProducts, customers, orders, line items, categories.
products
idπ PKnamepricecategoryπ β categories.namein_stockcreated_at
customers
idπ PKnameemailcountrysigned_up
orders
idπ PKcustomer_idπ β customers.idplaced_atstatus
order_items
idπ PKorder_idπ β orders.idproduct_idπ β products.idquantityunit_price
categories
idπ PKname
How these tables join
orders.customer_idβcustomers.idorder_items.order_idβorders.idorder_items.product_idβproducts.idproducts.categoryβcategories.nametext match, not an enforced FK
Example JOIN
SELECT
c.name AS customer,
o.placed_at,
p.name AS product,
oi.quantity,
oi.unit_price
FROM orders o
JOIN customers c ON c.id = o.customer_id
JOIN order_items oi ON oi.order_id = o.id
JOIN products p ON p.id = oi.product_id
WHERE o.status = 'shipped'
ORDER BY o.placed_at DESC
LIMIT 20;Finance
financeCustomers, accounts, ~200k transactions, securities, holdings, trades.
finance.customers
idπ PKnameemailcountrykyc_statusrisk_scoreopened_at
finance.accounts
idπ PKcustomer_idπ β finance.customers.idaccount_typecurrencybalanceis_activeopened_at
finance.transactions
idπ PKaccount_idπ β finance.accounts.idamountkindcategorymerchant_nameoccurred_atstatus
finance.securities
symbolπ PKnamesectorexchangeipo_date
finance.holdings
account_idπ β finance.accounts.idsymbolπ β finance.securities.symbolquantityavg_cost
finance.trades
idπ PKaccount_idπ β finance.accounts.idsymbolπ β finance.securities.symbolsidequantitypriceexecuted_at
How these tables join
accounts.customer_idβcustomers.idtransactions.account_idβaccounts.idholdings.account_idβaccounts.idholdings.symbolβsecurities.symboltrades.account_idβaccounts.idtrades.symbolβsecurities.symbol
Example JOIN
SELECT
c.name AS customer,
a.account_type,
a.currency,
SUM(t.amount) AS total_spent
FROM finance.accounts a
JOIN finance.customers c ON c.id = a.customer_id
JOIN finance.transactions t ON t.account_id = a.id
WHERE t.kind = 'debit'
AND t.occurred_at >= now() - interval '90 days'
GROUP BY c.name, a.account_type, a.currency
ORDER BY total_spent DESC
LIMIT 10;Chatbot
chatbotUsers, conversations, ~200k messages, intents, feedback ratings.
chatbot.users
idπ PKemaildisplay_nameplansigned_up
chatbot.intents
idπ PKnamecategorydescription
chatbot.conversations
idπ PKuser_idπ β chatbot.users.idintent_idπ β chatbot.intents.idtitlemodelstarted_atended_at
chatbot.messages
idπ PKconversation_idπ β chatbot.conversations.idroletokens_intokens_outcontent_previewcreated_at
chatbot.feedback
idπ PKmessage_idπ β chatbot.messages.iduser_idπ β chatbot.users.idratingthumbs_upcommentcreated_at
How these tables join
conversations.user_idβusers.idconversations.intent_idβintents.idmessages.conversation_idβconversations.idfeedback.message_idβmessages.idfeedback.user_idβusers.id
Example JOIN
SELECT
u.display_name,
i.name AS intent,
COUNT(m.id) AS message_count,
AVG(f.rating) AS avg_rating
FROM chatbot.conversations cv
JOIN chatbot.users u ON u.id = cv.user_id
JOIN chatbot.intents i ON i.id = cv.intent_id
JOIN chatbot.messages m ON m.conversation_id = cv.id
LEFT JOIN chatbot.feedback f ON f.message_id = m.id
GROUP BY u.display_name, i.name
ORDER BY message_count DESC
LIMIT 10;Maps
mapsCities, 50k places (POIs), 200k reviews, users.
maps.cities
idπ PKnamecountrylatlngpopulation
maps.categories
idπ PKnameicon
maps.places
idπ PKnamecategoryπ β maps.categories.namecity_idπ β maps.cities.idlatlngratingreview_countprice_levelis_open_now
maps.users
idπ PKnamecountrysigned_upreviews
maps.reviews
idπ PKplace_idπ β maps.places.iduser_idπ β maps.users.idratingcommentvisited_atcreated_at
How these tables join
places.city_idβcities.idreviews.place_idβplaces.idreviews.user_idβusers.idplaces.categoryβcategories.nametext match, not an enforced FK
Example JOIN
SELECT
pl.name AS place,
ci.name AS city,
ROUND(AVG(r.rating)::numeric, 2) AS avg_rating,
COUNT(r.id) AS review_count
FROM maps.places pl
JOIN maps.cities ci ON ci.id = pl.city_id
JOIN maps.reviews r ON r.place_id = pl.id
GROUP BY pl.name, ci.name
HAVING COUNT(r.id) >= 5
ORDER BY avg_rating DESC
LIMIT 10;Health
healthHospitals, providers, patients, 100k visits, 60k prescriptions, 80k lab results. Synthetic only.
health.hospitals
idπ PKnamecitycountrybedstype
health.providers
idπ PKnamespecialtynpihospital_idπ β health.hospitals.idhired_at
health.patients
idπ PKnamedobsexblood_typecountryregistered_at
health.visits
idπ PKpatient_idπ β health.patients.idprovider_idπ β health.providers.idvisit_datevisit_typeprimary_complaintdiagnosis_codefollow_up
health.prescriptions
idπ PKvisit_idπ β health.visits.iddrug_namedosagefrequencydays_supplyprescribed_at
health.lab_results
idπ PKpatient_idπ β health.patients.idtest_nameresult_valueunitnormal_lownormal_highis_abnormaltaken_at
How these tables join
providers.hospital_idβhospitals.idvisits.patient_idβpatients.idvisits.provider_idβproviders.idprescriptions.visit_idβvisits.idlab_results.patient_idβpatients.id
Example JOIN
SELECT
p.name AS patient,
v.visit_date,
pr.name AS provider,
h.name AS hospital,
v.primary_complaint,
v.diagnosis_code
FROM health.visits v
JOIN health.patients p ON p.id = v.patient_id
JOIN health.providers pr ON pr.id = v.provider_id
JOIN health.hospitals h ON h.id = pr.hospital_id
WHERE v.visit_date >= now() - interval '30 days'
ORDER BY v.visit_date DESC
LIMIT 10;Ready to write your own JOINs?
Fire up the Playground and run a query against these tables, or start a lesson and practice with guided exercises.