Sample data
Two CSV tables, 1,198 subscriptions and 40,000 events, published so the SQL in the articles here has something real to run
against. They are the tables the queries name: subscriptions and
events.
One command reads them straight off the web, with nothing to download and no database to install:
duckdb -c "SELECT * FROM 'https://dataacademy.ai/data/subscriptions.csv' LIMIT 5" To keep them around for a session, load both into tables first. Every query in the articles assumes these two names:
CREATE TABLE subscriptions AS
SELECT * FROM 'https://dataacademy.ai/data/subscriptions.csv';
CREATE TABLE events AS
SELECT * FROM 'https://dataacademy.ai/data/events.csv'; subscriptions
One row per subscription — 1,198 rows, 1,085 customers, 566 still running. Signups run from 2024-08-07 to 2026-07-31.
| Column | Type | Meaning |
|---|---|---|
subscription_id | integer | Primary key. One row per subscription. |
customer_id | integer | The customer who holds it, and the join key to events. Not unique here: a customer who cancels and comes back gets a second row. |
plan | text | starter, standard, pro or enterprise. |
signed_up_at | date | The day the subscription started. |
cancelled_at | date | The day it ended. Empty for the 566 subscriptions still running at the snapshot. |
monthly_price | decimal | Fixed by plan: 12.00, 29.00, 79.00, 249.00. |
country | text | Two-letter code. Constant for a given customer. |
channel | text | How the subscription was won: organic, paid_search, referral, partner or outbound. |
events
One row per product event — 40,000 rows across 8,597 sessions, 2024-08-07 to 2026-07-31. 2,493 of them are purchases, and their amounts add up to 187,350.
| Column | Type | Meaning |
|---|---|---|
event_id | integer | Primary key, ordered by time. |
customer_id | integer | Always present in subscriptions. |
occurred_at | timestamp | Second resolution. Never before the customer signed up, never after they cancelled. |
event_type | text | login, view, export or purchase. |
session_id | integer | Groups the events of one sitting. Events inside a session are minutes apart; sessions are hours or days apart. |
amount | decimal | Set on purchase rows only, and always positive. Empty everywhere else. |
What holds true
The data is invented, but it is consistent, which is the part that
matters when a query is meant to teach something. Every
customer_id in events exists in
subscriptions. No event happens before its customer signed up
or after they cancelled. Only purchases carry an amount, and no amount is
negative. Each plan has one price.
It also behaves the way a subscription business does, which is what makes
the answers worth reading. Signups follow a season: quiet in July and
August, busy in September and January. Cancellations cluster in the first
two months and then slow down. Bigger plans stay longer. A customer uses
the product hardest in the weeks after signing up. A handful of customers
generate a large share of the events, and about one in ten cancels and
comes back later — which is why customer_id repeats in the
subscriptions table.
A seeded Python script builds both files, so the same script always produces the same rows. Use the data for anything, with no attribution and no licence to read: it describes no real person and no real company.
Articles that run on it
- Cohort retention in SQL, without the spreadsheet Cohorts from first login, a period spine, and the maturity filter.
- The four SQL window functions that do 90% of the work ROW_NUMBER, LAG, SUM OVER and moving averages over both tables.
- SQL or pandas? Push the heavy lifting to the database The reduction to one row per customer.
- Incremental tables that do not drift A watermark, a restated daily table, and a diff that proves it matches.
- How to sample data without lying to yourself Row sampling against entity sampling, on the same events.