Función SQL Row_Number () en la cláusula Where

90

Encontré una pregunta respondida con la Row_Number()función en la cláusula where. Cuando probé una consulta, recibí el siguiente error:

"Msg 4108, nivel 15, estado 1, línea 1 Las funciones con ventana solo pueden aparecer en las cláusulas SELECT o ORDER BY".

Aquí está la consulta que intenté. Si alguien sabe cómo resolver esto, hágamelo saber.

SELECT employee_id 
FROM V_EMPLOYEE 
WHERE row_number() OVER ( ORDER BY employee_id ) > 0 
ORDER BY Employee_ID
JohnnyRose
fuente
9
ROW_NUMBER() OVER (ORDER BY employee_id) > 0siempre evaluará aTRUE
Quassnoi
3
Sí, eso es correcto. No me preocupa la condición, que puedo cambiar en cualquier momento. Quiero que la consulta funcione primero, luego pienso en mantener el número de fila entre 500 y 800 ... gracias
2
@Joseph: ¿Por qué estás tratando de evitar el uso de un CTE?
OMG Ponies
1
@rexem: no soy un experto en SQL Server. Estoy tratando de ayudar a un equipo en un gran proyecto en el que se enfrentan a muchos problemas de rendimiento. Están usando UDF y CTE. En una de las tablas, tienen solo 5000 registros, y si 5 usuarios acceden a una búsqueda, se tarda más de un minuto en recuperar. En algún momento, falla y se agota el tiempo. Por lo tanto, estoy tratando de evitar CTE y UDF y tratando de crear una consulta SQL directa que pueda resolver los problemas de rendimiento.
1
Hola a todos, Por favor, vean el enlace que publiqué a continuación, que responde usando row_number () de una manera diferente. ¿Alguien puede comparar mi consulta inicial con la del enlace? Aprecio la ayuda ..

Respuestas:

91

Para solucionar este problema, envuelva su declaración de selección en un CTE, y luego puede consultar el CTE y usar los resultados de la función en ventana en la 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
fuente
7
Estoy tratando de evitar la CTE. Ese es el peor caso que estoy buscando. gracias
3
Puede ejecutarse más rápido si usa una subconsulta en lugar de un CTE. He visto un mejor desempeño por un factor de 1.5 en algunos casos
Brian Webster
3
También debe haber TOP en CTE SELECT; de lo contrario, SQL 2008 Server no ejecutará la consulta debido a ORDER BY (que no es compatible a menos que se use TOP)
Muflix
2
Estoy usando SQL2005 (ugh) - Puedo evitar el uso de "TOP", dejando caer "ORDER BY" después de FROM. De todos modos, es redundante con el (Ordenar por) después del OVER.
Joe B
Deseaba que hubiera una forma de usar la cláusula ROW_NUMBER()en WHEREsin 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

Tenga en cuenta que este filtro es redundante: ROW_NUMBER()comienza desde 1y siempre es mayor que 0.

Quassnoi
fuente
2
@ DavideChicco.it: en SQL Server, las tablas derivadas requieren un alias (debería haber escrito en su AS qlugar, pero esto tampoco funcionaría).
Quassnoi
2
La legibilidad es un enfoque que tengo al nombrar alias. Puede escribir rn como RowNumber yq como DerivedTable y la cláusula where como where DerivedTable.RowNumber> 0. En mi opinión, esto será mucho menos confuso en 6 meses cuando el código no esté fresco en su mente.
Edward Comeau
2
@EdwardComeau: rnes un acrónimo universalmente aceptado para el número de fila en estos días. Intente escribir "número_de_fila como ..." en la cadena de búsqueda de Google y vea lo que le sugiere.
Quassnoi
3
@Quassnoi, la legibilidad es clave para una buena codificación y el esfuerzo cognitivo de traducir rn (u otros alias abreviados) se suma para usted y las personas que mantienen su código. NB, primer golpe de Microsoft, SELECT ROW_NUMBER () OVER (ORDER BY SalesYTD DESC) AS Row, ... Tampoco me he encontrado con rn antes, por lo que su kilometraje en "universal" puede variar.
Edward Comeau
1
@Quassnoi, y segundo éxito, artículo SO - stackoverflow.com/questions/961007/how-do-i-use-row-number varias variaciones y no 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
fuente
19

Creo que quieres algo como esto:

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
fuente
4
Cree un alias para la tabla si la consulta anterior no le funciona. Modifique la penúltima línea, ya From V_EMPLOYEE) Aque agrega A como alias.
Hammad Khan
7

En respuesta a los comentarios sobre la respuesta de rexem, con respecto a si una vista en línea o CTE sería más rápida, modifiqué las consultas para usar una tabla que yo, y todos, teníamos 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

Los planes de consulta producidos fueron exactamente los mismos. En todos los casos, esperaría que el optimizador de consultas presentara el mismo plan, al menos en un simple reemplazo de CTE con vista en línea o viceversa.

Por supuesto, pruebe sus propias consultas en su propio sistema para ver si hay alguna diferencia.

Además, row_number()en la cláusula where hay un error común en las respuestas dadas en Stack Overflow. Logicaly row_number()no está disponible hasta que se procesa la cláusula de selección. Las personas olvidan eso y cuando responden sin probar la respuesta, la respuesta a veces es incorrecta. (Un cargo del que yo mismo he sido culpable).

Despido de Shannon
fuente
1
Thx Shannon. ¿Qué versión de SQL Server estaba usando?
OMG Ponies
1
¿Eso significa que la respuesta proporcionada en ese enlace es incorrecta? Pero, la persona que publicó la pregunta estuvo de acuerdo en que funciona ... Sorprendente ... :-)
2
@Joseph, pero si miras otra respuesta publicada por el OP en la pregunta vinculada, verás que se vincula a una versión del código que no es la misma que en la respuesta aceptada. No sé por qué aceptó la respuesta, aunque no se publicaría como se ingresó. Tal vez fue editado en algún momento después de ser aceptado, tal vez fue suficiente para ponerlo en marcha, incluso sin ser totalmente correcto.
Shannon Severance
1
@Rexem: SQL Server 2005 y SQL Server 2008. Las versiones anteriores no admiten CTE ni ROW_NUMBER ()
Shannon Severance
6

Siento que todas las respuestas que muestran el uso de CTE o Sub Query son soluciones suficientes para esto, pero no veo que nadie llegue al corazón de por qué OP tiene un problema. La razón por la que lo que OP sugirió no funciona se debe al orden de procesamiento de consultas lógicas aquí:

  1. DE
  2. EN
  3. UNIRSE
  4. DÓNDE
  5. AGRUPAR POR
  6. CON CUBO / ROLLUP
  7. TENIENDO
  8. SELECCIONE
  9. DISTINTO
  10. PEDIR POR
  11. PARTE SUPERIOR
  12. OFFSET / FETCH

Creo que esto contribuye en gran medida a la respuesta, porque explica por qué ocurren problemas como este. WHEREsiempre se procesa antes de SELECThacer un CTE o Sub Query necesario para muchas funciones. Verá esto mucho en SQL Server.

Jamie Marshall
fuente
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

Uso de la vista en línea / alternativa no equivalente a 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
Ponis dios mio
fuente
1
¿Cuál es mejor en rendimiento? ¿Utiliza CTE o subconsulta? gracias
1
Vea la respuesta de Shannon: en su prueba son iguales.
OMG Ponies
6
No, no es más rápido. En SQL Server, CTE's y vistas en línea son la misma cosa y tener el mismo rendimiento. Cuando se utilizan funciones no deterministas en a CTE, se reevalúan en cada llamada. Uno tiene que usar trucos sucios para forzar la materialización de un CTE. Vea estos artículos en mi blog: explainextended.com/2009/07/28/… explainextended.com/2009/05/28/generating-xml-in-subqueries
Quassnoi
2

basado en la respuesta de OP a la pregunta:

Consulte este enlace. Está teniendo una solución diferente, que parece funcionar para la persona que hizo la pregunta. Estoy tratando de encontrar una solución como esta.

Consulta paginada usando la clasificación en diferentes columnas usando ROW_NUMBER () OVER () en SQL Server 2005

~ José

El "método 1" es como la consulta del OP de la pregunta vinculada, y el "método 2" es como la consulta de la respuesta seleccionada. Tenía que mirar el código vinculado en esta respuesta para ver qué estaba sucediendo realmente, ya que el código en la respuesta seleccionada se modificó para que funcione. Prueba esto:

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

SALIDA:

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.
fuente
1
fyi, cuando se usa SET SHOWPLAN_ALL ON, el método 1 tenía un Costo TotalSubtree de 0.08424953, mientras que el método 2 estaba en 0.02627153. el método 2 fue tres veces mejor.
KM.
1
@rexem, tanto el método 1 como el 2 usan CTE, la forma en que paginan y ordenan las filas es diferente. No estoy seguro de por qué esta pregunta real es tan diferente de la pregunta a la que se vincula el OP (en la respuesta a esta pregunta del OP), pero mi respuesta crea un código de trabajo basado en el enlace al que se refiere el OP
KM.
1
Gracias, estoy tratando de comparar la publicación anterior y esta respuesta. [No sé cómo formatear esto] Aquí está la respuesta proporcionada por Tomalak. stackoverflow.com/questions/230058?sort=votes#sort-top ¿Esto está mal? Si publicó solo la mitad de la respuesta, ¿cómo voy a seguir adelante con su mejor forma de realizar mi consulta? Por favor, dame un poco más de luz para continuar ... gracias
@Joseph, la respuesta seleccionada en el enlace que proporciona ( stackoverflow.com/questions/230058?sort=votes#sort-top ) difiere del código de trabajo que la persona que hace la pregunta proporciona como trabajo en su respuesta: stackoverflow.com/ preguntas / 230058 / ... si lees esa respuesta, verás un enlace a su código: pastebin.com/f26a4b403 y un enlace a su versión de Tomalak: pastebin.com/f4db89a8e en mi respuesta proporciono una versión funcional de cada versión usando variables de tabla
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
sumitir
fuente
-1
 select salary from (
 select  Salary, ROW_NUMBER() over (order by Salary desc) rn from Employee 
 ) t where t.rn = 2
Aziz Khan
fuente
3
¡Bienvenido a Stack Overflow! Si bien este fragmento de código puede ser la solución, incluir una explicación realmente ayuda a mejorar la calidad de su publicación. Recuerde que está respondiendo la pregunta para los lectores en el futuro, y es posible que esas personas no conozcan los motivos de su sugerencia de código.
Johan
Agregue algo de contexto al fragmento de código para beneficio de los futuros lectores.
DebanjanB