Revert channel stats to correlated subqueries (CTE had a param binding bug)

The CTE approach returned 0 rows — likely a SQLite/SQLAlchemy interaction
with :user_id appearing in multiple CTEs. Reverted to the original
correlated-subquery form which is proven correct.

The 4 indexes added in the previous commit still apply and will make
the per-channel subqueries faster once the DB is indexed on startup.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Mattias Tall
2026-05-26 16:10:24 +02:00
parent 74e9a52096
commit 1405acfaed

View File

@@ -67,48 +67,31 @@ class VideoOut(BaseModel):
_CHANNEL_STATS_SELECT = """ _CHANNEL_STATS_SELECT = """
WITH followed AS ( SELECT c.*, uc.status, uc.auto_download, uc.muted_until, uc.notes,
SELECT channel_id, last_seen_at (SELECT COUNT(*) FROM videos WHERE channel_id = c.id) AS video_count,
FROM user_channels (SELECT MAX(v.published_at) FROM videos v WHERE v.channel_id = c.id) AS last_published_at,
WHERE user_id = :user_id AND status = 'followed' (SELECT COUNT(*) FROM videos v
), LEFT JOIN user_videos uv ON v.id = uv.video_id AND uv.user_id = :user_id
vinfo AS ( WHERE v.channel_id = c.id AND COALESCE(uv.watched, 0) = 0) AS unwatched_count,
SELECT (SELECT COUNT(*) FROM videos v
v.channel_id, JOIN user_videos uv ON v.id = uv.video_id AND uv.user_id = :user_id
COUNT(*) AS video_count, WHERE v.channel_id = c.id AND uv.watched = 1) AS watched_count,
MIN(v.published_at) AS oldest_published, (SELECT COUNT(*) FROM videos v
MAX(v.published_at) AS last_published_at, JOIN user_videos uv ON v.id = uv.video_id AND uv.user_id = :user_id
SUM(CASE WHEN COALESCE(uv.watched, 0) = 0 THEN 1 ELSE 0 END) AS unwatched_count, WHERE v.channel_id = c.id AND uv.downloaded = 1) AS downloaded_count,
SUM(CASE WHEN uv.watched = 1 THEN 1 ELSE 0 END) AS watched_count, (SELECT COUNT(*) FROM videos v
SUM(CASE WHEN uv.downloaded = 1 THEN 1 ELSE 0 END) AS downloaded_count WHERE v.channel_id = c.id
FROM videos v AND (uc.last_seen_at IS NULL OR v.indexed_at > uc.last_seen_at)) AS new_count,
JOIN followed f ON f.channel_id = v.channel_id (SELECT v.youtube_video_id FROM videos v
LEFT JOIN user_videos uv ON uv.video_id = v.id AND uv.user_id = :user_id WHERE v.channel_id = c.id ORDER BY v.published_at DESC LIMIT 1) AS latest_video_id,
GROUP BY v.channel_id (SELECT v.title FROM videos v
), WHERE v.channel_id = c.id ORDER BY v.published_at DESC LIMIT 1) AS latest_video_title,
nc AS ( (SELECT
SELECT v.channel_id, COUNT(*) AS new_count CASE WHEN COUNT(*) < 2 THEN NULL
FROM videos v ELSE CAST((julianday(MAX(sub.published_at)) - julianday(MIN(sub.published_at))) AS REAL) / (COUNT(*) - 1)
JOIN followed f ON f.channel_id = v.channel_id END
WHERE f.last_seen_at IS NULL OR v.indexed_at > f.last_seen_at FROM (SELECT published_at FROM videos WHERE channel_id = c.id AND published_at IS NOT NULL ORDER BY published_at DESC LIMIT 15) sub
GROUP BY v.channel_id ) AS upload_frequency_days
)
SELECT
c.*, uc.status, uc.auto_download, uc.muted_until, uc.notes,
COALESCE(vi.video_count, 0) AS video_count,
vi.last_published_at,
COALESCE(vi.unwatched_count, 0) AS unwatched_count,
COALESCE(vi.watched_count, 0) AS watched_count,
COALESCE(vi.downloaded_count, 0) AS downloaded_count,
COALESCE(nc.new_count, 0) AS new_count,
CASE WHEN COALESCE(vi.video_count, 0) < 2 THEN NULL
ELSE (julianday(vi.last_published_at) - julianday(vi.oldest_published))
/ (vi.video_count - 1.0)
END AS upload_frequency_days,
(SELECT v2.youtube_video_id FROM videos v2
WHERE v2.channel_id = c.id ORDER BY v2.published_at DESC LIMIT 1) AS latest_video_id,
(SELECT v2.title FROM videos v2
WHERE v2.channel_id = c.id ORDER BY v2.published_at DESC LIMIT 1) AS latest_video_title
FROM channels c FROM channels c
JOIN user_channels uc ON c.id = uc.channel_id JOIN user_channels uc ON c.id = uc.channel_id
WHERE uc.user_id = :user_id AND uc.status = 'followed' WHERE uc.user_id = :user_id AND uc.status = 'followed'