sql >> Database teknologi >  >> RDS >> PostgreSQL

Loop in funktion virker ikke som forventet

Der er meget Jeg ville gøre anderledes, og med stor effekt.

Tabeldefinition

Startende med tabeldefinitionen og navngivningskonventioner. Disse er for det meste kun meninger:

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
);

Vigtige punkter

  • Er du sikker på, at du har brug for en bigserial for afdelinger? Der er knap så mange på denne planet. En almindelig serial burde være tilstrækkeligt.

  • Jeg bruger næsten aldrig character varying med en længdebegrænsning. I modsætning til nogle andre RDBMS er der ingen præstationsgevinst overhovedet ved at bruge en begrænsning. Tilføj en CHECK begrænsning, hvis du virkelig har brug for at håndhæve en maksimal længde. Jeg bruger bare text , for det meste og spar mig selv for besværet.

  • Jeg foreslår en navnekonvention, hvor fremmednøglekolonnen deler navnet med den refererede kolonne, så master_id i stedet for master_fk , osv. Tillader også at bruge USING i slutter sig.

  • Og jeg sjældent brug det ikke-beskrivende kolonnenavn id . Bruger dept_id i stedet her.

PL/pgSQL-funktion

Det kan stort set forenkles til:

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;

Ring til:

SELECT f_retornar_plpgsql(2, 5);

Eller:

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

Når det er sagt, her kommer nederdelen:du har ikke brug for det meste af dette.

SQL-funktion med rCTE

Selv i Postgres 9.0, en rekursiv CTE gør dette en hel del enklere :

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;

Samme opkald.

Nært beslægtet svar med forklaring:

SQL Fiddle demonstrerer begge dele.



  1. Hvordan indsætter jeg værdier i en tabel fra to arrays i Postgres?

  2. Forstår ikke hvorfor min SQL ikke virker

  3. MYSQL-forespørgsel mellem to tidsstempler

  4. MySQL SELECT SUM baseret på en anden tabel