2016-07-11 21 views
3

In einer Grundfunktion Tutorial Postgres gibt es ein Beispiel mit OUT Parameter wie folgt:Split-Funktion zurückgegebenen Datensatz in mehrere Spalten

create or replace function hi_lo(a numeric, 
           b numeric, 
           c numeric, 
           OUT hi numeric, 
           OUT lo numeric) 
as $$ 
begin 
hi := greatest(a, b, c); 
lo := least(a, b, c); 
end; $$ 
language plpgsql; 

Dann schauen Ergebnisse wie

select hi_lo(2, 3, 4); 
-- returns one column, "hi_lo" with value "(4, 2)". 

select * from hi_lo(2, 3, 4); 
-- returns two columns, "hi"/4 and "lo"/2. 

Aber angenommen, Sie wollen Führen Sie die Funktion für Spalten aus, die von der Ausführung eines Joins stammen, und Sie haben keinen Zugriff, um die Funktion zu ändern oder eine alternative Funktion zu verwenden? Zum Beispiel mit einigen Spielzeug Daten:

select hi_lo(a.actor_id, length(a.name), ma.movie_id) 
from 
    actors a 
join 
    movies_actors ma 
on 
    a.actor_id = ma.movie_id 
limit 10; 

liefert Ergebnisse in einer einzigen Spalte "hi_lo" haben 2-Tupel-Werte.

Wrapping der Abfrage in Klammern und versuchen, eine select * von ihm ändert das Format der Ausgabe nicht. So

select * 
from (
    select hi_lo(a.actor_id, length(a.name), ma.movie_id) 
    from 
     actors a 
    join 
     movies_actors ma 
    on 
     a.actor_id = ma.movie_id 
    limit 10; 
) rr 

hat keinen Einfluss auf die Ergebnisform.

Folgende try Ergebnisse in dem Fehler

select (
    select * from hi_lo(a.actor_id, length(a.name), ma.movie_id) 
) 
from 
    actors a 
join 
    movies_actors ma 
on 
    a.actor_id = ma.movie_id 
limit 10; 

Schließlich „Unterabfrage nur eine Spalte zurückgeben muß“, versuchte ich auch unnest aber es gibt einen Fehlerargument Typen als die Tupel Werte als Arrays nicht behandelt werden.

Wie können Sie mehrere Spalten in der Ausgabe erzielen, wenn Sie die Funktionsauswertung nicht in den Bereich from verschieben können?

+0

Ihre Postgres Version? –

Antwort

3

In Postgres 9.3 oder später am besten mit einem LATERAL verbinden gelöst:

SELECT * 
FROM actors a 
JOIN movies_actors ma on a.actor_id = ma.movie_id 
LEFT JOIN LATERAL hi_lo(a.actor_id, length(a.name), ma.movie_id) x ON true 
LIMIT 10; 

Vermeidet wiederholte Auswertung der Funktion (für jede Spalte in der Ausgabe - die Funktion hat für jeden aufgerufen werden Eingabezeile in beide Richtungen).
LEFT JOIN LATERAL ... ON true zu vermeiden Reihen von der linken Seite fällt, wenn die Funktion keine Zeile zurückgibt:

Follow-up in your comment:

nur die erweiterten Spalten, die durch die erzeugten Funktionsaufruf

SELECT x.* -- that's all! 
FROM actors a 
JOIN movies_actors ma on a.actor_id = ma.movie_id 
LEFT JOIN LATERAL hi_lo(a.actor_id, length(a.name), ma.movie_id) x ON true 
LIMIT 10; 

Aber da Sie nicht über andere Spalten kümmern, können Sie vereinfachen zu:

SELECT x.* 
FROM actors a 
JOIN movies_actors ma on a.actor_id = ma.movie_id 
    , hi_lo(a.actor_id, length(a.name), ma.movie_id) x 
LIMIT 10; 

, die eine implizite CROSS JOIN LATERAL ist. Wenn die Funktion gelegentlich "no row" zurückgeben kann, kann das Ergebnis anders sein: Wir erhalten keine NULL-Werte für die Zeilen, diese Zeilen werden nur eliminiert - und LIMIT zählt sie nicht mehr.


In älteren Versionen (oder allgemein) Sie können auch zersetzen nur den Verbundtypen mit der richtigen Syntax:

SELECT *, (hi_lo(a.actor_id, length(a.name), ma.movie_id)).* -- note extra parentheses! 
FROM actors a 
JOIN movies_actors ma on a.actor_id = ma.movie_id 
LIMIT 10;

Der Nachteil ist, dass die Funktion für jede Spalte in der einmal ausgewertet wird Funktionsausgabe aufgrund einer Schwäche im Postgres-Abfrageplaner. Es ist besser, den Aufruf in eine Unterabfrage oder einen CTE zu verschieben und den Zeilentyp im äußeren SELECT zu zerlegen. Wie:

SELECT actor_id, movie_id, (x).* -- explicit column names for the rest 
FROM (
    SELECT *, hi_lo(a.actor_id, length(a.name), ma.movie_id) AS x 
    FROM actors a 
    JOIN movies_actors ma on a.actor_id = ma.movie_id 
    LIMIT 10 
    ) sub; 

Aber müssen Sie einzelne Spalten benennen und kann nicht weg mit SELECT *, wenn Sie mit dem Zeilentyp im Ergebnis redundant sind ok. Verwandte:

+0

Dies ist sehr hilfreich. Eine Folgefrage wäre, wie die Ausgabespalten so eingeschränkt werden, dass sie nur die vom Funktionsaufruf erzeugten erweiterten Spalten sind, und nicht alle Spalten im Join plus die zurückgegebenen Spalten des Funktionsaufrufs. Wenn Sie 'select * from hi_lo 'verwenden, wird dies direkt behandelt, aber das Ergebnis Ihrer aktuellen Abfrage besteht aus 6 Spalten in diesen Daten (4 zusätzliche Spalten aus den Joins). Nehmen Sie insbesondere an, dass Sie die Spaltennamen im Voraus nicht kennen oder dass sich die Funktion außerhalb Ihres Steuerelements ändert. – ely

+0

@ Mr.F: Ich habe eine andere Lösung hinzugefügt. –

+0

Dies ist unglaublich hilfreich, vor allem der Tipp über das Umwickeln des Anrufs in einer Unterabfrage. Das läuft ungefähr 10x schneller als nur den zusammengesetzten Typ ohne den Umbruch zu zerlegen. Was ist der Fehler im Abfrageplaner, der es so viel langsamer macht? – sumizome