En PostgreSQL, las funciones pueden retornar conjuntos de datos, lo que es equivalente a lo que en otros sistemas se conoce como conjuntos de resultados. A continuación, se presentan ejemplos de cómo implementar esto.
Contexto: En PostgreSQL, no existe el concepto de procedimiento almacenado como tal; en su lugar, todos se denominan funciones. Una función se compone de una definición (encabezado), un cuerpo y un lenguaje. Los lenguajes soportados incluyen C, PL/pgSQL, SQL e internal, y se pueden añadir extensiones para otros como Python. Para retornar un conjunto de datos, se utiliza la cláusula setof en lugar de tipos simples como int o varchar.
Preparación de datos
CREATE TABLE departamentos (id INT PRIMARY KEY, nombre TEXT);
CREATE TABLE empleados (id INT PRIMARY KEY, nombre TEXT, salario INT, depto_id INT REFERENCES departamentos);
INSERT INTO departamentos VALUES (1, 'Gerencia'), (2, 'TI'), (3, 'Dirección');
INSERT INTO empleados VALUES (1, 'ana', 32000, 1), (2, 'luis', 48000, 1), (3, 'maria', 65000, 2), (4, 'pedro', 115000, 3);
Ejemplos de funciones que retornan conjuntos de datos
Retorno básico de una tabla completa
Se puede definir una función en SQL puro que retorne todos los registros de una tabla:
CREATE OR REPLACE FUNCTION obtener_todos_empleados() RETURNS SETOF empleados AS $$ SELECT * FROM empleados; $$ LANGUAGE SQL;
<p>Alternativamente, usando PL/pgSQL con <code>RETURN QUERY</code>:</p>
<code>CREATE OR REPLACE FUNCTION obtener_empleados_con_query()
RETURNS SETOF empleados
AS $$
BEGIN
RETURN QUERY SELECT * FROM empleados;
END;
$$ LANGUAGE plpgsql;</code>
<p>Al consultar la función, se obtiene un conjunto de resultados similar a una tabla:</p>
<code>SELECT * FROM obtener_todos_empleados();</code>
<p>Resultado:</p>
<code> id | nombre | salario | depto_id
----+--------+---------+----------
1 | ana | 32000 | 1
2 | luis | 48000 | 1
3 | maria | 65000 | 2
4 | pedro | 115000 | 3
(4 filas)</code>
<p>La función puede tratarse como una tabla para filtrar datos:</p>
<code>SELECT * FROM obtener_todos_empleados() WHERE id > 2;</code>
<p>Resultado:</p>
<code> id | nombre | salario | depto_id
----+--------+---------+----------
3 | maria | 65000 | 2
4 | pedro | 115000 | 3
(2 filas)</code>
<h3>Retorno de conjuntos de datos personalizados</h3>
<p>Para estructuras no tabulares, se pueden usar tipos compuestos o parámetros de salida.</p>
<p><strong>Método 1: Usar un tipo compuesto definido por el usuario.</strong></p>
<code>CREATE TYPE resumen_depto AS (depto_id INT, salario_total INT);
CREATE OR REPLACE FUNCTION resumen_salarios_por_depto()
RETURNS SETOF resumen_depto
AS $$
DECLARE
registro resumen_depto%ROWTYPE;
BEGIN
FOR registro IN SELECT depto_id, SUM(salario) AS salario_total FROM obtener_todos_empleados() GROUP BY depto_id LOOP
RETURN NEXT registro;
END LOOP;
RETURN;
END;
$$ LANGUAGE plpgsql;</code>
<p><strong>Método 2: Usar parámetros de salida (OUT).</strong></p>
<code>CREATE OR REPLACE FUNCTION resumen_salarios_out(OUT depto_nombre TEXT, OUT monto_total TEXT)
RETURNS SETOF RECORD AS $$
DECLARE
fila RECORD;
BEGIN
FOR fila IN SELECT d.nombre AS depto_nombre, SUM(e.salario) AS monto_total FROM empleados e JOIN departamentos d ON e.depto_id = d.id GROUP BY d.nombre LOOP
depto_nombre := fila.depto_nombre;
monto_total := fila.monto_total;
RETURN NEXT;
END LOOP;
END;
$$ LANGUAGE plpgsql;</code>
<p>Ejecutando ambas funciones:</p>
<code>SELECT * FROM resumen_salarios_por_depto();</code>
<p>Resultado:</p>
<code> depto_id | salario_total
----------+---------------
1 | 80000
2 | 65000
3 | 115000
(3 filas)</code>
<code>SELECT * FROM resumen_salarios_out();</code>
<p>Resultado:</p>
<code> depto_nombre | monto_total
--------------+-------------
Gerencia | 80000
TI | 65000
Dirección | 115000
(3 filas)</code>
<h3>Retorno dinámico basado en parámetros</h3>
<p>Una función puede retornar diferentes conjuntos de datos según el argumento:</p>
<code>CREATE OR REPLACE FUNCTINO obtener_registros_dinamicos(nombre_tabla TEXT)
RETURNS SETOF RECORD AS $$
DECLARE
fila RECORD;
BEGIN
FOR fila IN EXECUTE 'SELECT * FROM ' || quote_ident(nombre_tabla) LOOP
RETURN NEXT fila;
END LOOP;
RETURN;
END;
$$ LANGUAGE plpgsql;</code>
<p>Al invocar, se debe definir la estructura del resultado:</p>
<code>SELECT * FROM obtener_registros_dinamicos('departamentos') AS d(id INT, nombre TEXT);</code>
<p>Resultado:</p>
<code> id | nombre
----+------------
1 | Gerencia
2 | TI
3 | Dirección
(3 filas)</code>
<code>SELECT * FROM obtener_registros_dinamicos('empleados') AS e(id INT, nombre TEXT, salario INT, depto_id INT);</code>
<p>Resultado:</p>
<code> id | nombre | salario | depto_id
----+--------+---------+----------
1 | ana | 32000 | 1
2 | luis | 48000 | 1
3 | maria | 65000 | 2
4 | pedro | 115000 | 3
(4 filas)</code>