This is the organization that makes sense to me:
create table(:categories) do
add :title, :string
end
create table(:versions) do
add :title, :string
add :category_id, :id
end
create table(:records) do
add :title, :string
add :category_id, :id
add :version_id, :id
end
defmodule Squery do
import Ecto.Query;
alias Squery.{Repo}
def init do
Repo.insert_all(
"categories",[
[title: "Category 1"],
[title: "Category 2"],
[title: "Category 3"]
]
)
Repo.insert_all(
"versions",[
[title: "1.5", category_id: 1],
[title: "3.0", category_id: 1],
[title: "6.3", category_id: 1],
[title: "1.0", category_id: 2],
[title: "1.0", category_id: 3]
]
)
Repo.insert_all(
"records",[
[title: "A1.0", category_id: 1, version_id: 1],
[title: "A2.0", category_id: 1, version_id: 1],
[title: "A3.0", category_id: 1, version_id: 1],
[title: "A1.1", category_id: 1, version_id: 2],
[title: "A2.1", category_id: 1, version_id: 2],
[title: "A3.1", category_id: 1, version_id: 2],
[title: "A1.2", category_id: 1, version_id: 3],
[title: "A2.2", category_id: 1, version_id: 3],
[title: "A3.2", category_id: 1, version_id: 3],
[title: "A4.2", category_id: 1, version_id: 3],
[title: "B1.0", category_id: 2, version_id: 4],
[title: "B2.0", category_id: 2, version_id: 4],
[title: "C1.0", category_id: 3, version_id: 5]
]
)
:ok
end
def query1 do
"records"
|> group_by([:category_id, :version_id])
|> select([r], %{
category_id: r.category_id,
version_id: r.version_id,
record_count: count(r.id)
})
|> subquery()
|> join(:inner, [i], c in "categories", i.category_id == c.id)
|> join(:inner, [i], v in "versions", i.version_id == v.id)
|> select([i,c,v], %{
title: c.title,
version: v.title,
count: i.record_count
})
|> order_by([i,c,v],[asc: c.title, desc: v.id])
|> Repo.all()
end
def query2(category_id) do
with [%{id: max_version_id}|_] = versions <- versions(category_id),
counts <- counts(category_id, max_version_id),
records <- records(max_version_id) do
%{counts: counts, versions: versions, records: records}
else
_ ->
nil
end
end
defp versions(category_id) do
"versions"
|> where(category_id: ^category_id)
|> select([:title, :id])
|> order_by([desc: :id])
|> Repo.all()
end
defp counts(category_id, version_id) do
"records"
|> where([category_id: ^category_id])
|> group_by([:version_id])
|> select([r],%{
version_id: r.version_id,
record_count: count(r.id)
})
|> subquery()
|> select([i],%{
total: type(sum(i.record_count), :integer),
versions: count(i.version_id),
latest: type(
fragment("SUM(CASE WHEN ? = ? THEN ? ELSE 0 END)", i.version_id, ^version_id, i.record_count),
:integer
)
})
|> Repo.one()
end
defp records(version_id) do
"records"
|> where([version_id: ^version_id])
|> select([:id, :title, :version_id])
|> order_by(:id)
|> Repo.all()
end
end
iex(1)> Squery.init()
18:09:45.477 [debug] QUERY OK db=11.9ms
INSERT INTO "categories" ("title") VALUES ($1),($2),($3) ["Category 1", "Category 2", "Category 3"]
18:09:45.480 [debug] QUERY OK db=2.1ms
INSERT INTO "versions" ("category_id","title") VALUES ($1,$2),($3,$4),($5,$6),($7,$8),($9,$10) [1, "1.5", 1, "3.0", 1, "6.3", 2, "1.0", 3, "1.0"]
18:09:45.483 [debug] QUERY OK db=2.2ms
INSERT INTO "records" ("category_id","title","version_id") VALUES ($1,$2,$3),($4,$5,$6),($7,$8,$9),($10,$11,$12),($13,$14,$15),($16,$17,$18),($19,$20,$21),($22,$23,$24),($25,$26,$27),($28,$29,$30),($31,$32,$33),($34,$35,$36),($37,$38,$39) [1, "A1.0", 1, 1, "A2.0", 1, 1, "A3.0", 1, 1, "A1.1", 2, 1, "A2.1", 2, 1, "A3.1", 2, 1, "A1.2", 3, 1, "A2.2", 3, 1, "A3.2", 3, 1, "A4.2", 3, 2, "B1.0", 4, 2, "B2.0", 4, 3, "C1.0", 5]
:ok
iex(2)> Squery.query1()
18:09:55.634 [debug] QUERY OK db=3.4ms
SELECT c1."title", v2."title", s0."record_count" FROM (SELECT r0."category_id" AS "category_id", r0."version_id" AS "version_id", count(r0."id") AS "record_count" FROM "records" AS r0 GROUP BY r0."category_id", r0."version_id") AS s0 INNER JOIN "categories" AS c1 ON s0."category_id" = c1."id" INNER JOIN "versions" AS v2 ON s0."version_id" = v2."id" ORDER BY c1."title", v2."id" DESC []
[
%{count: 4, title: "Category 1", version: "6.3"},
%{count: 3, title: "Category 1", version: "3.0"},
%{count: 3, title: "Category 1", version: "1.5"},
%{count: 2, title: "Category 2", version: "1.0"},
%{count: 1, title: "Category 3", version: "1.0"}
]
iex(3)> Squery.query2(1)
18:10:10.934 [debug] QUERY OK source="versions" db=2.3ms decode=2.5ms
SELECT v0."title", v0."id" FROM "versions" AS v0 WHERE (v0."category_id" = $1) ORDER BY v0."id" DESC [1]
18:10:10.937 [debug] QUERY OK db=3.0ms
SELECT sum(s0."record_count")::bigint, count(s0."version_id"), SUM(CASE WHEN s0."version_id" = $1 THEN s0."record_count" ELSE 0 END)::bigint FROM (SELECT r0."version_id" AS "version_id", count(r0."id") AS "record_count" FROM "records" AS r0 WHERE (r0."category_id" = $2) GROUP BY r0."version_id") AS s0 [3, 1]
18:10:10.939 [debug] QUERY OK source="records" db=1.9ms
SELECT r0."id", r0."title", r0."version_id" FROM "records" AS r0 WHERE ((r0."category_id" = $1) AND (r0."version_id" = $2)) ORDER BY r0."id" [1, 3]
%{
counts: %{latest: 4, total: 10, versions: 3},
records: [
%{id: 7, title: "A1.2", version_id: 3},
%{id: 8, title: "A2.2", version_id: 3},
%{id: 9, title: "A3.2", version_id: 3},
%{id: 10, title: "A4.2", version_id: 3}
],
versions: [
%{id: 3, title: "6.3"},
%{id: 2, title: "3.0"},
%{id: 1, title: "1.5"}
]
}
iex(4)> Squery.query2(2)
18:10:14.148 [debug] QUERY OK source="versions" db=2.6ms
SELECT v0."title", v0."id" FROM "versions" AS v0 WHERE (v0."category_id" = $1) ORDER BY v0."id" DESC [2]
18:10:14.151 [debug] QUERY OK db=2.6ms
SELECT sum(s0."record_count")::bigint, count(s0."version_id"), SUM(CASE WHEN s0."version_id" = $1 THEN s0."record_count" ELSE 0 END)::bigint FROM (SELECT r0."version_id" AS "version_id", count(r0."id") AS "record_count" FROM "records" AS r0 WHERE (r0."category_id" = $2) GROUP BY r0."version_id") AS s0 [4, 2]
18:10:14.153 [debug] QUERY OK source="records" db=2.1ms
SELECT r0."id", r0."title", r0."version_id" FROM "records" AS r0 WHERE ((r0."category_id" = $1) AND (r0."version_id" = $2)) ORDER BY r0."id" [2, 4]
%{
counts: %{latest: 2, total: 2, versions: 1},
records: [
%{id: 11, title: "B1.0", version_id: 4},
%{id: 12, title: "B2.0", version_id: 4}
],
versions: [%{id: 4, title: "1.0"}]
}
iex(5)> Squery.query2(3)
18:10:18.088 [debug] QUERY OK source="versions" db=1.3ms
SELECT v0."title", v0."id" FROM "versions" AS v0 WHERE (v0."category_id" = $1) ORDER BY v0."id" DESC [3]
18:10:18.090 [debug] QUERY OK db=2.2ms
SELECT sum(s0."record_count")::bigint, count(s0."version_id"), SUM(CASE WHEN s0."version_id" = $1 THEN s0."record_count" ELSE 0 END)::bigint FROM (SELECT r0."version_id" AS "version_id", count(r0."id") AS "record_count" FROM "records" AS r0 WHERE (r0."category_id" = $2) GROUP BY r0."version_id") AS s0 [5, 3]
18:10:18.092 [debug] QUERY OK source="records" db=1.3ms
SELECT r0."id", r0."title", r0."version_id" FROM "records" AS r0 WHERE ((r0."category_id" = $1) AND (r0."version_id" = $2)) ORDER BY r0."id" [3, 5]
%{
counts: %{latest: 1, total: 1, versions: 1},
records: [%{id: 13, title: "C1.0", version_id: 5}],
versions: [%{id: 5, title: "1.0"}]
}
iex(6)>