PL/pgSQL: concatenating row values to a JSON-like string - postgresql

I'm trying to retrieve player statistics for the last 20 weeks:
# select yw, money
from pref_money where id='OK122471020773'
order by yw desc limit 20;
yw | money
---------+-------
2010-52 | 1130
2010-51 | 3848
2010-50 | 4238
2010-49 | 2494
2010-48 | 936
2010-47 | 3453
2010-46 | 3923
2010-45 | 1110
2010-44 | 185
(9 rows)
But I would like to have the result as a string, where all values are concatenated by colons and semicolons like this:
"2010-44:185;2010-45:1110; .... ;2010-52:1130"
So I'm trying to create the following PL/pgSQL procedure:
create or replace function pref_money_stats(_id varchar)
returns varchar as $BODY$
begin
declare stats varchar;
for row in select yw, money from pref_money
where id=_id order by yw desc limit 20 loop
stats := row.id || ':' || row.money || ';' stats;
end loop;
return stats;
end;
$BODY$ language plpgsql;
But I get the syntax error:
ERROR: syntax error at or near "for"
LINE 7: for row in select yw, money from pref_money where id...
Using PostgreSQL 8.4.6 with CentOS 5.5 Linux.
UPDATE:
I'm trying to perform all this string concatenation with PL/pgSQL and not in PHP script, because I already have a main SQL select statement, which returns user information and that information is printed row by row as XML for my mobile app:
select u.id,
u.first_name,
u.female,
u.city,
u.avatar,
m.money,
u.login > u.logout as online
from pref_users u, pref_money m where
m.yw=to_char(current_timestamp, 'YYYY-IW')
and u.id=m.id
order by m.money desc
limit 20 offset ?
Here is the screenshot of the mobile app:
And here is an XML excerpt:
<?xml version="1.0"?>
<pref>
<user id="OK510352632290" name="ирина" money="2067" pos="1" medals="1" female="1" avatar="http://i221.odnoklassniki.ru/getImage?photoId=259607761026&photoType=0" city="староконстантинов" />
<user id="OK19895063121" name="Александр" money="1912" pos="2" online="1" avatar="http://i69.odnoklassniki.ru/getImage?photoId=244173589553&photoType=0" city="Сызрань" />
<user id="OK501875102516" name="Исмаил" money="1608" pos="3" online="1" avatar="http://i102.odnoklassniki.ru/res/stub_128x96.gif" city="Москва" />
.....
</pref>
But my problem is that I have 3 other tables, from which I need that statistics for the last 20 weeks. So I'm hoping to create 3 procedures returning varchars as in my original post and integrate them in this SQL select statement. So that I can add further attributes to the XML data:
<user id="OK12345" .... money_stats="2010-44:185;2010-45:1110; .... ;2010-52:1130" ..... />
Thank you!
Alex

Aggregate functions are good for concatenating values:
create or replace function test
(text, text, text)
returns text as
$$
select $1 || ':' || $2 || ';' || $3
$$
language sql;
drop function test(text, text);
drop aggregate test(text, text) cascade;
create aggregate test(text, text)
(
sfunc = test,
stype = text,
initcond = ''
);
test=# select test(a::text, b::text) from (select generate_series(1,3) as a, generate_series(4,5) a
s b) t;
:1;4:2;5:3;4:1;5:2;4:3;5
(I'll leave it to you to deal with the leading colin :-)

You probably have already found the answer to your problem. Even so, the problem was indeed syntax.
The problem was that the declare statement was misplaced: it should appear before the begin (docs):
create or replace function pref_money_stats(_id varchar)
returns varchar as $BODY$
declare stats varchar;
begin
...
Another detail to take notice of is that you need to declare row as a record:
declare
stats varchar;
row record;
Then this statement will run properly:
for row in select yw, money from pref_money where id=_id order by yw desc limit 20 loop

This is not exactly JSON but pretty close:
SELECT ARRAY
(
SELECT ROW(yw, money)
FROM pref_money
WHERE id = 'OK122471020773'
ORDER BY
yw DESC
LIMIT 20
)::TEXT
This will output this string:
{"(2010-44:185)","(2010-45:1110)",…,"(2010-52:1130)"}
which can later be cast back into the appropriate types.

Related

JSONB select result in one row

I need your help on this. I'm trying to achieve a query for a jsonb column information I have in a table. My jsonb is an array of objects and in every object I have two key/value pairs. In this case, I have a key/value to exclude and only get the another one key without it value. So, I figure it out how to do it like:
jsonb : '[{"track":"value","location":"value"},{"extra":"value","location":"value"},...{"another":"value","location":"value"}]'
SELECT id, jsonb_object_keys((item::jsonb - 'location')::jsonb)
FROM mytable, jsonb_array_elements(theJsonB) with ordinality arr(item,position)
WHERE offer = '0001'
This query, get me the result like
id | jsonb_object_keys
-----------------------
1 | track
1 | extra
... |
1 | another
But I need to get the result in only one row for each id like
id | column1 | column2 | ... | column+
------------------------
1 | track | extra | ... | another
2 | track | extra | ... | another
3 | track | extra | ... | another
4 | track | extra | ... | another
How I could solve this? Thanks in advance, I'm a pretty newbie in SQL but I'm working hard ;-)
If you know the list of the resulting columns only at the runtime then you need some piece of dynamic sql code.
Here is a full dynamic solution which relies on the creation of a user-defined composite type and on the standard functions jsonb_populate_record and jsonb_object_agg :
First you dynamically create the list of keys as a new composite type :
CREATE OR REPLACE PROCEDURE key_list (NewJsonB jsonb) LANGUAGE plpgsql AS
$$
DECLARE key_list text ;
BEGIN
IF NewJsonB IS NULL
THEN
SELECT string_agg(DISTINCT k.object->>'key' || ' text', ',')
INTO key_list
FROM mytable
CROSS JOIN LATERAL jsonb_path_query(theJsonB, '$[*].keyvalue()[*] ? (#.key != "location")') AS k(object) ;
ELSE SELECT string_agg(DISTINCT k.key :: text || ' text', ',')
FROM (SELECT jsonb_object_keys(to_jsonb(a.*)) AS key FROM (SELECT(null :: key_list).*) AS a
UNION ALL
SELECT jsonb_path_query(NewJsonB, '$[*].keyvalue()[*] ? (#.key != "location")')->>'key'
) AS k
INTO key_list ;
END IF ;
EXECUTE 'DROP TYPE IF EXISTS key_list ' ;
EXECUTE 'CREATE TYPE key_list AS (' || COALESCE(key_list, '') || ')' ;
END ;
$$ ;
CALL key_list(NULL) ;
Then you call the procedure key_list() by trigger when the list of keys is supposed to be modified :
CREATE OR REPLACE FUNCTION mytable_insert_update()
RETURNS trigger LANGUAGE plpgsql VOLATILE AS
$$
BEGIN
IF NOT EXISTS (SELECT jsonb_object_keys(to_jsonb(a.*)) FROM (SELECT(null :: key_list).*) AS a)
THEN CALL key_list(NULL) ;
ELSIF EXISTS ( SELECT jsonb_path_query(NEW.theJsonB, '$[*].keyvalue()[*] ? (#.key != "location")')->>'key'
EXCEPT ALL
SELECT jsonb_object_keys(to_jsonb(a.*)) FROM (SELECT(null :: key_list).*) AS a
)
THEN CALL key_list(NEW.theJsonB) ;
END IF ;
RETURN NEW ;
END ;
$$ ;
CREATE OR REPLACE TRIGGER mytable_insert_update AFTER INSERT OR UPDATE OF theJsonB ON mytable
FOR EACH ROW EXECUTE FUNCTION mytable_insert_update() ;
CREATE OR REPLACE FUNCTION mytable_delete()
RETURNS trigger LANGUAGE plpgsql VOLATILE AS
$$
BEGIN
CALL key_list (NULL) ;
RETURN OLD ;
END ;
$$ ;
CREATE OR REPLACE TRIGGER mytable_delete AFTER DELETE ON mytable
FOR EACH ROW EXECUTE FUNCTION mytable_delete() ;
Finally, you should get the expected result with the following query :
SELECT (jsonb_populate_record(NULL :: key_list, jsonb_object_agg(lower(c.object->>'key'), c.object->'key'))).*
FROM mytable AS t
CROSS JOIN LATERAL jsonb_path_query(t.theJsonB, '$[*].keyvalue()[*] ? (#.key != "location")') AS c(object)
GROUP BY t
full test result in dbfiddle.

postgres lag and window to create cohort table [duplicate]

I am trying to create crosstab queries in PostgreSQL such that it automatically generates the crosstab columns instead of hardcoding it. I have written a function that dynamically generates the column list that I need for my crosstab query. The idea is to substitute the result of this function in the crosstab query using dynamic sql.
I know how to do this easily in SQL Server, but my limited knowledge of PostgreSQL is hindering my progress here. I was thinking of storing the result of function that generates the dynamic list of columns into a variable and use that to dynamically build the sql query. It would be great if someone could guide me regarding the same.
-- Table which has be pivoted
CREATE TABLE test_db
(
kernel_id int,
key int,
value int
);
INSERT INTO test_db VALUES
(1,1,99),
(1,2,78),
(2,1,66),
(3,1,44),
(3,2,55),
(3,3,89);
-- This function dynamically returns the list of columns for crosstab
CREATE FUNCTION test() RETURNS TEXT AS '
DECLARE
key_id int;
text_op TEXT = '' kernel_id int, '';
BEGIN
FOR key_id IN SELECT DISTINCT key FROM test_db ORDER BY key LOOP
text_op := text_op || key_id || '' int , '' ;
END LOOP;
text_op := text_op || '' DUMMY text'';
RETURN text_op;
END;
' LANGUAGE 'plpgsql';
-- This query works. I just need to convert the static list
-- of crosstab columns to be generated dynamically.
SELECT * FROM
crosstab
(
'SELECT kernel_id, key, value FROM test_db ORDER BY 1,2',
'SELECT DISTINCT key FROM test_db ORDER BY 1'
)
AS x (kernel_id int, key1 int, key2 int, key3 int); -- How can I replace ..
-- .. this static list with a dynamically generated list of columns ?
You can use the provided C function crosstab_hash for this.
The manual is not very clear in this respect. It's mentioned at the end of the chapter on crosstab() with two parameters:
You can create predefined functions to avoid having to write out the
result column names and types in each query. See the examples in the
previous section. The underlying C function for this form of crosstab
is named crosstab_hash.
For your example:
CREATE OR REPLACE FUNCTION f_cross_test_db(text, text)
RETURNS TABLE (kernel_id int, key1 int, key2 int, key3 int)
AS '$libdir/tablefunc','crosstab_hash' LANGUAGE C STABLE STRICT;
Call:
SELECT * FROM f_cross_test_db(
'SELECT kernel_id, key, value FROM test_db ORDER BY 1,2'
,'SELECT DISTINCT key FROM test_db ORDER BY 1');
Note that you need to create a distinct crosstab_hash function for every crosstab function with a different return type.
Related:
PostgreSQL row to columns
Your function to generate the column list is rather convoluted, the result is incorrect (int missing after kernel_id), it can be replaced with this SQL query:
SELECT 'kernel_id int, '
|| string_agg(DISTINCT key::text, ' int, ' ORDER BY key::text)
|| ' int, DUMMY text'
FROM test_db;
And it cannot be used dynamically anyway.
#erwin-brandstetter: The return type of the function isn't an issue if you're always returning a JSON type with the converted results.
Here is the function I came up with:
CREATE OR REPLACE FUNCTION report.test(
i_start_date TIMESTAMPTZ,
i_end_date TIMESTAMPTZ,
i_interval INT
) RETURNS TABLE (
tab JSON
) AS $ab$
DECLARE
_key_id TEXT;
_text_op TEXT = '';
_ret JSON;
BEGIN
-- SELECT DISTINCT for query results
FOR _key_id IN
SELECT DISTINCT at_name
FROM report.company_data_date cd
JOIN report.company_data_amount cda ON cd.id = cda.company_data_date_id
JOIN report.amount_types at ON cda.amount_type_id = at.id
WHERE date_start BETWEEN i_start_date AND i_end_date
AND interval_type_id = i_interval
LOOP
-- build function_call with datatype of column
IF char_length(_text_op) > 1 THEN
_text_op := _text_op || ', ' || _key_id || ' NUMERIC(20,2)';
ELSE
_text_op := _text_op || _key_id || ' NUMERIC(20,2)';
END IF;
END LOOP;
-- build query with parameter filters
RETURN QUERY
EXECUTE '
SELECT array_to_json(array_agg(row_to_json(t)))
FROM (
SELECT * FROM crosstab(''SELECT date_start, at.at_name, cda.amount ct
FROM report.company_data_date cd
JOIN report.company_data_amount cda ON cd.id = cda.company_data_date_id
JOIN report.amount_types at ON cda.amount_type_id = at.id
WHERE date_start between $$' || i_start_date::TEXT || '$$ AND $$' || i_end_date::TEXT || '$$
AND interval_type_id = ' || i_interval::TEXT || ' ORDER BY date_start'')
AS ct (date_start timestamptz, ' || _text_op || ')
) t;';
END;
$ab$ LANGUAGE 'plpgsql';
So, when you run it, you get the dynamic results in JSON, and you don't need to know how many values were pivoted:
select * from report.test(now()- '1 week'::interval, now(), 1);
tab
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
[{"date_start":"2015-07-27T08:40:01.277556-04:00","burn_rate":0.00,"monthly_revenue":5800.00,"cash_balance":0.00},{"date_start":"2015-07-27T08:50:02.458868-04:00","burn_rate":34000.00,"monthly_revenue":15800.00,"cash_balance":24000.00}]
(1 row)
Edit: If you have mixed datatypes in your crosstab, you can add logic to look it up for each column with something like this:
SELECT a.attname as column_name, format_type(a.atttypid, a.atttypmod) AS data_type
FROM pg_attribute a
JOIN pg_class b ON (a.attrelid = b.relfilenode)
JOIN pg_catalog.pg_namespace n ON n.oid = b.relnamespace
WHERE n.nspname = $$schema_name$$ AND b.relname = $$table_name$$ and a.attstattarget = -1;"
I realise this is an older post but struggled for a little while on the same issue.
My Problem Statement:
I had a table with muliple values in a field and wanted to create a crosstab query with 40+ column headings per row.
My Solution was to create a function which looped through the table column to grab values that I wanted to use as column headings within the crosstab query.
Within this function I could then Create the crosstab query. In my use case I added this crosstab result into a separate table.
E.g.
CREATE OR REPLACE FUNCTION field_values_ct ()
RETURNS VOID AS $$
DECLARE rec RECORD;
DECLARE str text;
BEGIN
str := '"Issue ID" text,';
-- looping to get column heading string
FOR rec IN SELECT DISTINCT field_name
FROM issue_fields
ORDER BY field_name
LOOP
str := str || '"' || rec.field_name || '" text' ||',';
END LOOP;
str:= substring(str, 0, length(str));
EXECUTE 'CREATE EXTENSION IF NOT EXISTS tablefunc;
DROP TABLE IF EXISTS temp_issue_fields;
CREATE TABLE temp_issue_fields AS
SELECT *
FROM crosstab(''select issue_id, field_name, field_value from issue_fields order by 1'',
''SELECT DISTINCT field_name FROM issue_fields ORDER BY 1'')
AS final_result ('|| str ||')';
END;
$$ LANGUAGE plpgsql;
The approach described here worked well for me.
Instead of retrieving the pivot table directly. The easier approach is to let the function generate a SQL query string. Dynamically execute the resulting SQL query string on demand.

Get IDs from multiple columns in multiple tables as one set or array

I have multiple tables with each two rows of interest: connection_node_start_id and connection_node_end_id. My goal is to get a collection of all those IDs, either as a flat ARRAY or as a new TABLE consisting of one row.
Example output ARRAY:
result = {1,4,7,9,2,5}
Example output TABLE:
IDS
-------
1
4
7
9
2
5
My fist attempt is somewhat clumsy and does not work properly as the SELECT statement just returns one row. It seems there must be a simple way to do this, can someone point me into the right direction?
CREATE OR REPLACE FUNCTION get_connection_nodes(anyarray)
RETURNS anyarray AS
$$
DECLARE
table_name varchar;
result integer[];
sel integer[];
BEGIN
FOREACH table_name IN ARRAY $1
LOOP
RAISE NOTICE 'table_name(%)',table_name;
EXECUTE 'SELECT ARRAY[connection_node_end_id,
connection_node_start_id] FROM ' || table_name INTO sel;
RAISE NOTICE 'sel(%)',sel;
result := array_cat(result, sel);
END LOOP;
RETURN result;
END
$$
LANGUAGE 'plpgsql';
Test table:
connection_node_start_id | connection_node_end_id
--------------------------------------------------
1 | 4
7 | 9
Call:
SELECT get_connection_nodes(ARRAY['test_table']);
Result:
{1,4} -- only 1st row, rest is missing
For Postgres 9.3+
CREATE OR REPLACE FUNCTION get_connection_nodes(text[])
RETURNS TABLE (ids int) AS
$func$
DECLARE
_tbl text;
BEGIN
FOREACH _tbl IN ARRAY $1
LOOP
RETURN QUERY EXECUTE format('
SELECT t.id
FROM %I, LATERAL (VALUES (connection_node_start_id)
, (connection_node_end_id)) t(id)'
, _tbl);
END LOOP;
END
$func$ LANGUAGE plpgsql;
Related answer on dba.SE:
SELECT DISTINCT on multiple columns
Or drop the loop and concatenate a single query. Probably fastest:
CREATE OR REPLACE FUNCTION get_connection_nodes2(text[])
RETURNS TABLE (ids int) AS
$func$
BEGIN
RETURN QUERY EXECUTE (
SELECT string_agg(format(
'SELECT t.id FROM %I, LATERAL (VALUES (connection_node_start_id)
, (connection_node_end_id)) t(id)'
, tbl), ' UNION ALL ')
FROM unnest($1) tbl
);
END
$func$ LANGUAGE plpgsql;
Related:
Loop through like tables in a schema
LATERAL was introduced with Postgres 9.3.
For older Postgres
You can use the set-returning function unnest() in the SELECT list, too:
CREATE OR REPLACE FUNCTION get_connection_nodes2(text[])
RETURNS TABLE (ids int) AS
$func$
BEGIN
RETURN QUERY EXECUTE (
SELECT string_agg(
'SELECT unnest(ARRAY[connection_node_start_id
, connection_node_end_id]) FROM ' || tbl
, ' UNION ALL '
)
FROM (SELECT quote_ident(tbl) AS tbl FROM unnest($1) tbl) t
);
END
$func$ LANGUAGE plpgsql;
Should work with pg 8.4+ (or maybe even older). Works with current Postgres (9.4) as well, but LATERAL is much cleaner.
Or make it very simple:
CREATE OR REPLACE FUNCTION get_connection_nodes3(text[])
RETURNS TABLE (ids int) AS
$func$
BEGIN
RETURN QUERY EXECUTE (
SELECT string_agg(format(
'SELECT connection_node_start_id FROM %1$I
UNION ALL
SELECT connection_node_end_id FROM %1$I'
, tbl), ' UNION ALL ')
FROM unnest($1) tbl
);
END
$func$ LANGUAGE plpgsql;
format() was introduced with pg 9.1.
Might be a bit slower with big tables because each table is scanned once for every column (so 2 times here). Sort order in the result is different, too - but that does not seem to matter for you.
Be sure to sanitize escape identifiers to defend against SQL injection and other illegal syntax. Details:
Table name as a PostgreSQL function parameter
The EXECUTE ... INTO statement can only return data from a single row:
If multiple rows are returned, only the first will be assigned to the INTO variable.
In order to concatenate values from all rows you have to aggregate them first by column and then append the arrays:
EXECUTE 'SELECT array_agg(connection_node_end_id) ||
array_agg(connection_node_start_id) FROM ' || table_name INTO sel;
You're probably looking for something like this:
CREATE OR REPLACE FUNCTION d (tblname TEXT [])
RETURNS TABLE (c INTEGER) AS $$
DECLARE sql TEXT;
BEGIN
WITH x
AS (SELECT unnest(tblname) AS tbl),
y AS (
SELECT FORMAT('
SELECT connection_node_end_id
FROM %s
UNION ALL
SELECT connection_node_start_id
FROM %s
', tbl, tbl) AS s
FROM x)
SELECT string_agg(s, ' UNION ALL ')
INTO sql
FROM y;
RETURN QUERY EXECUTE sql;
END;$$
LANGUAGE plpgsql;
CREATE TABLE a (connection_node_end_id INTEGER, connection_node_start_id INTEGER);
INSERT INTO A VALUES (1,2);
CREATE TABLE b (connection_node_end_id INTEGER, connection_node_start_id INTEGER);
INSERT INTO B VALUES (100, 101);
SELECT * from d(array['a','b']);
c
-----
1
2
100
101
(4 rows)

PostgreSQL equivalent of Oracle "bulk collect"

In PostgreSQL exists some ways to make a statement using bulk collect into like in Oracle?
Example in Oracle:
create or replace procedure prc_tst_bulk_test is
type typ_person is table of tb_person%rowtype;
v_tb_person typ_person;
begin
select *
bulk collect into v_tb_person
from tb_person;
-- make a selection in v_tb_person, for instance
select name, count(*) from v_tb_person where age > 50
union
select name, count(*) from v_tb_person where gender = 1
end;
In PostgreSQL 10 you can use array_agg:
declare
v_ids int[];
begin
select array_agg(id) INTO v_ids
from mytable1
where host = p_host;
--use v_ids...
end;
You'll have array and it can be used to make select from it using unnest:
select * from unnest(v_ids) where ...
There is no such syntax in PostgreSQL, nor a close functional equivalent.
You can create a temporary table in your PL/PgSQL code and use that for the desired purpose. Temp tables in PL/PgSQL are a little bit annoying because the names are global within the session, but they work correctly in PostgreSQL 8.4 and up.
A better alternative for when you're doing all the work within a single SQL statement is to use a common table expression (CTE, or WITH query). This won't be suitable for all situations.
The example above would be much better solved by a simple RETURN QUERY in PL/PgSQL, but I presume your real examples are more complex.
Assuming that tb_person is some kind of expensive-to-generate view that you don't just want to scan in each branch of the union, you could do something like:
CREATE OR REPLACE FUNCTION prc_tst_bulk()
RETURNS TABLE (name text, rowcount integer) AS
$$
BEGIN
RETURN QUERY
WITH v_tb_person AS (SELECT * FROM tb_person)
select name, count(*) from v_tb_person where age > 50
union
select name, count(*) from v_tb_person where gender = 1;
END;
$$ LANGUAGE plpgsql;
This particular case can be further simplified into a plain SQL function:
CREATE OR REPLACE FUNCTION prc_tst_bulk()
RETURNS TABLE (name text, rowcount integer) AS
$$
WITH v_tb_person AS (SELECT * FROM tb_person)
select name, count(*) from v_tb_person where age > 50
union
select name, count(*) from v_tb_person where gender = 1;
$$ LANGUAGE sql;
You can use a PostgreSQL arrays too - it is similar to Oracle's collections:
postgres=# create table _foo(a int, b int);
CREATE TABLE
postgres=# insert into _foo values(10,20);
INSERT 0 1
postgres=# create or replace function multiply()
returns setof _foo as $$
/*
* two tricks are here
* table name can be used as type name
* table name can be used as fictive column that packs all fields
*/
declare a _foo[] = (select array(select _foo from _foo));
begin
return query select * from unnest(a)
union
all select * from unnest(a);
end;
$$ language plpgsql;
CREATE FUNCTION
postgres=# select * from multiply();
a | b
----+----
10 | 20
10 | 20
(2 rows)
But in your case Craig Ringer's proposal is perfect and should be preferable.
-- Fetch the next 5 rows in the cursor_01:
FETCH FORWARD 5 FROM cursor_01;
PostgreSQL 10+ works.
https://www.postgresql.org/docs/10/sql-fetch.html

Setting the comment of a column to that of another column in Postgresql

Suppose I create a table in Postgresql with a comment on a column:
create table t1 (
c1 varchar(10)
);
comment on column t1.c1 is 'foo';
Some time later, I decide to add another column:
alter table t1 add column c2 varchar(20);
I want to look up the comment contents of the first column, and associate with the new column:
select comment_text from (what?) where table_name = 't1' and column_name = 'c1'
The (what?) is going to be a system table, but after having looked around in pgAdmin and searching on the web I haven't learnt its name.
Ideally I'd like to be able to:
comment on column t1.c1 is (select ...);
but I have a feeling that's stretching things a bit far. Thanks for any ideas.
Update: based on the suggestions I received here, I wound up writing a program to automate the task of transferring comments, as part of a larger process of changing the datatype of a Postgresql column. You can read about that on my blog.
The next thing to know is how to obtain the table oid. I think that using this as part of comment on will not work, as you suspect.
postgres=# create table comtest1 (id int, val varchar);
CREATE TABLE
postgres=# insert into comtest1 values (1,'a');
INSERT 0 1
postgres=# select distinct tableoid from comtest1;
tableoid
----------
32792
(1 row)
postgres=# comment on column comtest1.id is 'Identifier Number One';
COMMENT
postgres=# select col_description(32792,1);
col_description
-----------------------
Identifier Number One
(1 row)
Anyhow, I whipped up a quick plpgsql function to copy comments from one table/column pair to another. You have to createlang plpgsql on the database and use it like this:
Copy the comment on the first column of table comtest1 to the id
column of the table comtest2. Yes, it should be improved but
that's left as work for the reader.
postgres=# select copy_comment('comtest1',1,'comtest2','id');
copy_comment
--------------
1
(1 row)
CREATE OR REPLACE FUNCTION copy_comment(varchar,int,varchar,varchar) RETURNS int AS $PROC$
DECLARE
src_tbl ALIAS FOR $1;
src_col ALIAS FOR $2;
dst_tbl ALIAS FOR $3;
dst_col ALIAS FOR $4;
row RECORD;
oid INT;
comment VARCHAR;
BEGIN
FOR row IN EXECUTE 'SELECT DISTINCT tableoid FROM ' || quote_ident(src_tbl) LOOP
oid := row.tableoid;
END LOOP;
FOR row IN EXECUTE 'SELECT col_description(' || quote_literal(oid) || ',' || quote_literal(src_col) || ')' LOOP
comment := row.col_description;
END LOOP;
EXECUTE 'COMMENT ON COLUMN ' || quote_ident(dst_tbl) || '.' || quote_ident(dst_col) || ' IS ' || quote_literal(comment);
RETURN 1;
END;
$PROC$ LANGUAGE plpgsql;
You can retrieve comments on columns using the system function col_description(table_oid, column_number). See this page for further details.