sql >> Base de Datos >  >> RDS >> Database

Números de fila con orden no determinista

La función de ventana ROW_NUMBER tiene numerosas aplicaciones prácticas, mucho más allá de las necesidades obvias de clasificación. La mayoría de las veces, cuando calcula números de fila, necesita calcularlos en función de algún orden, y proporciona la especificación de orden deseada en la cláusula de orden de ventana de la función. Sin embargo, hay casos en los que necesita calcular los números de fila sin ningún orden en particular; en otras palabras, basado en un orden no determinista. Esto podría ser en todo el resultado de la consulta o dentro de las particiones. Los ejemplos incluyen la asignación de valores únicos a las filas de resultados, la deduplicación de datos y la devolución de cualquier fila por grupo.

Tenga en cuenta que la necesidad de asignar números de fila según un orden no determinista es diferente a la necesidad de asignarlos según un orden aleatorio. Con el primero, simplemente no importa en qué orden se asignan, y si las ejecuciones repetidas de la consulta siguen asignando los mismos números de fila a las mismas filas o no. Con este último, espera que las ejecuciones repetidas sigan cambiando qué filas se asignan con qué números de fila. Este artículo explora diferentes técnicas para calcular números de fila con orden no determinista. La esperanza es encontrar una técnica que sea confiable y óptima.

¡Un agradecimiento especial a Paul White por el consejo sobre el plegado constante, por la técnica de tiempo de ejecución constante y por ser siempre una gran fuente de información!

Cuando el orden importa

Comenzaré con los casos en los que el orden de los números de fila es importante.

Usaré una tabla llamada T1 en mis ejemplos. Use el siguiente código para crear esta tabla y llénela con datos de muestra:

SET NOCOUNT ON;
 
USE tempdb;
 
DROP TABLE IF EXISTS dbo.T1;
GO
 
CREATE TABLE dbo.T1
(
  id INT NOT NULL CONSTRAINT PK_T1 PRIMARY KEY,
  grp VARCHAR(10) NOT NULL,
  datacol INT NOT NULL
);
 
INSERT INTO dbo.T1(id, grp, datacol) VALUES
  (11, 'A', 50),
  ( 3, 'B', 20),
  ( 5, 'A', 40),
  ( 7, 'B', 10),
  ( 2, 'A', 50);

Considere la siguiente consulta (la llamaremos Consulta 1):

SELECT id, grp, datacol,
  ROW_NUMBER() OVER(PARTITION BY grp ORDER BY datacol) AS n 
FROM dbo.T1;

Aquí desea que se asignen números de fila dentro de cada grupo identificado por la columna grp, ordenados por la columna datacol. Cuando ejecuté esta consulta en mi sistema, obtuve el siguiente resultado:

id  grp  datacol  n
--- ---- -------- ---
5   A    40       1
2   A    50       2
11  A    50       3
7   B    10       1
3   B    20       2

Los números de fila se asignan aquí en un orden parcialmente determinista y parcialmente no determinista. Lo que quiero decir con esto es que tiene la seguridad de que dentro de la misma partición, una fila con un valor de datacol mayor obtendrá un valor de número de fila mayor. Sin embargo, dado que datacol no es único dentro de la partición grp, el orden de asignación de números de fila entre filas con los mismos valores grp y datacol no es determinista. Tal es el caso de las filas con valores de id 2 y 11. Ambos tienen el valor grp A y el valor datacol 50. Cuando ejecuté esta consulta en mi sistema por primera vez, la fila con id 2 obtuvo el número de fila 2 y el la fila con id 11 obtuvo la fila número 3. No importa la probabilidad de que esto suceda en la práctica en SQL Server; si vuelvo a ejecutar la consulta, teóricamente, la fila con id 2 podría asignarse con el número de fila 3 y la fila con id 11 podría asignarse con el número de fila 2.

Si necesita asignar números de fila en función de un orden completamente determinista, garantizando resultados repetibles en las ejecuciones de la consulta, siempre que los datos subyacentes no cambien, necesita que la combinación de elementos en las cláusulas de partición y orden de la ventana sea única. Esto podría lograrse en nuestro caso agregando la identificación de la columna a la cláusula de orden de la ventana como desempate. La cláusula OVER sería entonces:

OVER (PARTITION BY grp ORDER BY datacol, id)

En cualquier caso, cuando se calculan los números de fila en función de alguna especificación de ordenación significativa, como en la Consulta 1, SQL Server necesita procesar las filas ordenadas por la combinación de partición de ventana y elementos de ordenación. Esto se puede lograr extrayendo los datos ordenados previamente de un índice u ordenando los datos. Por el momento, no hay un índice en T1 para respaldar el cálculo de ROW_NUMBER en la Consulta 1, por lo que SQL Server tiene que optar por ordenar los datos. Esto se puede ver en el plan para la Consulta 1 que se muestra en la Figura 1.

Figura 1:Plan para la Consulta 1 sin un índice de soporte

Observe que el plan analiza los datos del índice agrupado con una propiedad Ordenado:Falso. Esto significa que el escaneo no necesita devolver las filas ordenadas por la clave de índice. Ese es el caso, ya que el índice agrupado se usa aquí solo porque cubre la consulta y no por su orden clave. Luego, el plan aplica una ordenación, lo que genera un costo adicional, una escala N Log N y un tiempo de respuesta retrasado. El operador Segmento produce una bandera que indica si la fila es la primera en la partición o no. Finalmente, el operador Sequence Project asigna números de fila que comienzan con 1 en cada partición.

Si desea evitar la necesidad de ordenar, puede preparar un índice de cobertura con una lista clave basada en los elementos de partición y ordenación, y una lista de inclusión basada en los elementos de cobertura. Me gusta pensar en este índice como un índice POC (para particiones , pedir y cubrir ). Esta es la definición del POC que respalda nuestra consulta:

CREATE INDEX idx_grp_data_i_id ON dbo.T1(grp, datacol) INCLUDE(id);

Ejecute Consulta 1 de nuevo:

SELECT id, grp, datacol,
  ROW_NUMBER() OVER(PARTITION BY grp ORDER BY datacol) AS n 
FROM dbo.T1;

El plan para esta ejecución se muestra en la Figura 2.

Figura 2:Plan para la Consulta 1 con un índice POC

Observe que esta vez el plan escanea el índice POC con una propiedad Ordenado:Verdadero. Esto significa que la exploración garantiza que las filas se devolverán en orden de clave de índice. Dado que los datos se extraen ordenados previamente del índice como lo necesita la función de ventana, no hay necesidad de una clasificación explícita. El escalado de este plan es lineal y el tiempo de respuesta es bueno.

Cuando el orden no importa

Las cosas se complican un poco cuando necesita asignar números de fila con un orden completamente no determinista. Lo natural que se desea hacer en tal caso es usar la función ROW_NUMBER sin especificar una cláusula de orden de ventana. Primero, verifiquemos si el estándar SQL lo permite. Aquí está la parte relevante del estándar que define las reglas de sintaxis para las funciones de ventana:

Reglas de sintaxis

5) Sea WNS el . Sea WDX un descriptor de estructura de ventana que describe la ventana definida por WNS.

6) Si se especifica , , o ROW_NUMBER, entonces:

a) Si se especifica , , RANK o DENSE_RANK, entonces estará presente la cláusula de ordenación de ventanas WOC de WDX.

f) ROW_NUMBER() OVER WNS es equivalente a la :COUNT (*) OVER (WNS1 ROWS UNBOUNDED PRECEDING)

Observe que el ítem 6 enumera las funciones , , o NÚMERO_FILA, y luego el ítem 6a dice que para las funciones , , RANGO o DENSE_RANK la cláusula de orden de ventana estará presente. No hay un lenguaje explícito que indique si ROW_NUMBER requiere una cláusula de orden de ventana o no, pero la mención de la función en el elemento 6 y su omisión en 6a podría implicar que la cláusula es opcional para esta función. Es bastante obvio por qué funciones como RANK y DENSE_RANK requerirían una cláusula de orden de ventana, ya que estas funciones se especializan en el manejo de vínculos, y los vínculos solo existen cuando hay una especificación de orden. Sin embargo, ciertamente podría ver cómo la función ROW_NUMBER podría beneficiarse de una cláusula de orden de ventana opcional.

Entonces, intentémoslo e intentemos calcular los números de fila sin ordenar las ventanas en SQL Server:

SELECT id, grp, datacol,
  ROW_NUMBER() OVER() AS n 
FROM dbo.T1;

Este intento da como resultado el siguiente error:

Mensaje 4112, Nivel 15, Estado 1, Línea 53
La función 'ROW_NUMBER' debe tener una cláusula OVER con ORDER BY.

De hecho, si consulta la documentación de SQL Server de la función ROW_NUMBER, encontrará el siguiente texto:

“order_by_clause

La cláusula ORDER BY determina la secuencia en la que a las filas se les asigna su ROW_NUMBER único dentro de una partición específica. Es obligatorio.”

Entonces, aparentemente, la cláusula de orden de ventana es obligatoria para la función ROW_NUMBER en SQL Server. Ese también es el caso en Oracle, por cierto.

Debo decir que no estoy seguro de entender el razonamiento detrás de este requisito. Recuerde que está permitiendo definir números de fila en función de un orden parcialmente no determinista, como en la Consulta 1. Entonces, ¿por qué no permitir el no determinismo en todo momento? Tal vez hay alguna razón en la que no estoy pensando. Si se te ocurre una razón de este tipo, compártela.

En cualquier caso, podría argumentar que si no le importa el orden, dado que la cláusula de orden de ventana es obligatoria, puede especificar cualquier orden. El problema con este enfoque es que si ordena por alguna columna de las tablas consultadas, esto podría implicar una penalización de rendimiento innecesaria. Cuando no haya un índice de respaldo, pagará por una clasificación explícita. Cuando hay un índice de soporte en su lugar, está limitando el motor de almacenamiento a una estrategia de escaneo de orden de índice (siguiendo la lista de enlaces de índice). No le permite más flexibilidad como la que suele tener cuando el orden no importa al elegir entre un escaneo de orden de índice y un escaneo de orden de asignación (basado en páginas de IAM).

Una idea que vale la pena probar es especificar una constante, como 1, en la cláusula de orden de la ventana. Si es compatible, esperaría que el optimizador sea lo suficientemente inteligente como para darse cuenta de que todas las filas tienen el mismo valor, por lo que no hay una relevancia de orden real y, por lo tanto, no es necesario forzar una ordenación o un escaneo de orden de índice. Aquí hay una consulta que intenta este enfoque:

SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY 1) AS n 
FROM dbo.T1;

Desafortunadamente, SQL Server no admite esta solución. Genera el siguiente error:

Mensaje 5308, Nivel 16, Estado 1, Línea 56
Las funciones de ventana, los agregados y las funciones NEXT VALUE FOR no admiten índices enteros como expresiones de la cláusula ORDER BY.

Aparentemente, SQL Server asume que si está utilizando una constante de entero en la cláusula de orden de ventana, representa una posición ordinal de un elemento en la lista SELECT, como cuando especifica un entero en la cláusula de presentación ORDER BY. Si ese es el caso, otra opción que vale la pena probar es especificar una constante no entera, así:

SELECT id, grp, datacol,
  ROW_NUMBER() OVER(ORDER BY 'No Order') AS n 
FROM dbo.T1;

Resulta que esta solución tampoco es compatible. SQL Server genera el siguiente error:

Mensaje 5309, nivel 16, estado 1, línea 65
Las funciones de ventana, los agregados y las funciones NEXT VALUE FOR no admiten constantes como expresiones de la cláusula ORDER BY.

Aparentemente, la cláusula de orden de ventana no admite ningún tipo de constante.

Hasta ahora, hemos aprendido lo siguiente sobre la relevancia de la ordenación de ventanas de la función ROW_NUMBER en SQL Server:

  • Se requiere ORDENAR POR.
  • No se puede ordenar por una constante entera ya que SQL Server cree que está tratando de especificar una posición ordinal en SELECT.
  • No se puede ordenar por ningún tipo de constante.

    La conclusión es que se supone que debes ordenar por expresiones que no son constantes. Obviamente, puede ordenar por una lista de columnas de la(s) tabla(s) consultada(s). Pero estamos en una búsqueda para encontrar una solución eficiente donde el optimizador pueda darse cuenta de que no hay relevancia en los pedidos.

    Plegado constante

    La conclusión hasta ahora es que no puede usar constantes en la cláusula de orden de ventana de ROW_NUMBER, pero ¿qué pasa con las expresiones basadas en constantes, como en la siguiente consulta:

    SELECT id, grp, datacol,
      ROW_NUMBER() OVER(ORDER BY 1+0) AS n 
    FROM dbo.T1;

    Sin embargo, este intento es víctima de un proceso conocido como plegamiento constante, que normalmente tiene un impacto positivo en el rendimiento de las consultas. La idea detrás de esta técnica es mejorar el rendimiento de las consultas plegando alguna expresión basada en constantes a sus constantes de resultado en una etapa temprana del procesamiento de la consulta. Puede encontrar detalles sobre qué tipos de expresiones se pueden doblar constantemente aquí. Nuestra expresión 1+0 se pliega a 1, lo que da como resultado el mismo error que obtuvo al especificar la constante 1 directamente:

    Mensaje 5308, nivel 16, estado 1, línea 79
    Las funciones de ventana, los agregados y las funciones NEXT VALUE FOR no admiten índices enteros como expresiones de la cláusula ORDER BY.

    Te enfrentarías a una situación similar al intentar concatenar dos cadenas de caracteres literales, así:

    SELECT id, grp, datacol,
      ROW_NUMBER() OVER(ORDER BY 'No' + ' Order') AS n 
    FROM dbo.T1;

    Obtiene el mismo error que obtuvo al especificar el literal 'Sin orden' directamente:

    Mensaje 5309, nivel 16, estado 1, línea 55
    Las funciones de ventana, los agregados y las funciones NEXT VALUE FOR no admiten constantes como expresiones de la cláusula ORDER BY.

    Mundo bizarro:errores que evitan errores

    La vida está llena de sorpresas...

    Una cosa que evita el plegado constante es cuando la expresión normalmente resultaría en un error. Por ejemplo, la expresión 2147483646+1 se puede doblar constantemente ya que da como resultado un valor de tipo INT válido. En consecuencia, falla un intento de ejecutar la siguiente consulta:

    SELECT id, grp, datacol,
      ROW_NUMBER() OVER(ORDER BY 2147483646+1) AS n 
    FROM dbo.T1;
    Mensaje 5308, nivel 16, estado 1, línea 109
    Las funciones de ventana, los agregados y las funciones NEXT VALUE FOR no admiten índices enteros como expresiones de la cláusula ORDER BY.

    Sin embargo, la expresión 2147483647+1 no puede doblarse constantemente porque tal intento habría resultado en un error de desbordamiento de INT. La implicación en el pedido es bastante interesante. Pruebe la siguiente consulta (la llamaremos Consulta 2):

    SELECT id, grp, datacol,
      ROW_NUMBER() OVER(ORDER BY 2147483647+1) AS n 
    FROM dbo.T1;

    Curiosamente, ¡esta consulta se ejecuta correctamente! Lo que sucede es que por un lado SQL Server no logra aplicar el plegamiento de constantes, y por lo tanto el ordenamiento se basa en una expresión que no es una sola constante. Por otro lado, el optimizador calcula que el valor de ordenación es el mismo para todas las filas, por lo que ignora la expresión de ordenación por completo. Esto se confirma al examinar el plan para esta consulta como se muestra en la Figura 3.

    Figura 3:Plan para la consulta 2

    Observe que el plan escanea algún índice de cobertura con una propiedad Ordenado:Falso. Este era exactamente nuestro objetivo de rendimiento.

    De manera similar, la siguiente consulta implica un intento de plegamiento constante exitoso y, por lo tanto, falla:

    SELECT id, grp, datacol,
      ROW_NUMBER() OVER(ORDER BY 1/1) AS n 
    FROM dbo.T1;
    Mensaje 5308, Nivel 16, Estado 1, Línea 123
    Las funciones de ventana, los agregados y las funciones NEXT VALUE FOR no admiten índices enteros como expresiones de la cláusula ORDER BY.

    La siguiente consulta implica un intento fallido de plegado constante y, por lo tanto, tiene éxito, generando el plan que se muestra anteriormente en la Figura 3:

    SELECT id, grp, datacol,
      ROW_NUMBER() OVER(ORDER BY 1/0) AS n 
    FROM dbo.T1;

    La siguiente consulta implica un intento exitoso de plegado constante (el literal '1' de VARCHAR se convierte implícitamente a INT 1 y luego 1 + 1 se pliega a 2) y, por lo tanto, falla:

    SELECT id, grp, datacol,
      ROW_NUMBER() OVER(ORDER BY 1+'1') AS n 
    FROM dbo.T1;
    Mensaje 5308, nivel 16, estado 1, línea 134
    Las funciones de ventana, los agregados y las funciones NEXT VALUE FOR no admiten índices enteros como expresiones de la cláusula ORDER BY.

    La siguiente consulta implica un intento de plegado constante fallido (no se puede convertir 'A' en INT) y, por lo tanto, tiene éxito, generando el plan que se muestra anteriormente en la Figura 3:

    SELECT id, grp, datacol,
      ROW_NUMBER() OVER(ORDER BY 1+'A') AS n 
    FROM dbo.T1;

    Para ser honesto, a pesar de que esta técnica extraña logra nuestro objetivo de rendimiento original, no puedo decir que la considere segura y, por lo tanto, no me siento tan cómodo confiando en ella.

    Constantes de ejecución basadas en funciones

    Continuando con la búsqueda de una buena solución para calcular números de fila con un orden no determinista, existen algunas técnicas que parecen más seguras que la última solución peculiar:usar constantes de tiempo de ejecución basadas en funciones, usar una subconsulta basada en una constante, usar una columna con alias basada en una constante y usando una variable.

    Como explico en Errores, trampas y mejores prácticas de T-SQL:determinismo, la mayoría de las funciones en T-SQL se evalúan solo una vez por referencia en la consulta, no una vez por fila. Este es el caso incluso con la mayoría de las funciones no deterministas como GETDATE y RAND. Hay muy pocas excepciones a esta regla, como las funciones NEWID y CRYPT_GEN_RANDOM, que se evalúan una vez por fila. La mayoría de las funciones, como GETDATE, @@SPID y muchas otras, se evalúan una vez al comienzo de la consulta y sus valores se consideran constantes de tiempo de ejecución. Una referencia a tales funciones no se pliega constantemente. Estas características hacen que una constante de tiempo de ejecución que se basa en una función sea una buena opción como elemento de ordenación de ventanas y, de hecho, parece que T-SQL lo admite. Al mismo tiempo, el optimizador se da cuenta de que en la práctica no hay relevancia en los pedidos, lo que evita penalizaciones de rendimiento innecesarias.

    Aquí hay un ejemplo usando la función GETDATE:

    SELECT id, grp, datacol,
      ROW_NUMBER() OVER(ORDER BY GETDATE()) AS n 
    FROM dbo.T1;

    Esta consulta obtiene el mismo plan que se muestra anteriormente en la Figura 3.

    Aquí hay otro ejemplo que usa la función @@SPID (que devuelve el ID de la sesión actual):

    SELECT id, grp, datacol,
      ROW_NUMBER() OVER(ORDER BY @@SPID) AS n 
    FROM dbo.T1;

    ¿Qué pasa con la función PI? Pruebe la siguiente consulta:

    SELECT id, grp, datacol,
      ROW_NUMBER() OVER(ORDER BY PI()) AS n 
    FROM dbo.T1;

    Este falla con el siguiente error:

    Mensaje 5309, nivel 16, estado 1, línea 153
    Las funciones de ventana, los agregados y las funciones NEXT VALUE FOR no admiten constantes como expresiones de la cláusula ORDER BY.

    Funciones como GETDATE y @@SPID se vuelven a evaluar una vez por ejecución del plan, por lo que no se pueden doblar constantemente. PI representa siempre la misma constante y, por lo tanto, se pliega constantemente.

    Como se mencionó anteriormente, hay muy pocas funciones que se evalúan una vez por fila, como NEWID y CRYPT_GEN_RANDOM. Esto los convierte en una mala elección como elemento de ordenación de ventanas si necesita un orden no determinista, que no debe confundirse con un orden aleatorio. ¿Por qué pagar una penalización por clasificación innecesaria?

    Aquí hay un ejemplo usando la función NEWID:

    SELECT id, grp, datacol,
      ROW_NUMBER() OVER(ORDER BY NEWID()) AS n 
    FROM dbo.T1;

    El plan para esta consulta se muestra en la Figura 4, lo que confirma que SQL Server agregó una clasificación explícita basada en el resultado de la función.

    Figura 4:Plan para Consulta 3

    Si desea que los números de fila se asignen en orden aleatorio, por supuesto, esa es la técnica que desea utilizar. Solo debe tener en cuenta que incurre en el costo de clasificación.

    Usando una subconsulta

    También puede utilizar una subconsulta basada en una constante como expresión de ordenación de ventanas (p. ej., ORDENAR POR (SELECCIONAR 'Sin orden')). Además, con esta solución, el optimizador de SQL Server reconoce que no hay relevancia en el pedido y, por lo tanto, no impone una ordenación innecesaria ni limita las opciones del motor de almacenamiento a las que deben garantizar el orden. Intente ejecutar la siguiente consulta como ejemplo:

    SELECT id, grp, datacol,
      ROW_NUMBER() OVER(ORDER BY (SELECT 'No Order')) AS n 
    FROM dbo.T1;

    Obtiene el mismo plan que se muestra anteriormente en la Figura 3.

    Uno de los grandes beneficios de esta técnica es que puedes añadir tu propio toque personal. Tal vez realmente te gusten los valores NULL:

    SELECT id, grp, datacol,
      ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS n 
    FROM dbo.T1;

    Tal vez te guste mucho cierto número:

    SELECT id, grp, datacol,
      ROW_NUMBER() OVER(ORDER BY (SELECT 42)) AS n 
    FROM dbo.T1;

    Tal vez quieras enviarle un mensaje a alguien:

    SELECT id, grp, datacol,
      ROW_NUMBER() OVER(ORDER BY (SELECT 'Lilach, will you marry me?')) AS n 
    FROM dbo.T1;

    Entiendes el punto.

    Factible, pero incómodo

    Hay un par de técnicas que funcionan, pero son un poco incómodas. Una es definir un alias de columna para una expresión basada en una constante y luego usar ese alias de columna como el elemento de ordenación de la ventana. Puede hacerlo usando una expresión de tabla o con el operador CROSS APPLY y un constructor de valores de tabla. Aquí hay un ejemplo para este último:

    SELECT id, grp, datacol,
      ROW_NUMBER() OVER(ORDER BY [I'm a bit ugly]) AS n 
    FROM dbo.T1 CROSS APPLY ( VALUES('No Order') ) AS A([I'm a bit ugly]);

    Obtiene el mismo plan que se muestra anteriormente en la Figura 3.

    Otra opción es usar una variable como elemento de ordenación de ventanas:

    DECLARE @ImABitUglyToo AS INT = NULL;
     
    SELECT id, grp, datacol,
      ROW_NUMBER() OVER(ORDER BY @ImABitUglyToo) AS n 
    FROM dbo.T1;

    Esta consulta también obtiene el plan que se muestra anteriormente en la Figura 3.

    ¿Qué pasa si uso mi propio UDF?

    Podría pensar que usar su propia UDF que devuelve una constante podría ser una buena opción como elemento de ordenación de ventanas cuando desea un orden no determinista, pero no lo es. Considere la siguiente definición de UDF como ejemplo:

    DROP FUNCTION IF EXISTS dbo.YouWillRegretThis;
    GO
     
    CREATE FUNCTION dbo.YouWillRegretThis() RETURNS INT
    AS
    BEGIN
      RETURN NULL
    END;
    GO

    Intente usar el UDF como la cláusula de ordenación de ventanas, así (a esta la llamaremos Consulta 4):

    SELECT id, grp, datacol,
      ROW_NUMBER() OVER(ORDER BY dbo.YouWillRegretThis()) AS n 
    FROM dbo.T1;

    Antes de SQL Server 2019 (o nivel de compatibilidad paralela <150), las funciones definidas por el usuario se evaluaban por fila. Incluso si devuelven una constante, no se alinean. En consecuencia, por un lado, puede usar un UDF como el elemento de ordenación de ventanas, pero por otro lado, esto da como resultado una penalización de clasificación. Esto se confirma examinando el plan para esta consulta, como se muestra en la Figura 5.

    Figura 5:Plan para Consulta 4

    A partir de SQL Server 2019, con un nivel de compatibilidad>=150, estas funciones definidas por el usuario se integran, lo que en su mayoría es una gran cosa, pero en nuestro caso da como resultado un error:

    Mensaje 5309, nivel 16, estado 1, línea 217
    Las funciones de ventana, los agregados y las funciones NEXT VALUE FOR no admiten constantes como expresiones de la cláusula ORDER BY.

    Por lo tanto, usar una UDF basada en una constante como elemento de ordenación de ventanas fuerza una ordenación o un error según la versión de SQL Server que esté usando y el nivel de compatibilidad de su base de datos. En resumen, no hagas esto.

    Números de fila particionados con orden no determinista

    Un caso de uso común para números de fila particionados basados ​​en un orden no determinista es devolver cualquier fila por grupo. Dado que, por definición, existe un elemento de partición en este escenario, pensaría que una técnica segura en tal caso sería utilizar el elemento de partición de ventana también como elemento de ordenación de ventana. Como primer paso, calcula números de fila así:

    SELECT id, grp, datacol,
      ROW_NUMBER() OVER(PARTITION BY grp ORDER BY grp) AS n 
    FROM dbo.T1;

    El plan para esta consulta se muestra en la Figura 6.

    Figura 6:Plan para Consulta 5

    La razón por la que nuestro índice de soporte se escanea con una propiedad Ordered:True es porque SQL Server necesita procesar las filas de cada partición como una sola unidad. Ese es el caso antes de filtrar. Si filtra solo una fila por partición, tiene como opciones algoritmos basados ​​en orden y basados ​​en hash.

    El segundo paso es colocar la consulta con el cálculo del número de fila en una expresión de tabla y, en la consulta externa, filtrar la fila con el número de fila 1 en cada partición, así:

    WITH C AS
    (
      SELECT id, grp, datacol,
        ROW_NUMBER() OVER(PARTITION BY grp ORDER BY grp) AS n 
      FROM dbo.T1
    )
    SELECT id, grp, datacol
    FROM C
    WHERE n = 1;

    Teóricamente, se supone que esta técnica es segura, pero Paul White encontró un error que muestra que con este método puede obtener atributos de diferentes filas de origen en la fila de resultados devuelta por partición. El uso de una constante de tiempo de ejecución basada en una función o una subconsulta basada en una constante como elemento de pedido parece ser seguro incluso en este escenario, así que asegúrese de usar una solución como la siguiente:

    WITH C AS
    (
      SELECT id, grp, datacol,
        ROW_NUMBER() OVER(PARTITION BY grp ORDER BY (SELECT 'No Order')) AS n 
      FROM dbo.T1
    )
    SELECT id, grp, datacol
    FROM C
    WHERE n = 1;

    Nadie pasará por aquí sin mi permiso

    Tratar de calcular números de fila en función de un orden no determinista es una necesidad común. Hubiera sido bueno si T-SQL simplemente hiciera que la cláusula de orden de ventana fuera opcional para la función ROW_NUMBER, pero no es así. Si no, hubiera sido bueno si al menos permitiera usar una constante como elemento de pedido, pero esa tampoco es una opción admitida. Pero si lo pide amablemente, en forma de una subconsulta basada en una constante o una constante de tiempo de ejecución basada en una función, SQL Server lo permitirá. Estas son las dos opciones con las que me siento más cómoda. Realmente no me siento cómodo con las extravagantes expresiones erróneas que parecen funcionar, así que no puedo recomendar esta opción.