diff --git a/services/libs/data-access-layer/src/members/base.ts b/services/libs/data-access-layer/src/members/base.ts index 88d609e22d..9fc459e84b 100644 --- a/services/libs/data-access-layer/src/members/base.ts +++ b/services/libs/data-access-layer/src/members/base.ts @@ -120,7 +120,7 @@ const QUERY_FILTER_COLUMN_MAP: Map 'isBot' ->> 'default')::BOOLEAN, FALSE)` }], diff --git a/services/libs/data-access-layer/src/members/queryBuilder.ts b/services/libs/data-access-layer/src/members/queryBuilder.ts index 7a8a9cfbdb..e434f4a2bb 100644 --- a/services/libs/data-access-layer/src/members/queryBuilder.ts +++ b/services/libs/data-access-layer/src/members/queryBuilder.ts @@ -309,7 +309,11 @@ const buildActivityCountOptimizedQuery = ({ const ctes: string[] = [] if (searchConfig.cte) ctes.push(searchConfig.cte.trim()) - const searchJoinForTopMembers = searchConfig.cte + // We must keep msa available in the outer SELECT if "fields" references msa.* + const needsMsaInOuterSelect = /\bmsa\./.test(fields) + const filterNeedsMsa = /\bmsa\./.test(filterString) + + const searchJoinForFiltering = searchConfig.cte ? `\n INNER JOIN member_search ms ON ms."memberId" = msa."memberId"` : '' @@ -317,20 +321,37 @@ const buildActivityCountOptimizedQuery = ({ const oversampleMultiplier = hasNonIdMemberFields ? 10 : 1 // 10x oversampling for m.* filters const totalNeeded = Math.min(baseNeeded * oversampleMultiplier, 50000) // Cap at 50k + const prefetchLimit = Math.min(totalNeeded * 10, 50000) + + const msaJoinForFiltering = filterNeedsMsa + ? `\n INNER JOIN "memberSegmentsAgg" msa ON msa."memberId" = m.id AND msa."segmentId" = $(segmentId)` + : '' + ctes.push( ` - top_members AS ( + top_msa AS ( SELECT msa."memberId", msa."activityCount" FROM "memberSegmentsAgg" msa - INNER JOIN members m ON m.id = msa."memberId" - ${searchJoinForTopMembers} WHERE msa."segmentId" = $(segmentId) - AND (${filterString}) ORDER BY msa."activityCount" ${direction} NULLS LAST + LIMIT ${prefetchLimit} + ), + top_members AS ( + SELECT + t."memberId", + t."activityCount" + FROM top_msa t + INNER JOIN members m ON m.id = t."memberId" + ${msaJoinForFiltering} + ${searchJoinForFiltering} + WHERE + (${filterString}) + ORDER BY + t."activityCount" ${direction} NULLS LAST LIMIT ${totalNeeded} ) `.trim(), @@ -338,20 +359,25 @@ const buildActivityCountOptimizedQuery = ({ const withClause = `WITH ${ctes.join(',\n')}` - // Outer query is much simpler now - no more filtering needed + const msaOuterJoin = needsMsaInOuterSelect + ? ` + INNER JOIN "memberSegmentsAgg" msa + ON msa."memberId" = m.id + AND msa."segmentId" = $(segmentId) + ` + : '' + return ` ${withClause} SELECT ${fields} FROM top_members tm JOIN members m ON m.id = tm."memberId" - INNER JOIN "memberSegmentsAgg" msa - ON msa."memberId" = m.id - AND msa."segmentId" = $(segmentId) + ${msaOuterJoin} LEFT JOIN "memberEnrichments" me ON me."memberId" = m.id ORDER BY - msa."activityCount" ${direction} NULLS LAST + tm."activityCount" ${direction} NULLS LAST LIMIT ${limit} OFFSET ${offset} `.trim()