web-dev-qa-db-ja.com

CTEは期待どおりに機能しますが、関数にラップされた場合は機能しません

--testing table
CREATE TABLE public.test_patient_table (
                entity_id INTEGER NOT NULL,
                site_held_at INTEGER NOT NULL,
                CONSTRAINT entityid_pk PRIMARY KEY (entity_id)
);

CREATE TABLE public.test_messageq_table (
                entity_id VARCHAR NOT NULL,
                master_id INTEGER NOT NULL,
                message_body VARCHAR NOT NULL,
                CONSTRAINT mq_entity_id_pk PRIMARY KEY (entity_id)
);

CREATE INDEX test_patient_table_siteid_idx
 ON public.test_patient_table
 ( site_held_at );

ALTER TABLE public.test_messageq_table
ADD CONSTRAINT test_patient_table_test_messageq_table_fk
FOREIGN KEY (master_id)
REFERENCES public.test_patient_table (entity_id)
ON DELETE NO ACTION
ON UPDATE NO ACTION
NOT DEFERRABLE;

--test patient data
insert into test_patient_table values (1, 11111);
insert into test_patient_table values (2, 11111);
insert into test_patient_table values (3, 11111);
insert into test_patient_table values (4, 11111);

insert into test_patient_table values (5, 22222);
insert into test_patient_table values (6, 22222);
insert into test_patient_table values (7, 22222);
insert into test_patient_table values (8, 22222);

insert into test_patient_table values (9, 33333);
insert into test_patient_table values (10, 33333);

insert into test_patient_table values (11, 44444);

--testing message
insert into test_messageq_table values (1, 1, 'aaa');
insert into test_messageq_table values (2, 1, 'aaa');
insert into test_messageq_table values (3, 1, 'aaa');
insert into test_messageq_table values (4, 1, 'aaa');
insert into test_messageq_table values (5, 2, 'aaa');
insert into test_messageq_table values (6, 2, 'aaa');
insert into test_messageq_table values (7, 5, 'aaa');
insert into test_messageq_table values (8, 8, 'aaa');
insert into test_messageq_table values (9, 11, 'aaa');
insert into test_messageq_table values (10, 11, 'bbb');    

興味のあるサイトのメッセージテーブルからすべてのメッセージを見つけようとしたところ、CTEを作成しましたが、問題なく動作しました。たとえば、サイト11111および22222に興味があるとします。

WITH patient_msg_in_branches AS (
    select distinct test_messageq_table.master_id AS patient_id,
    test_patient_table.site_held_at as site_id
    from test_messageq_table 
    inner join test_patient_table 
    ON test_messageq_table.master_id = test_patient_table.entity_id 
    and site_held_at in (11111,22222) order by patient_id
),
messages_for_patients AS(
    select * from test_messageq_table where master_id in 
        (select patient_msg_in_branches.patient_id 
            from patient_msg_in_branches)
)select * from messages_for_patients

結果は期待どおりです。

"1";1;"aaa"
"2";1;"aaa"
"3";1;"aaa"
"4";1;"aaa"
"5";2;"aaa"
"6";2;"aaa"
"7";5;"aaa"
"8";8;"aaa"

しかし、すべてを関数でラップすると、間違った行が返されます。理由を教えてください。

drop function getMessageFromSites(text);
CREATE OR REPLACE FUNCTION getMessageFromSites(IN ids TEXT) RETURNS 
setof test_messageq_table AS $$ 
DECLARE
       sites INT[];
       result test_messageq_table%rowtype;

BEGIN
       sites = string_to_array(ids,',');
        raise info 'entire array: %', sites;

WITH patient_msg_in_branches AS (
    select distinct test_messageq_table.master_id AS patient_id,
    test_patient_table.site_held_at as site_id
    from test_messageq_table 
    inner join test_patient_table 
    ON test_messageq_table.master_id = test_patient_table.entity_id 
    and site_held_at = ANY(sites) order by patient_id
),
messages_for_patients AS(
    select * from test_messageq_table where master_id in 
        (select patient_msg_in_branches.patient_id 
            from patient_msg_in_branches)
)select * into result from messages_for_patients;
return query select * from result;
END;     
$$ LANGUAGE plpgsql;

関数を使用する場合:

select * from getMessageFromSites('11111,22222');
select * from getMessageFromSites('1')
select * from getMessageFromSites('33333')

それは常に同じ結果を返します複数の行ですが、明らかに間違った行です、なぜですか?ここでお手伝いできますか?

"1";1;"aaa"
"2";1;"aaa"
"3";1;"aaa"
"4";1;"aaa"
"5";2;"aaa"
"6";2;"aaa"
"9";11;"aaa"
"10";11;"bbb"

解決

@a_horse_with_no_nameのおかげで、今は2つの実用的なソリューションがあり、1つはSQLを使用し、もう1つはpl/pgsqlを使用しています。

ソリューション1(pl/pgsql)

CREATE OR REPLACE FUNCTION getMessageFromSites(IN ids TEXT) RETURNS 
setof test_messageq_table AS $$ 
DECLARE
       sites INT[];
       result test_messageq_table%rowtype;

BEGIN
       sites = string_to_array(ids,',');
       raise info 'entire array: %', sites;
 return QUERY

    WITH patient_msg_in_branches AS (
        select distinct test_messageq_table.master_id AS patient_id,
        test_patient_table.site_held_at as site_id
        from test_messageq_table 
        inner join test_patient_table 
        ON test_messageq_table.master_id = test_patient_table.entity_id 
        and site_held_at = ANY(sites) order by patient_id
    ),
    messages_for_patients AS(
        select * from test_messageq_table where master_id in 
            (select patient_msg_in_branches.patient_id 
                from patient_msg_in_branches)
    )
    select * from messages_for_patients;

END;     
$$ LANGUAGE plpgsql;

ソリューション2(SQL)

CREATE OR REPLACE FUNCTION getMessageFromSites2(ids TEXT) RETURNS 
   setof test_messageq_table 
AS 
$$ 
  WITH patient_msg_in_branches AS (
      select distinct test_messageq_table.master_id AS patient_id,
             test_patient_table.site_held_at as site_id
      from test_messageq_table 
      join test_patient_table ON test_messageq_table.master_id = test_patient_table.entity_id 
                                and site_held_at = ANY (string_to_array($1,',')::int[]) 
  ),
  messages_for_patients AS
  (
    select * 
    from test_messageq_table 
    where master_id in (select patient_msg_in_branches.patient_id 
                        from patient_msg_in_branches)
  )
  select * 
  from messages_for_patients;
$$ 
LANGUAGE sql;

コードのテスト

select * from getMessageFromSites('11111,44444');
select * from getMessageFromSites('22222');
select * from getMessageFromSites('1')
select * from getMessageFromSites('33333')

select * from getMessageFromSites2('11111');
select * from getMessageFromSites2('22222');
select * from getMessageFromSites2('33333');
select * from getMessageFromSites('44444,11111');
select * from getMessageFromSites('1');

両方のPGストアドプロシージャは期待どおりに動作しています。

解決策3:以下のErwinの回答からわかる、よりシンプルな解決策。

ケースはクローズされました!

7
Gob00st

これは、クエリの結果から最初の行のみを返すためだと思います。

select ... into ...は1行のみを取得し、query select * from resultは、その単一のレコードのみを返します。

PL/pgSQL関数も必要ありません。プレーンなSQL関数で問題なく動作します。

CREATE OR REPLACE FUNCTION getMessageFromSites(ids TEXT) RETURNS 
   setof test_messageq_table 
AS 
$$ 
  WITH patient_msg_in_branches AS (
      select distinct test_messageq_table.master_id AS patient_id,
             test_patient_table.site_held_at as site_id
      from test_messageq_table 
         join test_patient_table ON test_messageq_table.master_id = test_patient_table.entity_id 
                                and site_held_at = ANY (string_to_array(ids,',')::int[]) 
  ),
  messages_for_patients AS
  (
    select * 
    from test_messageq_table 
    where master_id in (select patient_msg_in_branches.patient_id 
                        from patient_msg_in_branches)
  )
  select * 
  from messages_for_patients;
$$ 
LANGUAGE sql;

CTE内での順序は実際には役に立たないことに注意してください。中間ステップではなく、最終選択をソートする必要があります。

関数でより多くのことを行うためにPL/pgSQLが必要な場合は、単に次のように変更する必要があります。

begin
  ....
  return query
    WITH patient_msg_in_branches AS (
        select distinct test_messageq_table.master_id AS patient_id,
        test_patient_table.site_held_at as site_id
        from test_messageq_table 
        inner join test_patient_table 
        ON test_messageq_table.master_id = test_patient_table.entity_id 
        and site_held_at = ANY(sites) order by patient_id
    ),
    messages_for_patients AS(
        select * from test_messageq_table where master_id in 
            (select patient_msg_in_branches.patient_id 
                from patient_msg_in_branches)
    )
    select * from messages_for_patients;
end;

「ケースクローズ」と書いてありましたが、再開します。間違いが多すぎます...

データベースの設計とテストの設定

CREATE TABLE patient (
   patient_id   int PRIMARY KEY
 , site_held_at int NOT NULL
);

CREATE TABLE messageq (
   messageq_id  varchar PRIMARY KEY  -- varchar ?!
 , patient_id   int NOT NULL REFERENCES patient
 , message_body varchar NOT NULL
);

CREATE INDEX patient_site_idx ON patient(site_held_at);
CREATE INDEX messageq_patient_id_idx ON patient(patient_id); -- !!

INSERT INTO patient VALUES
  (1, 11111)
, (2, 11111)
, (3, 11111)
, (4, 11111)
, (5, 22222)
, (6, 22222)
, (7, 22222)
, (8, 22222)
, (9, 33333)
, (10, 33333)
, (11, 44444);

INSERT INTO messageq VALUES
  ('m1', 1, 'aaa1')
, ('m2', 1, 'aaa2')
, ('m3', 1, 'aaa3')
, ('m4', 1, 'aaa4')
, ('m5', 2, 'aaa5')
, ('m6', 2, 'aaa6')
, ('m7', 5, 'aaa7')
, ('m8', 8, 'aaa8')
, ('m9', 11, 'aaa9')
, ('m10', 11, 'bbb10');

主なポイント

  • 読みやすくするために名前を簡略化します。

  • entity_idのような説明的でない列名は使用しないでください。有用な名前に置き換えられました。

  • 同じ内容の列には同じ名前を使用することをお勧めします。 messageqのFK列にpatient_idを使用します。

  • 実際にメッセージキューにvarchar PKがある場合は、実際のvarchar値でテストします。

  • INSERTステートメントを簡略化します。

  • messageq.patient_idのインデックスを追加します。これはパフォーマンスにとって非常に重要です。

関数

CREATE OR REPLACE FUNCTION f_get_msg_from_sites(VARIADIC _id int[])
  RETURNS SETOF messageq AS 
$func$ 
   SELECT m.*
   FROM   patient  p
   JOIN   messageq m USING (patient_id)
   WHERE  p.site_held_at = ANY($1)
$func$ LANGUAGE sql;

はい、それだけです。
コール:

SELECT * FROM f_get_msg_from_sites(11111, 44444);
SELECT * FROM f_get_msg_from_sites(22222);

SQLフィドル (pg9.2では、pg9.3がオーバーロードされています)

主なポイント

  • CTE(または2つ)は必要ありません。ここではコードと時間の無駄になります。結合を使用した簡単なクエリで十分です。

  • より簡単な呼び出しのために VARIADIC パラメータを使用する(オプション)。
    関連:

  • (patient_id int, site_held_at)がテーブルpatientで一意である場合、クエリにDISTINCTは必要ありません。それ以外の場合は追加します。

6