web-dev-qa-db-ja.com

配列に複数の値を選択してループする方法は? (postgres 9.3)

議論のために簡単な表があります。 idを選択してそれらをループする_loop_test_という関数があります。 IDの配列を選択してループし、トランザクションに変更を加えることができます。

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

テーブル:

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

select loop_test()を呼び出すと、次の結果が得られます。

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

ただし、idと_other_id_の両方を選択して配列にする関数を作成したいと思います。 _agg_array_のようなものを使用するように言われました、しかし私はそれがどのように機能するか完全には理解していません。

私は次のようなものを想像していましたか?

_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 muchより良い方法ですが、更新するだけです。ループは必要ありません。

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

WHERE条件は、idがnullになる可能性があり、完全に同等のものが必要な場合にのみ役立ちます。

Loop

ループを探索しているだけなら、複数の変数を割り当てることができます。

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;

既知のタイプの2つの列が必要なだけですが、行全体(場合によっては大きい)をフェッチするよりも少し安くなる可能性があります。

3

@アーウィンの返答は完全に正しいです。説明されている例で配列を使用すると、パフォーマンスエラーが発生します(残念ながら一般的です)。一部の値を関数パラメーターとして渡す必要があるため、必要になる場合があります。

2つの手法があります。1。複合値の配列を渡す、2。多次元配列を渡す。パフォーマンスは+/-で同じである必要があります-コンポジットの配列を使用する方が読みやすい場合があります。 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;

おそらくまだ、関数UPDATEで循環せずに使用できるunnestステートメントは1つだけです。

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;

パフォーマンスへの影響は、アレイのサイズに依存します-小さなアレイの場合は最小限になりますが、それでもサイクルのネストが深くなる可能性があり、パフォーマンスの問題が発生する可能性があります。

多次元の場合 arrays PLpgSQL FOREACHステートメントには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

多次元配列については知りませんが、私がやろうとしていたことを実行するはるかに良い方法を見つけました:

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