Skip to content

feat(core-spec): default NULL placement in window ORDER BY - #587

Open
ekut wants to merge 2 commits into
apache:mainfrom
ekut:window-nulls-default
Open

ekut wants to merge 2 commits into
apache:mainfrom
ekut:window-nulls-default

Conversation

@ekut

@ekut ekut commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Sets where NULLs sort in a window ORDER BY when NULLS FIRST or NULLS LAST is omitted: as the largest value, last for ASC and first for DESC. Follows #577 and closes #498. On dev@ [1], @jbonofre proposed this default and @christianeu-db asked for it as a separate PR and vote.

The rule is the Order By default of #246 (foundational_semantics.md §5.1), so a window and a query order NULLs the same way. A new section after the tool mappings says how to produce it. Engines with the clause emit it. MySQL and SQL Server get a leading CASE sort key instead. A table lists the native default of each engine. The two examples without the clause are the conformance cases JB asked for. They can move to the compliance suite once #237 lands.

Impact on implementations: the Holistics (sqlrefs.transpile) and Hex converters render Ossie expressions into warehouse SQL through SQLGlot, reading them with the base dialect, which treats NULLs as smallest. With SQLGlot 30.23.0, RANK() OVER (ORDER BY x DESC) comes out as DESC NULLS LAST for Snowflake and PostgreSQL and unchanged for BigQuery and Databricks, the opposite of this default on all four. Reading with a dialect whose NULL_ORDERING is nulls_are_large fixes that. The Databricks converter already keeps windows out of the Ossie dialect. T-027 and D-029 in #237 expect NULLS LAST for DESC and would need to follow [2].

[1] https://lists.apache.org/thread/z2jr7q4pllsk5nkzv7d18ro8844n5tgy
[2] #237 (comment)

Without NULLS FIRST or NULLS LAST, NULL sorts as the largest value:
last for ASC, first for DESC. A mapping table lists which engines need
the explicit clause to match.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added the spec label Oct 15, 2026
@jbonofre
jbonofre self-requested a review October 15, 2026 07:06
RANK() OVER (ORDER BY revenue DESC NULLS LAST)
```

Without `NULLS FIRST` or `NULLS LAST`, NULL sorts as the largest value: last for `ASC`, first for `DESC`. Implementations MUST produce this order whatever the native default of the engine; see [Window ORDER BY NULL Placement](#window-order-by-null-placement).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a note: the MUST can't be met on engines with no NULLS FIRST/LAST syntax, such as MySQL/MariaDB and SQL Server (even if I know there are not the first target candidates 😄 ).

Both treat NULL as smallest, so ASC puts NULL first, the opposite of the Ossie default. Following the MUST there needs a rewrite like ORDER BY expr IS NULL, expr.

Maybe we could make the spec either describe that rewrite or say such items may be reported as unsupported? Otherwise conformance on those dialects is undefined.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. The new section now covers engines without the clause: a leading CASE WHEN order_expr IS NULL THEN 1 ELSE 0 END key. It is ascending for NULLs last and descending for NULLs first. CASE is the form SQLGlot writes for both MySQL and T-SQL, so one rule covers both.

Comment thread core-spec/expression_language.md Outdated

Sigma's table-calculation functions (`RowNumber`, `Rank`, `RunningSum`, `RunningAvg`, `Lag`, `Lead`, etc.) resolve their partition/order context from workbook UI configuration (which pivot/table the calculation is attached to) rather than from arguments passed in the formula text itself. Because that context isn't recoverable from the formula string alone, the Sigma converter (`converters/sigma/`) treats these as untranslatable to ANSI SQL and carries the original Sigma formula through in the `SIGMA` dialect only — see `converters/sigma/LIMITATIONS.md`.

### Window ORDER BY NULL Placement

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because this ### directly after the Sigma table-calculation note, it reads as a Sigma subsection.

Suggested change
### Window ORDER BY NULL Placement
## Window ORDER BY NULL Placement

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applied: it is now its own ## section after the tool mappings.

Comment thread core-spec/expression_language.md Outdated
| :---- | :---- | :---- | :---- |
| Snowflake, PostgreSQL | NULLS LAST | NULLS FIRST | nothing |
| BigQuery, Databricks | NULLS FIRST | NULLS LAST | `ASC NULLS LAST`, `DESC NULLS FIRST` |
| ClickHouse, DuckDB | NULLS LAST | NULLS LAST | `DESC NULLS FIRST` |

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should add other engines (SQL Server, MySQL/MariaDB, Oracle, Redshift, Trino/Athena, ...).

Oracle and Trino default to NULLs largest like Postgres, so omission is safe there.

MySQL/SQL Server need the workaround.

To be honest, I don't know if we need that, so it's more an open question 😄

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added Oracle, Redshift, Trino, MySQL and SQL Server, each checked against its current docs. One difference from your note: Trino puts NULLs last in both directions. Its docs say "The default null ordering is NULLS LAST, regardless of the ordering direction" (https://trino.io/docs/current/sql/select.html). I left MariaDB out because I couldn't confirm its syntax from the docs.

Comment thread core-spec/expression_language.md Outdated
| BigQuery, Databricks | NULLS FIRST | NULLS LAST | `ASC NULLS LAST`, `DESC NULLS FIRST` |
| ClickHouse, DuckDB | NULLS LAST | NULLS LAST | `DESC NULLS FIRST` |

Snowflake (`DEFAULT_NULL_ORDERING`) and DuckDB (`default_null_order`) let a session change the native default; an implementation that cannot rely on the setting emits the clause on every `ORDER BY` item.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this conflicts with the table. The table says Snowflake emits "nothing", but this paragraph says to emit the clause on every time if you can't rely on the session setting. A Snowflake or DuckDB converter that follows the table will silently reorder results under a non-default DEFAULT_NULL_ORDERING or default_null_order.

Maybe we should clearly state one rule, for example "omit only when the implementation controls the session setting, otherwise always emit". Also, the table has no entry for the Clickhouse and BigQuery settings that can change the native default, and it doesn't say whether Postgres is safe to omit because it has no such setting.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. The section now has one rule: emit the clause. A default placement may be left out only if the native default already gives it. It also needs a session that can't change that default, or an implementation that sets it. The table no longer says when to omit. PostgreSQL may omit: I found no NULL-ordering setting in its configuration reference. Nor could I find one in ClickHouse (system.settings on 26.9) or the BigQuery query syntax reference. If you know one, I'll add it next to Snowflake and DuckDB.

…n section

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Expression language does not say where NULLs sort in a window ORDER BY

2 participants