Skip to content

fix(mysql): qualify column reference in NULLS LAST CASE simulation#7641

Open
brdbry wants to merge 1 commit into
tobymao:mainfrom
brdbry:mysql-nulls-last-qualify-column
Open

fix(mysql): qualify column reference in NULLS LAST CASE simulation#7641
brdbry wants to merge 1 commit into
tobymao:mainfrom
brdbry:mysql-nulls-last-qualify-column

Conversation

@brdbry
Copy link
Copy Markdown
Contributor

@brdbry brdbry commented May 12, 2026

Problem

When transpiling ORDER BY <col> from a NULL_ORDERING = "nulls_are_last" source dialect (e.g. DuckDB) into MySQL, the generator emits a CASE WHEN <col> IS NULL THEN 1 ELSE 0 END, <col> shim to simulate NULLS LAST (MySQL has no native NULLS FIRST/LAST syntax). The shim inlines the raw ORDER BY expression verbatim — typically a bare column name.

That's fine for a single-table query, but in a multi-table FROM where the same unqualified column name exists in more than one table, MySQL raises:

ERROR 1052 (23000): Column '<name>' in order clause is ambiguous

…even when there is an unambiguous SELECT-list projection. MySQL's ORDER BY does apply alias-first column resolution against the SELECT list — but inside the CASE expression in ORDER BY, that alias-first rule does not apply. The bare reference falls through to base-table resolution and collides.

Reproducer

from sqlglot import parse_one

src = (
    "SELECT e.employee_id FROM employees e "
    "LEFT JOIN employee_positions ep ON e.employee_id = ep.employee_id "
    "ORDER BY employee_id"
)
print(parse_one(src, read="duckdb").sql(dialect="mysql"))

Output today (rejected by MySQL 8.0):

SELECT e.employee_id FROM employees AS e LEFT JOIN employee_positions AS ep
  ON e.employee_id = ep.employee_id
ORDER BY CASE WHEN employee_id IS NULL THEN 1 ELSE 0 END, employee_id

After this PR:

SELECT e.employee_id FROM employees AS e LEFT JOIN employee_positions AS ep
  ON e.employee_id = ep.employee_id
ORDER BY CASE WHEN e.employee_id IS NULL THEN 1 ELSE 0 END, e.employee_id

Verified against a running MySQL 8.0 container: the pre-fix SQL errors with 1052, the post-fix SQL succeeds.

Fix

New helper Generator._qualified_for_null_ordering_simulation on the base generator walks from the Ordered node to its enclosing Select and looks for a single qualified-column projection whose output_name matches the bare ORDER BY column. If exactly one is found (whether the projection is plain e.employee_id or e.employee_id AS <alias>), the simulation substitutes that qualified column inside both the CASE and the trailing sort key. In every other case (already-qualified ORDER BY, positional/ordinal ORDER BY, expression ORDER BY, no enclosing Select, ambiguous projection match, inside a window function), the helper returns None and the existing behaviour is preserved.

Single change: sqlglot/generator.py.

Test

Added TestMySQL.test_null_ordering_simulation_qualifies_ambiguous_columns in tests/dialects/test_mysql.py covering three scenarios: the reproducer (multi-table LEFT JOIN, unqualified ORDER BY), an aliased projection (SELECT e.employee_id AS emp ... ORDER BY emp), and an already-qualified ORDER BY (no change in output).

Full suite passes locally (1107 passed, 18002 subtests passed).

Scope / out-of-scope

The fix lives in the base Generator.ordered_sql simulation branch, so any dialect with NULL_ORDERING_SUPPORTED is None (currently MySQL and MSSQL/TSQL) benefits in one place. I've only added a MySQL-focused test and PR title because the reported bug is specifically the MySQL 1052 symptom; happy to broaden the test coverage to TSQL or any other affected dialect in a follow-up if maintainers want it.

The fix is deliberately conservative: it only qualifies when a single qualified-column projection unambiguously matches, and never strips or rewrites already-qualified ORDER BY references. I did not modify the parser to track explicit-vs-implicit NULL ordering on Ordered nodes (broader ripple) or add any per-dialect toggle.

Context

Same area of the codebase as #6655 (UPDATE … FROM → UPDATE … JOIN translation) which I authored earlier this year.

… avoid 1052 ambiguity

When transpiling ORDER BY <col> from a NULLS-LAST default dialect (e.g. DuckDB)
to MySQL, the simulated CASE WHEN <col> IS NULL THEN 1 ELSE 0 END, <col> inlines
the raw expression. Inside the CASE, MySQL does not apply ORDER BY's alias-first
column resolution, so unqualified references collide with same-named columns in
joined base tables (error 1052). Resolve the column against the enclosing SELECT
projection and substitute the qualified source where one uniquely exists.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant