web-dev-qa-db-fra.com

Comment sélectionner plusieurs valeurs dans un tableau et boucler? (postgres 9.3)

J'ai un tableau simple à des fins d'argument. J'ai une fonction qui sélectionne les identifiants et les boucle à travers eux appelés loop_test. Je peux sélectionner un tableau d'ID et les parcourir, provoquant mes modifications dans une transaction.

CREATE OR REPLACE FUNCTION loop_test() RETURNS void AS $$
DECLARE
        _ids_array INTEGER[];
        _id INTEGER;
BEGIN
        SELECT ARRAY(SELECT id FROM loop_test) INTO _ids_array; 
        FOREACH _id IN ARRAY _ids_array
        LOOP
                UPDATE loop_test SET looped = TRUE WHERE id = _id;
        END LOOP;
END;
$$ LANGUAGE plpgsql;

Table:

db=# \d loop_test;
      Table "public.loop_test"
    Column     |  Type   | Modifiers 
---------------+---------+-----------
 id            | integer | 
 other_id      | integer | 
 id_copy       | integer | 
 other_id_copy | integer | 
 looped        | boolean | 

db=# select * from loop_test;
 id | other_id | id_copy | other_id_copy | looped 
----+----------+---------+---------------+--------
  1 |       10 |         |               | 
  6 |       15 |         |               | 
  2 |       11 |         |               | 
  7 |       16 |         |               | 
  3 |       12 |         |               | 
  4 |       13 |         |               | 
  5 |       14 |         |               | 
(7 rows)

Lorsque j'appelle select loop_test(), j'obtiens les résultats suivants:

db=# select * from loop_test;
 id | other_id | id_copy | other_id_copy | looped 
----+----------+---------+---------------+--------
  1 |       10 |         |               | t
  6 |       15 |         |               | t
  2 |       11 |         |               | t
  7 |       16 |         |               | t
  3 |       12 |         |               | t
  4 |       13 |         |               | t
  5 |       14 |         |               | t
(7 rows)

Je voudrais cependant créer une fonction pour sélectionner à la fois le id et le other_id Dans un tableau. On m'a dit d'utiliser quelque chose comme agg_array, mais je ne comprends pas complètement comment cela fonctionne.

J'imaginais quelque chose comme ce qui suit?

CREATE OR REPLACE FUNCTION agg_loop_test() RETURNS void AS $$
DECLARE
        _ids_array INTEGER[][];
        _id INTEGER;
BEGIN
        SELECT AGG_ARRAY(SELECT id, other_id FROM loop_test) INTO _ids_array;
        FOREACH _id IN ARRAY _ids_array
        LOOP
                UPDATE loop_test SET id_copy = _id[0], other_id_copy = _id[1] WHERE id = _id[0];
        END LOOP;
END;
$$ LANGUAGE plpgsql;
2
NONONO

A beaucoup meilleure façon, encore: il suffit de mettre à jour. Aucune boucle nécessaire.

UPDATE loop_test
SET    id_copy = id
     , other_id_copy = other_id;
WHERE  id IS NOT NULL;

La condition WHERE n'est utile que si id peut être nulle et que vous voulez un équivalent parfait de ce que vous aviez.

Loop

Si vous explorez simplement des boucles - vous pouvez affecter plusieurs variables:

CREATE OR REPLACE FUNCTION better_loop_test()
  RETURNS void AS
$func$
DECLARE
   _id int;
   _other_id int;
BEGIN
   -- example makes no sense, just a loop demo
   FOR _id, _other_id IN
      SELECT id, other_id FROM loop_test
   LOOP
      UPDATE loop_test
      SET    id_copy = _id
           , other_id_copy = _other_id
      WHERE id = _id;
   END LOOP;
END
$func$  LANGUAGE plpgsql;

Bien que vous ayez juste besoin des deux colonnes de type connu, cela peut être un peu moins cher que de récupérer des lignes entières (éventuellement grandes).

3
Erwin Brandstetter

La réponse de @ Erwin est absolument correcte. L'utilisation d'un tableau pour l'exemple décrit est une erreur de performance (malheureusement courante). Parfois, cela peut être nécessaire, car vous devez transmettre certaines valeurs en tant que paramètres de fonction.

Il existe deux techniques: 1. passer un tableau de valeurs composites, 2. passer un tableau multidimensionnel. Les performances devraient être +/- identiques, pour moi - l'utilisation d'un tableau de composite peut être dans certains cas plus lisible. Pas sûr, si vous pouvez créer des tableaux multidimensionnels à partir du résultat de la requête sur 9.3.

CREATE TYPE test_type AS (id1 int, id2 int);

CREATE OR REPLACE FUNCTION fx1(ids test_type[])
RETURNS void AS $$
DECLARE r test_type;
FOR r IN ARRAY ids
LOOP
  UPDATE ...
END LOOP;

probablement encore, il ne peut être utilisé qu'une seule instruction UPDATE sans cycle avec la fonction unnest:

CREATE TABLE test (id1 integer, id2 integer);

UPDATE test SET id2 = u.id2 
  FROM unnest(array[(1,10),(3,4)]::test_type[]) u
 WHERE test.id1 = u.id1;

L'impact sur les performances dépend de la taille des baies - pour les petites baies, il sera minime - mais il peut toujours y avoir une imbrication plus profonde des cycles, et là, il peut y avoir un problème de performances.

Pour les instructions multidimensionnelles tableaux PLpgSQL FOREACH a la clause SLICE:

CREATE OR REPLACE FUNCTION fx2(ids int[])
RETURNS void AS $$
DECLARE _ids int[];
BEGIN
  FOREACH _ids SLICE 1 IN ARRAY ids
  LOOP
    RAISE NOTICE 'ids[0]=% ids[1]=%', _ids[0], _ids[1];
  END LOOP;
END;
$$ LANGUAGE plpgsql;

postgres=# SELECT fx2(ARRAY[[1,2],[3,4]]);
NOTICE:  ids[0]=<NULL> ids[1]=1
NOTICE:  ids[0]=<NULL> ids[1]=3
1
Pavel Stehule

Je ne connais pas les tableaux multidimensionnels, mais j'ai trouvé une bien meilleure façon de faire ce que j'essayais de faire:

CREATE OR REPLACE FUNCTION better_loop_test() RETURNS void AS $$
DECLARE
        _row RECORD;
BEGIN
        FOR _row IN SELECT * FROM loop_test LOOP
                UPDATE loop_test SET id_copy = _row.id, other_id_copy = _row.other_id WHERE id = _row.id;
        END LOOP;
END;
$$ LANGUAGE plpgsql;
0
NONONO