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