The Curriculum

What you'll learn.
Why it gets you hired.

Six topic areas. Every one tied to a real job outcome.

LearnQL covers 23 SQL skills — from SELECT basics and filtering to JOINs, window functions, CTEs, and advanced analytical patterns. Each skill has learn exercises, a no-hints test, and AI feedback that explains exactly what went wrong. Built for data analysts, engineers, and anyone preparing for a SQL interview.

Start free →

Foundations

The core of every SQL query — selecting columns, filtering rows, sorting results, and handling missing data.

SELECT Basics, Filtering, Sorting & Limits, NULL Handling

Read the reference →

What you'll be able to answer

  • Which customers signed up in the last 30 days?
  • Show me all orders over $100, sorted by date
  • Which products have no category assigned?
Filter and sort
SELECT name, email, created_at
FROM customers
WHERE created_at >= NOW() - INTERVAL '30 days'
ORDER BY created_at DESC;
Find missing values
SELECT name, category
FROM products
WHERE category IS NULL;

Aggregation

Summarizing data — counts, totals, averages — grouped by categories.

Aggregations, GROUP BY

Read the reference →

What you'll be able to answer

  • How many orders did each customer place last month?
  • What's the total revenue per product category?
  • Which artists have more than 10 tracks in the catalog?
Revenue by category
SELECT category, SUM(price) AS total_revenue
FROM orders
GROUP BY category
ORDER BY total_revenue DESC;
Filter groups with HAVING
SELECT artist_id, COUNT(*) AS track_count
FROM tracks
GROUP BY artist_id
HAVING COUNT(*) > 10;

Functions

Transforming raw data — cleaning text, working with dates, and categorizing values into meaningful labels.

String Functions, Pattern Matching, Date & Time, Date Arithmetic, CASE WHEN

Read the reference →

What you'll be able to answer

  • How many users signed up each month this year?
  • Which customers have a gmail.com email address?
  • How long on average between a customer's first and second order?
Signups by month
SELECT DATE_TRUNC('month', created_at) AS month,
       COUNT(*) AS signups
FROM users
GROUP BY 1
ORDER BY 1;
Categorize with CASE WHEN
SELECT order_id,
       CASE
         WHEN total > 500 THEN 'High'
         WHEN total > 100 THEN 'Mid'
         ELSE 'Low'
       END AS value_tier
FROM orders;

Joins

Combining data from multiple tables into a single result.

JOINs Core, JOINs Advanced, Multi-Table JOINs

Read the reference →

What you'll be able to answer

  • Which customers placed an order but never left a review?
  • Show me each track with its album name and artist
  • Which products have never been ordered?
LEFT JOIN to find gaps
SELECT c.name
FROM customers c
LEFT JOIN reviews r ON r.customer_id = c.id
WHERE r.id IS NULL;
Multi-table join
SELECT t.title, al.title AS album, ar.name AS artist
FROM tracks t
JOIN albums al ON al.id = t.album_id
JOIN artists ar ON ar.id = al.artist_id;

Advanced Queries

Writing queries that reference other queries, and performing calculations across rows without collapsing them.

Subqueries, Correlated Subqueries, Window Functions, Advanced Windows

Read the reference →

What you'll be able to answer

  • Which customers spent more than the average customer?
  • Rank each artist by total streams within their genre
  • What's the running total of revenue by month?
Subquery filter
SELECT name, total_spent
FROM customers
WHERE total_spent > (SELECT AVG(total_spent) FROM customers);
Window function rank
SELECT artist, genre, streams,
       RANK() OVER (PARTITION BY genre ORDER BY streams DESC) AS rank
FROM artist_stats;

Expert

Writing complex, maintainable queries — breaking logic into reusable steps, combining result sets, and solving advanced analytical problems.

CTEs, Recursive CTEs, Set Operations, Analytical Patterns, Advanced Patterns

Read the reference →

What you'll be able to answer

  • Find all customers who ordered in Q1 but not Q2
  • Build a full customer journey from first touch to purchase
  • Which tracks appear in both the top 10 by streams and revenue?
CTE for readable logic
WITH q1_customers AS (
  SELECT DISTINCT customer_id FROM orders
  WHERE order_date BETWEEN '2024-01-01' AND '2024-03-31'
),
q2_customers AS (
  SELECT DISTINCT customer_id FROM orders
  WHERE order_date BETWEEN '2024-04-01' AND '2024-06-30'
)
SELECT * FROM q1_customers
EXCEPT
SELECT * FROM q2_customers;
Set operation
SELECT track_id FROM top10_by_streams
INTERSECT
SELECT track_id FROM top10_by_revenue;