> Every UQL docs page, as Markdown: https://uql-orm.dev/llms.txt
> The same docs over MCP: https://uql-orm.dev/mcp
> Before writing UQL code, read the skill: https://uql-orm.dev/.well-known/agent-skills/uql-orm/SKILL.md

# Sorting

> Order rows with $sort, and say where nulls land so the answer is the same on every engine.

Source: https://uql-orm.dev/querying/sorting

`$sort` orders by any field of the entity, by a JSON path inside one, and by a joined relation’s field. A direction is `'asc'` / `1` or `'desc'` / `-1`, and keys order by in the order you write them:

```ts
const posts = await pool.findMany(Post, {
  $select: { title: true, publishedAt: true },
  $sort: { publishedAt: 'desc', title: 'asc' },
});
```

```sql title="PostgreSQL"
ORDER BY "publishedAt" DESC, "title"
```

## Where nulls land

A column is nullable in UQL unless it says `nullable: false`, and each engine has its own idea of where those nulls belong:

| Engine | Unqualified `asc` puts nulls |
| - | - |
| PostgreSQL, CockroachDB, Neon, PGlite, Bun SQL | last |
| SQLite, libSQL, Turso, D1, MySQL, MariaDB, SQL Server | first |
| MongoDB | first (a missing field counts as null) |

So the same query answers in a different order depending on where it runs. Say which one you want, and it reads the same everywhere:

```ts
const posts = await pool.findMany(Post, {
  $sort: { publishedAt: 'descNullsLast' },
});
```

The four placements are `ascNullsFirst`, `ascNullsLast`, `descNullsFirst` and `descNullsLast`. They work anywhere a direction does: a field, a JSON path, a joined relation’s field, a populated relation’s own `$sort`, and an aggregate’s.

Each engine gets there its own way, and the result is identical:

```sql title="PostgreSQL, CockroachDB, SQLite, libSQL, Turso, D1"
ORDER BY "publishedAt" DESC NULLS LAST
```

```sql title="MySQL, MariaDB"
ORDER BY `publishedAt` IS NULL, `publishedAt` DESC
```

```sql title="SQL Server"
ORDER BY CASE WHEN [publishedAt] IS NULL THEN 1 ELSE 0 END, [publishedAt] DESC
```

MongoDB has no placement at all, so the pipeline flags each row and orders by the flag first, then takes the flag back off before you see the document.

> **Ask for a placement only when you need one**
>
> On the engines with no clause, a placement is an extra `ORDER BY` term, and no index serves an expression: a sort that was an index scan becomes a sort of the matched rows. That is why an unqualified `asc` is left exactly as the engine writes it, and why the placement is opt-in rather than normalized for you.

## Next steps

- [Relations](https://uql-orm.dev/querying/relations.md): sorting by a joined field, and a populated collection’s own `$sort`.
- [Counting](https://uql-orm.dev/querying/counting.md): ordering parents by how many rows a relation holds.
- [Full-text search](https://uql-orm.dev/querying/full-text.md) and [semantic search](https://uql-orm.dev/querying/semantic-search.md): ranking by relevance or vector distance.
