sql >> Databáze >  >> RDS >> PostgreSQL

Funkce Loop in nefunguje podle očekávání

Je toho hodně Udělal bych to jinak a s velkým efektem.

Definice tabulky

Počínaje definicí tabulky a konvencemi pojmenování. Jsou to většinou jen názory:

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

Hlavní body

  • Jste si jisti, že potřebujete bigserial pro oddělení? Na této planetě jich není tolik. Prostý serial by mělo stačit.

  • Téměř nikdy nepoužívám character varying s omezením délky. Na rozdíl od některých jiných RDBMS nedochází při použití omezení k žádnému zvýšení výkonu. Přidejte CHECK omezení, pokud opravdu potřebujete vynutit maximální délku. Používám pouze text , většinou a ušetřím si problémy.

  • Navrhuji konvenci pojmenování, kde sloupec cizího klíče sdílí název s odkazovaným sloupcem, takže master_id místo master_fk , atd. Umožňuje také použít USING v připojení.

  • A já zřídka použijte nepopisný název sloupce id . Pomocí dept_id místo toho zde.

Funkce PL/pgSQL

Lze to do značné míry zjednodušit na:

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;

Volejte:

SELECT f_retornar_plpgsql(2, 5);

Nebo:

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

Vše, co bylo řečeno, přichází problém:většinu z toho nepotřebujete.

Funkce SQL s rCTE

I v Postgresu 9.0 je rekurzivní CTE to o dost jednodušší :

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;

Stejný hovor.

Úzce související odpověď s vysvětlením:

SQL Fiddle demonstruje obojí.



  1. Upozornění, když někdo nahraje video

  2. Získejte krátký název měsíce v PostgreSQL

  3. Jak bezpečně vložit kód do databáze mySQL

  4. Nápověda k případu aktualizace MySQL