I have postgresql function which accepts 4 arguments
CREATE OR REPLACE FUNCTION public.manual_table_parsing_monthly_custom_edits(
schema_name text,
main_permit text,
manual_table text,
permit_num text)
RETURNS void AS
$BODY$
DECLARE
e record;
load_move_count double precision;
BEGIN
EXECUTE format('Insert into %I.%I(permit_date,permit_number,year,month_number,
report_permit_date,job_category,address,legal_description,neighbourhood,neighbourhood_number,
job_description,work_type,floor_area,construction_value,zoning,units_added,
latitude,longitude,location,count,building_type,building_type_number,filter,community,
groups,plan,block,lot,unit_id,dwelling,condo_fh,developer,builder,unit_address,construction_type,geom)
select permit_date,permit_number,year,month_number,
report_permit_date,job_category,address,legal_description,neighbourhood,neighbourhood_number,
job_description,work_type,floor_area,construction_value,zoning,units_added,
latitude,longitude,location,count,building_type,building_type_number,filter,community,
groups,plan,block,lot,unit_id,dwelling,condo_fh,developer,builder,unit_address,construction_type,geom
from %I.%I
where flag like ''move'' or flag like ''load'' and permit_number
=%I',schema_name,main_permit,schema_name,manual_table,permit_number);
The last argument in a function permit_number which is used in the where statement is raising error no column 'yyyy'. Its because the %I is taking the value within double quotes. How should i edit so that the value will be within single quotes
As per documentiaion %I should be used for identifiers, and %L should be used for literal values.
Also should use $$ quoting, to ease reading of your function...
Related
I tried to write function like this in PostgreSQL but I'm getting error like
ERROR: syntax error at or near "elems"
LINE 22: RETURN elems;
I want get output like
input: we###ty;rer##2hjjj
output:
we###ty
rer##2hjjj
please help me to solve this error
CREATE OR REPLACE FUNCTION public.fn_split(
inputstr text,
delimeter text)
RETURNS text[]
LANGUAGE 'plpgsql'
COST 100
VOLATILE SECURITY DEFINER PARALLEL UNSAFE
AS $BODY$
DECLARE
delimeter text;
elems text[];
var text;
arr_len int;
BEGIN
SELECT unnest(string_to_array(inputstr,delimeter))
INTO elems
RETURN elems;
END
$BODY$;
CREATE OR REPLACE FUNCTION public.fn_split(
inputstr text,
delimeter text)
RETURNS text[]
LANGUAGE 'plpgsql'
COST 100
VOLATILE SECURITY DEFINER
AS $BODY$
DECLARE
elems text[];
BEGIN
SELECT string_to_array(inputstr,delimeter) INTO elems;
RETURN elems;
END;
$BODY$;
Now call this function like this
SELECT UNNEST(fn_split('1,2,3',',')) as retval
Above is the screenshot which includes function definition and in the first list the command to call this function
Here is the command that you need to execute in order to call this function in the PostgreSQL query window after the creation of the function.
Your function is defined to return an array, however unnest would turn the result of creating the array into rows of strings. There is also no need to duplicate the parameter definition as local variables in a DECLARE block. And as you don't seem to want to manipulate the created array somehow, there is no need to store it in a local variable.
It seems you just want:
CREATE OR REPLACE FUNCTION public.fn_split(
inputstr text,
delimeter text)
RETURNS text[]
LANGUAGE plpgsql
immutable
AS $BODY$
BEGIN
return string_to_array(inputstr,delimeter);
END
$BODY$;
Or simpler as a SQL function:
CREATE OR REPLACE FUNCTION public.fn_split(
inputstr text,
delimeter text)
RETURNS text[]
LANGUAGE sql
immutable
AS
$BODY$
select string_to_array(inputstr,delimeter);
$BODY$;
Note that the language name is an identifier and should not be enclosed in single quotes. This syntax is deprecated and support for it will be removed in a future Postgres version.
Edit:
It seems you don't actually want an array, but one row per element after splitting the input value. In that case the function should be declared as returns table() not returns text[]
CREATE OR REPLACE FUNCTION public.fn_split(
inputstr text,
delimeter text)
RETURNS table(element text)
LANGUAGE sql
immutable
AS
$BODY$
select unnest(string_to_array(inputstr,delimeter));
$BODY$;
Then use it like this:
select *
from fn_split('we###ty;rer##2hjjj', ';');
Since Postgres 14
select unnest(string_to_array(inputstr,delimeter));
can be simplified to
select string_to_table(inputstr,delimeter);
I have function called list_customers, taking i_entity_id, i_finyear as input params. The schema name is built from i_finyear, I need to execute the query based on the given schema.
I tried the below code:
CREATE OR REPLACE FUNCTION list_customers(i_entity_id integer,
i_finyear integer)
RETURNS TABLE(entity_id integer, client_id
integer, financial_yr integer) LANGUAGE 'plpgsql' AS
$BODY$
declare finyear integer := i_finyear;
schema_1 text := 'tds'||''||i_finyear;
begin
set search_path to schema_1;
return query select
d.entity_id, d.client_id, d.financial_yr
from schema_1.deductor d where d.entity_id = 1331;
end;
$BODY$;
Then:
select tds2020.list_customers(1331,2022);
imagelink
You need dynamic SQL with EXECUTE:
CREATE OR REPLACE FUNCTION list_customers(i_entity_id int, i_finyear int)
RETURNS TABLE (entity_id int, client_id int, financial_yr int)
LANGUAGE plpgsql AS
$func$
BEGIN
RETURN QUERY EXECUTE
'SELECT d.entity_id, d.client_id, d.financial_yr
FROM tds' || i_finyear || '.deductor d
WHERE d.entity_id = $1'
USING i_entity_id;
END
$func$;
Since the input parameter i_finyear is type integer, there is no danger of SQL injection and you can use plain concatenation to concatenate your schema name like "tbl2016". Else, you'd use format() to defend against that. See:
Table name as a PostgreSQL function parameter
You can also concatenate (properly quoted) values, but it's safer and more efficient to pass the value with the USING keyword. See:
Replace double quotes with single quotes in Postgres (plpgsql)
No need to change the search_path additionally. That would just add an expensive context switch.
begin;
create type public.ltree as (a int, b int);
create table public.parent_tree(parent_id int,l_tree ltree);
insert into public.parent_tree values(1,(2,2)),(2,(1,2)),(3, (1,28));
commit;
Trying to replicate the solution in this answer:
Format specifier for integer variables in format() for EXECUTE?
For a function with composite type:
CREATE OR REPLACE FUNCTION public.get_parent_ltree
(_parent_id int, tbl_name regclass , OUT _l_tree ltree)
LANGUAGE plpgsql AS
$func$
BEGIN
EXECUTE format('SELECT l_tree FROM %s WHERE parent_id = $1', tbl_name)
INTO _l_tree
USING _parent_id;
END
$func$;
The effective query executed:
select l_tree from parent_tree where parent_id = 1;
Executing the function:
select get_parent_ltree(1,'parent_tree');
select get_parent_ltree(1,'public.parent_tree');
I get this error:
ERROR: invalid input syntax for type integer: "(2,2)"
CONTEXT: PL/pgSQL function get_parent_ltree(integer,regclass) line 3 at EXECUTE
Context of line 3:
The output parameter _l_tree is a "row variable". (A composite type is treated as row variable.) SELECT INTO assigns fields of a row variable one-by-one. The manual:
The optional target is a record variable, a row variable, or a comma-separated list of simple variables and record/row fields, [...]
So, currently (pg 14), a row or record variables must stand alone as target. Or as the according Postgres error message would put it:
ERROR: record variable cannot be part of multiple-item INTO list
This works:
CREATE OR REPLACE FUNCTION public.get_parent_ltree (IN _parent_id int
, IN _tbl_name regclass
, OUT _l_tree ltree)
LANGUAGE plpgsql AS
$func$
BEGIN
EXECUTE format('SELECT (l_tree).* FROM %s WHERE parent_id = $1', _tbl_name)
INTO _l_tree
USING _parent_id;
END
$func$;
Or this:
CREATE OR REPLACE FUNCTION public.get_parent_ltree2 (IN _parent_id int
, IN _tbl_name regclass
, OUT _l_tree ltree)
LANGUAGE plpgsql AS
$func$
BEGIN
EXECUTE format('SELECT (l_tree).a, (l_tree).b FROM %s WHERE parent_id = $1', _tbl_name)
INTO _l_tree.a, _l_tree.b
USING _parent_id;
END
$func$;
db<>fiddle here
I agree that this is rather tricky. One might expect that a composite field is treated as a single field (like a simple type). But that's currently not so in PL/pgSQL assignments.
A related quote from the manual about composite types:
A composite type represents the structure of a row or record; it is
essentially just a list of field names and their data types.
PostgreSQL allows composite types to be used in many of the same ways that simple types can be used.
Bold emphasis mine.
Many. Not all.
Related:
Use of custom return types in a FOR loop in plpgsql
PostgreSQL: ERROR: 42601: a column definition list is required for functions returning "record"
Aside: Consider the additional module ltree instead of "growing your own". And if you continue working with your own composite type, consider a different name to avoid confusion / conflict with that module.
It looks like a Postgres bug but Erwin clarifies the issue in the adjacent answer. One of the natural workarounds is to use an auxiliary text variable in the way like this:
create or replace function get_parent_ltree(_parent_id int, tbl_name regclass)
returns ltree language plpgsql as
$func$
declare
rslt text;
begin
execute format('select l_tree from %s where parent_id = $1', tbl_name)
into rslt
using _parent_id;
return rslt::ltree;
end
$func$;
I have a table with multiple columns in PostgreSQL. I try to make a function returning a table with a few default columns and a variable column. The column name should be passed as function parameter. Example:
SELECT * FROM get_gas('temperature');
This is my code right now:
CREATE OR REPLACE FUNCTION get_gas(gas text)
RETURNS TABLE (id INTEGER, node_id INTEGER,
gas_name DOUBLE PRECISION,
measurement_timestamp timestamp without time zone )
AS
$$
BEGIN
SELECT measurements_lora.id, measurements_lora.node_id, gas, measurements_lora.measurement_timestamp
AS measure
FROM public.measurements_lora;
END
$$ LANGUAGE plpgsql;
When passing, for example, 'temperature' as column name (gas), I want to get a table with these columns from the function call.
id - node_id - temperature - measurement_timestamp
How would I achieve this?
You can use EXECUTE statement.
CREATE OR REPLACE FUNCTION get_gas(gas text) RETURNS TABLE (f1 INTEGER, f2 INTEGER, f3 DOUBLE PRECISION, f4 timestamp without time zone ) AS
$$
DECLARE
sql_to_execute TEXT;
BEGIN
SELECT 'SELECT measurements_lora.id,
measurements_lora.node_id, '
|| gas ||',
measurements_lora.measurement_timestamp AS measure
FROM public.measurements_lora '
INTO sql_to_execute;
RETURN QUERY EXECUTE sql_to_execute;
END
$$ LANGUAGE plpgsql;
This will create a variable sql_to_execute with your field and. QUERY EXECUTE will execute your interpreted query.
EDIT 1: Look at the another answer the concernings about security issues.
If you really need dynamic SQL in a PL/pgSQL function (which you don't), be sure to defend against SQL injection! Like:
CREATE OR REPLACE FUNCTION get_gas(gas text)
RETURNS TABLE (id integer
, node_id integer
, gas_name double precision
, measurement_timestamp timestamp)
LANGUAGE plpgsql AS
$func$
BEGIN
RETURN QUERY EXECUTE format(
'SELECT m.id, m.node_id, m.%I, m.measurement_timestamp
FROM public.measurements_lora m'
, gas
);
END
$func$;
The format specifier %I in format() double-quotes identifiers where needed,
See:
SQL injection in Postgres functions vs prepared queries
Insert text with single quotes in PostgreSQL
I have a function to left pad bit stings in PostgreSQL 9.5:
CREATE OR REPLACE FUNCTION lpad_bits(val bit varying)
RETURNS bit varying as
$BODY$
BEGIN return val::bit(32) >> (32-length(val));
END;
$BODY$
LANGUAGE plpgsql IMMUTABLE;
which works fine:
# select lpad_bits(b'1001100111000');
lpad_bits
----------------------------------
00000000000000000001001100111000
(1 row)
My problem is when I try to add a parameter to change the amount of padding:
CREATE OR REPLACE FUNCTION lpad_bits(val bit varying, sz integer default 1024)
RETURNS bit varying as
$BODY$
BEGIN return val::bit(sz) >> (sz-length(val));
END;
$BODY$
LANGUAGE plpgsql IMMUTABLE;
The function is now broken:
# select lpad_bits(b'1001100111000', 32);
ERROR: invalid input syntax for integer: "sz"
LINE 1: SELECT val::bit(sz) >> (sz-length(val))
^
QUERY: SELECT val::bit(sz) >> (sz-length(val))
CONTEXT: PL/pgSQL function lpad_bits(bit varying,integer) line 2 at RETURN
I have stared at the bitstring documentation and PL/pgSQL function documentation, am simply not seeing what is fundamentally different between these two implementations.
Why?
PL/pgSQL executes SQL queries like prepared statements. The manual about parameter substituion:
Prepared statements can take parameters: values that are substituted
into the statement when it is executed.
Note the term values. Only actual values can be parameterized, but not key words, identifiers or type names. 32 in bit(32) looks like a value, but the modifier of a data type is only a "value" internally and can't be parameterized. SQL demands to know data types at planning stage, it cannot wait for the execution stage.
You could achieve your goal with dynamic SQL and EXECUTE. As proof of concept:
CREATE OR REPLACE FUNCTION lpad_bits(val varbit, sz int = 32, OUT outval varbit) AS
$func$
BEGIN
EXECUTE format('SELECT $1::bit(%s) >> $2', sz) -- literal
USING val, sz - length(val) -- values
INTO outval;
END
$func$ LANGUAGE plpgsql IMMUTABLE;
Call:
SELECT lpad_bits(b'1001100111000', 32);
Note the distinction between sz being used as literal to build the statement and its second occurrence where it's used as value, that can be passed as parameter.
Faster alternatives
A superior solution for this particular task is to just use lpad() like #Abelisto suggested:
CREATE OR REPLACE FUNCTION lpad_bits2(val varbit, sz int = 32)
RETURNS varbit AS
$func$
SELECT lpad(val::text, sz, '0')::varbit;
$func$ LANGUAGE sql IMMUTABLE;
(Simpler as plain SQL function, which also allows function inlining in the context of outer queries.)
Several times faster than the above function. A minor flaw: we have to cast to text and back to varbit. Unfortunately, lpad() is not currently implemented for varbit. The manual:
The following SQL-standard functions work on bit strings as well as
character strings: length, bit_length, octet_length, position, substring, overlay.
overlay() is available, we can have a cheaper function:
CREATE OR REPLACE FUNCTION lpad_bits3(val varbit, base varbit = '00000000000000000000000000000000')
RETURNS varbit AS
$func$
SELECT overlay(base PLACING val FROM bit_length(base) - bit_length(val))
$func$ LANGUAGE sql IMMUTABLE;
Faster if you can work with varbit values to begin with. (The advantage is (partly) voided, if you have to cast text to varbit anyway.)
Call:
SELECT lpad_bits3(b'1001100111000', '00000000000000000000000000000000');
SELECT lpad_bits3(b'1001100111000', repeat('0', 32)::varbit);
We might overlaod the function with a variant taking an integer to generate base itself:
CREATE OR REPLACE FUNCTION lpad_bits3(val varbit, sz int = 32)
RETURNS varbit AS
$func$
SELECT overlay(repeat('0', sz)::varbit PLACING val FROM sz - bit_length(val))
$func$ LANGUAGE sql IMMUTABLE;
Call:
SELECT lpad_bits3(b'1001100111000', 32;
Related:
Postgresql Convert bit varying to integer
Convert hex in text representation to decimal number
The parser does not allow a variable at that place. The alternative is to use a constant and trim it:
select right((val::bit(128) >> (128 -length(val)))::text, sz)::bit(sz)
from (values (b'1001100111000', 32)) s(val,sz)
;
right
----------------------------------
00000000000000000001001100111000
Or the lpad function as suggested in the comments.