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

PostgreSQL:FEJL:42601:en kolonnedefinitionsliste er påkrævet for funktioner, der returnerer post

Returner valgte kolonner

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

Ring til:

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

Du havde DECLARE result record; men brugte ikke variablen. Jeg slettede cruften.

Du kan returnere posten direkte fra UPDATE , hvilket er meget hurtigere end at kalde en ekstra SELECT udmelding. Brug RETURN QUERY og UPDATE med en RETURNING klausul.

Hvis brugeren ikke er _online , standard til en almindelig SELECT . Dette er også den (sikre) standard, hvis den anden parameter er udeladt - hvilket kun er muligt efter at have angivet denne standard med DEFAULT false i funktionsdefinitionen.

Hvis du ikke tabelkvalificerer kolonnenavne (tablename.columnname ) i forespørgsler inde i funktionen skal du være på vagt over for navnekonflikter mellem kolonnenavne og navngivne parametre, som er synlige (de fleste) overalt i en funktion.
Du kan også undgå sådanne konflikter ved at bruge positionsreferencer ($n ) for parametre. Eller brug et præfiks, som du aldrig brug til kolonnenavne:som en understregning (_username ).

Hvis users.username er defineret unik i din tabel, derefter LIMIT 1 i den anden forespørgsel er bare cruft. Hvis det ikke er , derefter UPDATE kan opdatere flere rækker, hvilket højst sandsynligt er forkert . Jeg antager et unikt username og trim støjen.

Definer returtype for funktionen (som @ertx demonstreret), eller du skal levere en kolonnedefinitionsliste med hvert funktionskald, hvilket er akavet.

At oprette en type til det formål (som @ertx foreslået) er en gyldig tilgang, men sandsynligvis overkill for en enkelt funktion. Det var vejen at gå i gamle versioner af Postgres, før vi havde RETURNS TABLE til det formål - som vist ovenfor.

Du behøver ikke en løkke for denne simple funktion.

Hver funktion har brug for en sprogerklæring. LANGUAGE plpgsql i dette tilfælde.

Jeg bruger timestamptz (timestamp with time zone ) i stedet for timestamp (timestamp without time zone ), som er den fornuftige standard. Se:

  • Ignorerer tidszoner helt i Rails og PostgreSQL

Returner (sæt af) hele række(r)

For at returnere alle kolonner af den eksisterende tabel users , der er en enklere måde. Postgres definerer automatisk en sammensat type med samme navn for hver tabel . Brug bare RETURNS SETOF users for at forenkle forespørgslen markant:

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

Returner hele rækken plus tilpasset tilføjelse

For at besvare spørgsmålet tilføjet af TheRealChx101 i en kommentar nedenfor:

Hvad hvis du også har en beregnet værdi ud over en hel tabel? 😑

Ikke så simpelt, men gennemførligt. Vi kan sende hele rækketypen som én felt, og tilføj flere:

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

"Magien" er i funktionskaldet, hvor vi (valgfrit) dekomponerer rækketypen:

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

db<>spil her (viser alle)

Hvis du har brug for noget mere "dynamisk", så overvej:

  • Refaktorer en PL/pgSQL-funktion for at returnere output fra forskellige SELECT-forespørgsler


  1. Bedste tilgang til at fjerne tid en del af datetime i SQL Server

  2. Hvordan får man den første og sidste dato for indeværende år?

  3. Er en deadlock mulig, når du opdaterer og sletter forskellige rækker i en tabel?

  4. to_date funktion med sysdate