Alias ​​de référence (calculé dans SELECT) dans la clause WHERE

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

La valeur calculée «BalanceDue» définie comme variable dans la liste des colonnes sélectionnées ne peut pas être utilisée dans la clause WHERE.

Y a-t-il un moyen pour cela? Dans cette question connexe ( Utilisation d'une variable dans l'instruction MySQL Select dans une clause Where ), il semble que la réponse serait, en fait, non, vous écririez simplement le calcul ( et effectueriez ce calcul dans la requête) deux fois, aucun des ce qui est satisfaisant.

Nicholas Petersen
la source

Réponses:

237

Vous ne pouvez pas référencer un alias sauf dans ORDER BY car SELECT est l'avant-dernière clause évaluée. Deux solutions de contournement:

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

Ou répétez simplement l'expression:

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

Je préfère ce dernier. Si l'expression est extrêmement complexe (ou coûteuse à calculer), vous devriez probablement envisager une colonne calculée (et peut-être persistante) à la place, surtout si de nombreuses requêtes font référence à cette même expression.

PS vos craintes ne semblent pas fondées. Dans cet exemple simple au moins, SQL Server est suffisamment intelligent pour n'effectuer le calcul qu'une seule fois, même si vous l'avez référencé deux fois. Allez-y et comparez les plans; vous verrez qu'ils sont identiques. Si vous avez un cas plus complexe où vous voyez l'expression évaluée plusieurs fois, veuillez publier la requête la plus complexe et les plans.

Voici 5 exemples de requêtes qui donnent toutes exactement le même plan d'exécution:

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 résultant pour les cinq requêtes:

entrez la description de l'image ici

Aaron Bertrand
la source
11
Sensationnel. SQL Server est assez intelligent pour n'effectuer le calcul qu'une seule fois
Alternatefaraz
5
Wow c'est une réponse de très haute qualité!
Siddhartha
J'avais besoin de certaines conditions supplémentaires dans une instruction MERGE, et c'était la seule façon de le faire fonctionner. Merci!
Eric Burdo du
1
@EricBurdo Si vous utilisez MERGE, assurez-vous d'avoir pris en compte tout cela: MERGEavec prudence .
Aaron Bertrand
11

Vous pouvez le faire en utilisant cross apply

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

Il est en fait possible de définir efficacement une variable qui peut être utilisée à la fois dans les clauses SELECT, WHERE et dans d'autres clauses.

Une jointure croisée ne permet pas nécessairement une liaison appropriée aux colonnes de table référencées, contrairement à OUTER APPLY - et traite les valeurs nulles de manière plus 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

Félicitations à Syed Mehroz Alam .

Peter Aylett
la source