chore(): Further improve promotions computation (#13556)

* chore(): Further improve promotions

* chore(): Further improve promotions

* chore(): Further improve promotions

* chore(): Further improve promotions

* chore(): Further improve promotions

* Create lazy-lemons-occur.md

* chore(): Further improve promotions

* WIP

* fix

* improve:

* fix attribute tests

* fix tests

* union
This commit is contained in:
Adrien de Peretti
2025-09-19 21:34:35 +02:00
committed by GitHub
parent cb716856b6
commit 4c1c1dd4c0
12 changed files with 431 additions and 161 deletions
@@ -69,7 +69,6 @@ export async function buildPromotionRuleQueryFilterFromContext(
})
})
// count the number of attributes in the map
const numberOfAttributes = attributeValueMap.size
if (numberOfAttributes > 10) {
const manager = (sharedContext.transactionManager ??
@@ -96,18 +95,9 @@ export async function buildPromotionRuleQueryFilterFromContext(
})
}
// Build conditions for a NOT EXISTS subquery to exclude promotions with unsatisfiable rules
const sqlConditions: string[] = []
// First, check for rules where the attribute doesn't exist in context at all
// These rules can never be satisfied
sqlConditions.push(
`pr.attribute NOT IN (${Array.from(attributeValueMap.keys())
.map((attr) => `'${attr.replace(/'/g, "''")}'`)
.join(",")})`
)
// Then, for attributes that exist in context, check if the values don't satisfy the rules
// For each attribute, check if the values don't satisfy the rules
attributeValueMap.forEach((valueSet, attribute) => {
const values = Array.from(valueSet)
const stringValues = values
@@ -121,7 +111,6 @@ export async function buildPromotionRuleQueryFilterFromContext(
})
.filter((v) => v !== null) as number[]
// Escape attribute name to prevent SQL injection
const escapedAttribute = `'${attribute.replace(/'/g, "''")}'`
// For 'in' and 'eq' operators - rule is unsatisfiable if NO rule values overlap with context
@@ -165,17 +154,25 @@ export async function buildPromotionRuleQueryFilterFromContext(
// Handle the case where context has no attributes at all, it means
// that any promotion that have a rule cant be satisfied by the context
if (attributeValueMap.size === 0) {
// If context has no attributes, exclude all promotions that have any rules
const notExistsSubquery = (alias: string) =>
// If context has no attributes, exclude all promotions that have any rules (promotion rules, target rules, or buy rules)
const noRulesSubquery = (alias: string) =>
`
NOT EXISTS (
SELECT 1 FROM promotion_promotion_rule ppr
WHERE ppr.promotion_id = ${alias}.id
${alias}.id NOT IN (
SELECT DISTINCT ppr.promotion_id
FROM promotion_promotion_rule ppr
UNION
SELECT DISTINCT am.promotion_id
FROM promotion_application_method am
JOIN application_method_target_rules amtr ON am.id = amtr.application_method_id
UNION
SELECT DISTINCT am2.promotion_id
FROM promotion_application_method am2
JOIN application_method_buy_rules ambr ON am2.id = ambr.application_method_id
)
`.trim()
return {
[raw((alias) => notExistsSubquery(alias))]: true,
[raw((alias) => noRulesSubquery(alias))]: true,
}
}
@@ -188,18 +185,69 @@ export async function buildPromotionRuleQueryFilterFromContext(
return null
}
const notExistsSubquery = (alias: string) =>
const attributeKeys = Array.from(attributeValueMap.keys())
.map((attr) => `'${attr.replace(/'/g, "''")}'`)
.join(",")
// Use anti-join approach for better performance than NOT IN
const antiJoinSubquery = (alias: string) =>
`
NOT EXISTS (
SELECT 1 FROM promotion_promotion_rule ppr
JOIN promotion_rule pr ON ppr.promotion_rule_id = pr.id
LEFT JOIN promotion_rule_value prv ON prv.promotion_rule_id = pr.id
WHERE ppr.promotion_id = ${alias}.id
AND (${joinedConditions})
SELECT 1 FROM (
-- Promotions with rules for attributes not in context
SELECT ppr.promotion_id
FROM promotion_promotion_rule ppr
JOIN promotion_rule pr ON ppr.promotion_rule_id = pr.id
WHERE pr.attribute NOT IN (${attributeKeys})
UNION
SELECT am.promotion_id
FROM promotion_application_method am
JOIN application_method_target_rules amtr ON am.id = amtr.application_method_id
JOIN promotion_rule pr ON amtr.promotion_rule_id = pr.id
WHERE pr.attribute NOT IN (${attributeKeys})
UNION
SELECT am2.promotion_id
FROM promotion_application_method am2
JOIN application_method_buy_rules ambr ON am2.id = ambr.application_method_id
JOIN promotion_rule pr ON ambr.promotion_rule_id = pr.id
WHERE pr.attribute NOT IN (${attributeKeys})
UNION
-- Promotions with unsatisfiable rules for context attributes
SELECT ppr.promotion_id
FROM promotion_promotion_rule ppr
JOIN promotion_rule pr ON ppr.promotion_rule_id = pr.id
LEFT JOIN promotion_rule_value prv ON prv.promotion_rule_id = pr.id
WHERE pr.attribute IN (${attributeKeys}) AND (${joinedConditions})
UNION
SELECT am.promotion_id
FROM promotion_application_method am
JOIN application_method_target_rules amtr ON am.id = amtr.application_method_id
JOIN promotion_rule pr ON amtr.promotion_rule_id = pr.id
LEFT JOIN promotion_rule_value prv ON prv.promotion_rule_id = pr.id
WHERE pr.attribute IN (${attributeKeys}) AND (${joinedConditions})
UNION
SELECT am2.promotion_id
FROM promotion_application_method am2
JOIN application_method_buy_rules ambr ON am2.id = ambr.application_method_id
JOIN promotion_rule pr ON ambr.promotion_rule_id = pr.id
LEFT JOIN promotion_rule_value prv ON prv.promotion_rule_id = pr.id
WHERE pr.attribute IN (${attributeKeys}) AND (${joinedConditions})
) excluded_promotions
WHERE excluded_promotions.promotion_id = ${alias}.id
)
`.trim()
return {
[raw((alias) => notExistsSubquery(alias))]: true,
[raw((alias) => antiJoinSubquery(alias))]: true,
}
}