web-dev-qa-db-de.com

Gibt es eine Möglichkeit, eine benannte Konstante in einer PostgreSQL-Abfrage zu definieren?

Gibt es eine Möglichkeit, eine benannte Konstante in einer PostgreSQL-Abfrage zu definieren? Zum Beispiel:

MY_ID = 5;
SELECT * FROM users WHERE id = MY_ID;
37
Ajedi32

Diese Frage wurde zuvor gestellt ( Wie verwendet man Skriptvariablen in PostgreSQL? ). Es gibt jedoch einen Trick, den ich manchmal für Abfragen verwende:

with const as (
    select 1 as val
)
select . . .
from const cross join
     <more tables>

Das heißt, ich definiere einen CTE namens const mit den dort definierten Konstanten. Ich kann dies dann auf jeder Ebene beliebig oft in meine Abfrage einfügen. Ich habe dies besonders nützlich gefunden, wenn ich mit Datumsangaben arbeite und Datumskonstanten in vielen Unterabfragen behandeln muss.

37
Gordon Linoff

PostgreSQL hat keine eingebaute Möglichkeit, (globale) Variablen wie MySQL oder Oracle zu definieren. (Es gibt eine begrenzte Problemumgehung mit "benutzerdefinierten Optionen" ). Je nachdem, was Sie genau wollen, gibt es andere Möglichkeiten:

Für Eins Abfrage

Sie können Werte oben in einer Abfrage in einem CTE wie @Gordon angeben, das bereits bereitgestellt wurde.

Globale, dauerhafte Konstante:

Sie könnten dazu eine einfache IMMUTABLE-Funktion erstellen:

CREATE FUNCTION public.f_myid()
  RETURNS int IMMUTABLE LANGUAGE SQL AS
'SELECT 5';

Es muss in einem Schema leben, das sichtbar für den aktuellen Benutzer ist, d. H. Sich im jeweiligen search_path befindet. Wie das Schema public standardmäßig. Wenn die Sicherheit ein Problem ist, stellen Sie sicher, dass es das erste Schema in search_path ist oder das Schema in Ihrem Aufruf qualifiziert:

SELECT public.f_myid();

Sichtbar für alle Benutzer in der Datenbank (die auf das Schema public zugreifen dürfen).

Mehrere Werte für aktuelle Sitzung:

CREATE TEMP TABLE val (val_id int PRIMARY KEY, val text);
INSERT INTO val(val_id, val) VALUES
  (  1, 'foo')
, (  2, 'bar')
, (317, 'baz');

CREATE FUNCTION f_val(_id int)
  RETURNS text STABLE LANGUAGE SQL AS
'SELECT val FROM val WHERE val_id = $1';

SELECT f_val(2);  -- returns 'baz'

Da plpgsql das Vorhandensein einer Tabelle beim Erstellen überprüft, müssen Sie eine (temporäre) Tabelle val erstellen, bevor Sie die Funktion erstellen können - auch wenn eine temporäre Tabelle am Ende der Sitzung gelöscht wird, während die Funktion bestehen bleibt. Die Funktion löst eine Ausnahme aus, wenn die zugrunde liegende Tabelle zum Zeitpunkt des Aufrufs nicht gefunden wird.

Das aktuelle Schema für temporäre Objekte liegt standardmäßig vor dem Rest Ihres search_path - sofern nicht ausdrücklich anders angegeben. Das temporäre Schema aus search_path kann nicht ausschließen, aber Sie können andere Schemas zuerst einfügen.
Schlechte Kreaturen der Nacht (mit den erforderlichen Privilegien) können mit dem search_path basteln und ein anderes Objekt desselben Namens voranstellen:

CREATE TABLE myschema.val (val_id int PRIMARY KEY, val text);
INSERT INTO val(val_id, val) VALUES (2, 'wrong');

SET search_path = myschema, pg_temp;

SELECT f_val(2);  -- returns 'wrong'

Dies ist keine große Bedrohung, da nur privilegierte Benutzer die globalen Einstellungen ändern können. Andere Benutzer können dies nur für ihre eigene Sitzung tun. Um dies zu verhindern, überhaupt, legen Sie den search_path für Ihre Funktion fest und qualifizieren Sie ihn im Aufruf:

CREATE FUNCTION f_val(_id int)
  RETURNS text STABLE LANGUAGE SQL AS
'SELECT val FROM val WHERE val_id = $1'
SET search_path = pg_temp;

Oder benutze stattdessen:

... SET search_path = pg_temp, param;

Dies würde Ihnen (oder jedem, der über die erforderlichen Berechtigungen verfügt) ermöglichen, globale (permanente) Standardwerte in einer Tabelle bereitzustellen param.val ...

Beachten Sie das entsprechende Kapitel des Handbuchs zum Erstellen von Funktionen mit SECURITY DEFINER .

Diese äußerst sichere Funktion kann jedoch nicht "eingebettet" werden und ist möglicherweise langsamer als eine einfachere Alternative mit einem fest verdrahteten Schema:

CREATE FUNCTION f_val(_id int)
  RETURNS text STABLE LANGUAGE SQL AS
'SELECT val FROM pg_temp.val WHERE val_id = $1';

Verwandte Antworten mit mehr Optionen:

36

Zusätzlich zu den sinnvollen Optionen, die Gordon und Erwin bereits erwähnt haben (temporäre Tabellen, ständig wiederkehrende Funktionen, CTEs usw.), können Sie (ab) den PostgreSQL-GUC-Mechanismus verwenden, um Variablen auf globaler, Sitzungs- und Transaktionsebene zu erstellen.

Siehe diesen vorherigen Beitrag der den Ansatz im Detail zeigt. 

Ich empfehle dies nicht für den allgemeinen Gebrauch, aber es kann in engen Fällen nützlich sein, wie z. B. in der verlinkten Frage, in der das Plakat eine Möglichkeit suchte, den Benutzernamen auf Anwendungsebene für Auslöser und Funktionen bereitzustellen.

5
Craig Ringer

Ich habe diese Lösung gefunden:

with vars as (
    SELECT * FROM (values(5)) as t(MY_ID)
)
SELECT * FROM users WHERE id = (SELECT MY_ID FROM vars)
2

Ich habe eine Mischung der verfügbaren Ansätze für am besten gefunden:

  • Speichern Sie Ihre Variablen in einer Tabelle:
CREATE TABLE vars (
  id INT NOT NULL PRIMARY KEY DEFAULT 1,
  zipcode INT NOT NULL DEFAULT 90210,
  -- etc..
  CHECK (id = 1)
);
  • Erstellen Sie eine dynamische Funktion, die den Inhalt Ihrer Tabelle lädt und für Folgendes verwendet:
    • Erstellen/Erstellen Sie eine weitere separate statische unveränderliche Getterfunktion.
CREATE FUNCTION generate_var_getter()
RETURNS VOID AS $$
DECLARE
  var_name TEXT;
  var_value TEXT;
  new_rows TEXT[];
  new_sql TEXT;
BEGIN
  FOR var_name IN (
    SELECT columns.column_name
    FROM information_schema.columns
    WHERE columns.table_schema = 'public'
      AND columns.table_name = 'vars'
    ORDER BY columns.ordinal_position ASC
  ) LOOP
    EXECUTE
      FORMAT('SELECT %I FROM vars LIMIT 1', var_name)
      INTO var_value;

    new_rows := ARRAY_APPEND(
      new_rows,
      FORMAT('(''%s'', %s)', var_name, var_value)
    );
  END LOOP;

  new_sql := FORMAT($sql$
    CREATE OR REPLACE FUNCTION var_get(key_in TEXT)
    RETURNS TEXT AS $config$
    DECLARE
      result NUMERIC;
    BEGIN
      result := (
        SELECT value FROM (VALUES %s)
        AS vars_tmp (key, value)
        WHERE key = key_in
      );
      RETURN result;
    END;
    $config$ LANGUAGE plpgsql IMMUTABLE;
  $sql$, ARRAY_TO_STRING(new_rows, ','));

  EXECUTE new_sql;
  RETURN;
END;
$$ LANGUAGE plpgsql;
  • Fügen Sie Ihrer Tabelle einen Aktualisierungsauslöser hinzu, damit nach dem Ändern einer Ihrer Variablen generate_var_getter() aufgerufen wird und die unveränderliche var_get()-Funktion neu erstellt wird.
CREATE FUNCTION vars_regenerate_update()
RETURNS TRIGGER AS $$
BEGIN
  PERFORM generate_var_getter();
  RETURN NULL;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trigger_vars_regenerate_change
  AFTER INSERT OR UPDATE ON vars
  EXECUTE FUNCTION vars_regenerate_update();

Jetzt können Sie Ihre Variablen problemlos in einer Tabelle aufbewahren, erhalten aber auch einen unveränderlichen Zugriff auf diese Variablen. Das Beste aus beiden Welten:

INSERT INTO vars DEFAULT VALUES;
-- INSERT 0 1

SELECT var_get('zipcode')::INT; 
-- 90210

UPDATE vars SET zipcode = 84111;
-- UPDATE 1

SELECT var_get('zipcode')::INT;
-- 84111
0
Brev