SQL & PL/pgSQL Functions
Function declarations in all languages: simple SQL, PL/pgSQL, set-returning, variadic, trigger, and named dollar-quoting.
Functions are written in complete, unmodified PostgreSQL syntax — CREATE OR REPLACE FUNCTION with only the CREATE OR REPLACE verb removed. The dollar-quoted body is Part 1. The { } block holds grants, comments, and deprecation notices.
Simple SQL function
SCHEMA public {
FUNCTION active_user_count() RETURNS BIGINT
LANGUAGE sql STABLE PARALLEL SAFE
AS $$
SELECT COUNT(*) FROM users WHERE status = 'active';
$$;
}
-- emits
CREATE OR REPLACE FUNCTION "public"."active_user_count"()
RETURNS bigint
LANGUAGE sql STABLE PARALLEL SAFE
AS $$
SELECT COUNT(*) FROM users WHERE status = 'active';
$$;
PL/pgSQL function with grants and comment
FUNCTION get_user(p_email TEXT) RETURNS users
LANGUAGE plpgsql STABLE SECURITY DEFINER SET search_path = public
AS $$
DECLARE
v_user users;
BEGIN
SELECT * INTO STRICT v_user FROM users WHERE email = p_email;
RETURN v_user;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RAISE EXCEPTION 'User not found: %', p_email;
END;
$$ {
COMMENT 'Fetch a user record by verified email address';
GRANTS { EXECUTE TO app_service; }
}
-- emits
CREATE OR REPLACE FUNCTION "public"."get_user"(p_email text)
RETURNS users
LANGUAGE plpgsql STABLE SECURITY DEFINER SET search_path = public
AS $$
DECLARE
v_user users;
BEGIN
SELECT * INTO STRICT v_user FROM users WHERE email = p_email;
RETURN v_user;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RAISE EXCEPTION 'User not found: %', p_email;
END;
$$;
COMMENT ON FUNCTION "public"."get_user"(text) IS 'Fetch a user record by verified email address';
GRANT EXECUTE ON FUNCTION "public"."get_user"(text) TO "app_service";
Set-returning function (SETOF)
FUNCTION users_in_org(p_org_id UUID) RETURNS SETOF users
LANGUAGE sql STABLE
AS $$
SELECT * FROM users WHERE org_id = p_org_id ORDER BY email;
$$;
CREATE OR REPLACE FUNCTION "public"."users_in_org"(p_org_id uuid)
RETURNS SETOF users
LANGUAGE sql STABLE
AS $$
SELECT * FROM users WHERE org_id = p_org_id ORDER BY email;
$$;
Returns TABLE inline
FUNCTION monthly_summary(p_year INT)
RETURNS TABLE (month INT, revenue NUMERIC, count BIGINT)
LANGUAGE sql STABLE
AS $$
SELECT
EXTRACT(MONTH FROM created_at)::INT,
SUM(total),
COUNT(*)
FROM orders
WHERE EXTRACT(YEAR FROM created_at) = p_year
GROUP BY 1 ORDER BY 1;
$$;
CREATE OR REPLACE FUNCTION "public"."monthly_summary"(p_year integer)
RETURNS TABLE ("month" integer, "revenue" numeric, "count" bigint)
LANGUAGE sql STABLE
AS $$
SELECT
EXTRACT(MONTH FROM created_at)::INT,
SUM(total),
COUNT(*)
FROM orders
WHERE EXTRACT(YEAR FROM created_at) = p_year
GROUP BY 1 ORDER BY 1;
$$;
Variadic arguments
FUNCTION log_event(p_type TEXT, VARIADIC p_tags TEXT[]) RETURNS VOID
LANGUAGE plpgsql
AS $$
BEGIN
INSERT INTO event_log (event_type, tags) VALUES (p_type, p_tags);
END;
$$;
CREATE OR REPLACE FUNCTION "public"."log_event"(p_type text, VARIADIC p_tags text[])
RETURNS void
LANGUAGE plpgsql
AS $$
BEGIN
INSERT INTO event_log (event_type, tags) VALUES (p_type, p_tags);
END;
$$;
Default parameters
FUNCTION paginate_users(p_limit INT DEFAULT 20, p_offset INT DEFAULT 0)
RETURNS SETOF users
LANGUAGE sql STABLE
AS $$
SELECT * FROM users LIMIT p_limit OFFSET p_offset;
$$;
CREATE OR REPLACE FUNCTION "public"."paginate_users"(
p_limit integer DEFAULT 20,
p_offset integer DEFAULT 0
)
RETURNS SETOF users
LANGUAGE sql STABLE
AS $$
SELECT * FROM users LIMIT p_limit OFFSET p_offset;
$$;
Trigger function
FUNCTION notify_email_change() RETURNS TRIGGER
LANGUAGE plpgsql SECURITY DEFINER SET search_path = public
AS $$
BEGIN
PERFORM pg_notify('email_changed', NEW.id::TEXT);
RETURN NEW;
END;
$$;
CREATE OR REPLACE FUNCTION "public"."notify_email_change"()
RETURNS trigger
LANGUAGE plpgsql SECURITY DEFINER SET search_path = public
AS $$
BEGIN
PERFORM pg_notify('email_changed', NEW.id::TEXT);
RETURN NEW;
END;
$$;
Named dollar-quoting
Use a named tag when the function body itself contains $$ literals.
FUNCTION format_price(p_amount NUMERIC) RETURNS TEXT
LANGUAGE plpgsql IMMUTABLE STRICT
AS $func$
BEGIN
RETURN '$' || TO_CHAR(p_amount, 'FM999,999,990.00');
END;
$func$ {
COMMENT 'Format a numeric amount as a dollar price string';
GRANTS { EXECUTE TO app_readonly, app_service; }
}
CREATE OR REPLACE FUNCTION "public"."format_price"(p_amount numeric)
RETURNS text
LANGUAGE plpgsql IMMUTABLE STRICT
AS $func$
BEGIN
RETURN '$' || TO_CHAR(p_amount, 'FM999,999,990.00');
END;
$func$;
COMMENT ON FUNCTION "public"."format_price"(numeric) IS 'Format a numeric amount as a dollar price string';
GRANT EXECUTE ON FUNCTION "public"."format_price"(numeric) TO "app_readonly", "app_service";
Diffing behaviour
Function identity is (schema, name, argument_types). Any change to the body text (including whitespace) changes the stored hash and causes CREATE OR REPLACE FUNCTION to be emitted. No semantic diff is performed.
Function attributes reference: Attributes