Una función ventana realiza una operación sobre un conjunto de filas de una tabla que de alguna manera están relacionadas con la fila actual. Esto es similar al tipo de cálculo que se puede hacer con Funciones de agregados. Pero a diferencia de estas, el uso de las funciones ventana no hace que las filas se agrupen en una sola fila como resultado (las filas mantienen sus identidades por separado). Entre bastidores, la función ventana puede acceder a más que solo la fila del resultado de la consulta.
Este es un ejemplo que muestra cómo comparar el salario de cada empleado con el salario promedio del departamento al que pertenecen:
SELECT depname, empno, salary, avg(salary) OVER (PARTITION BY depname) FROM empsalary;
depname | empno | salary | avg
-----------+-------+--------+-----------------------
develop | 11 | 5200 | 5020.0000000000000000
develop | 7 | 4200 | 5020.0000000000000000
develop | 9 | 4500 | 5020.0000000000000000
develop | 8 | 6000 | 5020.0000000000000000
develop | 10 | 5200 | 5020.0000000000000000
personnel | 5 | 3500 | 3700.0000000000000000
personnel | 2 | 3900 | 3700.0000000000000000
sales | 3 | 4800 | 4866.6666666666666667
sales | 1 | 5000 | 4866.6666666666666667
sales | 4 | 4800 | 4866.6666666666666667
(10 rows)
Las tres primeras columnas del resultado vienen directamente de la tabla empsalary, y por cada fila en la tabla hay una fila como resultado. La cuarta columna representa un promedio tomado de todas las filas de la tabla que tienen el mismo valor en depname que la fila actual. (De hecho, esta es la misma función que desempeña la función de agregado avg, pero la cláusula OVER hace que sea tratada como una función ventana y computada sobre un conjunto apropiado de filas).
La llamada a una función ventana siempre contiene la cláusula OVER después del nombre de la función y sus argumentos. Esto es lo que la distingue sintácticamente de una función común y corriente o de una función de agregado. La cláusula OVER determina exactamente cómo se deben partir las filas de la consulta para que sean procesadas por la función ventana. La lista PARTITION BY dentro de OVER especifica la división de las filas en grupos, o particiones, que comparten los mismos valores de la expresión (o expresiones) PARTITION BY. Para cada fila, la función ventana se computa sobre las filas que están dentro de la misma partición que la fila actual.
Aunque avg produce el mismo resultado sin importar en qué orden procese las filas de la partición, no pasa lo mismo con las funciones ventana. En estas, se puede controlar el orden usando ORDER BY dentro de OVER. Como en este ejemplo:
SELECT depname, empno, salary, rank() OVER (PARTITION BY depname ORDER BY salary DESC) FROM empsalary;
depname | empno | salary | rank
-----------+-------+--------+------
develop | 8 | 6000 | 1
develop | 10 | 5200 | 2
develop | 11 | 5200 | 2
develop | 9 | 4500 | 4
develop | 7 | 4200 | 5
personnel | 2 | 3900 | 1
personnel | 5 | 3500 | 2
sales | 1 | 5000 | 1
sales | 4 | 4800 | 2
sales | 3 | 4800 | 2
(10 rows)
Como se muestra aquí, la función rank produce una gradación numérica dentro de la partición de la fila actual para cada valor diferente de ORDER BY, en el orden definido por la cláusula ORDER BY. rank no necesita parámetros explícitos porque su comportamiento lo determina la cláusula OVER en su totalidad.
Las filas consideradas por una función ventana son aquellas de la “tabla virtual” producida por la cláusula FROM de la consulta, filtrada por sus cláusulas WHERE, GROUP BY y HAVING. Por ejemplo, una fila removida porque no cumple la condición WHERE es invisible para cualquier función ventana. Una consulta puede tener muchas funciones ventana que recortan los datos de diferentes formas por medio de diferentes cláusulas OVER, pero todas ellas actúan sobre la misma colección de filas definidas por la tabla virtual.
Ya vimos que ORDER BY se puede omitir si el orden de las filas no importa. También es posible omitir PARTITION BY, en cuyo caso habría solamente una partición que contiene todas las filas.
Hay otro concepto importante asociado con las funciones ventana: para cada fila, hay un conjunto de filas dentro de su partición que se conoce como su “marco de ventana”. Muchas funciones ventana (pero no todas) actúan solamente sobre las filas del marco, en vez de actuar sobre toda la partición. Predeterminadamente, si se usa ORDER BY, entonces el marco consta de todas las filas desde el inicio de la partición hasta la fila actual, más cualquier otra fila siguiente que sea igual a la fila actual de acuerdo con la cláusula ORDER BY. Cuando se omite ORDER BY, el marco predeterminado consta de todas las filas de la partición [1]. Aquí hay un ejemplo que usa sum:
SELECT salary, sum(salary) OVER () FROM empsalary;
salary | sum
--------+-------
5200 | 47100
5000 | 47100
3500 | 47100
4800 | 47100
3900 | 47100
4200 | 47100
4500 | 47100
4800 | 47100
6000 | 47100
5200 | 47100
(10 rows)
Arriba, como no se usa ORDER BY en la cláusula OVER, el marco de la ventana es lo mismo que la partición, que por la omisión de PARTITION BY es toda la tabla; en otras palabras, cada suma se hace sobre toda la tabla y por eso se obtiene el mismo resultado para cada fila resultante. Pero si se agrega una cláusula ORDER BY, se obtienen resultados muy diferentes:
SELECT salary, sum(salary) OVER (ORDER BY salary) FROM empsalary;
salary | sum
--------+-------
3500 | 3500
3900 | 7400
4200 | 11600
4500 | 16100
4800 | 25700
4800 | 25700
5000 | 30700
5200 | 41100
5200 | 41100
6000 | 47100
(10 rows)
Aquí la suma se hace desde el primer salario (el más bajo) hasta el actual, incluyendo cualquiera igual al actual (note los resultados para ver los salarios repetidos).
Las funciones ventana se permiten únicamente en la lista SELECT y la cláusula ORDER BY de la consulta. En cualquier otro lugar están prohibidas, por ejemplo en las cláusulas GROUP BY, HAVING y WHERE. Esto se debe a que las funciones se ejecutan lógicamente después de estas cláusulas. También se ejecutan después de las funciones de agregados. Quiere decir que es válido incluir llamadas a funciones de agregados en los argumentos de una función ventana, pero no al contrario.
Si se necesita filtrar o agrupar las filas después de terminar el cálculo de la ventana, se puede usar una subselección. Por ejemplo:
SELECT depname, empno, salary, enroll_date
FROM
(SELECT depname, empno, salary, enroll_date,
rank() OVER (PARTITION BY depname ORDER BY salary DESC, empno) AS pos
FROM empsalary
) AS ss
WHERE pos < 3;
La consulta de arriba solamente muestra las filas de la consulta interna que tengan un valor de rank menor que 3.
Cuando una consulta involucra muchas funciones ventana, es posible escribir cada una de ellas con una cláusula OVER separadamente, pero esto es redundante y propenso a errores si se desea el mismo comportamiento de ventana para varias funciones. En lugar de esto, a cada comportamiento de ventana se le puede dar un nombre en una cláusula WINDOW y luego hacer referencia al mismo en OVER. Por ejemplo:
SELECT sum(salary) OVER w, avg(salary) OVER w
FROM empsalary
WINDOW w AS (PARTITION BY depname ORDER BY salary DESC);
Puede encontrar más detalles sobre funciones ventana en la Sección 4.2.8, Sección 7.2.4 y la página de referencia de SELECT en la documentación de PostgreSQL.
Notas
[1] | Existen opciones para definir el marco de otras formas, pero este tutorial no las cubre. Vea la Sección 4.2.8 de la documentación de PostgreSQL para más detalles. |