CTE funktioniert wie erwartet, jedoch nicht, wenn es in eine Funktion eingebunden ist


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

Als ich versucht habe, alle Nachrichten aus der Nachrichtentabelle in der Site zu finden, die mich interessiert, habe ich einen CTE geschrieben und er funktioniert einwandfrei. Nehmen wir an, ich interessiere mich für die Site 11111 und 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

Das Ergebnis ist wie erwartet:

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

Aber wenn ich das Ganze in eine Funktion einbinde, werden die falschen Zeilen zurückgegeben. Kannst du mir helfen zu verstehen warum?

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;

Bei Verwendung der Funktion:

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

es gibt immer das gleiche Ergebnis unterhalb mehrerer Zeilen aber offensichtlich falsch Zeilen, warum? kannst du hier helfen

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

Lösung

Dank @a_horse_with_no_name habe ich jetzt zwei funktionierende Lösungen, eine mit SQL, eine mit pl / pgsql:

Lösung 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;

Lösung 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;

Testen des Codes

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

Beide gespeicherten PG-Prozeduren funktionieren wie erwartet!

Lösung 3: Eine besser vereinfachte Lösung siehe Erwins Antwort unten.

Jetzt Fall geschlossen!


Das ist eine Funktion, keine SP
James Anderson

3
@ JamesAnderson: Postgres hat keine echten gespeicherten Prozeduren. In der Postgres-Welt wird der Begriff "Prozedur" häufig als Synonym für "Funktion" verwendet
a_horse_with_no_name

Ich habe das Postgres-Tag nicht gesehen. Mein Fehler
James Anderson

Antworten:


8

Ich denke, das liegt daran, dass Sie immer nur die erste Zeile aus dem Ergebnis der Abfrage zurückgeben.

Der select ... into ...ruft nur eine Zeile ab und query select * from resultgibt nur diesen einzelnen Datensatz zurück:

Sie benötigen auch keine PL / pgSQL-Funktion, eine einfache SQL-Funktion funktioniert einwandfrei:

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;

Beachten Sie, dass die Reihenfolge innerhalb des CTE nicht wirklich nützlich ist. Sie müssen die endgültige Auswahl sortieren, nicht die Zwischenschritte.

Wenn Sie PL / pgSQL benötigen, weil Sie mehr Dinge in der Funktion tun, sollten Sie es einfach ändern in:

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;

6

Sie haben "Fall geschlossen" geschrieben, aber ich werde wieder öffnen. Es ist einfach zu viel schief gelaufen ...

Datenbankdesign und Testeinstellung

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

Hauptpunkte

  • Vereinfachen Sie Namen für eine bessere Lesbarkeit.

  • Verwenden Sie keine nicht beschreibenden Spaltennamen wie entity_id. Durch nützliche Namen ersetzt.

  • Es wird empfohlen, für Spalten mit identischem Inhalt denselben Namen zu verwenden. Verwenden patient_idfür die FK-Spalte in messageq.

  • Wenn Sie tatsächlich eine varcharPK in Ihrer Nachrichtenwarteschlange haben, testen Sie mit tatsächlichen varcharWerten.

  • Vereinfachen Sie INSERTAnweisungen.

  • Fügen Sie einen Index hinzumessageq.patient_id . Dies ist entscheidend für die Leistung.

Funktion

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;

Ja das ist alles
Anruf:

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

SQL Fiddle (auf Seite 9.2 ist Seite 9.3 überladen)

Hauptpunkte

  • Kein CTE (oder sogar zwei) erforderlich. Das wäre hier eine Verschwendung von Code und Zeit. Eine einfache Abfrage mit Joins erledigt den Job.

  • Verwendung eines VARIADICParameters für einfachere Aufrufe (optional).
    Verbunden:

  • Wenn (patient_id int, site_held_at)in der Tabelle eindeutig ist patient, benötigen Sie DISTINCTin der Abfrage nicht. Sonst füge es hinzu.


Danke Erwin für all die guten Punkte. Bei der Verwendung von JOIN USING ist mir jedoch aufgefallen, dass bei Verwendung von JOIN ON (SELECT messageq. * FROM patient p JOIN messageq on messageq.patient_id = messageq.patient_id) das erwartete Ergebnis angezeigt wird. Es zeigt ein doppeltes Ergebnis über 110 Datensätze. Wird das erwartet? In beiden Fällen wird DISTINCT nicht mit denselben Daten verwendet.
Gob00st

@ Gob00st: Deine JOIN Bedingung ist falsch, müsste sein SELECT m.* FROM patient p JOIN messageq m ON m.patient_id = p.patient_id ....
Erwin Brandstetter

Entschuldigung, mein Fehler !
Gob00st
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.