PostgreSQL
 sql >> Database >  >> RDS >> PostgreSQL

PostgreSQL:ERRORE:42601:è richiesto un elenco di definizioni di colonna per le funzioni che restituiscono record

Restituisce le colonne selezionate

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$;

Chiama:

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

Avevi DECLARE result record; ma non ha usato la variabile. Ho eliminato il cruft.

Puoi restituire il record direttamente da UPDATE , che è molto più veloce che chiamare un ulteriore SELECT dichiarazione. Usa RETURN QUERY e UPDATE con un RETURNING clausola.

Se l'utente non è _online , il valore predefinito è un semplice SELECT . Questa è anche l'impostazione predefinita (sicura) se il secondo parametro viene omesso, il che è possibile solo dopo aver fornito a tale impostazione predefinita DEFAULT false nella definizione della funzione.

Se non qualifichi la tabella, i nomi delle colonne (tablename.columnname ) nelle query all'interno della funzione, fai attenzione ai conflitti di denominazione tra nomi di colonna e parametri denominati, che sono visibili (per la maggior parte) ovunque all'interno di una funzione.
Puoi anche evitare tali conflitti utilizzando i riferimenti posizionali ($n ) per i parametri. Oppure usa un prefisso che mai utilizzare per i nomi delle colonne:come un trattino basso (_username ).

Se users.username è definito unico nella tabella, quindi LIMIT 1 nella seconda query è solo cruft. Se lo è non , quindi UPDATE può aggiornare più righe, il che molto probabilmente è errato . Presumo un username univoco e taglia il rumore.

Definisci il tipo di restituzione della funzione (come dimostrato da @ertx) oppure devi fornire un elenco di definizioni di colonna con ogni chiamata di funzione, il che è imbarazzante.

La creazione di un tipo per quello scopo (come proposto da @ertx) è un approccio valido, ma probabilmente eccessivo per una singola funzione. Questa era la strada da percorrere nelle vecchie versioni di Postgres prima che avessimo RETURNS TABLE a tale scopo - come dimostrato sopra.

Non hai bisogno di un ciclo per questa semplice funzione.

Ogni funzione necessita di una dichiarazione di lingua. LANGUAGE plpgsql in questo caso.

Uso timestamptz (timestamp with time zone ) invece di timestamp (timestamp without time zone ), che è l'impostazione predefinita sana. Vedi:

  • Ignora del tutto i fusi orari in Rails e PostgreSQL

Restituisci (insieme di) righe intere

Per restituire tutte le colonne della tabella esistente users , c'è un modo più semplice. Postgres definisce automaticamente un tipo composito con lo stesso nome per ogni tabella . Usa semplicemente RETURNS SETOF users per semplificare notevolmente la query:

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$;

Restituisci l'intera riga più l'aggiunta personalizzata

Per rispondere alla domanda aggiunta da TheRealChx101 in un commento qui sotto:

Cosa succede se si dispone anche di un valore calcolato oltre a un'intera tabella? 😑

Non così semplice, ma fattibile. Possiamo inviare l'intero tipo di riga come uno campo e aggiungi altro:

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$;

La "magia" è nella chiamata di funzione, dove (opzionalmente) scomponiamo il tipo di riga:

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

db<>gioca qui (mostra tutto)

Se hai bisogno di qualcosa di più "dinamico", considera:

  • Refactoring di una funzione PL/pgSQL per restituire l'output di varie query SELECT