Ссылочный псевдоним (вычисляется в SELECT) в предложении WHERE

131
SELECT (InvoiceTotal - PaymentTotal - CreditTotal) AS BalanceDue
FROM Invoices
WHERE BalanceDue > 0 --error

Вычисленное значение BalanceDue, заданное как переменная в списке выбранных столбцов, нельзя использовать в предложении WHERE.

Есть ли способ, которым это возможно? В этом связанном вопросе (с использованием переменной в MySQL Select Statment в предложении Where ) кажется, что ответ будет, на самом деле, нет, вы просто выпишете расчет ( и выполните это вычисление в запросе) дважды, ни один из что удовлетворительно.

Николас Петерсен
источник

Ответы:

238

Вы не можете ссылаться на псевдоним, кроме ORDER BY, потому что SELECT - это второе последнее вычисляемое предложение. Два обходных пути:

SELECT BalanceDue FROM (
  SELECT (InvoiceTotal - PaymentTotal - CreditTotal) AS BalanceDue
  FROM Invoices
) AS x
WHERE BalanceDue > 0;

Или просто повторите выражение:

SELECT (InvoiceTotal - PaymentTotal - CreditTotal) AS BalanceDue
FROM Invoices
WHERE  (InvoiceTotal - PaymentTotal - CreditTotal)  > 0;

Я предпочитаю последнее. Если выражение чрезвычайно сложное (или требует больших затрат для вычисления), вам, вероятно, следует вместо этого рассмотреть вычисляемый столбец (и, возможно, постоянный), особенно если много запросов относятся к этому же выражению.

PS ваши опасения кажутся беспочвенными. По крайней мере, в этом простом примере SQL Server достаточно умен, чтобы выполнить вычисление только один раз, даже если вы дважды ссылались на него. Идите и сравните планы; вы увидите, что они идентичны. Если у вас более сложный случай, когда вы видите, что выражение оценивается несколько раз, опубликуйте более сложный запрос и планы.

Вот 5 примеров запросов, каждый из которых дает один и тот же план выполнения:

SELECT LEN(name) + column_id AS x
FROM sys.all_columns
WHERE LEN(name) + column_id > 30;

SELECT x FROM (
SELECT LEN(name) + column_id AS x
FROM sys.all_columns
) AS x
WHERE x > 30;

SELECT LEN(name) + column_id AS x
FROM sys.all_columns
WHERE column_id + LEN(name) > 30;

SELECT name, column_id, x FROM (
SELECT name, column_id, LEN(name) + column_id AS x
FROM sys.all_columns
) AS x
WHERE x > 30;

SELECT name, column_id, x FROM (
SELECT name, column_id, LEN(name) + column_id AS x
FROM sys.all_columns
) AS x
WHERE LEN(name) + column_id > 30;

Итоговый план по всем пяти запросам:

введите описание изображения здесь

Аарон Бертран
источник
11
Вот это да. SQL Server достаточно умен, чтобы выполнить вычисление только один раз
alternatefaraz
5
Ух ты, это очень качественный ответ!
Сиддхартха
Мне требовались дополнительные условные выражения в операторе MERGE, и это был единственный способ заставить его работать. Спасибо!
Эрик Бурдо
1
@EricBurdo Если вы используете MERGE, пожалуйста, убедитесь, что вы приняли все это во внимание: MERGEс осторожностью .
Аарон Бертран
11

Вы можете сделать это, используя cross apply

SELECT c.BalanceDue AS BalanceDue
FROM Invoices
cross apply (select (InvoiceTotal - PaymentTotal - CreditTotal) as BalanceDue) as c
WHERE  c.BalanceDue  > 0;
Манодж
источник
4

Фактически возможно эффективно определить переменную, которая может использоваться как в предложениях SELECT, WHERE, так и в других предложениях.

Перекрестное соединение не обязательно обеспечивает соответствующую привязку к столбцам таблицы, на которые есть ссылки, однако OUTER APPLY делает это и обрабатывает значения NULL более прозрачно.

SELECT
    vars.BalanceDue
FROM
    Entity e
OUTER APPLY (
    SELECT
        -- variables   
        BalanceDue = e.EntityTypeId,
        Variable2 = ...some..long..complex..expression..etc...
    ) vars
WHERE
    vars.BalanceDue > 0

Престижность Сайед Мехроз Алам .

Питер Айлетт
источник