2

I have a table in postgres:

create table fubar (
name1 text,
name2 text, ...,
key integer);

I want to write a function which returns field values from fubar given the column names:

function getFubarValues(col_name text, key integer) returns text ...

where getFubarValues returns the value of the specified column in the row identified by key. Seems like this should be easy.

I'm at a loss. Can someone help? Thanks.

1
  • "which returns field values from fubar given the column names" - why can't you just write select column_name from fubar? What is the real problem you are trying to solve here? Commented Dec 20, 2015 at 11:28

2 Answers 2

3

Klin's answer is a good (i.e. safe) approach to the question as posed, but it can be simplified:

PostgreSQL's -> operator allows expressions. For example:

CREATE TABLE test (
    id SERIAL,
    js JSON NOT NULL,
    k TEXT NOT NULL
);
INSERT INTO test (js,k) VALUES ('{"abc":"def","ghi":"jkl"}','abc');
SELECT js->k AS value FROM test;

Produces

 value
-------
 "def"

So we can combine that with row_to_json:

CREATE TABLE test (
    id SERIAL,
    a TEXT,
    b TEXT,
    k TEXT NOT NULL
);
INSERT INTO test (a,b,k) VALUES
    ('foo','bar','a'),
    ('zip','zag','b');
SELECT row_to_json(test)->k AS value FROM test;

Produces:

 value 
-------
 "foo"
 "zag"

Here I'm getting the key from the table itself but of course you could get it from any source / expression. It's just a value. Also note that the result returned is a JSON value type (it doesn't know if it's text, numeric, or boolean). If you want it to be text, just cast it: (row_to_json(test)->k)::TEXT


Now that the question itself is answered, here's why you shouldn't do this, and what you should do instead!

Never trust any data. Even if it already lives inside your database, you shouldn't trust it. The method I've posted here is safe against SQL injection attacks, but an attacker could still set k to 'id' and see a column which was not intended to be visible to them.

A much better approach is to structure your data with this type of query in mind. Postgres has some excellent datatypes for this; HSTORE and JSON/JSONB. Merge your dynamic columns into a single column with one of those types (I'd suggest HSTORE for its simplicity and generally being more complete).

This has several advantages: your schema is well-defined and does not need to change if you add more dynamic columns, you do not need to perform expensive re-casting (i.e. row_to_json), and you are able to take advantage of indexes on your columns (thanks to PostgreSQL's functional indexes).

The equivalent to the code I wrote above would be:

CREATE EXTENSION HSTORE; -- necessary if you're not already using HSTORE
CREATE TABLE test (
    id SERIAL,
    cols HSTORE NOT NULL,
    k TEXT NOT NULL
);
INSERT INTO test (cols,k) VALUES
    ('a=>"foo",b=>"bar"','a'),
    ('a=>"zip",b=>"zag"','b');
SELECT cols->k AS value FROM test;

Or, for automatic escaping of your values when inserting, you can use one of:

INSERT INTO test (cols,k) VALUES
    (hstore( 'a', 'foo' ) || hstore( 'b', 'bar' ), 'a'),
    (hstore( ARRAY['a','b'], ARRAY['zip','zag'] ), 'b');

See http://www.postgresql.org/docs/9.1/static/hstore.html for more details.

Sign up to request clarification or add additional context in comments.

Comments

0

You can use dynamic SQL to select a column by name:

create or replace function get_fubar_values (col_name text, row_key integer) 
returns setof text language plpgsql as $$begin
    return query execute 'select ' || quote_ident(col_name) || 
      ' from fubar where key = $1' using row_key;
end$$;

1 Comment

This is likely to be faster than kiln's answer, but the OP should note that they must be careful that the column name is safe before using this method (any spaces, quotes or other special characters will break it, and if the col_name input cannot be trusted — i.e. it is from the user or the database itself, which seems likely in this question's context — this is a SQL injection risk)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.