PostgreSQL
 sql >> Datenbank >  >> RDS >> PostgreSQL

Loop-in-Funktion funktioniert nicht wie erwartet

Es gibt viel Ich würde es anders machen und mit großer Wirkung.

Tabellendefinition

Angefangen bei der Tabellendefinition und den Namenskonventionen. Dies sind meistens nur Meinungen:

CREATE TEMP TABLE conta (conta_id bigint primary key, ...);

CREATE TEMP TABLE departamento (
   dept_id   serial PRIMARY KEY
 , master_id int REFERENCES departamento (dept_id)
 , conta_id  bigint NOT NULL REFERENCES conta (conta_id)
 , nome      text NOT NULL
);

Wichtige Punkte

  • Sind Sie sicher, dass Sie eine bigserial für Abteilungen? Es gibt kaum so viele auf diesem Planeten. Eine einfache Seriennummer sollte reichen.

  • Ich verwende kaum variable Zeichen mit Längenbeschränkung. Anders als bei manch anderem RDBMS gibt es keinerlei Performance-Gewinn durch die Verwendung einer Restriktion. Fügen Sie ein CHECK hinzu Einschränkung, wenn Sie wirklich eine maximale Länge erzwingen müssen. Ich verwende einfach text , meistens und erspare mir die Mühe.

  • Ich schlage eine Namenskonvention vor, bei der die Fremdschlüsselspalte den Namen mit der referenzierten Spalte teilt, also master_id statt master_fk usw. Erlaubt auch die Verwendung von USING in Joins.

  • Und ich selten Verwenden Sie den nicht beschreibenden Spaltennamen id . Verwenden von dept_id stattdessen hier.

PL/pgSQL-Funktion

Es kann weitgehend vereinfacht werden zu:

CREATE OR REPLACE FUNCTION f_retornar_plpgsql(lista_ini_depts VARIADIC int[])
  RETURNS int[] AS
$func$
DECLARE
   _row departamento;                     -- %ROWTYPE is just noise
BEGIN

IF NOT EXISTS (                           -- simpler in 9.1+, see below
    SELECT FROM pg_catalog.pg_class
    WHERE  relnamespace = pg_my_temp_schema()
    AND    relname      = 'tbl_temp_dptos') THEN

   CREATE TEMP TABLE tbl_temp_dptos (dept_id bigint NOT NULL)
   ON COMMIT DELETE ROWS;
END IF;

FOR i IN array_lower(lista_ini_depts, 1)  -- simpler in 9.1+, see below
      .. array_upper(lista_ini_depts, 1) LOOP
   SELECT *  INTO _row                    -- since rowtype is defined, * is best
   FROM   departamento
   WHERE  dept_id = lista_ini_depts[i];

   CONTINUE WHEN NOT FOUND;

   INSERT INTO tbl_temp_dptos VALUES (_row.dept_id);

   LOOP
      SELECT *  INTO _row
      FROM   departamento
      WHERE  dept_id = _row.master_id;

      EXIT WHEN NOT FOUND;

      INSERT INTO tbl_temp_dptos
      SELECT _row.dept_id
      WHERE  NOT EXISTS (
         SELECT FROM tbl_temp_dptos
         WHERE dept_id =_row.dept_id);
   END LOOP;
END LOOP;

RETURN ARRAY(SELECT dept_id FROM tbl_temp_dptos);

END
$func$  LANGUAGE plpgsql;

Aufruf:

SELECT f_retornar_plpgsql(2, 5);

Oder:

SELECT f_retornar_plpgsql(VARIADIC '{2,5}');

Alles in allem kommt hier der Mist:Sie brauchen das meiste davon nicht.

SQL-Funktion mit rCTE

Sogar in Postgres 9.0 ein rekursiver CTE macht dies viel einfacher :

CREATE OR REPLACE FUNCTION f_retornar_sql(lista_ini_depts VARIADIC int[])
  RETURNS int[] AS
$func$
WITH RECURSIVE cte AS (
   SELECT dept_id, master_id
   FROM   unnest($1) AS t(dept_id)
   JOIN   departamento USING (dept_id)

   UNION ALL
   SELECT d.dept_id, d.master_id
   FROM   cte
   JOIN   departamento d ON d.dept_id = cte.master_id
   )
SELECT ARRAY(SELECT DISTINCT dept_id FROM cte)    -- distinct values
$func$  LANGUAGE sql;

Gleicher Aufruf.

Eng verwandte Antwort mit Erklärung:

SQL Fiddle demonstriert beides.