@@ -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-
246251export const buildCountQuery = ( {
247252 withAggregates,
248253 searchConfig,
0 commit comments