Learning Platform
Глоссарий Troubleshooting
Урок 04.01 · 20 мин
Средний
friendly-sqlfrom-firstsql-dialect

FROM-first синтаксис и trailing commas

Стандартный SQL заставляет писать запрос в порядке, который не совпадает ни с порядком исполнения, ни с порядком мышления. Вы пишете SELECT первым, хотя движок сначала читает таблицу (FROM), потом фильтрует (WHERE), потом группирует, и только в самом конце вычисляет список колонок SELECT. И когда вы пишете запрос с нуля, вы тоже сначала думаете «откуда данные», а уже потом «какие колонки мне нужны». Порядок SELECT ... FROM ... — это исторический артефакт SQL-86, который пережил сорок лет просто по инерции.

DuckDB разрывает эту инерцию. Его диалект — расширение PostgreSQL-совместимого SQL, и одно из первых расширений «friendly SQL» — возможность ставить FROM перед SELECT. Это не косметика: FROM-first меняет то, как пишутся, читаются и автодополняются запросы. В этом уроке разберём три связанные вещи: FROM-first порядок, FROM вообще без SELECT, и trailing commas — мелочь, которая убирает целый класс ошибок при правках кода.


Порядок исполнения против порядка написания

Логический порядок вычисления SQL-запроса фиксирован стандартом и не зависит от того, в каком порядке вы написали клаузы. Движок всегда идёт так:

Логический порядок вычисления SQL
FROM / JOINСначала определяется источник: какие таблицы читаются и как соединяются. Без этого шага неизвестно, какие колонки вообще существуют.
WHEREФильтрация строк до группировки. Работает с колонками таблиц, но ещё не с агрегатами.
GROUP BYСтроки сворачиваются в группы по ключам группировки.
HAVINGФильтрация уже сгруппированных данных по результатам агрегатов.
SELECTТолько здесь вычисляется список выходных колонок и выражений. Поэтому алиасы из SELECT не видны в WHERE.
ORDER BY / LIMITСортировка и ограничение результата выполняются последними.

Обратите внимание: SELECT стоит пятым из шести шагов. Именно поэтому в стандартном SQL алиас, заданный в SELECT, не виден в WHEREWHERE уже отработал, когда движок дошёл до SELECT. FROM-first синтаксис приводит написание запроса в соответствие с этим порядком: вы пишете источник первым, потому что движок и так читает его первым.

В DuckDB обе формы валидны и абсолютно эквивалентны — это просто два способа записать один и тот же запрос:

-- Классический порядок
SELECT region, count(*) AS orders
FROM sales
WHERE amount > 100
GROUP BY region;

-- FROM-first: тот же запрос, источник вынесен вперёд
FROM sales
WHERE amount > 100
SELECT region, count(*) AS orders
GROUP BY region;

Парсер DuckDB принимает оба варианта и строит из них идентичное логическое дерево. Никакой разницы в производительности нет — оптимизатор видит один и тот же план. Разница только в эргономике написания.


FROM без SELECT

Раз SELECT логически идёт после FROM, возникает вопрос: а обязателен ли он вообще? В DuckDB — нет. Если вы написали только FROM, движок подставляет SELECT * неявно:

-- Полная форма
SELECT * FROM sales;

-- Сокращённая: SELECT * подразумевается
FROM sales;

Обе строки возвращают всю таблицу. Это мелочь, но она радикально ускоряет интерактивную работу. Когда вы исследуете незнакомый датасет в CLI или ноутбуке, главная команда — «покажи, что в таблице». FROM orders; — это весь запрос. То же работает и с функциями чтения файлов:

-- Посмотреть содержимое Parquet-файла одной командой
FROM 'data/orders.parquet';

-- С фильтром и лимитом, по-прежнему без SELECT
FROM 'data/orders.parquet'
WHERE order_date >= '2026-01-01'
LIMIT 10;

Последний запрос полностью валиден: FROM, WHERE, LIMIT без единого SELECT. Движок понимает, что выходные колонки не заданы, и берёт все. Это превращает DuckDB CLI в удобный инструмент быстрого просмотра данных — FROM 'file' вместо SELECT * FROM read_parquet('file').

TIP

FROM-first особенно полезен для автодополнения в IDE и CLI. Когда источник написан первым, редактор уже знает, какие таблицы и колонки существуют, и может подсказать имена колонок в SELECT. В классическом порядке на момент написания SELECT редактор ещё не видел FROM и подсказать ничего не может.


Trailing commas: мелочь против целого класса ошибок

Trailing comma — это запятая после последнего элемента списка. Стандартный SQL её запрещает: лишняя запятая перед FROM — синтаксическая ошибка. DuckDB её разрешает везде, где есть список через запятую: в SELECT, в GROUP BY, в ORDER BY, в списке значений VALUES, в аргументах функций.

Почему это важно — видно на типичной правке кода. Представьте список колонок, который растёт со временем:

-- Версия запроса вчера
SELECT
    region,
    product,
    amount
FROM sales;

Завтра вам нужно добавить customer_id. В стандартном SQL правка затрагивает ДВЕ строки: вы дописываете новую строку И ставите запятую после amount. Если забыли запятую — ошибка. Если правите через git, в diff видно изменение двух строк, хотя смысловое изменение одно. С trailing comma добавление колонки — это всегда добавление ровно одной строки:

-- С trailing comma: каждая строка заканчивается запятой
SELECT
    region,
    product,
    amount,
FROM sales;

-- Добавить колонку = дописать одну строку, ничего не трогая выше
SELECT
    region,
    product,
    amount,
    customer_id,
FROM sales;
Правка списка колонок: стандартный SQL против trailing comma
Без trailing commaЧтобы добавить колонку, надо изменить предыдущую строку (дописать запятую) и добавить новую. Две строки в diff, легко забыть запятую.
vs
С trailing commaКаждая строка уже заканчивается запятой. Добавление колонки — это всегда ровно одна новая строка. Чистый diff, нечего забывать.

Эффект ощутимее всего в code review. Diff, где добавление одной колонки выглядит как одна добавленная строка, читается мгновенно. Diff, где та же правка трогает две строки, заставляет ревьюера проверять, не сломалось ли что-то в «изменённой» предыдущей строке. Trailing commas — это не про удобство печати, это про чистоту истории изменений и снижение когнитивной нагрузки при ревью.

Trailing commas работают и в других списках:

-- В GROUP BY и ORDER BY
SELECT region, product, sum(amount) AS total
FROM sales
GROUP BY
    region,
    product,
ORDER BY
    total DESC,
;

-- В VALUES
INSERT INTO sales VALUES
    ('EU', 'A', 100),
    ('US', 'B', 200),
;
NOTE

DuckDB заимствовал trailing commas из языков программирования — в Python, JavaScript, Go, Rust они давно норма ровно по той же причине: чистые diff-ы. SQL долго оставался исключением. DuckDB и несколько других современных движков (BigQuery тоже разрешает trailing comma в SELECT) приводят SQL к этому стандарту.


Когда какой стиль выбирать

FROM-first и классический порядок взаимозаменяемы, и в одной кодовой базе можно встретить оба. Практическое правило такое:

СитуацияРекомендуемый стиль
Интерактивный просмотр данных в CLIFROM tbl; без SELECT
Быстрый запрос к файлуFROM 'file.parquet' WHERE ...
Сложный аналитический запрос с CTEЛюбой — выберите один и держитесь его
Код, который читают аналитики из мира классического SQLКлассический SELECT ... FROM
Списки колонок, которые будут растиВсегда с trailing comma

Главное — последовательность внутри проекта. Смешивать FROM-first и классический порядок в соседних моделях одного dbt-проекта не стоит: это сбивает читателя. Trailing commas же стоит включить везде без исключений — у них нет минусов, и они работают независимо от того, какой порядок клауз вы выбрали.


Попробуй сам

Запустите DuckDB CLI (duckdb) и создайте тестовую таблицу:

CREATE TABLE sales AS
  SELECT * FROM (VALUES
    ('EU', 'A', 120),
    ('US', 'B', 80),
    ('EU', 'C', 200),
  ) t(region, product, amount);

Задания:

  1. Выведите всю таблицу запросом без единого SELECT.
  2. Напишите один и тот же агрегирующий запрос (сумма amount по region) дважды: в классическом порядке и в FROM-first. Убедитесь, что результат идентичен.
  3. Намеренно поставьте trailing comma после последней колонки в SELECT и после последнего ключа в GROUP BY. Проверьте, что запрос всё равно выполняется.
  4. Попробуйте FROM sales WHERE amount > 100 LIMIT 2; — запрос без SELECT, но с фильтром и лимитом. Объясните себе, почему это валидно с точки зрения логического порядка вычисления.

Логический порядок выполнения SQL-запроса
Проверка знанийKnowledge check
Почему DuckDB может позволить себе FROM-first синтаксис и FROM без SELECT, и связано ли это с порядком исполнения запроса?
ОтветAnswer
Логический порядок вычисления SQL-запроса фиксирован стандартом: сначала FROM/JOIN определяет источник, затем WHERE, GROUP BY, HAVING, и только пятым шагом вычисляется SELECT, после чего идут ORDER BY и LIMIT. Этот порядок не зависит от того, в каком порядке клаузы написаны в тексте запроса. Поскольку FROM логически всегда исполняется первым, DuckDB разрешает и писать его первым — парсер строит идентичное логическое дерево из обеих форм, и оптимизатор видит один и тот же план, разницы в производительности нет. По той же причине SELECT можно опустить вовсе: если источник задан, а выходные колонки нет, движок подставляет SELECT * неявно. Это делает FROM-first не косметикой, а приведением синтаксиса в соответствие с реальной семантикой исполнения, и заодно улучшает автодополнение, потому что редактор видит источник раньше списка колонок.

Проверьте понимание

Результат: 0 из 0
Концептуальный
Вопрос 1 из 4. Почему DuckDB может позволить себе синтаксис, где FROM пишется раньше SELECT?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 7