Postgres function returning a string from a select statement - postgresql

I am trying to get this function to return a string that comes from the SELECT statement.
CREATE OR REPLACE FUNCTION dbo.fnRepID (pram_ID BIGINT)
RETURNS varchar AS $$
DECLARE
pram varchar := '';
BEGIN
SELECT pram = pram || (Case COALESCE(a.Name, '') WHEN '' THEN '' ELSE b.Name || ' - ' END )
|| (Case COALESCE(b.Name, '' ) WHEN '' THEN '' ELSE b.Name || ' - ' END )
|| f.NAME || ';'
FROM dbo.ClassRelationship a
LEFT JOIN dbo.ClassRelationship b ON a.ParentClassID = b.ClassID and b.Type = 2 and a.Type = 1;
RETURN pram;
END;
$$ LANGUAGE plpgsql;
GRANT EXECUTE ON FUNCTION dbo.fnRepID (BIGINT) TO "postgres";
And this function creation seem to go through just fine, now I want to call this function with 0 as pram_ID but it's saying this basically no matter what I do.
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 dbo.fnrepid(bigint) line 5 at SQL statement
SQL statement "SELECT dbo.fnRepID(0)"
PL/pgSQL function inline_code_block line 2 at PERFORM
SQL state: 42601
I have tried a DO PERFORM block as I have seen similar examples
DO $$ BEGIN PERFORM dbo.fnRepID(0); END $$;but I basically want to get the return value from this function somehow. If there's anything wrong with my procedures please let me know, as I am new to postgres. Thank you.
Edit: Would the following work if I want to append each row of the results into the same var? (can I use select into with the variable on both sides to append to itself?)
CREATE OR REPLACE FUNCTION dbo.fnRepID (pram_ID BIGINT)
RETURNS varchar AS $$
DECLARE
pram varchar := '';
BEGIN
SELECT pram || (Case COALESCE(a.Name, '') WHEN '' THEN '' ELSE b.Name || ' - ' END )
|| (Case COALESCE(b.Name, '' ) WHEN '' THEN '' ELSE b.Name || ' - ' END )
|| f.NAME || ';' INTO pram
FROM dbo.ClassRelationship a
LEFT JOIN dbo.ClassRelationship b ON a.ParentClassID = b.ClassID and b.Type = 2 and a.Type = 1;
RETURN pram;
END;
$$ LANGUAGE plpgsql;
GRANT EXECUTE ON FUNCTION dbo.fnRepID (BIGINT) TO "postgres";

You're doing
SELECT pram = (…) FROM dbo.ClassRelationship a …;
where (…) is an expression that is evaluated and then compared to the current value of pram (which was initialised to an empty string). The query does nothing else, there is no destination for this boolean value (comparison result) it computes, you're getting an error.
You most likely meant to either perform an assignment
pram = SELECT (…) FROM dbo.ClassRelationship a …;
or use an INTO clause:
SELECT (…) INTO pram FROM dbo.ClassRelationship a …;
Notice that you don't even need pl/pgsql to do this. A plain sql function would do as well:
CREATE OR REPLACE FUNCTION dbo.fnRepID(pram_ID BIGINT) RETURNS varchar
LANGUAGE sql
STABLE
RETURN (
SELECT
'' ||
(CASE COALESCE(a.Name, '') WHEN '' THEN '' ELSE b.Name || ' - ' END) ||
(CASE COALESCE(b.Name, '' ) WHEN '' THEN '' ELSE b.Name || ' - ' END) ||
f.NAME ||
';'
FROM dbo.ClassRelationship a
LEFT JOIN dbo.ClassRelationship b ON a.ParentClassID = b.ClassID AND b.Type = 2 AND a.Type = 1
);

Related

Concat text variables in postgresql function

I have this code and I want to concatenate the variables but don't work.
This is my DDL code for the view:
CREATE OR REPLACE function acd.add_credito2()
RETURNS void
SET SCHEMA 'acd'
SET search_path = acd
AS $$
DECLARE
auxsigla text;
auxnome text;
_sql text := 'CREATE OR REPLACE VIEW acd.teste AS SELECT md.matriz_disciplina_id AS id, dcp.nome, mc.curso, mc.versao AS matriz';
_join text := ' FROM matriz_disciplina as md LEFT JOIN disciplina as dcp ON md.disciplina_id = dcp.disciplina_id LEFT JOIN matriz_curricular as mc ON md.matriz_curricular_id = mc.matriz_curricular_id';
BEGIN
select into auxsigla, auxnome from ( select sigla, nome from acd.categoria_credito where categoria_credito_id = 9) as foo;
_join := _join || ' LEFT JOIN (SELECT creditos, matriz_disciplina_id FROM acd.disciplina_credito WHERE categoria_credito_id = ' || x || ') AS ' || "auxsigla" ' ON ' || "auxsigla" || '.matriz_disciplina_id = md.matriz_disciplina_id';
_sql := _sql || ', ' || "auxsigla" || '.' || auxnome || ' AS ' || auxnome;
_sql := _sql || _join;
EXECUTE _sql;
END;
$$ LANGUAGE plpgsql
So, when I execute the function
database-1=# select acd.add_credito2();
This error appears:
ERROR: type "auxsigla" does not exist
LINE 1: ...WHERE categoria_credito_id = ' || x || ') AS ' || "auxsigla"...
^
QUERY: SELECT _join || ' LEFT JOIN (SELECT creditos, matriz_disciplina_id FROM acd.disciplina_credito WHERE categoria_credito_id = ' || x || ') AS ' || "auxsigla" ' ON ' || "auxsigla" || '.matriz_disciplina_id = md.matriz_disciplina_id'
CONTEXT: PL/pgSQL function add_credito2() line 13 at assignment
Can anyone help me? I don't know what to do now.
(I know, this study view don't have a purpose but this is the idea that I want to use in the real view)
The error comes from this construct:
"auxsigla" ' ON '
You forgot a concatenation operator || between these two tokens, and now the SQL parser interprets it as
data_type string_constant
which is a way to specify constants of a certain data type.
Working examples would be DATE '2018-09-20' or INTEGER '-20'.
Your function has numerous other problems, two of which I could spot:
select into auxsigla, auxnome from will always set the variables to NULL because you forgot to specify which columns you want to select.
you do not properly escape single quotes while composing your dynamic query string. What if auxsigla has the value with'quote?
Use format() or quote_literal() and quote_ident() for that.

Using array in dynamic commands inside PL/pgSQL function

In a PL/pgSQL function, I am creating a view using the EXECUTE statement. The where clause in the view takes as input some jenkins job names. These job names are passed to the function as a comma-separated string. They are then converted to an array so that they can be used as argument to ANY in the where clause. See basic code below:
CREATE OR REPLACE FUNCTION FETCH_ALL_TIME_AGGR_KPIS(jobs VARCHAR)
RETURNS SETOF GenericKPI AS $$
DECLARE
job_names TEXT[];
BEGIN
job_names = string_to_array(jobs,',');
EXECUTE 'CREATE OR REPLACE TEMP VIEW dynamicView AS ' ||
'with pipeline_aggregated_kpis AS (
select
jenkins_build_parent_id,
sum (duration) as duration
from test_all_finished_job_builds_enhanced_view where job_name = ANY (' || array(select quote_ident(unnest(job_names))) || ') and jenkins_build_parent_id is not null
group by jenkins_build_parent_id)
select ' || quote_ident('pipeline-job') || ' as job_name, b1.jenkins_build_id, pipeline_aggregated_kpis.status, pipeline_aggregated_kpis.duration FROM job_builds_enhanced_view b1 INNER JOIN pipeline_aggregated_kpis ON (pipeline_aggregated_kpis.jenkins_build_parent_id = b1.jenkins_build_id)';
RETURN QUERY (select
count(*) as total_executions,
round(avg (duration) FILTER (WHERE status = 'SUCCESS')::numeric,2) as average_duration
from dynamicView);
END
$$
LANGUAGE plpgsql;
The creation of the function is successful but an error message is returned when I try to call the function. See below:
eea_ci_db=> select * from FETCH_ALL_TIME_AGGR_KPIS('integration,test');
ERROR: malformed array literal: ") and jenkins_build_parent_id is not null
group by jenkins_build_parent_id)
select "
LINE 7: ...| array(select quote_ident(unnest(job_names))) || ') and jen...
^
DETAIL: Array value must start with "{" or dimension information.
CONTEXT: PL/pgSQL function fetch_all_time_aggr_kpis(character varying) line 8 at EXECUTE
It seems like there is something going wrong with quotes & the passing of an array of string. I tried all following options with the same result:
where job_name = ANY (' || array(select quote_ident(unnest(job_names))) || ') and jenkins_build_parent_id is not null
or
where job_name = ANY (' || quote_ident(job_names)) || ') and jenkins_build_parent_id is not null
or
where job_name = ANY (' || job_names || ') and jenkins_build_parent_id is not null
Any ideas?
Thank you
There is no need for dynamic SQL at all. There isn't even the need for PL/pgSQL to do this:
CREATE OR REPLACE FUNCTION FETCH_ALL_TIME_AGGR_KPIS(jobs VARCHAR)
RETURNS SETOF GenericKPI
AS
$$
with pipeline_aggregated_kpis AS (
select jenkins_build_parent_id,
sum (duration) as duration
from test_all_finished_job_builds_enhanced_view
where job_name = ANY (string_to_array(jobs,','))
and jenkins_build_parent_id is not null
group by jenkins_build_parent_id
), dynamic_view as (
select "pipeline-job" as job_name,
b1.jenkins_build_id,
pipeline_aggregated_kpis.status,
pipeline_aggregated_kpis.duration
FROM job_builds_enhanced_view b1
JOIN pipeline_aggregated_kpis
ON pipeline_aggregated_kpis.jenkins_build_parent_id = b1.jenkins_build_id
)
select count(*) as total_executions,
round(avg (duration) FILTER (WHERE status = 'SUCCESS')::numeric,2) as average_duration
from dynamic_view;
$$
language sql;
You could do this with PL/pgSQL as well, you just need to use RETURN QUERY WITH ....

Postgresql create a log schema

So my problem is simple. I have a schema prod with many tables, and another one log with the exact same tables and structure (primary keys change that's it).
When I do UPDATE or DELETE in the schema prod, I want to record old data in the log schema.
I have the following function called after a update or delete:
CREATE FUNCTION prod.log_data() RETURNS trigger
LANGUAGE plpgsql AS $$
DECLARE
v RECORD;
column_names text;
value_names text;
BEGIN
-- get column names of current table and store the list in a text var
column_names = '';
value_names = '';
FOR v IN SELECT * FROM information_schema.columns WHERE table_name = quote_ident(TG_TABLE_NAME) AND table_schema = quote_ident(TG_TABLE_SCHEMA) LOOP
column_names = column_names || ',' || v.column_name;
value_names = value_names || ',$1.' || v.column_name;
END LOOP;
-- remove first char ','
column_names = substring( column_names FROM 2);
value_names = substring( value_names FROM 2);
-- execute the insert into log schema
EXECUTE 'INSERT INTO log.' || TG_TABLE_NAME || ' ( ' || column_names || ' ) VALUES ( ' || value_names || ' )' USING OLD;
RETURN NULL; -- no need to return, it is executed after update
END;$$;
The annoying part is that I have to get column names from information_schema for each row.
I would rather use this:
EXECUTE 'INSERT INTO log.' || TG_TABLE_NAME || ' SELECT ' || OLD;
But some values can be NULL so this will execute:
INSERT INTO log.user SELECT 2,,,"2015-10-28 13:52:44.785947"
instead of
INSERT INTO log.user SELECT 2,NULL,NULL,"2015-10-28 13:52:44.785947"
Any idea to convert ",," to ",NULL,"?
Thanks
-Quentin
First of all I must say that in my opinion using PostgreSQL system tables (like information_schema) is the proper way for such a usecase. Especially that you must write it once: you create the function prod.log_data() and your done. Moreover it may be dangerous to use OLD in that context (just like *) as always because of not specified elements order.
But,
to answer your exact question the only way I know is to do some operations on OLD. Just observe that you cast OLD to text by doing concatenation ... ' SELECT ' || OLD. The default casting create that ugly double-commas. So, next you can play with that text. In the end I propose:
DECLARE
tmp TEXT
...
BEGIN
...
/*to make OLD -> text like (2,,3,4,,)*/
SELECT '' || OLD INTO tmp; /*step 1*/
/*take care of commas at the begining and end: '(,' ',)'*/
tmp := replace(replace(tmp, '(,', '(NULL,'), ',)', ',NULL)'); /*step 2*/
/* replace rest of commas to commas with NULL between them */
SELECT array_to_string(string_to_array(tmp, ',', ''), ',', 'NULL') INTO tmp; /*step 3*/
/* Now we can do EXECUTE*/
EXECUTE 'INSERT INTO log.' || TG_TABLE_NAME || ' SELECT ' || tmp;
Of course you can do steps 1-3 in one big step
SELECT array_to_string(string_to_array(replace(replace('' || NEW, '(,', '(NULL,'), ',)', ',NULL)'), ',', ''), ',', 'NULL') INTO tmp;
In my opinion this approach isn't any better from using information_schema, but it's your call.

Postgres Function returning no results but when the same query returns results outside function

I am running a simple postgres function to return the count of rows. I am able to run the same query outside function with the output of raise option , but the function doesn't return any rows. I have tried different ways to produce results but unable to. Please find my function below,
CREATE OR REPLACE FUNCTION my_schema.usp_spellcheck3(SearchORItems_WithPipe varchar, site varchar, lan varchar, rows_display integer)
RETURNS TABLE (docmnt int) AS $BODY$
DECLARE
arrSearchTerms text[];
NewTerm varchar;
i integer;
AltSearch_withComma varchar;
AltSearch_withPipe varchar;
strDidYouMean varchar;
dpDidYouMean double precision;
txtDidYouMean Text;
SearchORItems_withComma varchar;
SearchORItems varchar;
SearchORItem varchar;
ws varchar;
arrSearchORItems_withComma varchar[];
BEGIN
strDidYouMean = 'DidYouMeanRow';
dpDidYouMean = 0.0;
txtDidYouMean = 'DidYouMeanRow';
ws = '''' || '%' || site || '%' || '''' ;
RAISE NOTICE '%', ws;
SearchORItems = REPLACE(SearchORItems_WithPipe, '|', ',');
SELECT regexp_split_to_array(SearchORItems, ',') INTO arrSearchORItems_withComma;
RAISE NOTICE '%', SearchORItems;
FOR i IN 1 .. coalesce(array_upper(arrSearchORItems_withComma, 1), 1) LOOP
IF (i = 1) THEN
SearchORItems_withComma = '''' || arrSearchORItems_withComma[i] || '''';
ELSE
SearchORItems_withComma = SearchORItems_withComma||','||'''' || arrSearchORItems_withComma[i] || '''';
END IF;
END LOOP;
RAISE NOTICE '%',SearchORItems_withComma;
SELECT COUNT(*) INTO res_count
FROM (
SELECT 1 FROM my_schema.features f , my_schema.documents d
WHERE term IN (SearchORItems_withComma)
AND d.docid = f.docid
AND d.url LIKE ws
GROUP BY f.docid, d.url) t;
RAISE NOTICE '%', res_count;
SearchORItem = 'SELECT COUNT(*) INTO res_count
FROM (SELECT 1 FROM my_schema.features f , my_schema.documents d
WHERE term IN ('||SearchORItems_withComma||')
AND d.docid = f.docid AND d.url LIKE ' || ws ||'
GROUP BY f.docid, d.url) t';
RAISE NOTICE '%',SearchORItem;
END;
$BODY$ LANGUAGE SQL VOLATILE;
this is my query output :
NOTICE: '%uni%'
NOTICE: daniel,data
NOTICE: 'daniel','data'
NOTICE: 0
NOTICE: select count(*) into res_count
from ( select 1 from my_schema.features f , my_schema.documents d
where term in ('daniel','data')
and d.docid=f.docid and d.url like '%uni%'
group by f.docid,d.url)t
Total query runtime: 16 ms.
0 rows retrieved.
I dont know where I'm going wrong, any help would be appreciated .. Thanks..
The simple reason that nothing is returned is that you have no RETURN statements in your code. When a function RETURNS TABLE you need to explicitly put one or more RETURN NEXT or RETURN QUERY statements in the body of your code, with a final RETURN statement to indicate the end of the function. See the documentation here: http://www.postgresql.org/docs/9.3/static/plpgsql-control-structures.html#PLPGSQL-STATEMENTS-RETURNING. What exactly you want to return is not clear but likely candidates are res_count and d.docid.
Other than that, your code could use a real clean-up reducing clutter like:
ws := '''%' || site || '%''' ;
instead of:
ws = '''' || '%' || site || '%' || '''' ;
and:
SELECT COUNT(*) INTO res_count
FROM my_schema.features f,
JOIN my_schema.documents d ON d.docid = f.docid
WHERE term IN (SearchORItems_withComma)
AND d.url LIKE ws
GROUP BY f.docid, d.url;
instead of:
SELECT COUNT(*) INTO res_count
FROM (
SELECT 1 FROM my_schema.features f , my_schema.documents d
WHERE term IN (SearchORItems_withComma)
AND d.docid = f.docid
AND d.url LIKE ws
GROUP BY f.docid, d.url) t;
And you should use the assignment operator (:=) instead of the equality operator in any plpgsql statement that is not a SQL statement.

How can I measure the amount of space taken by blobs on a Firebird 2.1 database?

I have a production database, using Firebird 2.1, where I need to find out how much space is used by each table, including the blobs. The blob-part is the tricky one, because it is not covered using the standard statistical report.
I do not have easy access to the server's desktop, so installing UDFs etc. is not a good solution.
How can I do this easily?
You can count total size of all BLOB fields in a database with following statement:
EXECUTE BLOCK RETURNS (BLOB_SIZE BIGINT)
AS
DECLARE VARIABLE RN CHAR(31) CHARACTER SET UNICODE_FSS;
DECLARE VARIABLE FN CHAR(31) CHARACTER SET UNICODE_FSS;
DECLARE VARIABLE S BIGINT;
BEGIN
BLOB_SIZE = 0;
FOR
SELECT r.rdb$relation_name, r.rdb$field_name
FROM rdb$relation_fields r JOIN rdb$fields f
ON r.rdb$field_source = f.rdb$field_name
WHERE f.rdb$field_type = 261
INTO :RN, :FN
DO BEGIN
EXECUTE STATEMENT
'SELECT SUM(OCTET_LENGTH(' || :FN || ')) FROM ' || :RN ||
' WHERE NOT ' || :FN || ' IS NULL'
INTO :S;
BLOB_SIZE = :BLOB_SIZE + COALESCE(:S, 0);
END
SUSPEND;
END
I modified the code example of Andrej to show the size of each blob field, not only the sum of all blobs.
And used SET TERM so you can copy&paste this snippet directly to tools like FlameRobin.
SET TERM #;
EXECUTE BLOCK
RETURNS (BLOB_SIZE BIGINT, TABLENAME CHAR(31), FIELDNAME CHAR(31) )
AS
DECLARE VARIABLE RN CHAR(31) CHARACTER SET UNICODE_FSS;
DECLARE VARIABLE FN CHAR(31) CHARACTER SET UNICODE_FSS;
DECLARE VARIABLE S BIGINT;
BEGIN
BLOB_SIZE = 0;
FOR
SELECT r.rdb$relation_name, r.rdb$field_name
FROM rdb$relation_fields r JOIN rdb$fields f
ON r.rdb$field_source = f.rdb$field_name
WHERE f.rdb$field_type = 261
INTO :RN, :FN
DO BEGIN
EXECUTE STATEMENT
'SELECT SUM(OCTET_LENGTH(' || :FN || ')) AS BLOB_SIZE, ''' || :RN || ''', ''' || :FN || '''
FROM ' || :RN ||
' WHERE NOT ' || :FN || ' IS NULL'
INTO :BLOB_SIZE, :TABLENAME, :FIELDNAME;
SUSPEND;
END
END
#
SET TERM ;#
This example doesn't work with ORDER BY, maybe a more elegant solution without EXECUTE BLOCK exists.