Gleiche Funktion in der SELECT- und WHERE-Klausel


11

Anfängerfrage:

Ich habe eine teure Funktion f(x, y)für zwei Spalten x und y in meiner Datenbanktabelle.

Ich möchte eine Abfrage ausführen, die mir das Ergebnis der Funktion als Spalte gibt und sie einschränkt, so etwas wie

SELECT *, f(x, y) AS func FROM table_name WHERE func < 10;

Dies funktioniert jedoch nicht, so dass ich so etwas schreiben muss

SELECT *, f(x, y) AS func FROM table_name WHERE f(x, y) < 10;

Wird dadurch die teure Funktion zweimal ausgeführt? Was ist der beste Weg, dies zu tun?


1
Ist die Funktion STABLE/ IMMUTABLEoder VOLATILE?
Evan Carroll

Antworten:


22

Lassen Sie uns eine Funktion erstellen, die einen Nebeneffekt hat, damit wir sehen können, wie oft sie ausgeführt wird:

CREATE OR REPLACE FUNCTION test.this_here(val integer)
    RETURNS numeric
    LANGUAGE plpgsql
AS $function$
BEGIN
    RAISE WARNING 'I am called with %', val;
    RETURN sqrt(val);
END;
$function$;

Und dann nenne das so wie du:

SELECT this_here(i) FROM generate_series(1,10) AS t(i) WHERE this_here(i) < 2;

WARNING:  I am called with 1
WARNING:  I am called with 1
WARNING:  I am called with 2
WARNING:  I am called with 2
WARNING:  I am called with 3
WARNING:  I am called with 3
WARNING:  I am called with 4
WARNING:  I am called with 5
WARNING:  I am called with 6
WARNING:  I am called with 7
WARNING:  I am called with 8
WARNING:  I am called with 9
WARNING:  I am called with 10
    this_here     
──────────────────
                1
  1.4142135623731
 1.73205080756888
(3 rows)

Wie Sie sehen, wird die Funktion mindestens einmal (aus der WHEREKlausel) aufgerufen , und wenn die Bedingung erfüllt ist, erneut, um die Ausgabe zu erzeugen.

Um die zweite Ausführung zu vermeiden, können Sie das tun, was Edgar vorschlägt - nämlich die Abfrage umbrechen und die Ergebnismenge filtern:

SELECT * 
  FROM (SELECT this_here(i) AS val FROM generate_series(1,10) AS t(i)) x 
 WHERE x.val < 2;

WARNING:  I am called with 1
... every value only once ...
WARNING:  I am called with 10

Um weiter zu überprüfen, wie dies funktioniert, kann man dorthin gehen pg_stat_user_functionsund nachsehen calls(angegeben track_functionsist auf 'all' gesetzt).

Versuchen wir es mit etwas, das keine Nebenwirkungen hat:

CREATE OR REPLACE FUNCTION test.simple(val numeric)
 RETURNS numeric
 LANGUAGE sql
AS $function$
SELECT sqrt(val);
$function$;

SELECT simple(i) AS v 
  FROM generate_series(1,10) AS t(i)
 WHERE simple(i) < 2;
-- output omitted

SELECT * FROM pg_stat_user_functions WHERE funcname = 'simple';
-- 0 rows

simple()ist eigentlich zu einfach, so dass es inline sein kann , daher erscheint es nicht in der Ansicht. Machen wir es unsinnig:

CREATE OR REPLACE FUNCTION test.other_one(val numeric)
 RETURNS numeric
 LANGUAGE sql
AS $function$
SELECT 1; -- to prevent inlining
SELECT sqrt(val);
$function$;

SELECT other_one(i) AS v
  FROM generate_series(1,10) AS t(i)
 WHERE other_one(i) < 2;

SELECT * FROM pg_stat_user_functions ;
 funcid  schemaname  funcname   calls  total_time  self_time 
────────┼────────────┼───────────┼───────┼────────────┼───────────
 124311  test        other_one     13       0.218      0.218

SELECT *
  FROM (SELECT other_one(i) AS v FROM generate_series(1,10) AS t(i)) x 
 WHERE v < 2;

SELECT * FROM pg_stat_user_functions ;
 funcid  schemaname  funcname   calls  total_time  self_time 
────────┼────────────┼───────────┼───────┼────────────┼───────────
 124311  test        other_one     23       0.293      0.293

Wie es aussieht, ist das Bild mit oder ohne Nebenwirkungen dasselbe.

Durch Ändern other_one()in IMMUTABLEwird das Verhalten (möglicherweise überraschend) verschlechtert, da es in beiden Abfragen 13 Mal aufgerufen wird.


Könnte die Entscheidung, die Funktion erneut aufzurufen, durch das Vorhandensein einer Nebenwirkung im Funktionskörper bestimmt werden? Ist es möglich, anhand des Abfrageplans herauszufinden, ob eine Funktion mit denselben Parametern ein- oder mehrmals pro Zeile aufgerufen wird (wenn sie beispielsweise keinen Nebeneffekt hatte)?
Andriy M

@AndriyM Ich kann mir ja vorstellen, habe aber momentan keine Zeit mit einem Debugger zu spielen, um zu sehen, wie es eigentlich heißt. Fügt ein wenig über Inline-Funktionen hinzu (was nicht der Fall ist, den das OP erwarten sollte, wie es sich anhört).
Dekso

1
@AndriyM, laut: postgresql.org/docs/9.1/static/sql-createfunction.html wird angenommen, dass eine Funktion VOLATIL ist, wenn sie nicht als IMMUTABLE oder STABLE deklariert ist. VOLATILE gibt an, dass sich der Funktionswert auch innerhalb eines einzelnen Tabellenscans ändern kann, sodass keine Optimierungen vorgenommen werden können.
Lennart

5

Versuchen Sie es erneut:

SELECT
     *
FROM (
SELECT
     *,
     f(x, y) AS func
FROM table_name
) a
WHERE a.func < 10;
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.