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

La funzione Loop in non funziona come previsto

C'è molto Farei diversamente, e con grande efficacia.

Definizione tabella

A partire dalla definizione della tabella e dalle convenzioni di denominazione. Queste sono per lo più solo opinioni:

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

Punti principali

  • Sei sicuro di aver bisogno di un bigserial per i reparti? Non ce ne sono così tanti su questo pianeta. Un semplice serial dovrebbe essere sufficiente.

  • Non uso quasi mai character varying con un limite di lunghezza. A differenza di altri RDBMS, l'utilizzo di una restrizione non comporta alcun guadagno in termini di prestazioni. Aggiungi un CHECK vincolo se è davvero necessario imporre una lunghezza massima. Uso solo text , principalmente e risparmiami la fatica.

  • Suggerisco una convenzione di denominazione in cui la colonna della chiave esterna condivide il nome con la colonna di riferimento, quindi master_id invece di master_fk , ecc. Permette anche di usare USING nei join.

  • E io raramente usa il nome di colonna non descrittivo id . Usando dept_id invece qui.

Funzione PL/pgSQL

Può essere ampiamente semplificato in:

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;

Chiama:

SELECT f_retornar_plpgsql(2, 5);

Oppure:

SELECT f_retornar_plpgsql(VARIADIC '{2,5}');
  • ALIAS FOR $1 è una sintassi obsoleta e scoraggiato . Utilizzare invece i parametri della funzione.

  • Il VARIADIC parametro rende più conveniente chiamare. Correlati:

  • Non hai bisogno di EXECUTE per query senza elementi dinamici. Niente da guadagnare qui.

  • Non è necessaria la gestione delle eccezioni per creare una tabella. Citando il manuale qui :

  • Postgres 9.1 o versioni successive ha CREATE TEMP TABLE IF NOT EXISTS . Uso una soluzione alternativa per 9.0 per creare condizionalmente la tabella temporanea.

  • Postgres 9.1 offre anche FOREACH per scorrere un array .

Detto questo, ecco la pecca:non ti serve la maggior parte di questo.

Funzione SQL con rCTE

Anche in Postgres 9.0, un CTE ricorsivo rende tutto questo molto più semplice :

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;

Stessa chiamata.

Risposta strettamente correlata con spiegazione:

SQL Fiddle che mostra entrambi.