How to display column headers returned by PostgreSQL function?
Use your set returning function in the FROM
clause
SELECT * FROM userbyid(1);
as opposed to
SELECT userbyid(1);
Here is dbfiddle demo
Sample output:
id | username | email | last_login
----+----------+-------------------+------------------------
1 | user1 | user1@example.com | 2017-06-13 12:00:00-04
How to derive a column name in the return type from input parameters to the function?
How to derive a column name in the return type from input parameters to the function?
The short answer: Not possible.
SQL is very rigid about column data types and names. Those have to be declared before or at call time at the latest. No exceptions. No truly dynamic column names.
I can think of 3 half-way workarounds:
1. Column aliases
Use your function as is (or rather the audited version I suggest below) and add column aliases in the function call:
SELECT * FROM func_getratio_laglag('order_first',1,'order_last',0,'customers_hist')
AS f(date_t, customer_code, index, order_first_1_order_last_0)
I would do that.
2. Column definition list
Create your function to return anonymous records:
RETURNS SETOF record
Then you have to provide a column definition list with every call:
SELECT * FROM func_getratio_laglag('order_first',1,'order_last',0,'customers_hist')
AS f(date_t timestamp, customer_code text, index text, order_first_1_order_last_0 real)
I would not do that.
3. Use a registered row type as polymorphic input / output type.
Mostly useful if you happen to have row types at hand. You could register a row type on the fly by crating a temporary table, but that seems like overkill for your use case.
Details in the last chapter of this answer:
- Refactor a PL/pgSQL function to return the output of various SELECT queries
Function audit
Use format()
to make building query string much more safe and simple.
Read the manual if you are not familiar with it.
CREATE OR REPLACE FUNCTION func_getratio_laglag(
_numerator_lag text, _n1 int
, _denominator_lag text, _n2 int
, _table regclass)
RETURNS TABLE (date_t timestamp, customer_code text, index text, ratio real) AS
$func$
BEGIN
RETURN QUERY EXECUTE format (
'SELECT date_t, customer_code, index
, (lag(%I, %s) OVER w / lag(%I, %s) OVER w) -- data type must match
FROM %s
WINDOW w AS (PARTITION BY customer_code ORDER BY date_t)'
, _numerator_lag, _n1, _denominator_lag, _n2, _table::text
);
END
$func$ LANGUAGE plpgsql;
Note the data type regclass
for the table name. That's my personal (optional) suggestion.
- Table name as a PostgreSQL function parameter
Aside: I would also advise not to use mixed-case identifiers in Postgres.
- Are PostgreSQL column names case-sensitive?
PostgreSQL function with table and column names as parameters,and would return min and max of that column (any type) as an array?
You can't do this with the standard polymorphic types, but you could do this with jsonb.
CREATE OR REPLACE FUNCTION minmax(tablename regclass, columnname character varying)
RETURNS JSONB AS
$function$
DECLARE
minmaxa jsonb;
BEGIN
EXECUTE FORMAT('SELECT jsonb_agg(foo) AS %I FROM (
(SELECT min(%I) AS foo FROM %I)
UNION
(SELECT max(%I) AS foo FROM %I)
) AS foobar'
, columnname
, columnname, tablename
, columnname, tablename)
INTO minmaxa;
RETURN minmaxa;
END
$function$
STABLE
LANGUAGE plpgsql;
-- Test with integers
CREATE TABLE test (a int);
insert into test (a) values (1), (2), (3);
select minmax('test'::regclass, 'a');
minmax
--------
[1, 2]
(1 row)
-- Test with timestamps
create table test2 (a timestamp);
insert into test2 values ('2012-01-01T00:00:00'), ('2015-01-01T00:00:00');
select minmax('test2'::regclass, 'a');
minmax
------------------------------------------------
["2012-01-01T00:00:00", "2015-01-01T00:00:00"]
(1 row)
I used regclass instead of string for the tablename to help find the correct table according to your search path.
postgres functions loses column names
This is a bug in pgAdmin (reported here).
The function definition is stored correctly when you first create it. pgAdmin displays it incorrectly, but calls to the function should still behave as expected.
The problem only arises when you take pgAdmin's CREATE OR REPLACE
statement and run it. The recreation will appear to succeed, but the function will fail at runtime due to the missing variable declaration.
As long as you remember to fix the return type every time you recreate the function, you shouldn't have a problem.
Function returning a table for given table and column names
SQL is a strictly typed language and Postgres functions must declare their return type. Returning a variable number of columns from a function is only possible with workarounds, like polymorphic types. See:
- How to return a table by rowtype in PL/pgSQL
But we can't work with the row type in your case, as that varies from database to database. The remaining option: return anonymous records and provide a column definition list with every call. I don't usually recommend this, as providing a column definition list with every call can be tedious - and often pointless. But yours might be one of the rare use cases where it makes sense.
Still, you have to know the data type of possibly missing columns. I'll assume integer
for the purpose of this demo. Else you have to pass data types additionally and build the query accordingly.
CREATE OR REPLACE FUNCTION f_dynamic_select(_tbl regclass
, _cols VARIADIC text[]) -- ①
RETURNS SETOF record -- ② anonymous records
LANGUAGE plpgsql AS
$func$
BEGIN
RETURN QUERY EXECUTE -- ③ dynamic SQL
format(
'SELECT %s FROM %s' -- ④ safe against SQLi
, (
SELECT string_agg(COALESCE(quote_ident(a.attname)
, '0 AS ' || quote_ident(t.col) -- assuming integer!
), ', ' ORDER BY t.ord) -- ⑤
FROM unnest(_cols) WITH ORDINALITY t(col, ord) -- ⑤
LEFT JOIN pg_attribute a ON a.attrelid = _tbl -- ⑥
AND a.attnum > 0
AND NOT a.attisdropped
AND a.attname = t.col
)
, _tbl
);
END
$func$;
Call (important!)
SELECT *
FROM f_dynamic_select('static', 'pool', 'spa', 'sauna', 'house_size', 'no_rooms')
AS t(pool int, spa int, house_size int, sauna int, no_rooms int); -- ② column definition list
Your example call, with expressions based on these columns:
SELECT pool, case when spa = 1 then 1 else 0 end as has_spa -- ⑦ expressions
, sauna, house_size
, case when no_rooms > 2 then 1 else 0 end as rooms
FROM f_dynamic_select('static', 'pool', 'spa', 'sauna', 'house_size', 'no_rooms')
AS t(pool int, spa int, house_size int, sauna int, no_rooms int);
db<>fiddle here
① The function takes a table name as regclass
type. See:
- Table name as a PostgreSQL function parameter
... followed by an arbitrary list of column names - in meaningful order. VARIADIC
should be convenient for this. See:
- Pass multiple values in single parameter
Note that we pass column names as case-sensitive, single-quoted strings. Not (double-quoted) identifiers.
② This may be the first time ever I recommend returning anonymous records from a function - after close to 1000 answers on the [plpgsql] tag. The manual:
If the function has been defined as returning the
record
data type,
then an alias or the key wordAS
must be present, followed by a column
definition list in the form( column_name data_type [, ... ])
. The
column definition list must match the actual number and types of
columns returned by the function.
③ The manual on dynamic SQL.
④ Safe against SQL injection, because the table name is passed as regclass
, and the SELECT
list is concatenated using quote_ident()
carefully. See:
- Define table and column names as arguments in a plpgsql function?
⑤ Use WITH ORDINALITY
to preserve original order of columns. See:
- PostgreSQL unnest() with element number
⑥ LEFT JOIN
to the system catalog pg_attribute
to identify existing columns. See:
- Select columns with particular column names in PostgreSQL
⑦ Move expressions building on the passed columns to the outer SELECT
.
Disclaimer: I would only introduce this level of sophistication if I had to. Maybe you can work with simple views in each database after all?
Return list of columns via function in PostgreSQL
There is no need for loop at all. You could simply use string_agg
:
SELECT STRING_AGG(column_name, ',')
FROM information_schema.columns
WHERE table_schema = 'public'
AND table_name = 'testx';
You could use it as subquery in your code.
Rextester Demo
Or if you need a function:
CREATE OR REPLACE FUNCTION myschema.return_column_list(tab_name TEXT)
RETURNS text AS
$BODY$
BEGIN
RETURN (SELECT string_agg(column_name, ',') FROM information_schema.columns
WHERE table_schema = 'myschema'
AND table_name = format('%s', tab_name));
END;
BODY$ LANGUAGE 'plpgsql';
Rextester Demo 2
As for variable initialization. Default value is NULL
and NULL
concatenated to column_name is still NULL
.
SELECT NULL || 'a';
-- NULL
Function Returning Set Of Columns
There is no need to for dynamic SQL or PL/pgSQL.
create or replace function Table_Schema(schemaname text, tablename text)
RETURNS SETOF information_schema.columns
as
$body$
select *
from information_schema.columns
WHERE table_name = tablename
and table_schema = schemaname;
$body$
language sql;
But to export all columns into a CSV file, you don't really need a function at all:
\copy (select * from information_schema.columns where table_schema = 'public' order by schema_name, table_name, ordinal_position) to '/path/to/output.csv' with (format csv)
Record returned from function has columns concatenated
Generally, to decompose rows returned from a function and get individual columns:
SELECT * FROM account_servicetier_for_day(20424, '2014-08-12');
As for the query:
Postgres 9.3 or newer
Cleaner with JOIN LATERAL
:
SELECT '2014-08-12' AS day, 0 AS inbytes, 0 AS outbytes
, a.username, a.accountid, a.userid
, f.* -- but avoid duplicate column names!
FROM account_tab a
, account_servicetier_for_day(a.accountid, '2014-08-12') f -- <-- HERE
WHERE a.isdsl = 1
AND a.dslservicetypeid IS NOT NULL
AND NOT EXISTS (
SELECT FROM dailyaccounting_tab
WHERE day = '2014-08-12'
AND accountid = a.accountid
)
ORDER BY a.username;
The LATERAL
keyword is implicit here, functions can always refer earlier FROM
items. The manual:
LATERAL
can also precede a function-callFROM
item, but in this
case it is a noise word, because the function expression can refer to
earlierFROM
items in any case.
Related:
- Insert multiple rows in one table based on number in another table
Short notation with a comma in the FROM
list is (mostly) equivalent to a CROSS JOIN LATERAL
(same as [INNER] JOIN LATERAL ... ON TRUE
) and thus removes rows from the result where the function call returns no row. To retain such rows, use LEFT JOIN LATERAL ... ON TRUE
:
...
FROM account_tab a
LEFT JOIN LATERAL account_servicetier_for_day(a.accountid, '2014-08-12') f ON TRUE
...
Also, don't use NOT IN (subquery)
when you can avoid it. It's the slowest and most tricky of several ways to do that:
- Select rows which are not present in other table
I suggest NOT EXISTS
instead.
Postgres 9.2 or older
You can call a set-returning function in the SELECT
list (which is a Postgres extension of standard SQL). For performance reasons, this is best done in a subquery. Decompose the (well-known!) row type in the outer query to avoid repeated evaluation of the function:
SELECT '2014-08-12' AS day, 0 AS inbytes, 0 AS outbytes
, a.username, a.accountid, a.userid
, (a.rec).* -- but be wary of duplicate column names!
FROM (
SELECT *, account_servicetier_for_day(a.accountid, '2014-08-12') AS rec
FROM account_tab a
WHERE a.isdsl = 1
AND a.dslservicetypeid Is Not Null
AND NOT EXISTS (
SELECT FROM dailyaccounting_tab
WHERE day = '2014-08-12'
AND accountid = a.accountid
)
) a
ORDER BY a.username;
Related answer by Craig Ringer with an explanation, why it's better not to decompose on the same query level:
- How to avoid multiple function evals with the (func()).* syntax in an SQL query?
Postgres 10 removed some oddities in the behavior of set-returning functions in the SELECT
:
- What is the expected behaviour for multiple set-returning functions in SELECT clause?
Related Topics
SQL Query - Delete Duplicates If More Than 3 Dups
Replace Unicode Characters in T-Sql
Why Do SQL Id Sequences Go Out of Sync (Specifically Using Postgres)
Search Count of Words Within a String Using SQL
Oracle/Sql: Wm_Concat & Order By
Detecting Circular References in SQL
Support for JSON in Oracle 11G
Orderby in SQL Server to Put Positive Values Before Negative Values
Update Oracle Table with Values from CSV File
Xquery - How to Use the SQL:Variable in 'Value()' Function
How to Cancel a SQL Server Execution Process Programmatically
Generating Xml File from SQL Server 2008
SQL Server Query for Many to Many Relationship
Hebrew Encoding in SQL Server2005