sql >> Database >  >> RDS >> PostgreSQL

PostgreSQL:FOUT:42601:een kolomdefinitielijst is vereist voor functies die record retourneren

Geselecteerde kolommen retourneren

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

Bel:

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

U had DECLARE result record; maar heb de variabele niet gebruikt. Ik heb de cruft verwijderd.

U kunt het record rechtstreeks terugsturen vanuit de UPDATE , wat veel sneller is dan het aanroepen van een extra SELECT uitspraak. Gebruik RETURN QUERY en UPDATE met een RETURNING clausule.

Als de gebruiker niet _online . is , standaard een gewone SELECT . Dit is ook de (veilige) standaard als de tweede parameter wordt weggelaten - wat alleen mogelijk is na het verstrekken van die standaard met DEFAULT false in de functiedefinitie.

Als u kolomnamen niet kwalificeert voor tabellen (tablename.columnname ) in zoekopdrachten binnen de functie, wees op uw hoede voor naamgevingsconflicten tussen kolomnamen en benoemde parameters, die (de meeste) overal in een functie zichtbaar zijn.
U kunt dergelijke conflicten ook vermijden door positionele verwijzingen te gebruiken ($n ) voor parameters. Of gebruik een voorvoegsel dat u nooit gebruik voor kolomnamen:zoals een onderstrepingsteken (_username ).

Als users.username is gedefinieerd uniek in uw tabel, dan LIMIT 1 in de tweede vraag is gewoon cruft. Als het niet is , dan de UPDATE kan meerdere rijen bijwerken, wat hoogstwaarschijnlijk fout is . Ik ga uit van een unieke username en trim het geluid.

Definieer het retourtype van de functie (zoals @ertx demonstreerde) of je moet een kolomdefinitielijst opgeven bij elke functieaanroep, wat onhandig is.

Het creëren van een type voor dat doel (zoals @ertx voorgesteld) is een geldige benadering, maar waarschijnlijk overkill voor een enkele functie. Dat was de manier om te gaan in oude versies van Postgres voordat we RETURNS TABLE hadden voor dat doel - zoals hierboven aangetoond.

U geen lus nodig voor deze eenvoudige functie.

Elke functie heeft een taaldeclaratie nodig. LANGUAGE plpgsql in dit geval.

Ik gebruik timestamptz (timestamp with time zone ) in plaats van timestamp (timestamp without time zone ), wat de normale standaard is. Zie:

  • Tijdzones volledig negeren in Rails en PostgreSQL

Retourneer (set van) hele rij(en)

Om alle kolommen terug te geven van de bestaande tabel users , is er een eenvoudigere manier. Postgres definieert automatisch een samengesteld type met dezelfde naam voor elke tafel . Gebruik gewoon RETURNS SETOF users om de zoekopdracht enorm te vereenvoudigen:

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

Gehele rij retourneren plus aangepaste toevoeging

Om de vraag te beantwoorden die door TheRealChx101 in een opmerking hieronder is toegevoegd:

Wat als je naast een hele tabel ook een berekende waarde hebt?

Niet zo simpel, maar wel te doen. We kunnen het hele rijtype verzenden als één veld en voeg meer toe:

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

De "magie" zit in de functieaanroep, waar we (optioneel) het rijtype ontleden:

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

db<>viool hier (laat alles zien)

Als u iets "dynamischer" nodig heeft, overweeg dan:

  • Refactor een PL/pgSQL-functie om de uitvoer van verschillende SELECT-query's te retourneren


  1. Ik wil de database herstellen met een ander schema

  2. Hoe SQL-variabelen in PHP te binden?

  3. Hoe converteer ik een string tot nu toe in MySQL?

  4. Migreren van Oracle naar PostgreSQL - wat u moet weten