Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 96 additions & 0 deletions lab-sql-joins.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
-- ==================================================
-- SETTING UP THE DATABASE
-- ==================================================

USE sakila;

-- ==================================================
-- CHALLENGE: JOINING ON MULTIPLE TABLES
-- ==================================================

-- 1. Number of films per category.
SELECT c.name AS category, COUNT(fc.film_id) AS film_count
FROM category c
INNER JOIN film_category fc ON c.category_id = fc.category_id
GROUP BY c.name
ORDER BY film_count DESC;
-- Sports leads with 74, Foreign 73, Family 69.

-- 2. Store ID, city, and country for each store.
-- store -> address (where the store is) -> city -> country: 3 joins to get from a store_id
-- to a human-readable location, since none of that lives directly on `store`.
SELECT s.store_id, ci.city, co.country
FROM store s
INNER JOIN address a ON s.address_id = a.address_id
INNER JOIN city ci ON a.city_id = ci.city_id
INNER JOIN country co ON ci.country_id = co.country_id;
-- Store 1: Lethbridge, Canada. Store 2: Woodridge, Australia.

-- 3. Total revenue generated by each store, in dollars.
-- payment doesn't have store_id directly -- it has staff_id, and staff belongs to a store,
-- so payment -> staff -> store is the only path from a payment to "which store made it."
SELECT s.store_id, ROUND(SUM(p.amount), 2) AS total_revenue
FROM payment p
INNER JOIN staff st ON p.staff_id = st.staff_id
INNER JOIN store s ON st.store_id = s.store_id
GROUP BY s.store_id;
-- Store 1: $33,482.50. Store 2: $33,924.06 -- almost perfectly split, not one store dominating.

-- 4. Average running time of films for each category.
SELECT c.name AS category, ROUND(AVG(f.length), 2) AS avg_length
FROM category c
INNER JOIN film_category fc ON c.category_id = fc.category_id
INNER JOIN film f ON fc.film_id = f.film_id
GROUP BY c.name
ORDER BY avg_length DESC;

-- ==================================================
-- BONUS
-- ==================================================

-- 5. Film categories with the longest average running time -- same query as #4, just read
-- top-down since it's already ORDER BY avg_length DESC: Sports (128.20 min) is the longest,
-- Games (127.84) a close second.

-- 6. Top 10 most frequently rented movies, descending.
SELECT f.title, COUNT(r.rental_id) AS rental_count
FROM film f
INNER JOIN inventory i ON f.film_id = i.film_id
INNER JOIN rental r ON i.inventory_id = r.inventory_id
GROUP BY f.film_id, f.title
ORDER BY rental_count DESC
LIMIT 10;
-- Bucket Brotherhood tops it at 34 rentals.

-- 7. Can "Academy Dinosaur" be rented from Store 1?
-- First check: does Store 1 even have a copy in inventory?
SELECT f.title, i.store_id, COUNT(*) AS copies
FROM film f
INNER JOIN inventory i ON f.film_id = i.film_id
WHERE f.title = 'Academy Dinosaur' AND i.store_id = 1
GROUP BY f.title, i.store_id;
-- Yes -- Store 1 has 4 copies. Went one step further than "does a copy exist" though: having
-- a copy in inventory doesn't mean it's actually available right now if every copy happens to
-- be checked out. Checked whether any of those 4 copies is currently rented (an open rental
-- row with no return_date yet):
SELECT i.inventory_id,
CASE WHEN EXISTS (
SELECT 1 FROM rental r WHERE r.inventory_id = i.inventory_id AND r.return_date IS NULL
) THEN 'currently out' ELSE 'on the shelf' END AS status
FROM inventory i
INNER JOIN film f ON i.film_id = f.film_id
WHERE f.title = 'Academy Dinosaur' AND i.store_id = 1;
-- All 4 copies are "on the shelf" (no open rental) -- so yes, it can genuinely be rented from
-- Store 1 right now, not just theoretically stocked there.

-- 8. All distinct film titles with an 'Available' / 'NOT available' column.
-- LEFT JOIN (not INNER) is required here on purpose -- an inner join would silently drop every
-- film with zero inventory rows, which is exactly the information this query needs to show.
SELECT f.title,
CASE WHEN COUNT(i.inventory_id) = 0 THEN 'NOT available' ELSE 'Available' END AS availability
FROM film f
LEFT JOIN inventory i ON f.film_id = i.film_id
GROUP BY f.film_id, f.title
ORDER BY f.title;
-- Verified count: exactly 42 titles come back 'NOT available' -- matches the number the lab
-- instructions state, confirming the LEFT JOIN + CASE approach is catching all of them.