Fonction SQL Row_Number () dans la clause Where

90

J'ai trouvé une question répondue avec la Row_Number()fonction dans la clause where. Lorsque j'ai essayé une requête, j'obtenais l'erreur suivante:

"Msg 4108, Niveau 15, État 1, Ligne 1 Les fonctions fenêtrées ne peuvent apparaître que dans les clauses SELECT ou ORDER BY."

Voici la requête que j'ai essayée. Si quelqu'un sait comment résoudre ce problème, faites-le moi savoir.

SELECT employee_id 
FROM V_EMPLOYEE 
WHERE row_number() OVER ( ORDER BY employee_id ) > 0 
ORDER BY Employee_ID
johnnyRose
la source
9
ROW_NUMBER() OVER (ORDER BY employee_id) > 0évaluera toujours àTRUE
Quassnoi
3
Oui, c'est vrai. Je ne suis pas inquiet de la condition, que je peux changer à tout moment. Je veux que la requête fonctionne d'abord, puis je pense à garder le numéro de rang entre 500 et 800 ... merci
2
@Joseph: Pourquoi essayez-vous d'éviter d'utiliser un CTE?
OMG Ponies
1
@rexem - Je ne suis pas un expert en SQL Server. J'essaie d'aider une équipe dans un grand projet où elle est confrontée à de nombreux problèmes de performance. Ils utilisent des UDF et des CTE. Dans l'un des tableaux, ils n'ont que 5000 enregistrements, et si 5 utilisateurs accèdent à une recherche, la récupération prend plus d'une minute. Un certain temps, il échoue et expire. Donc, j'essaie d'éviter les CTE et les UDF et j'essaie de proposer une requête SQL simple qui peut résoudre les problèmes de performances.
1
Salut à tous, S'il vous plaît voir le lien que j'ai posté ci-dessous qui répond en utilisant row_number () d'une manière différente. Quelqu'un peut-il comparer ma requête initiale avec celle du lien? Appréciez l'aide ..

Réponses:

91

Pour contourner ce problème, enveloppez votre instruction select dans un CTE, puis vous pouvez interroger le CTE et utiliser les résultats de la fonction fenêtrée dans la clause where.

WITH MyCte AS 
(
    select   employee_id,
             RowNum = row_number() OVER ( order by employee_id )
    from     V_EMPLOYEE 
    ORDER BY Employee_ID
)
SELECT  employee_id
FROM    MyCte
WHERE   RowNum > 0
Scott Ivey
la source
7
J'essaye d'éviter CTE. C'est le pire des cas que je recherche. merci
3
Il peut s'exécuter plus rapidement si vous utilisez une sous-requête au lieu d'un CTE. J'ai vu de meilleures performances d'un facteur de 1,5 dans certains cas
Brian Webster
3
Il devrait également y avoir TOP dans le CTE SELECT, sinon SQL 2008 Server n'exécutera pas la requête en raison de ORDER BY (qui n'est pas pris en charge sauf si TOP est utilisé)
Muflix
2
J'utilise SQL2005 (ugh) - Je peux éviter l'utilisation de "TOP", en supprimant le "ORDER BY" après le FROM. Il est redondant avec le (Order By) après le OVER de toute façon.
Joe B
Je souhaitais qu'il y ait un moyen d'utiliser une clause ROW_NUMBER()in WHEREsans CTE :(
Jalal
61
SELECT  employee_id
FROM    (
        SELECT  employee_id, ROW_NUMBER() OVER (ORDER BY employee_id) AS rn
        FROM    V_EMPLOYEE
        ) q
WHERE   rn > 0
ORDER BY
        Employee_ID

Notez que ce filtre est redondant: ROW_NUMBER()commence à partir de 1et est toujours supérieur à 0.

Quassnoi
la source
2
@ DavideChicco.it: dans SQL Server, les tables dérivées nécessitent un alias (j'aurais dû écrire à la AS qplace, mais cela fonctionnerait non plus).
Quassnoi le
2
La lisibilité est un objectif que j'ai lors de la dénomination des alias. Vous pouvez écrire rn comme RowNumber et q comme DerivedTable et la clause where comme where DerivedTable.RowNumber> 0. À mon avis, ce sera beaucoup moins déroutant dans 6 mois lorsque le code n'est pas frais dans votre esprit.
Edward Comeau
2
@EdwardComeau: rnest un acronyme universellement accepté pour le numéro de ligne de nos jours. Essayez de taper "row_number over as ..." dans la chaîne de recherche Google et voyez ce qu'il vous suggère.
Quassnoi
3
@Quassnoi, la lisibilité est la clé d'un bon codage et l'effort cognitif de traduction de rn (ou d'autres alias abrégés) s'additionne pour vous et pour les personnes qui gèrent votre code. NB, Microsoft a d'abord frappé, SELECT ROW_NUMBER () OVER (ORDER BY SalesYTD DESC) AS Row, ... Je n'ai pas non plus rencontré rn auparavant, donc votre kilométrage en "universel" peut varier.
Edward Comeau
1
@Quassnoi, et deuxième succès, article SO - stackoverflow.com/questions/961007/how-do-i-use-row-number plusieurs variations et non rn ;-)
Edward Comeau
32
Select * from 
(
    Select ROW_NUMBER() OVER ( order by Id) as 'Row_Number', * 
    from tbl_Contact_Us
) as tbl
Where tbl.Row_Number = 5
swa
la source
19

Je pense que vous voulez quelque chose comme ça:

SELECT employee_id 
FROM  (SELECT employee_id, row_number() 
       OVER (order by employee_id) AS 'rownumber' 
       FROM V_EMPLOYEE) TableExpressionsMustHaveAnAliasForDumbReasons
WHERE rownumber > 0
Matthew Jones
la source
4
Créez un alias pour la table si la requête ci-dessus ne fonctionne pas pour vous. Modifiez l'avant-dernière ligne pour From V_EMPLOYEE) Aajouter A comme alias.
Hammad Khan
7

En réponse aux commentaires sur la réponse de rexem, pour savoir si une vue en ligne ou un CTE serait plus rapide, j'ai refondu les requêtes pour utiliser une table que je, et tout le monde, avait disponible: sys.objects.

WITH object_rows AS (
    SELECT object_id, 
        ROW_NUMBER() OVER ( ORDER BY object_id) RN
    FROM sys.objects)
SELECT object_id
FROM object_rows
WHERE RN > 1

SELECT object_id
FROM (SELECT object_id, 
        ROW_NUMBER() OVER ( ORDER BY object_id) RN
    FROM sys.objects) T
WHERE RN > 1

Les plans de requête produits étaient exactement les mêmes. Je m'attendrais à ce que dans tous les cas, l'optimiseur de requêtes propose le même plan, au moins en simple remplacement de CTE par une vue en ligne ou vice versa.

Bien sûr, essayez vos propres requêtes sur votre propre système pour voir s'il y a une différence.

De plus, row_number()dans la clause where, il y a une erreur courante dans les réponses données sur Stack Overflow. La logique row_number()n'est pas disponible tant que la clause select n'est pas traitée. Les gens oublient cela et quand ils répondent sans tester la réponse, la réponse est parfois fausse. (Une accusation dont j'ai moi-même été coupable.)

Indemnité de Shannon
la source
1
Thx Shannon. Quelle version de SQL Server utilisiez-vous?
OMG Ponies
1
Cela signifie donc que la réponse fournie dans ce lien est fausse? Mais, la personne qui a posté la question a convenu que cela fonctionnait .. Surprenant .. :-)
2
@Joseph, mais si vous regardez une autre réponse publiée par l'OP dans la question liée, vous verrez qu'il est lié à une version du code qui n'est pas la même que dans la réponse acceptée. Je ne sais pas pourquoi il a accepté la réponse, même si elle ne fonctionnerait pas comme entrée. Peut-être que cela a été édité à un moment donné après avoir été accepté, peut-être que c'était suffisant pour le faire avancer, même sans être totalement correct.
Shannon Severance
1
@Rexem: SQL Server 2005 et SQL Server 2008. Les versions antérieures ne prennent pas en charge les CTE ou ROW_NUMBER ()
Shannon Severance
6

J'ai l'impression que toutes les réponses montrant l'utilisation d'un CTE ou d'une sous-requête sont des correctifs suffisants pour cela, mais je ne vois personne entrer au cœur des raisons pour lesquelles OP a un problème. La raison pour laquelle ce que l'OP suggéré ne fonctionne pas est due à l'ordre logique de traitement des requêtes ici:

  1. DE
  2. SUR
  3. JOINDRE
  4. PAR GROUPE
  5. AVEC CUBE / ROLLUP
  6. AYANT
  7. SÉLECTIONNER
  8. DISTINCT
  9. COMMANDÉ PAR
  10. HAUT
  11. OFFSET / FETCH

Je pense que cela contribue grandement à la réponse, car cela explique pourquoi des problèmes comme celui-ci se produisent. WHEREest toujours traité avant d' SELECTeffectuer une CTE ou une sous-requête nécessaire pour de nombreuses fonctions. Vous verrez cela souvent dans SQL Server.

Jamie Marshall
la source
4

Utilisation de CTE (SQL Server 2005+):

WITH employee_rows AS (
  SELECT t.employee_id,
         ROW_NUMBER() OVER ( ORDER BY t.employee_id ) 'rownum'
    FROM V_EMPLOYEE t)
SELECT er.employee_id
  FROM employee_rows er
 WHERE er.rownum > 1

Utilisation de la vue en ligne / alternative équivalente non CTE:

SELECT er.employee_id
  FROM (SELECT t.employee_id,
               ROW_NUMBER() OVER ( ORDER BY t.employee_id ) 'rownum'
          FROM V_EMPLOYEE t) er
 WHERE er.rownum > 1
Poneys OMG
la source
1
Lequel est meilleur en performance? Vous utilisez CTE ou une sous-requête? merci
1
Voir la réponse de Shannon - dans son test, ils sont égaux.
OMG Ponies
6
Non, ce n'est pas plus rapide. Dans SQL Server, CTEl » et des vues en ligne sont la même chose et ont les mêmes performances. Lorsque des fonctions non déterministes sont utilisées dans a CTE, elles sont réévaluées à chaque appel. Il faut utiliser de sales trucs pour forcer la matérialisation d'un CTE. Voir ces articles dans mon blog: explainextended.com/2009/07/28/... explainextended.com/2009/05/28/generating-xml-in-subqueries
Quassnoi
2

basé sur la réponse d'OP à la question:

Veuillez consulter ce lien. C'est avoir une solution différente, qui semble fonctionner pour la personne qui a posé la question. J'essaie de trouver une solution comme celle-ci.

Requête paginée utilisant le tri sur différentes colonnes à l'aide de ROW_NUMBER () OVER () dans SQL Server 2005

~ Joseph

"méthode 1" est comme la requête du PO à partir de la question liée, et "méthode 2" est comme la requête de la réponse sélectionnée. Vous deviez regarder le code lié dans cette réponse pour voir ce qui se passait réellement, car le code de la réponse sélectionnée avait été modifié pour que cela fonctionne. Essaye ça:

DECLARE @YourTable table (RowID int not null primary key identity, Value1 int, Value2 int, value3 int)
SET NOCOUNT ON
INSERT INTO @YourTable VALUES (1,1,1)
INSERT INTO @YourTable VALUES (1,1,2)
INSERT INTO @YourTable VALUES (1,1,3)
INSERT INTO @YourTable VALUES (1,2,1)
INSERT INTO @YourTable VALUES (1,2,2)
INSERT INTO @YourTable VALUES (1,2,3)
INSERT INTO @YourTable VALUES (1,3,1)
INSERT INTO @YourTable VALUES (1,3,2)
INSERT INTO @YourTable VALUES (1,3,3)
INSERT INTO @YourTable VALUES (2,1,1)
INSERT INTO @YourTable VALUES (2,1,2)
INSERT INTO @YourTable VALUES (2,1,3)
INSERT INTO @YourTable VALUES (2,2,1)
INSERT INTO @YourTable VALUES (2,2,2)
INSERT INTO @YourTable VALUES (2,2,3)
INSERT INTO @YourTable VALUES (2,3,1)
INSERT INTO @YourTable VALUES (2,3,2)
INSERT INTO @YourTable VALUES (2,3,3)
INSERT INTO @YourTable VALUES (3,1,1)
INSERT INTO @YourTable VALUES (3,1,2)
INSERT INTO @YourTable VALUES (3,1,3)
INSERT INTO @YourTable VALUES (3,2,1)
INSERT INTO @YourTable VALUES (3,2,2)
INSERT INTO @YourTable VALUES (3,2,3)
INSERT INTO @YourTable VALUES (3,3,1)
INSERT INTO @YourTable VALUES (3,3,2)
INSERT INTO @YourTable VALUES (3,3,3)
SET NOCOUNT OFF

DECLARE @PageNumber     int
DECLARE @PageSize       int
DECLARE @SortBy         int

SET @PageNumber=3
SET @PageSize=5
SET @SortBy=1


--SELECT * FROM @YourTable

--Method 1
;WITH PaginatedYourTable AS (
SELECT
    RowID,Value1,Value2,Value3
        ,CASE @SortBy
             WHEN  1 THEN ROW_NUMBER() OVER (ORDER BY Value1 ASC)
             WHEN  2 THEN ROW_NUMBER() OVER (ORDER BY Value2 ASC)
             WHEN  3 THEN ROW_NUMBER() OVER (ORDER BY Value3 ASC)
             WHEN -1 THEN ROW_NUMBER() OVER (ORDER BY Value1 DESC)
             WHEN -2 THEN ROW_NUMBER() OVER (ORDER BY Value2 DESC)
             WHEN -3 THEN ROW_NUMBER() OVER (ORDER BY Value3 DESC)
         END AS RowNumber
    FROM @YourTable
    --WHERE
)
SELECT
    RowID,Value1,Value2,Value3,RowNumber
        ,@PageNumber AS PageNumber, @PageSize AS PageSize, @SortBy AS SortBy
    FROM PaginatedYourTable
    WHERE RowNumber>=(@PageNumber-1)*@PageSize AND RowNumber<=(@PageNumber*@PageSize)-1
    ORDER BY RowNumber



--------------------------------------------
--Method 2
;WITH PaginatedYourTable AS (
SELECT
    RowID,Value1,Value2,Value3
        ,ROW_NUMBER() OVER
         (
             ORDER BY
                 CASE @SortBy
                     WHEN  1 THEN Value1
                     WHEN  2 THEN Value2
                     WHEN  3 THEN Value3
                 END ASC
                ,CASE @SortBy
                     WHEN -1 THEN Value1
                     WHEN -2 THEN Value2
                     WHEN -3 THEN Value3
                 END DESC
         ) RowNumber
    FROM @YourTable
    --WHERE  more conditions here
)
SELECT
    RowID,Value1,Value2,Value3,RowNumber
        ,@PageNumber AS PageNumber, @PageSize AS PageSize, @SortBy AS SortBy
    FROM PaginatedYourTable
    WHERE 
        RowNumber>=(@PageNumber-1)*@PageSize AND RowNumber<=(@PageNumber*@PageSize)-1
        --AND more conditions here
    ORDER BY
        CASE @SortBy
            WHEN  1 THEN Value1
            WHEN  2 THEN Value2
            WHEN  3 THEN Value3
        END ASC
       ,CASE @SortBy
            WHEN -1 THEN Value1
            WHEN -2 THEN Value2
            WHEN -3 THEN Value3
        END DESC

PRODUCTION:

RowID  Value1 Value2 Value3 RowNumber  PageNumber  PageSize    SortBy
------ ------ ------ ------ ---------- ----------- ----------- -----------
10     2      1      1      10         3           5           1
11     2      1      2      11         3           5           1
12     2      1      3      12         3           5           1
13     2      2      1      13         3           5           1
14     2      2      2      14         3           5           1

(5 row(s) affected

RowID  Value1 Value2 Value3 RowNumber  PageNumber  PageSize    SortBy
------ ------ ------ ------ ---------- ----------- ----------- -----------
10     2      1      1      10         3           5           1
11     2      1      2      11         3           5           1
12     2      1      3      12         3           5           1
13     2      2      1      13         3           5           1
14     2      2      2      14         3           5           1

(5 row(s) affected)
KM.
la source
1
fyi, lors de l'utilisation de SET SHOWPLAN_ALL ON, la méthode 1 avait un TotalSubtreeCost de 0,08424953, tandis que la méthode 2 était de 0,02627153. la méthode 2 était trois fois meilleure.
KM.
1
@rexem, les méthodes 1 et 2 utilisent les CTE, la façon dont elles paginent et ordonnent les lignes est différente. Je ne sais pas pourquoi cette question réelle est si différente de la question à laquelle l'OP renvoie (dans la réponse à cette question par l'OP), mais ma réponse crée un code de travail basé sur le lien auquel l'OP fait référence
KM.
1
Merci, j'essaye de comparer l'ancien message et ces réponses. [Je ne sais pas comment formater ceci] Voici la réponse fournie par Tomalak. stackoverflow.com/questions/230058?sort=votes#sort-top Est-ce faux? S'il n'a publié que la moitié de la réponse, comment vais-je procéder avec sa meilleure performance pour faire ma requête? S'il vous plaît, donnez-moi un peu plus de lumière pour continuer ... merci
@Joseph, la réponse sélectionnée dans le lien que vous fournissez ( stackoverflow.com/questions/230058?sort=votes#sort-top ) diffère du code de travail que la personne qui pose la question fournit comme travaillant dans sa réponse: stackoverflow.com/ questions / 230058 /… si vous lisez cette réponse, vous verrez un lien vers leur code: pastebin.com/f26a4b403 et un lien vers leur version de Tomalak: pastebin.com/f4db89a8e dans ma réponse Je fournis une version fonctionnelle de chaque version en utilisant variables de table
KM.
2
WITH MyCte AS 
(
    select 
       employee_id,
       RowNum = row_number() OVER (order by employee_id)
    from V_EMPLOYEE 
)
SELECT  employee_id
FROM    MyCte
WHERE   RowNum > 0
ORDER BY employee_id
sumit
la source
-1
 select salary from (
 select  Salary, ROW_NUMBER() over (order by Salary desc) rn from Employee 
 ) t where t.rn = 2
Aziz Khan
la source
3
Bienvenue dans Stack Overflow! Bien que cet extrait de code puisse être la solution, inclure une explication contribue vraiment à améliorer la qualité de votre message. N'oubliez pas que vous répondez à la question aux lecteurs à l'avenir, et que ces personnes pourraient ne pas connaître les raisons de votre suggestion de code.
Johan
Veuillez ajouter un peu de contexte à l' extrait de code pour le bénéfice des futurs lecteurs.
DebanjanB