Files
minstrel/internal/db/queries/discover.sql
T
bvandeusen 1f0f7eee1a fix(playlists): unblock Discover by removing SELECT DISTINCT + ORDER BY plan error
The cross-user bucket query combined SELECT DISTINCT with ORDER BY md5(...),
which Postgres rejects at plan time (SQLSTATE 42P10). buildDiscoverCandidates
returned that error on first bucket failure, so the whole Discover playlist
was skipped every nightly run — even though the random bucket pool was
healthy. Switched to GROUP BY so the md5 ordering expression no longer needs
to appear in the select list, and hardened the function so future single-
bucket failures degrade gracefully via slot redistribution instead of taking
out the whole playlist.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 09:50:01 -04:00

105 lines
3.7 KiB
SQL

-- Discover playlist bucket queries. Each returns up to its LIMIT
-- count of (track_id, album_id, artist_id) triples ordered
-- daily-deterministically via md5(track_id || dateStr). The Go-side
-- bucket allocator (internal/playlists/discover.go) applies
-- per-album (<=2) and per-artist (<=3) caps then redistributes any
-- bucket deficit equally across the others. LIMIT values are
-- generous so the caps + redistribution have headroom.
-- name: ListDormantArtistTracksForDiscover :many
-- Tracks whose artist this user has played fewer than 10 times total.
-- The artist threshold defines "dormant" — we want to surface the
-- long tail of artists in the user's library that they rarely listen
-- to. Excludes tracks the user has played, liked, or has quarantined.
-- $1 = user_id, $2 = date string for md5 ordering.
WITH user_artist_counts AS (
SELECT t.artist_id, COUNT(*) AS play_count
FROM play_events pe
JOIN tracks t ON t.id = pe.track_id
WHERE pe.user_id = $1
AND pe.was_skipped = false
GROUP BY t.artist_id
),
dormant_artists AS (
SELECT a.id
FROM artists a
LEFT JOIN user_artist_counts uac ON uac.artist_id = a.id
WHERE COALESCE(uac.play_count, 0) < 10
)
SELECT t.id, t.album_id, t.artist_id
FROM tracks t
JOIN dormant_artists da ON da.id = t.artist_id
WHERE NOT EXISTS (
SELECT 1 FROM play_events pe
WHERE pe.user_id = $1
AND pe.track_id = t.id
AND pe.was_skipped = false
)
AND NOT EXISTS (
SELECT 1 FROM general_likes gl
WHERE gl.user_id = $1 AND gl.track_id = t.id
)
AND NOT EXISTS (
SELECT 1 FROM lidarr_quarantine q
WHERE q.user_id = $1 AND q.track_id = t.id
)
ORDER BY md5(t.id::text || $2::text)
LIMIT 80;
-- name: ListCrossUserLikedTracksForDiscover :many
-- Tracks any OTHER user has liked, that this user hasn't played,
-- liked, or had quarantined. On single-user servers this returns 0
-- rows (the caller's slot redistribution rolls the deficit into the
-- other two buckets).
-- $1 = user_id, $2 = date string for md5 ordering.
--
-- GROUP BY (not SELECT DISTINCT) so the md5 ORDER BY expression need
-- not appear in the select list — Postgres rejects DISTINCT + ORDER BY
-- by-expression at plan time (SQLSTATE 42P10), which previously caused
-- the entire Discover build to fail silently.
SELECT t.id, t.album_id, t.artist_id
FROM general_likes gl
JOIN tracks t ON t.id = gl.track_id
WHERE gl.user_id != $1
AND NOT EXISTS (
SELECT 1 FROM play_events pe
WHERE pe.user_id = $1
AND pe.track_id = t.id
AND pe.was_skipped = false
)
AND NOT EXISTS (
SELECT 1 FROM general_likes ml
WHERE ml.user_id = $1 AND ml.track_id = t.id
)
AND NOT EXISTS (
SELECT 1 FROM lidarr_quarantine q
WHERE q.user_id = $1 AND q.track_id = t.id
)
GROUP BY t.id, t.album_id, t.artist_id
ORDER BY md5(t.id::text || $2::text)
LIMIT 60;
-- name: ListRandomUnheardTracksForDiscover :many
-- Random sample from the entire library. Same exclusion filters as
-- the other two buckets. The generous LIMIT (200) gives the caller
-- headroom for the per-album/artist caps and the slot redistribution.
-- $1 = user_id, $2 = date string for md5 ordering.
SELECT t.id, t.album_id, t.artist_id
FROM tracks t
WHERE NOT EXISTS (
SELECT 1 FROM play_events pe
WHERE pe.user_id = $1
AND pe.track_id = t.id
AND pe.was_skipped = false
)
AND NOT EXISTS (
SELECT 1 FROM general_likes gl
WHERE gl.user_id = $1 AND gl.track_id = t.id
)
AND NOT EXISTS (
SELECT 1 FROM lidarr_quarantine q
WHERE q.user_id = $1 AND q.track_id = t.id
)
ORDER BY md5(t.id::text || $2::text)
LIMIT 200;