Optimize equipment assignment breakdown N+1 query

Replaced application-level logic with single SQL query using CASE statement
to determine assignment source directly in the database.

Before: Loaded all equipment, called get_effective_agent_token_with_source
for each (500 equipment = 500+ queries)

After: Single query with CASE statement to classify assignment source

Performance improvement: Reduces from O(n) queries to 1 query regardless
of equipment count.
This commit is contained in:
Graham McIntire 2026-01-14 18:59:48 -06:00
parent ae9983c4f6
commit 07debd98a7
No known key found for this signature in database

View file

@ -91,25 +91,42 @@ defmodule Towerops.Agents.Stats do
}
"""
def get_equipment_assignment_breakdown(organization_id) do
# Get all equipment for organization
equipment_query =
from(e in Equipment,
join: s in assoc(e, :site),
where: s.organization_id == ^organization_id and e.snmp_enabled == true,
preload: [:site, site: :organization]
# Use a single SQL query with CASE to determine assignment source
# This avoids loading all equipment into memory and calling get_effective_agent_token_with_source
results =
Repo.all(
from(e in Equipment,
join: s in assoc(e, :site),
join: o in assoc(s, :organization),
left_join: aa in AgentAssignment,
on: aa.equipment_id == e.id and aa.enabled == true,
where: s.organization_id == ^organization_id and e.snmp_enabled == true,
select: %{
source:
fragment(
"""
CASE
WHEN ? IS NOT NULL THEN 'equipment'
WHEN ? IS NOT NULL THEN 'site'
WHEN ? IS NOT NULL THEN 'organization'
ELSE 'cloud'
END
""",
aa.agent_token_id,
s.agent_token_id,
o.default_agent_token_id
)
}
)
)
equipment_list = Repo.all(equipment_query)
# Count by assignment type
Enum.reduce(equipment_list, %{direct: 0, site: 0, organization: 0, cloud: 0}, fn eq, acc ->
{_agent_id, source} = Towerops.Agents.get_effective_agent_token_with_source(eq)
# Count by assignment source
Enum.reduce(results, %{direct: 0, site: 0, organization: 0, cloud: 0}, fn %{source: source}, acc ->
case source do
:equipment -> %{acc | direct: acc.direct + 1}
:site -> %{acc | site: acc.site + 1}
:organization -> %{acc | organization: acc.organization + 1}
:none -> %{acc | cloud: acc.cloud + 1}
"equipment" -> %{acc | direct: acc.direct + 1}
"site" -> %{acc | site: acc.site + 1}
"organization" -> %{acc | organization: acc.organization + 1}
"cloud" -> %{acc | cloud: acc.cloud + 1}
end
end)
end