sql >> Base de Datos >  >> RDS >> PostgreSQL

PostgreSQL:ERROR:42601:se requiere una lista de definición de columna para las funciones que devuelven un registro

Devolver las columnas seleccionadas

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

Llamar:

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

Tenías DECLARE result record; pero no usó la variable. Eliminé la cruft.

Puede devolver el registro directamente desde UPDATE , que es mucho más rápido que llamar a un SELECT adicional declaración. Utilice RETURN QUERY y UPDATE con un RETURNING cláusula.

Si el usuario no está _online , predeterminado a un simple SELECT . Este también es el valor predeterminado (seguro) si se omite el segundo parámetro, lo cual solo es posible después de proporcionar ese valor predeterminado con DEFAULT false en la definición de la función.

Si no califica los nombres de las columnas en la tabla (tablename.columnname ) en las consultas dentro de la función, tenga cuidado con los conflictos de nombres entre los nombres de las columnas y los parámetros con nombre, que son visibles (la mayoría) en todas partes dentro de una función.
También puede evitar tales conflictos usando referencias posicionales ($n ) para los parámetros. O usa un prefijo que nunca usar para nombres de columna:como un guión bajo (_username ).

Si users.username se define único en su tabla, entonces LIMIT 1 en la segunda consulta es simplemente cruft. Si es no , luego el UPDATE puede actualizar varias filas, lo que probablemente sea incorrecto . Asumo un username único y recorta el ruido.

Definir el tipo de retorno de la función (como demostró @ertx) o debe proporcionar una lista de definición de columna con cada llamada de función, lo cual es incómodo.

Crear un tipo para ese propósito (como propuso @ertx) es un enfoque válido, pero probablemente sea excesivo para una sola función. Ese era el camino a seguir en las versiones antiguas de Postgres antes de que tuviéramos RETURNS TABLE para ese propósito, como se demostró anteriormente.

No necesitas un bucle para esta sencilla función.

Cada función necesita una declaración de idioma. LANGUAGE plpgsql en este caso.

Yo uso timestamptz (timestamp with time zone ) en lugar de timestamp (timestamp without time zone ), que es el valor predeterminado sensato. Ver:

  • Ignorar las zonas horarias por completo en Rails y PostgreSQL

Devolver (conjunto de) fila(s) completa(s)

Para devolver todas las columnas de la tabla existente users , hay una forma más sencilla. Postgres define automáticamente un tipo compuesto del mismo nombre para cada tabla . Simplemente use RETURNS SETOF users para simplificar enormemente la consulta:

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

Devuelve la fila completa más la adición personalizada

Para abordar la pregunta agregada por TheRealChx101 en un comentario a continuación:

¿Qué sucede si también tiene un valor calculado además de una tabla completa? 😑

No tan simple, pero factible. Podemos enviar todo el tipo de fila como uno y agregue más:

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" está en la llamada a la función, donde (opcionalmente) descomponemos el tipo de fila:

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

db<>violín aquí (mostrando todo)

Si necesita algo más "dinámico", considere:

  • Refactorice una función PL/pgSQL para devolver el resultado de varias consultas SELECT