Если вы пишете сложный 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)

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

Почему не 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