Если вы пишете сложный SELECT, в котором одно и тоже вычисляется несколько раз, например, для массовой обработки данных пачками, то наверняка вам хотелось завести локальную переменную
вот пример:
SELECT price * quantity AS total_price, (price * quantity) * 0.15 AS tax, ((price * quantity) + ((price * quantity) * 0.15)) AS grand_total FROM orders;
Здесь price * quantity вычисляется несколько раз, и tax вычисляется дважды. Но это выглядит еще ок, в реальности это зачастую всякие монструозные свитч-кейсы с NULLIF, COALESCE и прочими ребятами.
Но не суть, вам хочется один раз вычислить значение, без дублирования кода.
Long story short, это делается так:
SELECT o.item_name, o.price, o.quantity, v1.subtotal, v2.tax, v1.subtotal + v2.tax as grandtotal FROM orders o -- "Объявляем" subtotal CROSS JOIN LATERAL ( SELECT o.price * o.quantity AS subtotal ) AS v1 -- "Объявляем" tax, используя subtotal CROSS JOIN LATERAL ( SELECT v1.subtotal * 0.15 AS tax ) AS v2
Т.е. мы выделяем расчеты в cross join lateral, давая получившимся полям человеческие имена. И хотя в целом строк больше, но меньше дублирования кода, в котором можно ошибиться, и читается это зачастую проще.
Также можно вспомнить, что в Postgres есть альтернативный синтаксис для CROSS JOIN, а именно: просто запятая. Тогда код будет выглядить еще компактнее (хотя лично я предпочитаю явный ситаксис):
SELECT o.item_name, o.price, o.quantity, v1.subtotal, -- используем переменные v2.tax, v1.subtotal + v2.tax as grandtotal FROM orders o, -- вот тут запятая в конце LATERAL ( SELECT o.price * o.quantity AS subtotal ) AS v1, -- и тут LATERAL ( SELECT v1.subtotal * 0.15 AS tax ) AS v2
Вот db-fiddle, чтобы поиграться.
Что там по плану запроса?
Ясное дело, люди боятся лишних джойнов, и это понятно. Но в данном случае, когда в этих cross join lateral всего одна строка, вычислимая без побочных эффектов, то оптимизатор скорее всего это всё заинлайнит, и в Query Plan вы вообще не увидите ничего. В данном случае остался только Seq Scan по orders
QUERY PLAN |
|---|
Seq Scan on orders o (cost=0.00..17055.68 rows=388570 width=192) |
Почему не CTE?
Иногда можно для целей читабельности использовать и CTE, почему нет. Но вот в данном конкретном случае, пришлось бы делать два подзапроса с перечислением вообще всех нужных полей, а не только subtotal и tax.
WITH base AS ( SELECT o.item_name, o.price, o.quantity, o.price * o.quantity AS subtotal FROM orders o ), tax_calculated AS ( SELECT item_name, price, quantity, subtotal, subtotal * 0.15 AS tax FROM base ) SELECT item_name, price, quantity, subtotal, tax, subtotal + tax AS grandtotal FROM tax_calculated;
Традиционно приглашаю вас подписаться на мой tg-канал Cross Join
