PostgreSQL
 sql >> Datenbank >  >> RDS >> PostgreSQL

PostgreSQL:FEHLER:42601:Für Funktionen, die einen Datensatz zurückgeben, ist eine Spaltendefinitionsliste erforderlich

Ausgewählte Spalten zurückgeben

CREATE OR REPLACE FUNCTION get_user_by_username(_username text
                                              , _online bool DEFAULT false)
  RETURNS TABLE (
    user_id int
  , user_name varchar
  , last_activity timestamptz
  )
  LANGUAGE plpgsql AS
$func$
BEGIN
   IF _online THEN
      RETURN QUERY
      UPDATE users u 
      SET    last_activity = current_timestamp  -- ts with time zone
      WHERE  u.user_name = _username
      RETURNING u.user_id
              , u.user_name
              , u.last_activity;
   ELSE
      RETURN QUERY
      SELECT u.user_id
           , u.user_name
           , u.last_activity
      FROM   users u
      WHERE  u.user_name = _username;
   END IF;
END
$func$;

Aufruf:

SELECT * FROM get_user_by_username('myuser', true);

Sie hatten DECLARE result record; habe die Variable aber nicht verwendet. Ich habe die Cruft gelöscht.

Sie können den Datensatz direkt aus dem UPDATE zurückgeben , was viel schneller ist, als ein zusätzliches SELECT aufzurufen Erklärung. Verwenden Sie RETURN QUERY und UPDATE mit einem RETURNING Klausel.

Wenn der Benutzer nicht _online ist , standardmäßig ein einfaches SELECT . Dies ist auch die (sichere) Standardeinstellung, wenn der zweite Parameter weggelassen wird – was nur möglich ist, nachdem diese Standardeinstellung mit DEFAULT false versehen wurde in der Funktionsdefinition.

Wenn Sie Spaltennamen nicht tabellenqualifizieren (tablename.columnname ) in Abfragen innerhalb der Funktion, achten Sie auf Namenskonflikte zwischen Spaltennamen und benannten Parametern, die (fast) überall innerhalb einer Funktion sichtbar sind.
Sie können solche Konflikte auch vermeiden, indem Sie Positionsreferenzen verwenden ($n ) für Parameter. Oder verwenden Sie ein Präfix, das Sie nie verwenden Verwenden Sie für Spaltennamen:wie einen Unterstrich (_username ).

Wenn users.username ist eindeutig definiert in Ihrer Tabelle, dann LIMIT 1 in der zweiten Abfrage ist nur cruft. Wenn es nicht ist , dann das UPDATE kann mehrere Zeilen aktualisieren, was höchstwahrscheinlich falsch ist . Ich gehe von einem eindeutigen username aus und trimme das Rauschen.

Definieren Sie den Rückgabetyp der Funktion (wie @ertx demonstriert) oder Sie müssen bei jedem Funktionsaufruf eine Spaltendefinitionsliste angeben, was umständlich ist.

Das Erstellen eines Typs für diesen Zweck (wie von @ertx vorgeschlagen) ist ein gültiger Ansatz, aber wahrscheinlich übertrieben für eine einzelne Funktion. Das war der Weg in alten Versionen von Postgres, bevor wir RETURNS TABLE hatten zu diesem Zweck - wie oben gezeigt.

Sie brauchen keine Schleife für diese einfache Funktion.

Jede Funktion benötigt eine Sprachdeklaration. LANGUAGE plpgsql in diesem Fall.

Ich verwende timestamptz (timestamp with time zone ) anstelle von timestamp (timestamp without time zone ), was die vernünftige Standardeinstellung ist. Siehe:

  • Zeitzonen in Rails und PostgreSQL komplett ignorieren

Gib (Satz von) ganzen Zeilen zurück

Um alle Spalten zurückzugeben der bestehenden Tabelle users , es gibt einen einfacheren Weg. Postgres definiert automatisch einen zusammengesetzten Typ mit demselben Namen für jede Tabelle . Verwenden Sie einfach RETURNS SETOF users um die Abfrage erheblich zu vereinfachen:

CREATE OR REPLACE FUNCTION get_user_by_username(_username text
                                              , _online bool DEFAULT false)
  RETURNS SETOF users
  LANGUAGE plpgsql AS
$func$
BEGIN
   IF _online THEN
      RETURN QUERY
      UPDATE users u 
      SET    last_activity = current_timestamp
      WHERE  u.user_name = _username
      RETURNING u.*;
   ELSE
      RETURN QUERY
      SELECT *
      FROM   users u
      WHERE  u.user_name = _username;
   END IF;
END
$func$;

Ganze Zeile plus benutzerdefinierter Zusatz zurückgeben

Um die von TheRealChx101 in einem Kommentar unten hinzugefügte Frage zu beantworten:

Was ist, wenn Sie neben einer ganzen Tabelle auch einen berechneten Wert haben? 😑

Nicht so einfach, aber machbar. Wir können den gesamten Zeilentyp als einen senden Feld und fügen Sie weitere hinzu:

CREATE OR REPLACE FUNCTION get_user_by_username3(_username text
                                               , _online bool DEFAULT false)
  RETURNS TABLE (
    users_row users
  , custom_addition text
  )
  LANGUAGE plpgsql AS
$func$
BEGIN
   IF _online THEN
      RETURN QUERY
      UPDATE users u 
      SET    last_activity = current_timestamp  -- ts with time zone
      WHERE  u.user_name = _username
      RETURNING u  -- whole row
              , u.user_name || u.user_id;
   ELSE
      RETURN QUERY
      SELECT u, u.user_name || u.user_id
      FROM   users u
      WHERE  u.user_name = _username;
   END IF;
END
$func$;

Die "Magie" liegt im Funktionsaufruf, wo wir (optional) den Zeilentyp zerlegen:

SELECT (users_row).*, custom_addition FROM get_user_by_username('foo', true);

db<>hier fummeln (zeigt alle)

Wenn Sie etwas "Dynamischeres" brauchen, ziehen Sie in Betracht:

  • Refaktorisieren Sie eine PL/pgSQL-Funktion, um die Ausgabe verschiedener SELECT-Abfragen zurückzugeben