Function returning rowset with dynamic column name

无人久伴 提交于 2021-02-11 18:19:22

问题


I have a generic key/value table that I would like to preprocess in a function to filter by the key, and then name the value according to the key.

The table is like this where id points into another table (but really is don't care for the purposes of this question):

CREATE TABLE keyed_values (id INTEGER, key TEXT, value TEXT, PRIMARY KEY(id, key));

INSERT INTO keyed_values(id, key, value) VALUES 
    (1, 'x', 'Value for x for 1'),
    (1, 'y', 'Value for y for 1'),
    (2, 'x', 'Value for x for 2'),
    (2, 'z', 'Value for z for 2');

and so forth. The key values can vary and not all IDs will use the same keys.

Specifically, what I'd like to do is to create a function that would produce rows matching a given key. Then it would return a row for each matching key with the value named with the key.

So the output of the function given the key name argument 'x' would be the same as if I executed this:

SELECT id, value x FROM keyed_values WHERE key = 'x';
 id |         x
----+-------------------
  1 | Value for x for 1
  2 | Value for x for 2
(2 rows)

Here's my stab, put together from looking at some other SO answers:

CREATE FUNCTION value_for(key TEXT) RETURNS SETOF RECORD AS $$
    BEGIN
        RETURN QUERY EXECUTE format('SELECT id, value %I FROM keyed_values WHERE key = %I', key, key);
    END;
$$ LANGUAGE 'plpgsql';

The function is accepted. But when I execute it, I get this error:

SELECT * FROM value_for('x');
ERROR:  a column definition list is required for functions returning "record"
LINE 1: SELECT * FROM value_for('x');

I get what it's telling me, but not how to fix that. How can I dynamically define the output column name?


回答1:


The key used in the WHERE clause is not an identifier but a literal, so the function should looks like:

CREATE FUNCTION value_for(key TEXT) RETURNS SETOF RECORD AS $$
    BEGIN
        RETURN QUERY EXECUTE format('SELECT id, value %I FROM keyed_values WHERE key = %L', key, key);
    END;
$$ LANGUAGE plpgsql;

From the documentation:

If the function has been defined as returning the record data type, then an alias or the key word AS 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.

Use a column definition list:

SELECT * FROM value_for('x') AS (id int, x text);

Note that you do not need dynamic SQL nor plpgsql, as the column name is defined in the above list. The simple SQL function should be a bit faster:

CREATE OR REPLACE FUNCTION value_for(_key text)
RETURNS SETOF RECORD AS $$
    SELECT id, value
    FROM keyed_values
    WHERE key = _key
$$ LANGUAGE SQL;

If you do not like being forced to append a column definition list to every function call, you can return a table from the function:

CREATE OR REPLACE FUNCTION value_for_2(_key text)
RETURNS TABLE(id int, x text) AS $$
    SELECT id, value
    FROM keyed_values
    WHERE key = _key
$$ LANGUAGE SQL;

SELECT * FROM value_for_2('x');

 id |         x         
----+-------------------
  1 | Value for x for 1
  2 | Value for x for 2
(2 rows)    

However this does not solve the issue. There is no way to dynamically define a column name returned from a function. The names and types of returned columns must be specified before the query is executed.



来源:https://stackoverflow.com/questions/51334001/function-returning-rowset-with-dynamic-column-name

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!