Pull to refresh

Comments 16

Также можно вспомнить, что в Postgres есть альтернативный синтаксис для CROSS JOIN, а именно: просто запятая.

CROSS JOIN и операция "запятая" хоть и очень близки, но не полностью эквивалентны. Поэтому термин "альтернативный синтаксис" в корне неверен.

К тому же операция "запятая" есть практически в любом диалекте SQL, Постгресс тут совершенно не оригинален.

db-fiddle с эксплейном

db-fiddle, как и большинство иных online fiddle, в нынешних реалиях весьма плохо проходят мимо ТСПУ и прочих фильтров. Какой смысл их использовать? тем более что у вас там всего кода только 2 запроса на 8 строк:

CREATE TABLE orders (
  id bigint,
  item_name text,
  price decimal,
  quantity decimal  
);
insert into orders
select generate_series(1, 1000000), 'test', 100, 100;

Что же до латеральных запросов - их нужно использовать с крайней осторожностью. Нужно понимать, что любой чих может превратить групповые операции в итерационные, и тогда прощай всё - и скорость, и память, и т.д.

Можно немного конкретики? Где именно разница между запятой и cross join? Я не смог найти.

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

Где именно разница между запятой и cross join?

Разница в приоритете этих операций.

FROM A CROSS JOIN B INNER JOIN C ON a.x=c.y -- correct
FROM A, B INNER JOIN C ON a.x=c.y           -- error

Про групповые и итерационные - пример был бы супер, так не оч понятно.

Если у вас между FROM orders o и CROSS JOIN LATERALбудет ещё несколько JOIN к orders или производным, в том числе как минимум один из них внешний, то скорее всего LATERAL будет выполняться после всего этого пакета, а не сразу после сканирования orders. А в случае операции "запятая" это будет почти что гарантированно. Вот получение JOIN-пирога и хреновой горы наJOINенных записей всё и скушает...

а, понял, спасибо!

Я бы вообще не стал смешивать синтаксис с запятой и JOIN в одном запросе, это превращает код в ребус

Нужно понимать, что любой чих может превратить групповые операции в итерационные

Lateral join, это аналог аналитической функции, где каждая строка дочерней таблицы формируется из строки родительской. Те операция nested loops между двумя таблицами. На небольших объёмах данных незаметно, но начиная от 1млн строк будет падение производительности.

Я бы использовал cte materialize.

Postgres не знаю, но наверняка задачу можно решить вложенным запросом:

SELECT c.*,
       total_price + tax AS grand_total
FROM (
  SELECT b.*,
         total_price * 0.15 AS tax
  FROM (
    SELECT a.*,
           price * quantity AS total_price
    FROM orders a
  ) b
) c

Однако, ваш вариант с CROSS JOIN LATERAL мне больше нравится.

Я так нашёл, что в принципе они идентичны (вложенный запрос и латерал - апплай на мс). Почему латерал использовать удобней - при множественных джойнах и зависимостях вложенность может возрасти нелинейно И даже преобразоваться в дубликат запросов. А потому латерал в конец на преобразование результата запроса. То есть чтобы он обработку производил после выгрузки и фильтрации а не до. Иначе мы будем тратить цпу на все строки, а потом какую-то часть отфильтруем.

Справедливости ради, и в CTE можно написать base.*. Тогда и вложенности не будет, и догадываться «что хотел сказать автор» не придется.

Можно ещё проще. Хитрость в том, чтобы вместо подзапроса выполнять вызов функции.

Например,

LATERAL (
    SELECT o.price * o.quantity AS subtotal
) AS v1, 

можно упростить до

COALESCE(o.price * o.quantity) AS v1(subtotal), 

Пример конвейера (цепочек) вычислений в одном SELECT запросе без CTE: https://github.com/rin-nas/postgresql-patterns-library/tree/master/experiments/compression/README.md

А зачем тут COALESCE(), если все исходные данные в одной базовой таблице?

Так можно вызывать любую функцию, в том числе возвращающую скаляр. Это особенность PostgreSQL. Но если убрать вызов функции, то будет ошибка. Тут функция COALESCE используется поскольку просто возвращает свой аргумент.

Но если убрать вызов функции, то будет ошибка.

А операция скобка в Постгре не работает? т.е. просто (o.price * o.quantity) AS v1(subtotal), ?

Обязательно должен быть вызов функции (я проверял на PostgreSQL 17.5)
• если функция возвращает SETOF → даёт несколько строк (это очевидно)
• если возвращает скаляр → даёт одну строку.
Это работает точно так же как CROSS JOIN LATERAL.
В СУБД Microsoft и Oralce вместо CROSS JOIN LATERAL надо писать CROSS APPLY. Краткого способа записи в этих СУБД нет.
Информация из ответа ChatGPT.

Ну то есть операции "скобка" == "конвертировать в скалярное значение" в Постгрессе нет. Обидно, хотя и ожидаемо.

Значение скалярное как со скобками, так и без скобок. Надо конвертировать в табличное значение. Магия PostgreSQL: вызов скалярной функции в FROM конвертируется в таблицу.

Простой вариант в стиле вьюшки, но без её создания. Можно и в CTE, но нет большого смысла здесь.

SELECT
	amount,
	tax,
  	amount + tax AS amount_with_tax
FROM (
	SELECT
		amount,
	  	amount * 0.15 AS tax
	FROM (
		SELECT 
			price * quantity AS amount
		FROM orders
	) o1
) o2
;
Sign up to leave a comment.

Articles