Skip to content
Sarj Code Standards

prefer-application-model-joins

Compose distinct model records in application code instead of a cross-model SQL join.

Why

A SQL join across separate persistence relations couples their read representations and can bypass the owning store's access and conversion policy. This architecture prefers bounded store reads and typed application composition when their consistency contract permits it.

Fix

Review the relation owners, then load the related records through their typed stores, batch by IDs and compose them in application code. Preserve tenant filters, null-extension, cardinality, snapshot consistency, authorization and FOR UPDATE locks. Keep an explained exception for an atomic database operation; replacing a join with unrelated reads can change behavior or introduce N+1 queries.

Examples

Before — flagged Separate model relations share one SQL projection
app/scenario_store.py
from psycopg import AsyncCursor
async def load(cursor: AsyncCursor):
await cursor.execute(
'SELECT s.id, u.email FROM scenario s LEFT JOIN "user" u ON u.id = s.actor_id WHERE s.id = %s',
(1,),
)
After — preferred A self join does not prove distinct model ownership
app/scenario_store.py
from psycopg import AsyncCursor
async def load(cursor: AsyncCursor):
await cursor.execute(
"SELECT s.id, u.email FROM scenario s LEFT JOIN scenario u ON u.id = s.actor_id WHERE s.id = %s",
(1,),
)