Skip to content

Commit 30f0918

Browse files
committed
fix: force using the index
1 parent f9244e9 commit 30f0918

File tree

1 file changed

+31
-26
lines changed

1 file changed

+31
-26
lines changed

services/libs/data-access-layer/src/members/queryBuilder.ts

Lines changed: 31 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -141,62 +141,66 @@ export const buildQuery = ({
141141
const fallbackDir: OrderDirection = orderDirection || 'DESC'
142142
const { field: sortField, direction } = parseOrderBy(orderBy, fallbackDir)
143143

144-
// Detect if filters reference extra aliases.
145-
// TODO: capire che cosa sono questi mo. me.
144+
// If filter references mo.*, we must ensure member_orgs is joined in the outer query.
146145
const filterHasMo = filterString.includes('mo.')
147-
const filterHasMe = filterString.includes('me.')
148-
149-
// If filter references mo.*, we must ensure member_orgs is joined.
150146
const needsMemberOrgs = includeMemberOrgs || filterHasMo
151147

152-
// Optimized path is only safe if:
153-
// - withAggregates is true
154-
// - sort is by activityCount (or default)
155-
// - filter does NOT reference mo. or me. (those aliases do not exist in top_members)
156-
const useActivityCountOptimized =
157-
withAggregates && !filterHasMo && !filterHasMe && (!sortField || sortField === 'activityCount')
148+
// We use the optimized path when:
149+
// - aggregates are requested (msa available)
150+
// - sorting is by activityCount (or not specified → default)
151+
// NOTE: we DO NOT check for mo./me. here because we keep all filters OUTSIDE the CTE.
152+
const useActivityCountOptimized = withAggregates && (!sortField || sortField === 'activityCount')
158153

159154
log.info(`buildQuery: useActivityCountOptimized=${useActivityCountOptimized}`)
155+
160156
if (useActivityCountOptimized) {
161157
const ctes: string[] = []
162158

163-
if (includeMemberOrgs) {
164-
const memberOrgsCTE = buildMemberOrgsCTE(true)
165-
ctes.push(memberOrgsCTE.trim())
159+
// Include member_orgs CTE only if we need it in the OUTER query (never inside top_members)
160+
if (needsMemberOrgs) {
161+
ctes.push(buildMemberOrgsCTE(true).trim())
166162
}
167163

164+
// Include search CTE if present
168165
if (searchConfig.cte) {
169166
ctes.push(searchConfig.cte.trim())
170167
}
171168

172-
const searchJoinInTopMembers = searchConfig.join
173-
? `\n ${searchConfig.join}` // INNER JOIN member_search ms ON ms."memberId" = m.id
169+
// Join search to msa WITHOUT touching members (so index on msa can be used)
170+
const searchJoinForTopMembers = searchConfig.cte
171+
? `\n INNER JOIN member_search ms ON ms."memberId" = msa."memberId"`
174172
: ''
175173

174+
// Oversample: fetch more rows than needed before applying outer filters,
175+
// then apply LIMIT/OFFSET on the final ordered result.
176+
const oversampleMultiplier = 5
177+
const prefetch = Math.max(limit * oversampleMultiplier + offset, limit + offset)
178+
176179
ctes.push(
177180
`
178181
top_members AS (
179182
SELECT
180-
msa."memberId"
183+
msa."memberId",
184+
msa."activityCount"
181185
FROM "memberSegmentsAgg" msa
182-
JOIN members m ON m.id = msa."memberId"
183-
${searchJoinInTopMembers}
186+
${searchJoinForTopMembers}
184187
WHERE
185188
msa."segmentId" = $(segmentId)
186-
AND (${filterString})
187189
ORDER BY
188190
msa."activityCount" ${direction} NULLS LAST
189-
LIMIT ${limit} OFFSET ${offset}
191+
LIMIT ${prefetch}
190192
)
191193
`.trim(),
192194
)
193195

194196
const withClause = `WITH ${ctes.join(',\n')}`
195197

196-
const memberOrgsJoin = includeMemberOrgs
197-
? `LEFT JOIN member_orgs mo ON mo."memberId" = m.id`
198-
: ''
198+
const memberOrgsJoin = needsMemberOrgs ? `LEFT JOIN member_orgs mo ON mo."memberId" = m.id` : ''
199199

200+
// IMPORTANT:
201+
// - All filters on members/orgs/enrichments are applied OUTSIDE the CTE.
202+
// - Final ORDER BY keeps activityCount (already aligned with top_members).
203+
// - Final LIMIT/OFFSET ensure correct pagination after applying filters.
200204
return `
201205
${withClause}
202206
SELECT ${fields}
@@ -212,10 +216,12 @@ export const buildQuery = ({
212216
WHERE (${filterString})
213217
ORDER BY
214218
msa."activityCount" ${direction} NULLS LAST
219+
LIMIT ${limit}
220+
OFFSET ${offset}
215221
`.trim()
216222
}
217223

218-
// Fallback path: any case that is not safe/eligible for optimization.
224+
// Fallback path: any other sort mode.
219225
const baseCtes = [needsMemberOrgs ? buildMemberOrgsCTE(true) : '', searchConfig.cte].filter(
220226
Boolean,
221227
)
@@ -242,7 +248,6 @@ export const buildQuery = ({
242248
OFFSET ${offset}
243249
`.trim()
244250
}
245-
246251
export const buildCountQuery = ({
247252
withAggregates,
248253
searchConfig,

0 commit comments

Comments
 (0)