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

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

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

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

Ответ 1

Вы не можете ссылаться на псевдоним, кроме 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 достаточно умен, чтобы выполнять только один раз, даже если вы ссылались на него дважды. Идем дальше и сравниваем планы; вы увидите, что они идентичны. Если у вас более сложный случай, когда вы видите выражение, оцениваемое несколько раз, отправьте более сложный запрос и планы.

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

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;

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

enter image description here

Ответ 2

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

SELECT c.BalanceDue AS BalanceDue
FROM Invoices
cross join (select (InvoiceTotal - PaymentTotal - CreditTotal) as BalanceDue) as c
WHERE  c.BalanceDue  > 0;