I have following function:
drop function if exists convert_reapeted_sections_to_labels_for_sl_export();
create or replace function convert_reapeted_sections_to_labels_for_sl_export(ids text[])
returns text as
$$
declare
result text;
begin
-- with cte_sl as (
--select
-- unnest (xpath('//control-rodzaj-wsparcia/item/label/text()', xml))::text as label,
-- unnest (xpath('//control-rodzaj-wsparcia/item/value/text()', xml))::text as value
--FROM sl_export_newest_definition
--)
result:= concat(select label from cte_sl where value=ids[1],select label from cte_sl where value=ids[2]);
return result;
end;
$$
language plpgsql;
I want to use this function for translate ids passed in function parameter to labels which are in cte_sl_export_control_rodzaj_przyznanego_wsparcia.
But if I uncomment cte table lines i got
result:= concat(select label from cte_sl where value=ids[1],select label from cte_sl where value=ids[2]);
It is possible to use cte function in that way? Or I need another way to achieve that?
It's a syntax error since a CTE may only prelude a query, but not a PL/PGSQL statement. What you can do instead is use
result := (
WITH cte_sl AS (
SELECT
unnest (xpath('//control-rodzaj-wsparcia/item/label/text()', xml))::text AS label,
unnest (xpath('//control-rodzaj-wsparcia/item/value/text()', xml))::text AS value
FROM sl_export_newest_definition
)
SELECT concat(
SELECT label FROM cte_sl WHERE value=ids[1],
SELECT label FROM cte_sl WHERE value=ids[2]
)
);
or
WITH cte_sl AS (
SELECT
unnest (xpath('//control-rodzaj-wsparcia/item/label/text()', xml))::text AS label,
unnest (xpath('//control-rodzaj-wsparcia/item/value/text()', xml))::text AS value
FROM sl_export_newest_definition
)
SELECT concat(
SELECT label FROM cte_sl WHERE value=ids[1],
SELECT label FROM cte_sl WHERE value=ids[2]
) INTO result;
or
result := (
SELECT concat(
SELECT label FROM cte_sl WHERE value=ids[1],
SELECT label FROM cte_sl WHERE value=ids[2]
)
FROM (
SELECT
unnest (xpath('//control-rodzaj-wsparcia/item/label/text()', xml))::text AS label,
unnest (xpath('//control-rodzaj-wsparcia/item/value/text()', xml))::text AS value
FROM sl_export_newest_definition
) AS cte_sl
);
Notice I'd simplify the entire function to use language-SQL:
CREATE OR REPLACE FUNCTION convert_reapeted_sections_to_labels_for_sl_export(ids text[])
RETURNS text
LANGUAGE SQL
AS $$
SELECT string_agg(label, '' ORDER BY idx)
FROM unnest(ids) WITH ORDINALITY AS id(value, idx)
JOIN (
SELECT
unnest (xpath('//control-rodzaj-wsparcia/item/label/text()', xml))::text AS label,
unnest (xpath('//control-rodzaj-wsparcia/item/value/text()', xml))::text AS value
FROM sl_export_newest_definition
) AS cte_sl USING (value);
$$;
Related
CREATE OR REPLACE FUNCTION update()
RETURNS TRIGGER
LANGUAGE PLPGSQL
AS
$$
BEGIN
with name_array as (select jsonb_array_elements(inputs #> '{lists}') AS lists
from random.model
)
select name_array.lists #>> '{name}' as name,
name_array.lists #>> '{value}' as value
from name_array;
IF NEW.value <> OLD.value THEN
INSERT INTO random.model_tracker(userid,modelid,datetime,oldjsoninput,newjsoninput,action)
Values (old.user_id,old.id,now(),
'{"lists": [{"name": "OLD.name"}, {"value": "OLD.value"}]}'
,'{"lists": [{"name": "NEW.name"},{"value": "NEW.value"}]}'
,'UPDATE');
END IF;
RETURN NEW;
END;
$$
Trigger
CREATE TRIGGER update
AFTER UPDATE
ON random.model
FOR EACH ROW
EXECUTE PROCEDURE update();
When i am running inner query, it produces outputs as text for name and value. This function gets created however when i update values on the table i am getting this error:
ERROR: query has no destination for result data
HINT: If you want to discard the results of a SELECT, use PERFORM instead.
CONTEXT: PL/pgSQL function update() line 6 at SQL statement
SQL state: 42601
any select inside of a function need to be placed inside of a variable, that was the error message.
made another version of the json :
CREATE OR REPLACE FUNCTION update()
RETURNS TRIGGER
LANGUAGE PLPGSQL
AS
$$
BEGIN
IF NEW.value <> OLD.value THEN
INSERT INTO random.model_tracker(userid,modelid,datetime,oldjsoninput,newjsoninput,action)
Values (old.user_id,old.id,now(),
(SELECT * FROM (WITH lists (lists ) AS (
WITH q ( name , value ) AS
( SELECT OLD.name , OLD.value ) SELECT array_to_json( ARRAY[row_to_json(q)] ) FROM q
)SELECT row_to_json(list.*) FROM lists
)) ,
(SELECT * FROM (
WITH lists (lists ) AS (
WITH q ( name , value ) AS
( SELECT NEW.name , NEW.value ) SELECT array_to_json( ARRAY[row_to_json(q)] ) FROM q
)SELECT row_to_json(list.*) FROM lists
)) ,'UPDATE');
END IF;
RETURN NEW;
END;
$$
I have following sql function:
CREATE OR REPLACE FUNCTION convert_reapeted_sections_to_labels_for_sl_export(ids text[])
RETURNS text
LANGUAGE SQL
AS $$
SELECT string_agg(label, '' ORDER BY idx)
FROM unnest(ids) WITH ORDINALITY AS id(value, idx)
JOIN (
SELECT
unnest (xpath('//control-rodzaj-wsparcia/item/label/text()', xml))::text AS label,
unnest (xpath('//control-rodzaj-wsparcia/item/value/text()', xml))::text AS value
FROM sl_export_newest_definition
) AS cte_sl USING (value);
$$;
It works for me, but I have over 10 field types like
(xpath('//control-rodzaj-wsparcia/item/label/text()', xml))
I can create one function like previous posted per field type, but I thinking about using case in this function with field_type paramter passed, something like that:
CREATE OR REPLACE FUNCTION convert_reapeted_sections_to_labels_for_sl_export(ids text[], field_type text)
RETURNS text
LANGUAGE SQL
AS $$
SELECT string_agg(label, ' | ' ORDER BY idx)
FROM unnest(ids) WITH ORDINALITY AS id(value, idx)
JOIN (
case when field_type='control-rodzaj-wsparcia' then
SELECT
unnest (xpath('//control-rodzaj-wsparcia/item/label/text()', xml))::text AS label,
unnest (xpath('//control-rodzaj-wsparcia/item/value/text()', xml))::text AS value
case when field_type='control-rodzaj-wsparcia2' then
SELECT
unnest (xpath('//control-rodzaj-wsparcia2/item/label/text()', xml))::text AS label,
unnest (xpath('//control-rodzaj-wsparcia2/item/value/text()', xml))::text AS value
end
FROM sl_export_newest_definition
) AS cte_sl USING (value);
$$;
But with this function I get 'syntax error near case'.
It is possible to use case inside join in my function? Or I must create one function per one field_type? It will be code duplication :/
CASE is a function to be used in an expression. It is not the equivalent of if that you are used to have in procedural languages like Python or C++. Especially you can't use CASE to "run" one SELECT statement or the other.
In your case however, you don't need multiple cases at all. Just build your path string dynamically like so:
xpath('//' || field_type || '/item/label/text()', xml)
I want to select a column from a table, with the column name being the result of a query like the following:
-- This query returns a single value
with x as (
select a from table1 where <condition>
)
-- my_function() yields a table
select x from my_function()
How do I do that?
Thank you very much.
You could write it in SQL with a temporary function:
CREATE FUNCTION pg_temp.tablefunc()
RETURNS SETOF my_function_result_type
LANGUAGE plpgsql AS
$$DECLARE
v_colname text;
BEGIN
SELECT a INTO v_colname
FROM table1
LIMIT 1;
RETURN QUERY EXECUTE
format(E'SELECT %I\n'
'FROM my_function()',
v_colname);
END;$$;
SELECT * FROM pg_temp.tablefunc();
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)
I am trying to execute dynamic query using PostgreSQL. I have a function with three parameters and need to suffix that parameters with some variables(to make view name) and need to retrieve rows from that variables(view) and return the result.
Example
create or replace function testing(abc varchar,def varchar,ghi varchar)
returns setof record as
$BODY$
Declare
temptable1 varchar :='temp1_';
temptable2 varchar :='temp2_';
viewname varchar :='view_';
Body
temptable1 := temptable1||abc;
temptable2 := temptable2||def;
viewname := viewname||ghi;
execute 'Drop table if exists'||temptable1;
execute 'Drop table if exists'||temptable2;
WITH cm
AS
(
SELECT "ssno","rlno",
DENSE_RANK() OVER(Partition by "ssno" Order By "rlno" )FoundIn
From viewname;
)
SELECT DISTINCT * INTO temptable1
FROM cm
WHERE FoundIn > 1;
SELECT DISTINCT cr."ssno", cdr."rlno"
INTO temptable2
FROM temptable1 l1
INNER JOIN viewname cr on l1."rlno" = cr."rlno"
ORDER BY "rlno";
/* Need to result should be display for below query */
SELECT DISTINCT cr.ssno AS Nos, cr.rlno, FoundIn,cr.Name, cr.Address,
from temptable1 l1
inner join viewname cr on l1.rlno = cr.rlno
order by "rlno"
end;
$BODY$
Language plpgsql;