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.