← Back to profile

SQL project · PostgreSQL

Apple Music, in SQL

An exploratory deep-dive into a catalogue of Apple Music tracks. Eight questions answered entirely in PostgreSQL, then visualised. Every query is one click away under its chart.

PostgreSQL Python · matplotlib 8 research questions Data via Kaggle
8,741
tracks analysed
cleaned from ~10K
57
genres
1,694
artists
2,778
collections
14%
explicit
1939–2024
release span

How the data was prepared

The raw Kaggle set ships ~10,000 rows. I pared it to 8,741 clean records: kept the US / USD catalogue and songs only, dropped rows missing a genre, artist, date or duration, and de-duplicated on trackId. A handful of 1900-01-01 placeholder dates are filtered out of the time-series.

SQL techniques used

CTEsWindow functionsRANK() OVER generate_seriescorr()percentile_cont CASE bucketingCross joinsdate_trunc
01

How have genres evolved over time?

Every mainstream genre inflects upward through the 2000s. Pop and Country surge hardest in the 2010s, while Rock peaks far earlier, the 1970s–80s, then recedes. (The 18 placeholder 1900 dates are excluded.)

Line chart of tracks released per year for Pop, Country and Rock, 1950–2024
Tracks released per year · top 3 genres by volume
+ View SQL
SELECT "primaryGenreName" AS genre,
       EXTRACT(year FROM "releaseDate")::int AS yr,
       COUNT("trackId") AS tracks
FROM apple_music_dataset
WHERE EXTRACT(year FROM "releaseDate") > 1900   -- drop placeholder dates
GROUP BY 1, 2
ORDER BY 1, 2;
02

Do explicit tracks cost more in some genres?

Barely. Explicit prices sit in a tight $1.02–$1.29 band. These are Apple's fixed price tiers, not genre premiums. Hip-Hop/Rap (highlighted) lands mid-table at $1.26, despite its cultural weight.

Horizontal bar chart of average explicit-track price by genre; Hip-Hop/Rap highlighted
Avg. price per explicit track, by genre (≥ 5 explicit tracks)
+ View SQL
SELECT "primaryGenreName" AS genre,
       ROUND(SUM("trackPrice") / COUNT("trackId")::decimal, 2) AS avg_price,
       COUNT("trackId") AS n
FROM apple_music_dataset
WHERE "trackExplicitness" = 'explicit'
GROUP BY 1
HAVING COUNT("trackId") >= 5
ORDER BY avg_price DESC;
03

How has genre popularity shifted by era?

Pop's dominance is recent: 990 tracks in the 2010s alone. Rock ruled the 1970s (335); Hip-Hop/Rap and Alternative only scale up after 2000. Track counts read as a density heatmap.

Heatmap of track counts by genre and decade; darker green means more tracks
Tracks released, top 8 genres × decade (darker = more)
+ View SQL
SELECT "primaryGenreName" AS genre,
       (FLOOR(EXTRACT(year FROM "releaseDate") / 10) * 10)::int AS decade,
       COUNT("trackId") AS tracks
FROM apple_music_dataset
WHERE EXTRACT(year FROM "releaseDate") > 1900
GROUP BY 1, 2;
04

Is a track's price related to its length?

For most genres, price and duration are uncorrelated (~0). The exceptions are telling: Children's Music trends pricier-when-longer (+0.46), while Classical is the mirror image (−0.54): long classical pieces are the cheapest. Computed with Postgres's corr() aggregate.

Diverging bar chart of price-vs-duration correlation per genre, green positive and orange negative
Pearson correlation of price ↔ duration, per genre (≥ 25 tracks)
+ View SQL
SELECT "primaryGenreName" AS genre,
       corr("trackPrice", "trackTimeMillis") AS price_duration_corr,
       COUNT(*) AS n
FROM apple_music_dataset
WHERE "trackPrice" > 0 AND "trackTimeMillis" IS NOT NULL
GROUP BY 1
HAVING COUNT(*) >= 25
ORDER BY price_duration_corr;
05

Which collections span the most years?

The widest-spanning "collections" are all greatest-hits and soundtrack compilations: The Essential Johnny Cash (50 yrs), Elv1s: 30 #1 Hits (46), and film soundtracks, where a single collectionId bundles music recorded decades apart.

Horizontal bar chart of the 12 collections with the widest release span in years
Years between a collection's first & last track · top 12
+ View SQL
WITH collection_span AS (
    SELECT "collectionId",
           MAX("collectionName") AS name,
           MAX("releaseDate")::date - MIN("releaseDate")::date AS span_days
    FROM apple_music_dataset
    WHERE EXTRACT(year FROM "releaseDate") > 1900
    GROUP BY "collectionId"
    HAVING COUNT(*) >= 2
)
SELECT name, ROUND(span_days / 365.0, 1) AS span_years
FROM collection_span
ORDER BY span_days DESC
LIMIT 12;
06

Do singles and collection tracks price differently?

Hardly. A track sold alone averages $1.24; inside a collection, $1.18, a few cents apart. Most of the catalogue (7,039 tracks) lives inside ~1,076 multi-track collections, at ~6–7 tracks each.

CategoryTracksAvg priceCollectionsTracks / collection
Collection7,039$1.181,0766.5
Single1,702$1.241,7021.0
+ View SQL
WITH categorized AS (
    SELECT "collectionId",
           CASE WHEN COUNT("trackId") = 1 THEN 'Single'
                ELSE 'Collection' END AS kind
    FROM apple_music_dataset
    GROUP BY "collectionId"
)
SELECT c.kind,
       COUNT(*) AS tracks,
       ROUND(AVG("trackPrice"), 2) AS avg_price,
       COUNT(DISTINCT a."collectionId") AS collections
FROM apple_music_dataset a
JOIN categorized c ON a."collectionId" = c."collectionId"
GROUP BY c.kind
ORDER BY tracks DESC;
07

Does a bigger collection mean cheaper tracks?

No relationship at all. Whether a collection holds 1 track or 47, its average price is flat, with points banding along $1.29 / $0.99 / $0.69, the fixed tiers surfacing again.

Scatter plot of average track price against number of tracks in a collection, showing no trend
Each point = one collection · median price marked
+ View SQL
SELECT "collectionId",
       COUNT("trackId") AS tracks_in_collection,
       AVG("trackPrice") AS avg_price
FROM apple_music_dataset
WHERE "trackPrice" > 0
GROUP BY "collectionId";
08

Which tracks are duration outliers?

Among the five most-prolific artists, most tracks cluster at 3–4 minutes. The clearest outlier: Taylor Swift's "All Too Well (10 Minute Version)" at 10.2 min, caught by a per-artist 5th / 95th-percentile filter (percentile_cont).

Strip plot of track durations for the top 5 artists, with percentile outliers highlighted in orange
Track duration by artist · orange = beyond 5th/95th percentile
+ View SQL
WITH top_artists AS (
    SELECT "artistId", MAX("artistName") AS name, COUNT(*) AS n
    FROM apple_music_dataset GROUP BY "artistId"
    ORDER BY n DESC LIMIT 5
),
bounds AS (
    SELECT "artistId",
           percentile_cont(0.95) WITHIN GROUP (ORDER BY "trackTimeMillis") AS p95,
           percentile_cont(0.05) WITHIN GROUP (ORDER BY "trackTimeMillis") AS p05
    FROM apple_music_dataset GROUP BY "artistId"
)
SELECT t.name, a."trackName",
       ROUND(a."trackTimeMillis" / 60000.0, 1) AS minutes
FROM apple_music_dataset a
JOIN top_artists t ON t."artistId" = a."artistId"
JOIN bounds b ON b."artistId" = a."artistId"
WHERE a."trackTimeMillis" > b.p95 OR a."trackTimeMillis" < b.p05;

Takeaways

Two themes run through the catalogue. Pricing is structural, not editorial: tracks snap to Apple's $0.69 / $0.99 / $1.29 tiers regardless of genre, length, or how they're bundled. And the catalogue skews modern: release volume explodes after 2000, led by Pop, even as its "collections" quietly bundle decades of back-catalogue under single albums.

The whole analysis runs on PostgreSQL (CTEs, window functions, corr(), and percentile_cont), with Python only for the charts. The Python and notebook are in the repository.

View the repository on GitHub ↗