web-dev-qa-db-fra.com

Pourquoi pas de fonctions fenêtrées dans les clauses where?

Le titre dit tout, pourquoi ne puis-je pas utiliser une fonction fenêtrée dans une clause where dans SQL Server?

Cette requête est parfaitement logique:

select id, sales_person_id, product_type, product_id, sale_amount
from Sales_Log
where 1 = row_number() over(partition by sales_person_id, product_type, product_id order by sale_amount desc)

Mais ça ne marche pas. Existe-t-il un meilleur moyen qu'un CTE/une sous-requête?

MODIFIER

Pour ce que cela vaut, c’est la requête avec un CTE:

with Best_Sales as (
    select id, sales_person_id, product_type, product_id, sale_amount, row_number() over (partition by sales_person_id, product_type, product_id order by sales_amount desc) rank
    from Sales_log
)
select id, sales_person_id, product_type, product_id, sale_amount
from Best_Sales
where rank = 1

MODIFIER

+1 pour les réponses montrant une sous-requête, mais je cherche vraiment le raisonnement derrière l'impossibilité d'utiliser des fonctions de fenêtrage dans les clauses where.

40
Crisfole

pourquoi ne puis-je pas utiliser une fonction fenêtrée dans une clause where dans SQL Server?

Une réponse, sans être particulièrement informative, est que la spécification dit que vous ne pouvez pas.

Voir l'article de Itzik Ben Gan - Traitement de requête logique: qu'est-ce que c'est et ce que cela signifie pour vous et en particulier l'image ici . Les fonctions de fenêtre sont évaluées au moment de la SELECT du jeu de résultats restant après que toutes les clauses WHERE/JOIN/GROUP BY/HAVING (étape 5.1) ont été traitées.

vraiment je cherche le raisonnement derrière ne pas pouvoir utiliser fonctions de fenêtrage dans les clauses where.

La raison pour laquelle ils ne sont pas autorisés dans la clause WHERE est que cela créerait une ambiguïté. Voler l'exemple d'Itzik Ben Gan dans T-SQL hautes performances avec les fonctions de fenêtre (p.25)

Supposons que votre table était 

CREATE TABLE T1
(
col1 CHAR(1) PRIMARY KEY
)

INSERT INTO T1 VALUES('A'),('B'),('C'),('D'),('E'),('F')

Et votre requête

SELECT col1
FROM T1
WHERE ROW_NUMBER() OVER (ORDER BY col1) <= 3
AND col1 > 'B'

Quel serait le bon résultat? Pensez-vous que le prédicat col1 > 'B' s'est exécuté avant ou après la numérotation des lignes?

52
Martin Smith

CTE n'est pas nécessaire, utilisez simplement la fonction de fenêtrage dans une sous-requête:

select id, sales_person_id, product_type, product_id, sale_amount
from
(
  select id, sales_person_id, product_type, product_id, sale_amount,
    row_number() over(partition by sales_person_id, product_type, product_id order by sale_amount desc) rn
  from Sales_Log
) sl
where rn = 1

Modifier, en déplaçant mon commentaire à la réponse. 

Les fonctions de fenêtrage ne sont exécutées que lorsque les données sont effectivement sélectionnées, après la clause WHERE. Donc, si vous essayez d'utiliser un row_number dans une clause WHERE, la valeur n'est pas encore affectée. 

8
Taryn

Vous n'avez pas nécessairement besoin d'utiliser un CTE, vous pouvez interroger le jeu de résultats après avoir utilisé row_number ().

select row, id, sales_person_id, product_type, product_id, sale_amount
from (
    select
        row_number() over(partition by sales_person_id, 
            product_type, product_id order by sale_amount desc) AS row,
        id, sales_person_id, product_type, product_id, sale_amount
    from Sales_Log 
    ) a
where row = 1
3
Khan

Enfin, il y a l'ancienne méthode pré-SQL Server 2005, avec une sous-requête corrélée:

select *
from   Sales_Log sl
where  sl.id = (
    Select Top 1 id
    from   Sales_Log sl2
    where  sales_person_id = sl.sales_person_id
       and product_type = sl.product_type
       and product_id = sl.product_id
    order by sale_amount desc
)

Je vous donne ceci pour être complet, simplement. 

1
Ann L.

Fondamentalement, la première condition de la clause "WHERE" est lue par SQL et le même identificateur de colonne/valeur est examiné dans la table, mais dans la table, row_num = 1 n'est pas encore présent. Par conséquent, cela ne fonctionnera pas ... C'est la raison pour laquelle nous allons commencer par utiliser des parenthèses, puis nous écrirons la clause WHERE.

1
Ayush Garg

C'est un vieux fil, mais je vais essayer de répondre spécifiquement à la question exprimée dans le sujet.

Pourquoi pas de fonctions fenêtrées dans les clauses where?

L'instruction SELECT a les clauses principales suivantes spécifiées dans ordre d'entrée au clavier:

SELECT DISTINCT TOP list
FROM  JOIN ON / APPLY / PIVOT / UNPIVOT
WHERE
GROUP BY  WITH CUBE / WITH ROLLUP
HAVING
ORDER BY
OFFSET-FETCH

Ordre de traitement des requêtes logiques, ou ordre de reliure, est ordre d'interprétation conceptuelle} _, il définit l'exactitude de la requête. Cet ordre détermine le moment où les objets définis dans une étape sont mis à la disposition des clauses dans les étapes suivantes.

----- Relational result
  1. FROM
    1.1. ON JOIN / APPLY / PIVOT / UNPIVOT
  2. WHERE
  3. GROUP BY
    3.1. WITH CUBE / WITH ROLLUP
  4. HAVING
  ---- After the HAVING step the Underlying Query Result is ready
  5. SELECT
    5.1. SELECT list
    5.2. DISTINCT
----- Relational result

----- Non-relational result (a cursor)
  6. ORDER BY
  7. TOP / OFFSET-FETCH
----- Non-relational result (a cursor)

Par exemple, si le processeur de requête peut lier (accéder) aux tables ou vues définies dans la clause FROM, ces objets et leurs colonnes sont mis à la disposition de toutes les étapes suivantes.

Inversement, toutes les clauses précédant la clause SELECT ne peuvent faire référence à aucun alias de colonne ni à aucune colonne dérivée définie dans la clause SELECT. Cependant, ces colonnes peuvent être référencées par des clauses ultérieures telles que la clause ORDER BY.

La clause OVER détermine le partitionnement et l'ordre d'un jeu de lignes avant que la fonction de fenêtre associée ne soit appliquée. Autrement dit, la clause OVER définit une fenêtre ou un ensemble de lignes spécifié par l'utilisateur dans un résultat de requête sous-jacent et la fonction window calcule le résultat dans cette fenêtre.

Msg 4108, Level 15, State 1, …
Windowed functions can only appear in the SELECT or ORDER BY clauses.

La raison derrière est parce que la façon dont traitement de requête logique fonctionne dans T-SQL. Étant donné que le résultat de la requête sous-jacente} est établi uniquement lorsque le traitement de la requête logique atteint l'étape SELECT. (c’est-à-dire qu'après le traitement des étapes FROM, WHERE, GROUP BY et HAVING), les fonctions de fenêtre sont autorisées uniquement dans les clauses SELECT et ORDER BY de la requête. 

Notez que les fonctions de fenêtre font toujours partie de la couche relationnelle même si Relational Model ne traite pas les données ordonnées. Le résultat après l'étape SELECT 5.1. avec toute fonction de fenêtre est toujours relationnelle.

De plus, à proprement parler, la raison pour laquelle la fonction de fenêtre n'est pas autorisée dans la clause WHERE n'est pas parce qu'elle créerait une ambiguïté, mais à cause de l'ordre suivant: traitement de requête logique traite l'instruction SELECT dans T-SQL.

Liens: ici , ici et ici

1
drumsta

Oui, malheureusement, lorsque vous exécutez une fonction fenêtrée, SQL se fâche contre vous, même si votre prédicat where est légitime. Vous créez un cte ou une sélection imbriquée ayant la valeur dans votre instruction select, puis vous référencez votre CTE ou la sélection imbriquée avec cette valeur ultérieurement. Exemple simple qui devrait être explicite. Si vous détestez vraiment les problèmes de performances liés à la création d'un ensemble de données volumineux, vous pouvez toujours passer à la table temporaire ou à la variable de table.

declare @Person table ( PersonID int identity, PersonName varchar(8));

insert into @Person values ('Brett'),('John');

declare @Orders table ( OrderID int identity, PersonID int, OrderName varchar(8));

insert into @Orders values (1, 'Hat'),(1,'Shirt'),(1, 'Shoes'),(2,'Shirt'),(2, 'Shoes');

--Select
--  p.PersonName
--, o.OrderName
--, row_number() over(partition by o.PersonID order by o.OrderID)
--from @Person p 
--  join @Orders o on p.PersonID = o.PersonID
--where row_number() over(partition by o.PersonID order by o.orderID) = 2

-- yields:
--Msg 4108, Level 15, State 1, Line 15
--Windowed functions can only appear in the SELECT or ORDER BY clauses.
;

with a as 
    (
    Select
    p.PersonName
,   o.OrderName
,   row_number() over(partition by o.PersonID order by o.OrderID) as rnk
from @Person p 
    join @Orders o on p.PersonID = o.PersonID
    )
select *
from a 
where rnk >= 2 -- only orders after the first one.
1
djangojazz