Alias ​​de referencia (calculado en SELECCIONAR) en la cláusula WHERE

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

El valor calculado 'BalanceDue' que se establece como una variable en la lista de columnas seleccionadas no se puede utilizar en la cláusula WHERE.

¿Hay alguna manera de que pueda? En esta pregunta relacionada ( usando una variable en MySQL Select Statment en una cláusula Where ), parece que la respuesta sería, en realidad, no, simplemente escribiría el cálculo ( y realizaría ese cálculo en la consulta) dos veces, ninguna de Lo cual es satisfactorio.

Nicholas Petersen
fuente

Respuestas:

237

No puede hacer referencia a un alias excepto en ORDER BY porque SELECT es la segunda última cláusula que se evalúa. Dos soluciones alternativas:

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

O simplemente repita la expresión:

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

Prefiero el último. Si la expresión es extremadamente compleja (o costosa de calcular), probablemente debería considerar una columna calculada (y tal vez persistente), especialmente si muchas consultas se refieren a esta misma expresión.

PD: tus miedos parecen infundados. Al menos en este simple ejemplo, SQL Server es lo suficientemente inteligente como para realizar el cálculo solo una vez, aunque lo haya referenciado dos veces. Anímate y compara los planes; Verás que son idénticos. Si tiene un caso más complejo en el que ve la expresión evaluada varias veces, publique la consulta más compleja y los planes.

Aquí hay 5 consultas de ejemplo que arrojan exactamente el mismo plan de ejecución:

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;

Plan resultante para las cinco consultas:

ingrese la descripción de la imagen aquí

Aaron Bertrand
fuente
11
Guau. SQL Server es lo suficientemente inteligente como para realizar el cálculo solo una vez
alternatefaraz
55
¡Guau, esta es una respuesta de muy alta calidad!
Siddhartha
Necesitaba algunos condicionales adicionales en una declaración MERGE, y esta era la única forma en que podía hacer que funcionara. ¡Gracias!
Eric Burdo
1
@EricBurdo Si está utilizando MERGE, asegúrese de haber tenido todo esto en cuenta: MERGEcon precaución .
Aaron Bertrand
11

Puedes hacer esto usando cross apply

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

En realidad, es posible definir efectivamente una variable que se pueda usar tanto en las cláusulas SELECT, WHERE como en otras.

Una unión cruzada no necesariamente permite el enlace apropiado a las columnas de la tabla referenciada, sin embargo, OUTER APPLY sí lo hace, y trata los nulos de manera más transparente.

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

Felicitaciones a Syed Mehroz Alam .

Peter Aylett
fuente