Devolución de conjuntos de datos mediante funciones en PostgreSQL

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>

Etiquetas: PostgreSQL SQL funciones conjuntos de datos PL/pgSQL

Publicado el 5-30 19:16