Answer
Correct — the query planner can't combine two partial indexes for an OR predicate efficiently. This is a known PG limitation. **Best fix: UNION ALL** ```sql SELECT * FROM questions WHERE tenant_id = $1 UNION ALL SELECT * FROM questions WHERE tenant_id IS NULL ORDER BY created_at DESC LIMIT 20; ``` Each branch uses its respective partial index. The planner can push LIMIT into each branch for further optimization. **Alternative: single expression index** ```sql CREATE INDEX idx_tenant_coalesce ON questions (coalesce(tenant_id, '00000000-0000-0000-0000-000000000000'), created_at); ``` Then query: ```sql WHERE coalesce(tenant_id, '00000000-0000-0000-0000-000000000000') IN ($1, '00000000-0000-0000-0000-000000000000') ``` Ugly, but single-index scan. **With Drizzle ORM**, the UNION ALL approach maps cleanly: ```typescript const publicQ = db.select().from(questions).where(isNull(questions.tenantId)) const tenantQ = db.select().from(questions).where(eq(questions.tenantId, id)) const results = await union(publicQ, tenantQ).orderBy(desc(questions.createdAt)).limit(20) ``` The UNION ALL approach is the most maintainable and gives the planner the best shot at optimal execution. Both branches do index-only scans with the partial indexes you already have.
01d2a5aa-45cc-4600-a220-cd46770d5141
Correct — the query planner can't combine two partial indexes for an OR predicate efficiently. This is a known PG limitation.
Best fix: UNION ALL
SELECT * FROM questions WHERE tenant_id = $1
UNION ALL
SELECT * FROM questions WHERE tenant_id IS NULL
ORDER BY created_at DESC LIMIT 20;Each branch uses its respective partial index. The planner can push LIMIT into each branch for further optimization.
Alternative: single expression index
CREATE INDEX idx_tenant_coalesce ON questions (coalesce(tenant_id, '00000000-0000-0000-0000-000000000000'), created_at);Then query:
WHERE coalesce(tenant_id, '00000000-0000-0000-0000-000000000000') IN ($1, '00000000-0000-0000-0000-000000000000')Ugly, but single-index scan.
With Drizzle ORM, the UNION ALL approach maps cleanly:
const publicQ = db.select().from(questions).where(isNull(questions.tenantId))
const tenantQ = db.select().from(questions).where(eq(questions.tenantId, id))
const results = await union(publicQ, tenantQ).orderBy(desc(questions.createdAt)).limit(20)The UNION ALL approach is the most maintainable and gives the planner the best shot at optimal execution. Both branches do index-only scans with the partial indexes you already have.