mirror of
https://github.com/Hestia-Homes/assessment-model.git
synced 2026-06-30 12:55:02 +00:00
perf(portfolio): only join EPC graph / plan LATERAL in count when a filter needs it
getPropertiesCount returns the portfolio's total property count for pagination, but it dragged the whole read model through the COUNT: the property_details_epc + property_baseline_performance + two epc_property joins, plus a correlated default-plan LATERAL that ran once per property (31k+ plan lookups for a large portfolio). None of those joins change a COUNT (none multiply rows), so for an unfiltered load they were pure cost — pushing the query to ~14.7s, past Vercel's 15s limit (intermittent timeout on /api/properties). Join only what an active filter references: no filters -> plain COUNT over property; EPC/provenance filter -> add the epc-graph joins; Expected-EPC filter -> add the plan LATERAL. Unfiltered count 14,667ms -> 93ms (portfolio 796); provenance-filtered 156ms. getProperties is unchanged (its LIMIT 1000 already bounds the LATERALs). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
ea988cfe52
commit
7bb2b093e5
1 changed files with 45 additions and 7 deletions
|
|
@ -34,6 +34,7 @@ import {
|
|||
} from "@/lib/services/epcSources";
|
||||
import {
|
||||
FilterGroups,
|
||||
FilterField,
|
||||
PropertyFilter,
|
||||
PROPERTY_TYPE_OPTIONS,
|
||||
BUILT_FORM_OPTIONS,
|
||||
|
|
@ -681,25 +682,62 @@ function buildWhereClause(filterGroups: FilterGroups): ReturnType<typeof sql> {
|
|||
: sql``;
|
||||
}
|
||||
|
||||
// Filter fields whose SQL references the EPC graph (epc/bp/epl/epp) vs the
|
||||
// default-plan LATERAL. The count query only needs a join when an active filter
|
||||
// references it — otherwise joining is pure cost. The `pl` LATERAL in particular
|
||||
// runs once per property (31k+ correlated plan lookups for a large portfolio),
|
||||
// which alone pushed the unfiltered count past Vercel's 15s limit.
|
||||
const EPC_JOIN_FILTER_FIELDS = new Set<FilterField>([
|
||||
"currentEpc",
|
||||
"lodgedEpc",
|
||||
"provenance",
|
||||
"co2Emissions",
|
||||
"floorArea",
|
||||
"epcExpiryDate",
|
||||
"mainfuel",
|
||||
]);
|
||||
const PLAN_JOIN_FILTER_FIELDS = new Set<FilterField>(["expectedEpc"]);
|
||||
|
||||
function filterFieldsInUse(filterGroups: FilterGroups): Set<FilterField> {
|
||||
const fields = new Set<FilterField>();
|
||||
for (const group of filterGroups) {
|
||||
for (const cond of group.conditions) fields.add(cond.field);
|
||||
}
|
||||
return fields;
|
||||
}
|
||||
|
||||
export async function getPropertiesCount(
|
||||
portfolioId: string,
|
||||
filterGroups: FilterGroups = []
|
||||
): Promise<number> {
|
||||
const combinedWhere = buildWhereClause(filterGroups);
|
||||
const fields = filterFieldsInUse(filterGroups);
|
||||
|
||||
const result = await db.execute<{ count: string }>(sql`
|
||||
SELECT COUNT(DISTINCT p.id)::int AS count
|
||||
FROM property p
|
||||
LEFT JOIN property_details_epc epc ON epc.property_id = p.id
|
||||
${newApproachJoins}
|
||||
LEFT JOIN LATERAL (
|
||||
// Only join what an active filter needs. COUNT is unaffected by the LEFT JOINs
|
||||
// otherwise (none multiply rows), so omitting them is purely a speed-up.
|
||||
const needsEpcJoins = [...fields].some((f) => EPC_JOIN_FILTER_FIELDS.has(f));
|
||||
const needsPlanJoin = [...fields].some((f) => PLAN_JOIN_FILTER_FIELDS.has(f));
|
||||
|
||||
const epcJoins = needsEpcJoins
|
||||
? sql`LEFT JOIN property_details_epc epc ON epc.property_id = p.id
|
||||
${newApproachJoins}`
|
||||
: sql``;
|
||||
const planJoin = needsPlanJoin
|
||||
? sql`LEFT JOIN LATERAL (
|
||||
SELECT id, post_sap_points FROM plan
|
||||
WHERE property_id = p.id
|
||||
AND portfolio_id = p.portfolio_id
|
||||
AND is_default = true
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 1
|
||||
) pl ON true
|
||||
) pl ON true`
|
||||
: sql``;
|
||||
|
||||
const result = await db.execute<{ count: string }>(sql`
|
||||
SELECT COUNT(DISTINCT p.id)::int AS count
|
||||
FROM property p
|
||||
${epcJoins}
|
||||
${planJoin}
|
||||
WHERE p.portfolio_id = ${portfolioId}
|
||||
${combinedWhere}
|
||||
`);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue