Powered by AppSignal & Oban Pro

Day 5

2025/day5/day5.livemd

Day 5

Mix.install([
  {:ecto_sql, "~> 3.10"},
  {:postgrex, ">= 0.0.0"},
  {:kino, "~> 0.18.0"}
])

Application.put_env(:myapp, Repo,
  url: "ecto://postgres:postgres@localhost/advent_of_sql_2025"
)

defmodule Repo do
  use Ecto.Repo, adapter: Ecto.Adapters.Postgres, otp_app: :myapp
end

{:ok, _pid} = Repo.start_link()

Setup

import Ecto.Query

defmodule ListeningLog do
  use Ecto.Schema

  schema "listening_logs" do
    field :user_name, :string
    field :artist, :string
    field :played_at, :naive_datetime
    field :content_type, :string
  end
end

Query

# from(l in ListeningLog, select: map(l, [:user_name, :artist, :played_at, :content_type]))
ranked =
  from(
    l in ListeningLog,
    group_by: [
      l.user_name,
      l.artist
    ],
    windows: [
      users: [
        partition_by: l.user_name,
        order_by: [desc: count(l)]
      ]
    ],
    select: %{
      user_name: l.user_name,
      artist: l.artist,
      count: count(l),
      rank: over(rank(), :users)
    },
    order_by: [l.user_name, desc: count(l)]
  )

from(
  r in subquery(ranked),
  where: r.rank <= 3,
  order_by: [r.user_name, desc: r.count],
  select: [
    user_name: r.user_name,
    artist: r.artist,
    count: r.count,
    rank: r.rank
  ]
)
|> Repo.all()
|> Kino.DataTable.new()