lunes, 7 de marzo de 2016

Usando Funciones de Ventana

Al igual que las funciones de grupo, las funciones de ventana también le permiten realizar cálculos de análisis de datos. La diferencia entre los dos está, en cómo define el conjunto de filas para la función con la que trabaja. Con funciones de grupo, utiliza consultas agrupadas para organizar las filas consultadas en grupos, y luego las funciones de grupo son aplicadas a cada grupo. Obtiene una fila de resultado por grupo, no por fila originaria. Con funciones de ventana, se define el conjunto de filas por función, y luego retorna un valor de resultado por cada función y fila originaria. Se define el conjunto de filas para la función a trabajar con el uso de una cláusula llamada OVER.
Esta post cubre tres tipos de funciones de ventana: aggregate, ranking, y offset.

Funciones Aggregate de Ventana

Las funciones agregadas de ventana son las mismas, que las funciones agregadas de agrupación (por ejemplo, SUM, COUNT, AVG, MIN y MAX), excepto que las funciones agregadas de ventana son aplicadas a una ventana de filas definidas por la cláusula OVER.
Uno de los beneficios de usar funciones de ventana, es que a diferencia de las consultas agrupadas, las consultas con ventanas no ocultan el detalle, retornan una fila por cada fila de consulta originaria. Esto significa que puede mezclar detalles y elementos agregados en la misma consulta, e incluso en la misma expresión. Usando la cláusula OVER, se define un conjunto de filas para la función a trabajar por cada fila originaria. En otras palabras, una consulta de ventana define una ventana de filas por cada función y fila en la consulta originaria.
Como se ha mencionado, se utiliza una cláusula OVER para definir una ventana de filas para la función. La ventana es definida con respecto a la fila actual. Cuando utiliza paréntesis vacíos, la cláusula OVER representa el conjunto resultado de la consulta originaria completa. Por ejemplo, la expresión SUM(val) OVER() representa el total general de todas las filas en la consulta originaria. Puede utilizar una cláusula de partición de ventana para restringir la ventana. Por ejemplo, la expresión SUM(val) OVER(PARTITION BY custid) representa el total de clientes actuales. Como un ejemplo, si la fila actual tiene un custid de 1, la cláusula OVER filtra sólo estas filas del conjunto resultado de la consulta originaria, donde el custid es 1; por lo tanto, la expresión retorna el total para el cliente 1.
He aquí un ejemplo de una consulta sobre la vista Sales.OrderValues, retornando para cada pedido el custid, orderid, y el valor de pedido; utilizando funciones de ventana, la consulta también retorna el total general de todos los valores y el total por cliente.
SELECT custid, orderid, val,
SUM(val) OVER(PARTITION BY custid) AS custtotal,
SUM(val) OVER() AS grandtotal
FROM Sales.OrderValues;
Esta consulta genera la siguiente salida (mostrado aquí en forma abreviada).
custid  orderid  val       custtotal  grandtotal
------- -------- ------- ---------- -----------
1        10643    814.50   4273.00    1265793.22
1        10692    878.00   4273.00    1265793.22
1        10702    330.00   4273.00    1265793.22
1        10835    845.80   4273.00    1265793.22
1        10952    471.20   4273.00    1265793.22
1        11011    933.50   4273.00    1265793.22
2        10926    514.40   1402.95    1265793.22
2        10759    320.00   1402.95    1265793.22
2        10625    479.75   1402.95    1265793.22
2        10308    88.80    1402.95    1265793.22
...
El total general es, por supuesto, el mismo para todas las filas. El total del cliente es el mismo para todas las filas con el mismo custid.
Puede mezclar elementos de detalle y agregados de ventana en la misma expresión. Por ejemplo, la siguiente consulta, calcula para cada pedido el porcentaje del valor de pedido actual sobre el total del cliente, y también el porcentaje del total general.
SELECT custid, orderid, val,
CAST(100.0*val/SUM(val) OVER(PARTITION BY custid) AS NUMERIC(5,2)) AS pctcust,
CAST(100.0*val/SUM(val) OVER() AS NUMERIC(5,2)) AS pcttotal
FROM Sales.OrderValues;
Esta consulta genera la siguiente salida (mostrada aquí en forma abreviada).
custid  orderid  val       pctcust  pcttotal
------- -------- ------- -------- ---------
1        10643    814.50   19.06   0.06
1        10692    878.00   20.55   0.07
1        10702    330.00   7.72     0.03
1        10835    845.80   19.79    0.07
1        10952    471.20   11.03   0.04
1        11011    933.50   21.85   0.07
2        10926    514.40   36.67   0.04
2        10759    320.00   22.81   0.03
2        10625    479.75   34.20   0.04
2        10308    88.80    6.33     0.01
...
La suma de todos los porcentajes sobre el total general es 100. La suma de todos los porcentajes sobre el total de clientes es 100, por cada partición de filas con el mismo cliente.
Las funciones agregadas de ventana soportan, otra opción de filtrado llamado enmarcado. La idea es que defina el ordenamiento en la partición utilizando una cláusula de orden de ventana, y luego basado en ese orden, puede confinar un marco de filas entre dos delimitadores. Define los delimitadores utilizando una cláusula marco de ventana. La cláusula marco de ventana requiere que una cláusula de orden de ventana este presente, debido a que un conjunto no tiene orden; y sin orden, el limitar las filas entre dos delimitadores podría no tener sentido.
En la cláusula marco de ventana, indica las unidades marco de ventana (ROWS o RANGE) y las extensiones marco de ventana (los delimitadores). Con la unidad marco de ventana ROWS, puede indicar los delimitadores como una de las tres opciones:
·         UNBOUNDED PRECEDING o FOLLOWING, es decir, el principio o el fin de la partición, respectivamente
·         CURRENT ROW, obviamente, representando la fila actual
·         <n> ROWS PRECEDING o FOLLOWING, es decir, n filas antes o después de la actual, respectivamente
A modo de ejemplo, suponga que quiere consultar la vista Sales.OrderValues y calcular los valores totales acumulados, desde el comienzo de la actividad del cliente actual hasta el pedido actual. Necesita utilizar el agregado SUM. Particionar la ventana por custid. Ordenar la ventana por orderdate, orderid. Luego, enmarcar las filas desde el inicio de la partición (UNBOUNDED PRECEDING) hasta la fila actual. Su consulta debería ser similar a la siguiente.
SELECT custid, orderid, orderdate, val,
  SUM(val) OVER(PARTITION BY custid
               ORDER BY orderdate, orderid
               ROWS BETWEEN UNBOUNDED PRECEDING
                        AND CURRENT ROW) AS runningtotal
FROM Sales.OrderValues;
Esta consulta genera la siguiente salida (mostrada aquí en forma abreviada).
custid  orderid  orderdate   val     runningtotal
------- -------- ----------- ------- -------------
1        10643    2007-08-25  814.50  814.50
1        10692    2007-10-03  878.00  1692.50
1        10702    2007-10-13  330.00  2022.50
1        10835    2008-01-15  845.80  2868.30
1        10952    2008-03-16  471.20  3339.50
1        11011    2008-04-09  933.50  4273.00
2        10308    2006-09-18  88.80   88.80
2        10625    2007-08-08  479.75  568.55
2        10759    2007-11-28  320.00  888.55
2        10926    2008-03-04  514.40  1402.95
...
Observe cómo los valores se van acumulando desde el principio de la partición de cliente hasta la fila actual. Por cierto, en vez de la forma detallada de la extensión marco ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW, puede utilizar la forma más corta ROWS UNBOUNDED PRECEDING, y mantener el mismo significado.
Usando las funciones agregadas de ventana, para realizar cálculos como totales acumulados, normalmente obtiene mucho mejor rendimiento en comparación con el uso de joins o subconsultas y funciones agregadas de grupo. Las funciones de ventana nos dan una buena optimización, especialmente cuando se utiliza UNBOUNDED PRECEDING como el primer delimitador.
En términos del procesamiento de consulta lógico, el resultado de una consulta es logrado cuando lo obtiene de la fase SELECT, después de que las fases FROM, WHERE, GROUP BY y HAVING han sido procesadas. Debido a que supuestamente las funciones de ventana operan, sobre el conjunto resultado de la consulta originaria, son permitidos sólo en las cláusulas SELECT y ORDER BY. Si necesita referirse al resultado de una función de ventana en cualquier cláusula, que es evaluada antes de la cláusula SELECT, necesita utilizar una expresión de tabla. Se invoca la función de ventana en la cláusula SELECT de la consulta interna, asignando la expresión con un alias de columna. Luego, puede referirse a ese alias de columna en la consulta externa en todas las cláusulas.
Por ejemplo, suponga que necesita filtrar el resultado de la última consulta, retornando sólo esas filas donde el total acumulado es inferior a 1,000.00. El siguiente código logra esto definiendo una expresión de tabla común (CTE), basada en la consulta anterior y luego haciendo el filtrado en la consulta externa.
WITH RunningTotals AS
(
  SELECT custid, orderid, orderdate, val,
    SUM(val) OVER(PARTITION BY custid
                 ORDER BY orderdate, orderid
                 ROWS BETWEEN UNBOUNDED PRECEDING
                          AND CURRENT ROW) AS runningtotal
  FROM Sales.OrderValues
)
SELECT *
FROM RunningTotals
WHERE runningtotal < 1000.00;
Esta consulta genera la siguiente salida (mostrada aquí en forma abreviada).
custid  orderid  orderdate   val     runningtotal
------- -------- ----------- ------- -------------
1        10643    2007-08-25  814.50  814.50
2        10308    2006-09-18  88.80   88.80
2        10625    2007-08-08  479.75  568.55
2        10759    2007-11-28  320.00  888.55
3        10365    2006-11-27  403.20  403.20
...
Como otro ejemplo de una extensión marco de ventana, si quiere que el marco incluya sólo las tres últimas filas, debería utilizar la forma ROWS BETWEEN 2 PRECEDING AND CURRENT ROW.
En cuanto a la extensión marco de ventana RANGE, de acuerdo al SQL estándar, se permite definir los delimitadores basados en offsets lógicos de la llave de ordenación de la fila actual. Recuerde que ROWS define los delimitadores basados en offsets físicos en términos del número de filas de la fila actual. Sin embargo, SQL Server 2012 tiene una implementación muy limitada de la opción RANGE, soportando sólo UNBOUNDED PRECEDING o FOLLOWING y CURRENT ROW como delimitadores. Una diferencia sutil entre ROWS y RANGE cuando se utiliza los mismos delimitadores es que el primero no incluye pares (filas empatadas en términos de la llave de ordenación) y el segundo si lo hace.
ROWS versus RANGE
En SQL Server 2012, la opción ROWS generalmente es optimizada mucho mejor que RANGE, cuando se utilizan los mismos delimitadores. Si se define una ventana con una cláusula de orden de ventana, pero sin una cláusula marco de ventana, el predeterminado es RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW. Por lo tanto, a menos que después del comportamiento especial que obtiene de RANGE incluya pares, asegúrese de utilizar explícitamente la opción ROWS.

Funciones Ranking de Ventana

Con funciones ranking de ventana, puede rankear las filas en una partición basada en un ordenamiento especificado. Al igual que con las otras funciones de ventana, si no indica una cláusula de partición de ventana, el resultado de consulta originario completo es considerado una partición. La cláusula de orden de ventana es obligatoria. Las funciones ranking de ventana no soportan una cláusula marco de ventana. T-SQL soporta cuatro funciones ranking de ventana: ROW_NUMBER, RANK, DENSE_RANK y NTILE.
La consulta siguiente demuestra el uso de estas funciones.
SELECT custid, orderid, val,
  ROW_NUMBER() OVER(ORDER BY val) AS rownum,
  RANK() OVER(ORDER BY val) AS rnk,
  DENSE_RANK() OVER(ORDER BY val) AS densernk,
  NTILE(100) OVER(ORDER BY val) AS ntile100
FROM Sales.OrderValues;
Esta consulta genera la siguiente salida (mostrada aquí en forma abreviada).
custid  orderid  val    rownum    rnk  densernk  ntile100
------- -------- ------ ------- ---- --------- ---------
12       10782    12.50  1        1      1         1
27       10807    18.40  2        2      2         1
66       10586    23.80  3        3      3         1
76       10767    28.00  4        4      4         1
54       10898    30.00  5        5      5         1
88       10900    33.75  6        6      6         1
48       10883    36.00  7        7      7         1
41       11051    36.00  8        7      7         1
71       10815    40.00  9        9      8         1
38       10674    45.00  10       10     9         2
53       11057    45.00  11       10     9         2
75       10271    48.00  12       12     10        2
...
Importante: Orden de Presentación versus Orden de Ventana
La consulta de muestra no tiene una cláusula ORDER BY de presentación, y por lo tanto, no hay garantía de que las filas serán presentadas en algún orden en particular. La cláusula de orden de ventana sólo determina el ordenamiento para el cálculo de función de ventana. Si se invoca una función de ventana en su consulta, pero no especifica una cláusula ORDER BY de presentación, no hay garantía de que las filas serán presentadas en el mismo orden como el orden de función de ventana. Si necesita una garantía, necesita agregar una cláusula ORDER BY de presentación.
La función ROW_NUMBER calcula un entero secuencial único a partir de 1 en la partición de ventana basado en el orden de ventana. Debido a que la consulta de ejemplo no tiene una cláusula de partición de ventana, la función considera el conjunto resultado de consulta completa, como una partición; por lo tanto, la función asigna números de fila únicos a través del conjunto de resultados de consulta completo.
Note que si el ordenamiento no es único, la función ROW_NUMBER no es determinística. Por ejemplo, note en el resultado, que dos filas tienen el mismo valor de ordenamiento de 36.00, pero las dos filas obtienen diferentes números de fila. Eso es porque la función debe generar enteros únicos, en la partición. Actualmente, no hay un desempate explícito, y por lo tanto la elección de que fila obtiene el número de fila más alto es arbitrario (dependiente de la optimización). Si necesita un cálculo determinístico (resultados repetibles garantizados), necesita agregar un desempate. Por ejemplo, puede agregar la llave primaria para hacer el ordenamiento único, como en ORDER BY val, orderid.
RANK y DENSE_RANK difieren de ROW_NUMBER en el sentido que asigna el mismo valor rango a todas las filas que comparten el mismo valor de ordenamiento. La función RANK retorna el número de filas en la partición que tiene un valor de ordenamiento menor que el actual, más 1. Por ejemplo, considere las filas en el resultado de consulta de ejemplo que tiene un valor de ordenamiento de 45.00. Nueve filas tienen valores de ordenamiento que son menores que 45.00; por lo tanto, estas filas obtuvieron el rango de 10 (9 + 1).
La función DENSE_RANK retorna el número de valores de ordenamiento distintos que son más bajos que el actual, más 1. Por ejemplo, las mismas filas que obtuvieron el rango de 10 obtuvieron el rango denso de 9. Eso es porque estas filas tienen un valor de ordenamiento de 45.00, y hay ocho valores de ordenamiento distintos que son inferiores que 45.00. Debido a que RANK considera filas y DENSE_RANK considera valores distintos, el primero puede tener brechas entre los valores ranking de resultado, y el último no puede tener brechas. Debido a que las funciones RANK y DENSE_RANK calculan el mismo valor ranking a las filas con el mismo valor de ordenamiento, ambas funciones son determinísticas, incluso cuando el ordenamiento no es único. De hecho, si utiliza el ordenamiento único, ambas funciones retornan el mismo resultado como la función ROW_NUMBER. Así que usualmente estas funciones son interesantes de utilizar cuando el ordenamiento no es único.
Con la función NTILE, puede organizar las filas en la partición, en un número solicitado de fichas de igual tamaño, basado en el orden especificado. Se especifica el número deseado de fichas como entrada a la función. En la consulta de ejemplo, solicitó 100 fichas. Hay 830 filas en el conjunto resultado, y por lo tanto el tamaño de ficha base es 830/100 = 8 con un resto de 30. Debido a que hay un resto de 30, las primeras 30 fichas son asignados con una fila adicional.
A saber, las fichas 1 al 30 tendrán nueve filas cada una, y todas las fichas restantes (31 a 100) tendrán ocho filas cada una. Observe en el resultado de esta consulta de muestra que las primeras nueve filas (de acuerdo al ordenamiento val) son asignados con la ficha número 1, entonces las siguientes nueve filas son asignadas con la ficha número 2, y así sucesivamente. Como ROW_NUMBER, la función NTILE no es determinística cuando el ordenamiento no es único. Si necesita garantizar el determinismo, necesita definir el ordenamiento único.
Tema Clave
Como se explica en la discusión de las funciones agregadas de ventana, las funciones de ventana son permitidas sólo en las cláusulas SELECT y ORDER BY de la consulta. Si necesita referirse a ellas en otras cláusulas, por ejemplo, en la cláusula WHERE, necesita usar una expresión de tabla, tal como un CTE. Se invoca la función de ventana en la cláusula SELECT de la consulta interna, asignando la expresión con un alias de columna. Luego se refiere a ese alias de columna en la consulta WHERE de la consulta externa.

Funciones Offset de Ventana

Las funciones offset de ventana retornan un elemento de una sola fila que está en un offset dado de la fila actual en la partición de ventana, o de la primera o la última fila en el marco de ventana. T-SQL soporta las siguientes funciones offset de ventana: LAG, LEAD, FIRST_VALUE y LAST_VALUE. Las funciones LAG y LEAD confían en un offset con respecto a la fila actual, y las funciones FIRST_VALUE y LAST_VALUE operan en la primera o la última fila en el marco, respectivamente.
Las funciones LAG y LEAD soportan cláusulas de partición y de ordenamiento de ventana. No soportan una cláusula marco de ventana. La función LAG retorna un elemento de la fila en la partición actual, que es un número solicitado de filas antes de la fila actual (basado en la ordenación de ventana), con 1 asumido como el offset predeterminado. La función LEAD retorna un elemento de la fila que está en el offset solicitado después de la fila actual.
A modo de ejemplo, la siguiente consulta utiliza las funciones LAG y LEAD para retornar junto con cada pedido, el valor del pedido del cliente anterior, además del valor del pedido del cliente posterior.
SELECT custid, orderid, orderdate, val,
  LAG(val) OVER(PARTITION BY custid
                ORDER BY orderdate, orderid) AS prev_val,
  LEAD(val) OVER(PARTITION BY custid
                ORDER BY orderdate, orderid) AS next_val
FROM Sales.OrderValues;
Esta consulta genera la siguiente salida (que se muestra aquí en forma abreviada).
custid  orderid  orderdate   val     prev_val  next_val
------- -------- ----------- ------- --------- ---------
1        10643    2007-08-25  814.50  NULL       878.00
1        10692    2007-10-03  878.00  814.50     330.00
1        10702    2007-10-13  330.00  878.00     845.80
1        10835    2008-01-15  845.80  330.00     471.20
1        10952    2008-03-16  471.20  845.80     933.50
1        11011    2008-04-09  933.50  471.20     NULL
2        10308    2006-09-18  88.80   NULL       479.75
2        10625    2007-08-08  479.75  88.80      320.00
2        10759    2007-11-28  320.00  479.75     514.40
2        10926    2008-03-04  514.40  320.00     NULL
...
Debido a que un \ explícito no fue especificado, ambas funciones confían en el offset predeterminado de 1. Si quiere un offset diferente de 1, se le especifica como el segundo argumento, como en LAG(val, 3). Note que si una fila no existe en el offset solicitado, la función retorna un NULL por defecto. Si desea retornar un valor diferente en tal caso, especifíquelo como el tercer argumento, como en LAG(val, 3, 0).
Las funciones FIRST_VALUE y LAST_VALUE retornan una expresión de valor de la primera o la última fila en el marco de ventana, respectivamente. Naturalmente, las funciones soportan cláusulas de partición de ventana, de orden y de marco. A modo de ejemplo, la siguiente consulta retorna junto con cada pedido los valores del primer y último pedido del cliente.
SELECT custid, orderid, orderdate, val,
  FIRST_VALUE(val) OVER(PARTITION BY custid
                        ORDER BY orderdate, orderid
                        ROWS BETWEEN UNBOUNDED PRECEDING
                                AND CURRENT ROW) AS first_val,
  LAST_VALUE(val) OVER(PARTITION BY custid
                       ORDER BY orderdate, orderid
                       ROWS BETWEEN CURRENT ROW
                               AND UNBOUNDED FOLLOWING) AS last_val
FROM Sales.OrderValues;
Esta consulta genera la siguiente salida (mostrada aquí en forma abreviada).
custid  orderid  orderdate   val     first_val  last_val
------- -------- ----------- ------- ---------- ----------
1        11011    2008-04-09  933.50  814.50      933.50
1        10952    2008-03-16  471.20  814.50      933.50
1        10835    2008-01-15  845.80  814.50      933.50
1        10702    2007-10-13  330.00  814.50      933.50
1        10692    2007-10-03  878.00  814.50      933.50
1        10643    2007-08-25  814.50  814.50      933.50
2        10926    2008-03-04  514.40  88.80       514.40
2        10759    2007-11-28  320.00  88.80       514.40
2        10625    2007-08-08  479.75  88.80       514.40
2        10308    2006-09-18  88.80   88.80       514.40
...
FRAME Predeterminado y Rendimiento de RANGE
Como recordatorio, cuando un marco de ventana es aplicable a una función, pero no se especifica una cláusula marco de ventana explícita, el predeterminado es RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW. Por motivos de rendimiento, en general se recomienda evitar la opción RANGE; para ello, tiene que ser explícito con la cláusula ROWS. Además, si esta después de la primera fila en la partición, utilizando la función FIRST_VALUE con el marco por defecto al menos le da el resultado correcto. Sin embargo, si está después de la última fila en la partición, utilizar la función LAST_VALUE con el marco por defecto no le dará lo que quiere, porque la última fila en el marco predeterminado es la fila actual. Así que con LAST_VALUE, necesita ser explícito sobre el marco de ventana con el fin de obtener lo que está después. Y si necesita un elemento de la última fila en la partición, el segundo delimitador en el marco debería ser UNBOUNDED FOLLOWING.

Ejercicio 1: Usar Funciones Agregadas de Ventana

En este ejercicio, se le da una tarea que requiere escribir consultas utilizando funciones agregadas de ventana. Trate primero de llegar con su propia solución, antes de mirar a la proporcionada.
1.    Abra el SSMS y conéctese a la base de datos de muestra TSQL2012.
2.    Escriba una consulta sobre la vista Sales.OrderValues que retorna por cada cliente y pedido el valor promedio de movimiento de los tres últimos pedidos del cliente.
Su consulta de solución debería ser similar a la siguiente consulta.
SELECT custid, orderid, orderdate, val,
  AVG(val) OVER(PARTITION BY custid
                ORDER BY orderdate, orderid
                ROWS BETWEEN 2 PRECEDING
                        AND CURRENT ROW) AS movingavg
FROM Sales.OrderValues;
Esta consulta genera el siguiente resultado, mostrada aquí en forma abreviada.
custid  orderid orderdate   val       movingavg
------ -------- ----------- ------- -----------
1     10643    2007-08-25   814.50  814.500000
1     10692    2007-10-03   878.00  846.250000
1     10702    2007-10-13   330.00  674.166666
1     10835    2008-01-15   845.80  684.600000
1     10952    2008-03-16   471.20  549.000000
1     11011    2008-04-09   933.50  750.166666
2     10308    2006-09-18   88.80   88.800000
2     10625    2007-08-08   479.75  284.275000
2     10759    2007-11-28   320.00  296.183333
2     10926    2008-03-04   514.40  438.050000
...

Ejercicio 2: Utilice Funciones Ranking y Offset de Ventana

En este ejercicio, tiene tareas dadas que requieren que escriba consultas utilizando funciones ranking y offset de ventana. Se le pide filtrar filas basadas en el resultado de una función de ventana, y escribir expresiones que mezclan elementos de detalle y funciones de ventana.
1.    Como la siguiente tarea, escriba una consulta sobre la tabla Sales.Orders, y filtre los tres pedidos con los valores de flete más altos por cada distribuidor utilizando orderid como desempate.
Necesita utilizar la función ROW_NUMBER para filtrar las filas deseadas. Pero recuerde que no se permite referirse a funciones de ventana directamente en la cláusula WHERE. La solución es definir una expresión de tabla basada en una consulta que invoca la función ROW_NUMBER y asigna la expresión con un alias de columna. Entonces puede manejar el filtrado de la consulta externa utilizando ese alias de columna. Aquí está la consulta de solución completa.
WITH C AS
(
  SELECT shipperid, orderid, freight,
    ROW_NUMBER() OVER(PARTITION BY shipperid 
                      ORDER BY freight DESC, orderid) AS rownum
  FROM Sales.Orders
)
SELECT shipperid, orderid, freight
FROM C
WHERE rownum <= 3
ORDER BY shipperid, rownum;
Esta consulta genera el siguiente resultado.
shipperid  orderid  freight
---------- -------- ---------
1           10430    458.78
1           10836    411.88
1           10658    364.15
2           10372    890.78
2           11030    830.75
2           10691    810.05
3           10540    1007.64
3           10479    708.95
3           11032    606.19
2.    Así como su última tarea, consulte la vista Sales.OrderValues. Necesita calcular la diferencia entre el valor del pedido actual y el valor del pedido previo del cliente, además a la diferencia entre el valor de pedido actual y el valor de pedido siguiente del cliente.
Para obtener los valores de los pedidos anteriores y posteriores del cliente, puede utilizar las funciones LAG y LEAD, respectivamente. Luego, puede restar los resultados de esas funciones de la columna val para obtener las diferencias deseadas. Aquí está la consulta de solución completa.
SELECT custid, orderid, orderdate, val, 
  val - LAG(val) OVER(PARTITION BY custid 
                      ORDER BY orderdate, orderid) AS diffprev,
  val - LEAD(val) OVER(PARTITION BY custid 
                       ORDER BY orderdate, orderid) AS diffnext
FROM Sales.OrderValues;
Esta consulta genera el siguiente resultado, que se muestra aquí en forma abreviada.
custid  orderid  orderdate    val      diffprev  diffnext
------- -------- ----------- ------- --------- ---------
1      10643    2007-08-25 814.50     NULL        -63.50
1      10692    2007-10-03 878.00     63.50       548.00
1      10702    2007-10-13 330.00     -548.00     -515.80
1      10835    2008-01-15 845.80     515.80      374.60
1      10952    2008-03-16 471.20     -374.60     -462.30
1      11011    2008-04-09 933.50     462.30      NULL
2      10308    2006-09-18 88.80      NULL        -390.95
2      10625    2007-08-08 479.75     390.95      159.75
2      10759    2007-11-28 320.00     -159.75     -194.40
2      10926    2008-03-04 514.40     194.40      NULL
...