2015-12-01 10 views
6

Wie kann ich das in einer Datensatzvariable gespeicherte Abfrageergebnis für eine andere Abfrage in derselben gespeicherten Funktion verwenden? Ich benutze Postgres 9.4.4.Wie verwendet man eine Datensatzvariable in plpgsql?

Mit einer Tabelle wie folgt:

create table test (id int, tags text[]); 
insert into test values (1,'{a,b,c}'), 
         (2,'{c,d,e}'); 

Ich schrieb eine Funktion (vereinfacht) wie unten:

CREATE OR REPLACE FUNCTION func(_tbl regclass) 
RETURNS TABLE (t TEXT[], e TEXT[]) 
LANGUAGE plpgsql AS $$ 
DECLARE 
    t RECORD; 
    c INT; 
BEGIN 
    EXECUTE format('SELECT id, tags FROM %s', _tbl) INTO t; 
    SELECT count(*) FROM t INTO c; 
    RAISE NOTICE '% results', c; 
    SELECT * FROM t; 
END 
$$; 

... aber hat nicht funktioniert:

select func('test'); 
ERROR: 42P01: relation "t" does not exist 
LINE 1: SELECT count(*) FROM t 
          ^
QUERY: SELECT count(*) FROM t 
CONTEXT: PL/pgSQL function func(regclass) line 7 at SQL statement 
LOCATION: parserOpenTable, parse_relation.c:986 

Antwort

15

Das Kern Missverständnis: eine record Variable enthält eine einzelne Zeile (oder ist NULL), keine Tabelle (0-n-Zeilen eines bekannten Typs). Es gibt keine "Tabellenvariablen" in Postgres oder PL/pgSQL. Je nach Aufgabenstellung gibt es verschiedene Alternativen:

Dementsprechend können Sie nicht mehrere Zeilen zu einem record Typ Variablen zuweisen. In dieser Erklärung:

EXECUTE format('SELECT id, tags FROM %s', _tbl) INTO t; 

... Postgres ordnet nur die erste Zeile und verwirft den Rest. Da "der erste" in Ihrer Abfrage nicht gut definiert ist, erhalten Sie eine willkürliche Auswahl. Offensichtlich aufgrund des eingangs erwähnten Missverständnisses. Eine record Variable kann auch nicht anstelle von Tabellen in SQL-Abfragen verwendet werden. Das ist die Hauptursache für den Fehler, den Sie erhalten:

Beziehung „t“ existiert nicht

Es sollte nun klar sein, dass count(*) keinen Sinn zu beginnen machen würde, da t ist nur ein einziger Datensatz/Reihe - abgesehen davon, dass es sowieso unmöglich ist.

Schließlich (auch wenn der Rest funktionieren würde), scheint der Rückgabetyp falsch zu sein: (t TEXT[], e TEXT[]) . Da Sie id, tags in t auswählen, möchten Sie etwas wie (id int, e TEXT[]) zurückgeben.

Was Sie versuchen, zu tun würde dieses wie arbeiten:

CREATE OR REPLACE FUNCTION func(_tbl regclass) 
    RETURNS TABLE (id int, e text[]) AS 
$func$ 
DECLARE 
    _ct int; 
BEGIN 
    EXECUTE format(
     'CREATE TEMP TABLE tmp ON COMMIT DROP AS 
     SELECT id, tags FROM %s' 
    , _tbl); 

    GET DIAGNOSTICS _ct = ROW_COUNT; -- cheaper than another count(*) 

    -- ANALYZE tmp; -- if you are going to run multiple queries 

    RAISE NOTICE '% results', _ct; 

    RETURN QUERY TABLE tmp; 
END 
$func$ LANGUAGE plpgsql; 

Anruf (beachten Sie die Syntax!):

SELECT * FROM func('test'); 

Verwandte:

nur ein Proof of Concept. Während Sie die gesamte Tabelle auswählen, verwenden Sie stattdessen nur die zugrunde liegende Tabelle. In Wirklichkeit werden Sie einige WHERE Klausel in der Abfrage haben ...

Sorgfältige des Ungleichgewichts Lauern Typ, count() kehrt bigint, Sie konnte sie nicht zuordnen zu einer integer Variable. Brauche eine Besetzung: count(*)::int.

Aber ich ersetzt, dass vollständig, es ist billiger, dieses Recht nach EXECUTE auszuführen:

GET DIAGNOSTICS _ct = ROW_COUNT; 

Details in the manual.

Warum ANALYZE?


Abgesehen: CTEs im Klar SQL kann oft die Arbeit machen:

+0

Danke so viel, Erwin, für Art und detaillierte Antwort. Jetzt ist der Grund für mich sehr klar. Ich werde CTE für den Zweck verwenden. –

Verwandte Themen