SQL differences

DISTINCT ON Works on PostgreSQL Only

DISTINCT ON (customer_id) is a neat way to keep each customer’s latest order on PostgreSQL, and none of the other three engines has it.

SQLite, MySQL and MariaDB report a syntax error. Window functions say the same thing on all four (SQLite 3.25, MySQL 8.0 and MariaDB 10.2 and later).

SELECT DISTINCT ON (a) a, b FROM t ORDER BY a, b, run on four engines: SQLite 3.45.1 error: near "ON": syntax error; PostgreSQL 16.15 1, 3; 2, 4; MySQL 8.0.46 error: You have an error in your SQL syntax ... near 'ON (a) a, b FROM t ORDER BY a, b' at line 1; MariaDB 10.11.14 error: You have an error in your SQL syntax ... near 'ON (a) a, b FROM t ORDER BY a, b' at line 1.

What Each Engine Answered

On a table made with

CREATE TABLE t (a INTEGER, b INTEGER);
INSERT INTO t VALUES (1, 5), (1, 3), (2, 4);

the example

SELECT DISTINCT ON (a) a, b FROM t ORDER BY a, b;

gave these answers:

Engine Answer
SQLite 3.45.1 error: near "ON": syntax error
PostgreSQL 16.15 1, 3; 2, 4
MySQL 8.0.46 error: You have an error in your SQL syntax ... near 'ON (a) a, b FROM t ORDER BY a, b' at line 1
MariaDB 10.11.14 error: You have an error in your SQL syntax ... near 'ON (a) a, b FROM t ORDER BY a, b' at line 1

What to Write Instead

This form answers the same on every engine:

SELECT a, b FROM (SELECT a, b, ROW_NUMBER() OVER (PARTITION BY a ORDER BY b) AS n FROM t) AS s WHERE n = 1 ORDER BY a;
Engine Answer
SQLite 3.45.1 1, 3; 2, 4
PostgreSQL 16.15 1, 3; 2, 4
MySQL 8.0.46 1, 3; 2, 4
MariaDB 10.11.14 1, 3; 2, 4

Number the rows of each group and keep the first: SELECT a, b FROM (SELECT a, b, ROW_NUMBER() OVER (PARTITION BY a ORDER BY b) AS n FROM t) AS s WHERE n = 1.

What BareSQL Portable Reports

Saved as example.sql, the example gets this from baresql lint --all:

$ baresql lint --all example.sql
example.sql:3:8: error: DISTINCT ON (a) keeps one row per value of a on PostgreSQL only. [distinct-on]
    DISTINCT ON (a)
    Number the rows of each group and keep the first: SELECT a, b FROM (SELECT a, b, ROW_NUMBER() OVER (PARTITION BY a ORDER BY b) AS n FROM t) AS s WHERE n = 1.
example.sql:3:45: info: SQLite, MySQL and MariaDB put NULL first in ascending order; PostgreSQL puts it last. [null-order]
    a
    Sort on x IS NULL first: ORDER BY x IS NULL, x puts NULL last on every engine, and ORDER BY x IS NOT NULL, x puts it first. NULLS FIRST and NULLS LAST do not work on MySQL and MariaDB.
example.sql:3:48: info: SQLite, MySQL and MariaDB put NULL first in ascending order; PostgreSQL puts it last. [null-order]
    b
    Sort on x IS NULL first: ORDER BY x IS NULL, x puts NULL last on every engine, and ORDER BY x IS NOT NULL, x puts it first. NULLS FIRST and NULLS LAST do not work on MySQL and MariaDB.

3 problems: 1 error, 2 notes.

The example also runs into Where NULL sorts, which has a post of its own.

baresql rules distinct-on prints the same answers in the terminal, and the BareSQL Portable page lists every difference BareSQL knows.