web-dev-qa-db-ja.com

pl / pgsql:レコードから列名を動的に取得します

挨拶、レコードから列名を動的に取得したい。以下のコードに示すように、カーソルを作成し、ループを使用して、各行をレコードタイプr1にフェッチすることにより、そのカーソル内の各レコードを処理しました。私のテーブルには、これらの列があります[dlq_2000、dlq_2001、...、dlq_2017、dlq_2017]。また、各列を個別に処理するループを作成しました。

私が直面している問題は、r1からフィールド名を動的に取得することであり、コードを実行するとこのエラーが発生します。

[エラー]エラー:レコード "r1"にはフィールド "'dlq_' || counter :: text"がありませんCONTEXT:SQLステートメント "SELECT(r1。" 'dlq_' || counter :: text "= 1)"

この問題を解決する方法をアドバイスしてください。

ありがとうございました、

CREATE OR REPLACE FUNCTION update()
   RETURNS VOID AS $$
DECLARE 
cur SCROLL CURSOR   FOR select * from my_tbl;
r1 RECORD;
begin
OPEN cur ;
FOR counter IN  2000..2017 LOOP
    r1 := NULL;
    LOOP
        FETCH cur INTO r1;
        EXIT WHEN NOT FOUND;

        IF (r1."'dl_'||counter::text" = 1 ) THEN
            -- do some thing
            RAISE NOTICE 'processing year of : %', counter;
        END IF;     
    END LOOP;
END LOOP;
CLOSE cur;
END; 

 $$

LANGUAGE plpgsql;
1
Eyla

「変数列」をチェックするだけの簡単な方法はありません。 CASEを使用してこの結果を達成する(あまりエレガントではない)方法があります。

CREATE OR REPLACE FUNCTION update()
    RETURNS VOID AS $$
DECLARE 
    cur SCROLL CURSOR   FOR select * from my_tbl;
    r1 RECORD;
BEGIN
    OPEN cur ;
    FOR counter IN  2000..2017 LOOP
        r1 := NULL;
        LOOP
            FETCH cur INTO r1;
            EXIT WHEN NOT FOUND;

            IF
              (CASE counter 
                WHEN 2000 THEN r1.dl_2000
                WHEN 2001 THEN r1.dl_2001
                WHEN 2002 THEN r1.dl_2002
                WHEN 2003 THEN r1.dl_2003
                WHEN 2004 THEN r1.dl_2004
                WHEN 2005 THEN r1.dl_2005
                WHEN 2006 THEN r1.dl_2006
                WHEN 2007 THEN r1.dl_2007
                WHEN 2008 THEN r1.dl_2008
                WHEN 2009 THEN r1.dl_2009
                WHEN 2010 THEN r1.dl_2010
                WHEN 2011 THEN r1.dl_2011
                WHEN 2012 THEN r1.dl_2012
                WHEN 2013 THEN r1.dl_2013
                WHEN 2014 THEN r1.dl_2014
                WHEN 2015 THEN r1.dl_2015
                WHEN 2016 THEN r1.dl_2016
                WHEN 2017 THEN r1.dl_2017
              END) = 1 
            THEN
                -- do some thing
                RAISE NOTICE 'processing year of : %', counter;
            END IF;     
        END LOOP;
    END LOOP;
    CLOSE cur;
END; 
$$
LANGUAGE plpgsql;

すべての列dl_2000 .. dl_2017integer(またはbit)として定義されていると想定しています。つまり、テーブル定義は次のようになります。

CREATE TABLE t
(
    /* some columns */
    dl_2000 integer, 
    dl_2001 integer, 
    dl_2002 integer, 
    dl_2003 integer, 
    /* ... */
    dl_2017 integer,
    /* more columns */
) ;

代わりに整数のARRAYを使用できます。

CREATE TABLE t
(
    /* some columns */
    dl integer[],
    /* more columns */
) ;

dlには、列と同じ方法でNOT NULL制約を設定できますが、異なる方法で記述されています。つまり、必要に応じて、CHECK (dl[2000] NOT NULL)を設定できます。FOREIGN KEYを設定することはできません。それらの場合の制約。

その場合、関数は次のようになります。

CREATE OR REPLACE FUNCTION update()
    RETURNS VOID AS $$
DECLARE 
    cur SCROLL CURSOR FOR select * from my_tbl;
    r1 RECORD;
BEGIN
    OPEN cur ;
    FOR counter IN  2000..2017 LOOP
        r1 := NULL;
        LOOP
            FETCH cur INTO r1;
            EXIT WHEN NOT FOUND;

            IF dl[counter] = 1 THEN
                -- do some thing
                RAISE NOTICE 'processing year of : %', counter;
            END IF;     
        END LOOP;
    END LOOP;
    CLOSE cur;
END; 
$$
LANGUAGE plpgsql;

注:関数のロジックは変更していませんが、この方法で変更するかどうかはよくわかりません。少なくとも、2つのループを交換します。カーソル用に大きく、次に列用(または配列のインデックス用)です。

1
joanolo

row_to_json関数

do $$
declare
  r json;
  i int;
begin
  for r in 
    select row_to_json(t.*) 
    from (values(1,'a1','a2','a3'),(2,'b1','b2','b3')) as t(x,y11,y12,y13) 
  loop
    raise info '%', r;
    for i in 11..13 loop
      if r->>('y'||i) like '%2' then -- Condition here
        raise info 'Do something for %', r->>('y'||i);
      end if;
    end loop;
  end loop;
end $$;
情報:{"x":1、 "y11": "a1"、 "y12": "a2"、 "y13": "a3"} 
情報:a2 
情報:{"x":2、 "y11": "b1"、 "y12": "b2"、 "y13": "b3"} 
情報:b2 

配列コンストラクタ の使用:

do $$
declare
  r record;
  i int;
begin
  for r in 
    select x, array[y11,y12,y13] as y 
    from (values(1,'a1','a2','a3'),(2,'b1','b2','b3')) as t(x,y11,y12,y13) 
  loop
    raise info '%', r;
    for i in 1..3 loop
      if r.y[i] like '%2' then -- Condition here
        raise info 'Do something for %', r.y[i];
      end if;
    end loop;
  end loop;
end $$;
情報:(1、 "{a1、a2、a3}")
情報:a2に対して何かを行う
情報:(2、 "{b1、b2、b3}" )
情報:b2に対して何かを行います

そして data normalization を使用します:

do $$
declare
  r record;
  i int;
begin
  for r in
    with
      test as (
        select * 
        from (values(1,'a1','a2','a3'),(2,'b1','b2','b3')) as t(x,y11,y12,y13)),
      norm as (
        select *, unnest(array[y11,y12,y13]) as y, unnest(array[11,12,13]) as z from test)
    select * from norm
    where y like '%2'  -- Condition here
  loop
    raise info 'Do something for %', r;
  end loop;
end $$;
情報:(1、a1、a2、a3、a2,12)に対して何かを行う
情報:(2、b1、b2、b3、b2,12)に対して何かを行う

より単純な例で、より複雑なタスクでどのように実行できるかを示します。
しかし、それはPostgreSQL 9.5でテストされました

3
Abelisto

@joanoloのつま先を踏むべきではありませんが、私があなたが何を求めているのか理解しているのであれば、ここに答えにアプローチする別の方法があります。データを別の方法で保存するとクエリが簡単になることに同意します。

次のようなデータがあるとします。

CREATE TABLE my_tbl ( id serial primary key, dl_2000 integer, dl_2001 integer, dl_2002 integer, dl_2003 integer, dl_2017 integer); insert into my_tbl (dl_2000,dl_2001,dl_2002,dl_2003,dl_2017) values (1,null,null,null,1), (1,null,1,null,null), (null,1,null,null,null), (2,4,5,6,7);

一致するかどうか動的に列をクエリできます。

DO $$ DECLARE rec RECORD; v_col_id TEXT; BEGIN FOR rec IN SELECT column_name,array_to_string(regexp_matches(column_name,'[0-9]+$'),',')::integer AS year FROM information_schema.columns WHERE table_name = 'my_tbl' LOOP EXECUTE FORMAT('SELECT id FROM my_tbl WHERE %I = 1', rec.column_name) INTO v_col_id; IF ( v_col_id IS NOT NULL) THEN RAISE NOTICE 'Column % for year %, with id of % matched.', rec.column_name, rec.year, v_col_id; END IF; END LOOP; END; $$;デモのために、匿名のplpgsql関数を使用しました。名前付き関数として簡単に書くことができます。

1
bma