PostgresSQL Dynamic Execute mit Argumentwerten im Array

8

Ich frage mich, ob dies in Postgres möglich ist:

Am besten anhand eines erfundenen Beispiels erklären:

create or replace function test_function(filter_param1 varchar default null
                                       , filter_param2 varchar default null)
  returns integer as
$$ 
declare
  stmt text;
  args varchar[];
  wher varchar[];
  retid integer;
begin
  if filter_param1 is not null then 
    array_append(args, filter_param1);
    array_append(wher, 'parameter_name = $1');
  end if;
  if filter_param2 is not null then 
    array_append(args, filter_param2);
    array_append(wher, 'parameter_name = $2');
  end if;

  stmt := 'select id from mytable where ' || array_to_string(wher, ' or ');
  execute stmt into retid using args;

  return retid;
end;
$$ language plpgsql;

In Python gibt es *args- vielleicht hat PostgreSQL einen ähnlichen Mechanismus?

EDIT für Erwin Brandstetter Fragen:

  • Alle filterParameter werden auf verschiedene Spalten angewendet, sollten jedoch UND-verknüpft sein.
  • Die Rückkehr setofist hier viel sinnvoller.
  • Alle Parameter können vom gleichen Spaltentyp sein (dh varchar).
Richard
quelle
Nein, EXECUTE ... USINGich kann kein Array von Argumenten annehmen, da PostgreSQL-Arrays keine heterogenen Typen unterstützen. Sie müssen ein Array eines einzelnen konkreten Typs sein, und SQL hat möglicherweise nicht für jeden Parameter den gleichen Typ. Es wäre nutzlos, außer für sehr enge Anwendungsfälle. Stattdessen USINGmüsste in der Lage sein, einen anonymen Datensatz zu erstellen, wie Sie ihn von einem ROW(...)Konstruktor erstellen ... was wahrscheinlich möglich, aber derzeit nicht implementiert ist.
Craig Ringer
Es wird am besten mit einem Beispiel und einer tatsächlichen Erklärung erklärt . Beabsichtigen Sie, alle Parameter auf dieselbe Spalte anzuwenden parameter_name? (Das könnte weitgehend vereinfacht werden.) Oder denken Sie wirklich an verschiedene Spalten? Möchten Sie einen einzelnen Wert oder eine Reihe von Werten zurückgeben? Eine Reihe von OR-Prädikaten gibt normalerweise mehrere Zeilen zurück, was nicht zu Ihrer RETURNSKlausel passt .
Erwin Brandstetter

Antworten:

6

In beiden Fällen ist dies durchaus möglich , da alle Ihre Parameter vom gleichen Datentyp sind .

EXECUTE ... USINGnimmt gerne ein Array, das als einzelnes Argument behandelt wird. Greifen Sie auf Elemente mit Array-Indizes zu.

create or replace function test_function(_filter1 text = null
                                       , _filter2 text = null
                                       , OUT retid int) as
$func$
declare
   _args text[] := ARRAY[_filter1, _filter2];
   _wher text[];
begin
   if _filter1 is not null then 
      _wher := _wher || 'parameter_name = $1[1]'; -- note array subscript
   end if;

   if _filter2 is not null then 
      _wher := _wher || 'parameter_name = $1[2]'; -- assign the result!
   end if;

   IF _args  IS NULL         -- check whether all params are NULL
      RAISE EXCEPTION 'At least one parameter required!';
   END IF;

   execute 'select id from mytable where ' -- cover case with all params NULL
         || array_to_string(_wher, ' or ')
         || ' ORDER BY id LIMIT 1';   -- For a single value (???)
   into  retid
   using _args;
end
$func$  language plpgsql;

Dies ist nur ein Proof of Concept und unnötig kompliziert. Dies wäre eine interessante Option für die tatsächliche Array-Eingabe, beispielsweise mit einer VARIADICFunktion .

Verwenden Sie für den vorliegenden Fall stattdessen:

CREATE OR REPLACE FUNCTION test_function(_filter1 text = null
                                       , _filter2 text = null)
  RETURNS SETOF int AS
$func$
DECLARE
   _wher text := concat_ws(' OR '
             , CASE WHEN _filter1 IS NOT NULL THEN 'parameter_name = $1' END
             , CASE WHEN _filter2 IS NOT NULL THEN 'parameter_name = $2' END);
BEGIN
   IF _wher = ''   -- check whether all params are NULL
      RAISE EXCEPTION 'At least one parameter required!';
   END IF;

   RETURN QUERY EXECUTE 'SELECT id FROM mytable WHERE ' || _wher
   USING  $1, $2;
   -- USING  _filter1 , filter2; -- alternatively use func param names
END
$func$  LANGUAGE plpgsql;

Erklären

  • Listen Sie alle Werte, auf die möglicherweise in der dynamischen Abfrage verwiesen werden kann, in der USINGKlausel in ihrer Reihenfolge auf. Wenn nicht alle in der dynamischen Abfrage referenziert werden, schadet dies nicht. Aber wir müssen die Ordnungspositionen intakt halten.

  • Beachten Sie insbesondere, dass in der dynamischen Abfrage bestimmte Werte der Klausel nach Ordnungszahl referenziert werden , während in der Klausel auf Funktionsparameter verwiesen wird. Gleiche Syntax, anderer Umfang! In meinem Beispiel Referenzen der Einfachheit halber. Man kann aber die Werte in der Klausel auf jede Art und Weise neu anordnen, so dass (zum Beispiel) in der dynamischen Abfrage auf die 2. Position in der Klausel verwiesen wird, die auf den 1. Funktionsparameter verweist.$n USING$n USING
    $2$2USING$2$1USING

  • Dies ermöglicht eine beliebige Anzahl von Parametern mit beliebigen (heterogenen) Datentypen .

  • Rückgabe einer Ganzzahl in diesem Beispiel ( RETURNS SETOF int), die besser zum Beispiel passt - RETURN QUERY EXECUTEentsprechend verwenden.

  • concat_ws() ist besonders praktisch, um eine Liste von ODER- oder UND-Prädikaten unter bestimmten Bedingungen zusammenzustellen.

Erwin Brandstetter
quelle
1
danke - noch besser! Interessant, dass sich pg im 2. Beispiel nicht über überschüssige Argumente beschwert - wenn ein Filter angegeben ist.
Richard
@Richard: In der USINGKlausel angegebene Argumente können 0-n-mal referenziert werden. Es ist nicht genau das gleiche Konzept wie *argsin Python, es ist tatsächlich flexibler.
Erwin Brandstetter
-2
RETURN QUERY EXECUTE SELECT
        TotalBookingAmount,
        TotalQuantity,
        TotalActualWeight,
        TotalChargedWeight,
        TotalLR,
        TotalDeliveryAmount,
        GrandTotalAmount,
        waybilltypeid,
        bookingmonth,
        bookingyear,
        sourcebranchid, 
        destinationbranchid 
                FROM '|| tableName || '
            where 
                accountgroupid          = ' || accountGropIdCol     :: numeric || '
            AND sourcebranchid          = ' || SourceBranchIdCol    :: numeric || '
            AND bookingmonth            = ' || bookingMonthCol      :: numeric || '
            AND bookingyear             = ' || bookingYearCol       :: numeric || '
            AND destinationbranchid     =  any (string_to_array(' || DestinationBranchIdCol ||',' || ',' || ')' :: numeric[] ||')';
PRAMOD
quelle
Willkommen bei den Datenbankadministratoren ! Bitte erläutern Sie, wie Ihre Anfrage das Problem des Autors löst. Antworten ohne Erklärung werden im Allgemeinen nicht gut aufgenommen.
Glorfindel
1
Diese Antwort hat nichts mit der Frage zu tun. Vielleicht hast du auf der falschen Seite gepostet?
Richard