Função SQL Row_Number () na cláusula Where

90

Encontrei uma pergunta respondida com a Row_Number()função na cláusula where. Quando tentei uma consulta, recebi o seguinte erro:

"Msg 4108, Nível 15, Estado 1, Linha 1 As funções em janela só podem aparecer nas cláusulas SELECT ou ORDER BY."

Aqui está a consulta que tentei. Se alguém souber como resolver isso, por favor me avise.

SELECT employee_id 
FROM V_EMPLOYEE 
WHERE row_number() OVER ( ORDER BY employee_id ) > 0 
ORDER BY Employee_ID
johnnyRose
fonte
9
ROW_NUMBER() OVER (ORDER BY employee_id) > 0sempre avaliará comoTRUE
Quassnoi
3
Sim, isso mesmo. Não estou preocupado com a condição, que posso mudar a qualquer momento. Quero que a consulta funcione primeiro, depois pensando em manter o número de linhas entre 500 e 800 ... obrigado
2
@Joseph: Por que você está tentando evitar o uso de um CTE?
Pôneis OMG
1
@rexem - Não sou um especialista em SQL Server. Estou tentando ajudar uma equipe em um grande projeto onde eles estão enfrentando muitos problemas de desempenho. Eles estão usando UDFs e CTEs. Em uma das tabelas, eles têm apenas 5.000 registros, e se 5 usuários acessarem uma pesquisa, leva mais de um minuto para recuperar. Algum tempo, ele falha e o tempo limite. Portanto, estou tentando evitar CTE e UDFs e tentando criar uma consulta SQL direta que possa resolver os problemas de desempenho.
1
Olá a todos, Por favor, vejam o link que postei abaixo que responde usando row_number () de uma maneira diferente. Alguém pode comparar minha consulta inicial com a do link? Agradeço a ajuda ..

Respostas:

91

Para contornar esse problema, envolva sua instrução select em uma CTE e, então, você pode consultar a CTE e usar os resultados da função em janela na cláusula 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
fonte
7
Estou tentando evitar CTE. Esse é o pior caso que estou procurando. obrigado
3
Ele pode ser executado mais rápido se você usar uma subconsulta em vez de um CTE. Eu vi um desempenho melhor por um fator de 1,5 em alguns casos
Brian Webster,
3
Deve haver também TOP no CTE SELECT, caso contrário, o SQL 2008 Server não executará a consulta por causa de ORDER BY (que não é compatível, a menos que TOP seja usado)
Muflix
2
Estou usando o SQL2005 (ugh) - posso evitar o uso de "TOP", descartando "ORDER BY" após FROM. De qualquer forma, é redundante com (Ordenar por) após OVER.
Joe B de
Gostaria que houvesse uma forma de usar ROW_NUMBER()na WHEREcláusula sem 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

Observe que este filtro é redundante: ROW_NUMBER()começa 1e é sempre maior que 0.

Quassnoi
fonte
2
@ DavideChicco.it: no SQL Server, as tabelas derivadas requerem um alias (eu deveria ter escrito em AS qvez disso, mas também funcionaria).
Quassnoi
2
A legibilidade é um foco que tenho ao nomear apelidos. Você poderia escrever rn como RowNumber eq como DerivedTable e a cláusula where como where DerivedTable.RowNumber> 0. Em minha opinião, isso será muito menos confuso em 6 meses, quando o código não estiver fresco em sua mente.
Edward Comeau,
2
@EdwardComeau: rné um acrônimo meio que universalmente aceito para o número da linha atualmente. Tente digitar "row_number over as ..." na string de pesquisa do Google e veja o que isso sugere para você.
Quassnoi
3
@Quassnoi, a legibilidade é a chave para uma boa codificação e o esforço cognitivo de traduzir rn (ou outros aliases abreviados) aumenta para você e para as pessoas que mantêm seu código. NB, o primeiro hit da Microsoft, SELECT ROW_NUMBER () OVER (ORDER BY SalesYTD DESC) AS Row, ... Eu também não encontrei rn antes, então sua milhagem em "universal" pode variar.
Edward Comeau,
1
@Quassnoi, e segundo hit, artigo SO - stackoverflow.com/questions/961007/how-do-i-use-row-number diversas variações e não 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
fonte
19

Eu acho que você quer algo assim:

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
fonte
4
Crie um alias para a tabela se a consulta acima não funcionar para você. Modifique a penúltima linha, pois From V_EMPLOYEE) Aadiciona A como alias.
Hammad Khan
7

Em resposta aos comentários sobre a resposta do rexem, com relação a se uma visão inline ou CTE seria mais rápida, eu reformulei as consultas para usar uma tabela que I, e todos, tinham disponível: 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

Os planos de consulta produzidos eram exatamente os mesmos. Eu esperaria que, em todos os casos, o otimizador de consulta surgisse com o mesmo plano, pelo menos na simples substituição de CTE por visualização em linha ou vice-versa.

Claro, tente suas próprias consultas em seu próprio sistema para ver se há uma diferença.

Além disso, row_number()na cláusula where é um erro comum nas respostas dadas no Stack Overflow. Logicamente row_number()não está disponível até que a cláusula de seleção seja processada. As pessoas esquecem isso e, quando respondem sem testar a resposta, às vezes a resposta está errada. (Uma acusação da qual eu mesmo sou culpado.)

Shannon Severance
fonte
1
Thx Shannon. Qual versão do SQL Server você estava usando?
Pôneis OMG
1
Isso significa que a resposta fornecida nesse link está errada? Mas, a pessoa que postou a pergunta concordou que está funcionando .. Surpreendente .. :-)
2
@Joseph, mas se você olhar para outra resposta postada pelo OP na pergunta vinculada, verá que ele vincula a uma versão do código que não é a mesma da resposta aceita. Não sei por que ele aceitou a resposta, embora não saísse como digitada. Talvez tenha sido editado em algum momento após ter sido aceito, talvez tenha sido o suficiente para que ele andasse, mesmo sem estar totalmente correto.
Shannon Severance
1
@Rexem: SQL Server 2005 e SQL Server 2008. As versões anteriores não oferecem suporte a CTEs ou ROW_NUMBER ()
Shannon Severance
6

Acho que todas as respostas que mostram o uso de um CTE ou Sub Query são correções suficientes para isso, mas não vejo ninguém chegando ao cerne de por que OP tem um problema. O motivo pelo qual o OP sugerido não funciona é devido à ordem lógica de processamento da consulta aqui:

  1. DE
  2. EM
  3. JUNTE-SE
  4. ONDE
  5. GRUPO POR
  6. COM CUBO / ROLLUP
  7. TENDO
  8. SELECIONE
  9. DISTINTO
  10. ORDENAR POR
  11. TOPO
  12. OFFSET / FETCH

Acredito que isso contribui muito para a resposta, porque explica por que problemas como este ocorrem. WHEREé sempre processado antes de SELECTfazer uma CTE ou Sub Query necessária para muitas funções. Você verá muito isso no SQL Server.

Jamie Marshall
fonte
4

Usando 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

Usando visualização embutida / alternativa equivalente não 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
Pôneis OMG
fonte
1
Qual deles tem melhor desempenho? Usando CTE ou subconsulta? obrigado
1
Veja a resposta de Shannon - em seu teste eles são iguais.
Pôneis OMG
6
Não, não é mais rápido. As visualizações In SQL Server, CTE'se Inline são a mesma coisa e têm o mesmo desempenho. Quando funções não determinísticas são usadas em a CTE, ela é reavaliada a cada chamada. É preciso usar truques sujos para forçar a materialização de um CTE. Veja estes artigos em meu blog: explainextended.com/2009/07/28/… explainextended.com/2009/05/28/generating-xml-in-subqueries
Quassnoi
2

com base na resposta do OP à pergunta:

Por favor, veja este link. É ter uma solução diferente, que parece funcionar para quem fez a pergunta. Estou tentando descobrir uma solução como esta.

Consulta paginada usando classificação em colunas diferentes usando ROW_NUMBER () OVER () no SQL Server 2005

~ Joseph

o "método 1" é como a consulta do OP da pergunta vinculada e o "método 2" é como a consulta da resposta selecionada. Você teve que olhar o código vinculado a esta resposta para ver o que realmente estava acontecendo, já que o código na resposta selecionada foi modificado para funcionar. Experimente isto:

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

RESULTADO:

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.
fonte
1
fyi, ao usar o método SET SHOWPLAN_ALL ON 1 teve um TotalSubtreeCost de 0,08424953, enquanto o método 2 foi de 0,02627153. o método 2 foi três vezes melhor.
KM.
1
@rexem, os métodos 1 e 2 usam CTEs, a maneira como eles paginam e ordenam as linhas é diferente. Não sei por que essa pergunta real é tão diferente da pergunta para a qual o OP se vincula (na resposta a esta pergunta pelo OP), mas minha resposta cria um código funcional com base no link ao qual o OP se refere
KM.
1
Obrigado, estou tentando comparar o post antigo e essas respostas. [Não sei como formatar isso] Aqui está a resposta fornecida por Tomalak. stackoverflow.com/questions/230058?sort=votes#sort-top Isso está errado? Se ele postou apenas metade da resposta, como continuarei com sua maneira de fazer minha consulta com melhor desempenho? Por favor, me dê mais luz para prosseguir .. obrigado
@Joseph, a resposta selecionada no link que você fornece ( stackoverflow.com/questions/230058?sort=votes#sort-top ) difere do código de trabalho que a pessoa que fez a pergunta fornece e funciona em sua resposta: stackoverflow.com/ question / 230058 /… se você ler essa resposta, verá um link para o código: pastebin.com/f26a4b403 e um link para a versão do Tomalak: pastebin.com/f4db89a8e em minha resposta, forneço uma versão funcional de cada versão usando variáveis ​​da tabela
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
fonte
-1
 select salary from (
 select  Salary, ROW_NUMBER() over (order by Salary desc) rn from Employee 
 ) t where t.rn = 2
Aziz Khan
fonte
3
Bem-vindo ao Stack Overflow! Embora este snippet de código possa ser a solução, incluir uma explicação realmente ajuda a melhorar a qualidade de sua postagem. Lembre-se de que você está respondendo à pergunta para leitores no futuro e essas pessoas podem não saber os motivos de sua sugestão de código.
Johan
Adicione algum contexto ao snippet de código para o benefício dos leitores futuros.
DebanjanB