Using a pl sql variable in sql select value - select

I'm having a function where I would like to get the info from the table based of the parameter.
p_text is something like 'name' , or 'email' or 'birthday' so in my cursist table that would be like calling the select cursist.name , cursist.email or cursist.birthday.
get_cursist_info(p_app => :APP_USER, p_text => 'name')
this is my function:
create or replace function get_cursist_info(p_app in varchar2, p_text in varchar2)
return varchar
is
v_userid varchar(200);
v_text varchar(200);
v_test varchar(200):= p_text;
begin
v_userid := get_cursist_id2(p_app);
select v_test
into v_text
from cursist
where cursist.cursistid = v_userid;
return v_text;
end get_cursist_info;
All it does now is getting the variabele v_test back (name) instead of the cursists name(Dave).
Isnt it possible to use a pl sql variable in the select? I don't really feel like making 8 functions for this.. :)

You can do this without dynamic sql which is less efficient by using CASE clause:
select CASE v_test
WHEN 'name' THEN name
WHEN 'email' THEN email
WHEN 'birthday' THEN birthday
ELSE null
END
into v_text
from cursist
where cursist.cursistid = v_userid;

You need to create your query dynamically:
create or replace function get_cursist_info(p_app in varchar2, p_text in varchar2)
return varchar
is
v_userid varchar2(200);
v_text varchar2(200);
v_test varchar2(200):= p_text;
v_sql varchar2(1000);
begin
v_userid := get_cursist_id2(p_app);
v_sql := 'select cursist.' || v_test || ' from cursist where cursist.cursistid = ' || v_userid;
execute immediate v_sql into v_text;
return v_text;
end get_cursist_info;
Hope that helps.

Related

postgresql dynamic query

I need to replace schema and table name by parameters in following function (that is currently working perfectly):
CREATE OR REPLACE FUNCTION public.my_function_119()
RETURNS integer
LANGUAGE plpgsql
AS $function$
DECLARE _check INTEGER;
BEGIN
SELECT SUM("length"/1000)
FROM public."National_Grid_multiline"
INTO _check;
RETURN _check;
END
$function$
I have tried following solution (and its numerous variations) :
CREATE OR REPLACE FUNCTION public.my_function_119(schema text, tablename text)
RETURNS INTEGER
LANGUAGE plpgsql
AS
$function$
DECLARE _check INTEGER;
BEGIN
RETURN
'(SELECT SUM((length/1000))::integer FROM ' || schema || '."' || tablename || '")::integer INTO _check' ;
RETURN _check;
END
$function$
but keep running into following error code :
psycopg2.errors.InvalidTextRepresentation: invalid input syntax for type integer: "(SELECT SUM((length/1000))::integer FROM public."National_Grid_multiline")::integer INTO _check"
CONTEXT: PL/pgSQL function my_function_119(text,text) while casting return value to function's return type
Why is this not working ? The 'length' column contains float values.
You have to use dynamic SQL, because you cannot use a parameter for an identifier.
Also, make sure to avoid SQL injection by using format rather than concatenating strings:
EXECUTE
format(
'SELECT SUM((length/1000))::integer FROM %I.%I',
schema,
table_name
)
INTO _check';
You can try this :
CREATE OR REPLACE FUNCTION public.my_function_119(schema text, tablename text)
RETURNS INTEGER
LANGUAGE plpgsql
AS
$function$
DECLARE
res integer ;
BEGIN
EXECUTE E'
(SELECT SUM((length/1000))::integer INTO res FROM ' || schema || '."' || tablename || '"):: integer' ;
RETURN res ;
END ;
$function$

ERROR: syntax error at or near "TABLE" LINE 5: SELECT * FROM TABLE(str_common_utils.splitstr(p_...)

CREATE OR REPLACE FUNCTION sql_builder.sql_or_builder (v_field_name text, p_string text, v_operator text DEFAULT '=', p_delimiter text DEFAULT ',') RETURNS varchar AS $body$
DECLARE
cur_fields CURSOR FOR
SELECT * FROM TABLE(str_common_utils.splitstr(p_string, p_delimiter));
v_sql varchar(4000) := '';
BEGIN
v_sql := v_sql || ' (';
FOR r_field IN cur_fields loop
-- FOR DEBUG
-- dbms_output.put_line(r_field.column_value);
v_sql := v_sql || v_field_name || ' ' || v_operator || ' ''' ||
r_field.column_value || '''';
v_sql := v_sql || ' or ';
END LOOP;
v_sql := substr(v_sql, 1, length(v_sql) - 4);
v_sql := v_sql || ') ';
-- FOR DEBUG
-- dbms_output.put_line(v_sql);
RETURN v_sql;
END;
$body$
LANGUAGE PLPGSQL
STABLE;
That TABLE keyword in
SELECT * FROM TABLE(str_common_utils.splitstr(p_string, p_delimiter));
does not exist. Just omit the TABLE() decoration around the function call. If you want a decoration, use FROM ROWS FROM (str_common_utils.splitstr(p_string, p_delimiter)).
the table() operator is not necessary in Postgres to use set returning functions, so you just need:
cur_fields CURSOR FOR
SELECT * FROM str_common_utils.splitstr(p_string, p_delimiter);
But the whole function is way more complicated than it needs to be. As far as I can tell you can simplify it to:
CREATE OR REPLACE FUNCTION sql_or_builder(v_field_name text, p_string text, v_operator text DEFAULT '=', p_delimiter text DEFAULT ',')
RETURNS text
AS
$body$
select concat('(', string_agg(format('%I = %L', v_field_name, r.column_value), ' OR '), ')')
from unnest(string_to_array(p_string, p_delimiter)) as r(column_value);
$body$
language sql
immutable;
e.g.
select sql_or_builder('some_name', 'one,two,three');
sql_or_builder
---------------------------------------------------------------
(some_name = 'one' OR some_name = 'two' OR some_name = 'three')
```

How to convert a PL/PgSQL procedure into a dynamic one?

I am trying to write a plpgsql procedure to perform spatial tiling of a postGIS table. I can perform the operation successfully using the following procedure in which the table names are hardcoded. The procedure loops through the tiles in tile_table and for each tile clips the area_table and inserts it into split_table.
CREATE OR REPLACE PROCEDURE splitbytile()
AS $$
DECLARE
tile RECORD;
BEGIN
FOR tile IN
SELECT tid, geom FROM test_tiles ORDER BY tid
LOOP
INSERT INTO split_table (id, areaname, ttid, geom)
SELECT id, areaname, tile.tid,
CASE WHEN st_within(base.geom, tile.geom) THEN st_multi(base.geom)
ELSE st_multi(st_intersection(base.geom, tile.geom)) END as geom
FROM area_table as base
WHERE st_intersects(base.geom, tile.geom);
COMMIT;
END LOOP;
END;
$$ LANGUAGE 'plpgsql';
Having tested this successfully, now I need to convert it to a dynamic procedure where I can provide the table names as parameters. I tried the following partial conversion, using format() for inside of loop:
CREATE OR REPLACE PROCEDURE splitbytile(in_table text, grid_table text, split_table text)
AS $$
DECLARE
tile RECORD;
BEGIN
FOR tile IN
EXECUTE format('SELECT tid, geom FROM %I ORDER BY tid', grid_table)
LOOP
EXECUTE
FORMAT(
'INSERT INTO %1$I (id, areaname, ttid, geom)
SELECT id, areaname, tile.tid,
CASE WHEN st_within(base.geom, tile.geom) THEN st_multi(base.geom)
ELSE st_multi(st_intersection(base.geom, tile.geom)) END as geom
FROM %2$I as base
WHERE st_intersects(base.geom, tile.geom)', split_table, in_table
);
COMMIT;
END LOOP;
END;
$$ LANGUAGE 'plpgsql';
But it throws an error
missing FROM-clause entry for table "tile"
So, how can I convert the procedure to a dynamic one? More specifically, how can I use the record data type (tile) returned by the for loop inside the loop? Note that it works when format is not used.
You can use EXECUTE ... USING to supply parameters to a dynamic query:
EXECUTE
format(
'SELECT r FROM %I WHERE c = $1.val',
table_name
)
INTO result_var
USING record_var;
The first argument to USING will be used for $1, the second for $2 and so on.
See the documentation for details.
Personally I use somehow different way to create dynamic functions. By concatination and execute function. You can also do like this.
CREATE OR REPLACE FUNCTION splitbytile()
RETURNS void AS $$
declare
result1 text;
table_name text := 'test_tiles';
msi text := '+7 9912 231';
msi text := 'Hello world';
code text := 'code_name';
_operator_id integer := 2;
begin
query1 := 'SELECT msisdn from ' || table_name || ' where msisdn = ''' || msi::text ||''';';
query2 := 'INSERT INTO ' || table_name || '(msisdn,usage,body,pr_code,status,sent_date,code_type,operator_id)
VALUES( ''' || msi::text || ''',' || true || ',''' || _body::text || ''',''' || code::text || ''',' || false || ',''' || time_now || ''',' || kod_type || ',' || _operator_id ||');';
execute query1 into result1;
execute query2;
END;
$function$
You just make your query as text then anywhere you want you can execute it. Maybe by checking result1 value inside If statement or smth like that.

Check to see if a record exists postgres function

I am attempting to create a function that will determine if a record exists for our applications developers to help simplify things.
CREATE FUNCTION records_exist(schema_name VARCHAR(255), table_name VARCHAR(255), field_name VARCHAR(255), field_value VARCHAR(255))
RETURNS BOOLEAN
LANGUAGE plpgsql
AS $$
DECLARE
_schema_name ALIAS FOR $1;
_table_name ALIAS FOR $2;
_field_name ALIAS FOR $3;
_field_value ALIAS FOR $4;
_sql_string VARCHAR(5000);
BEGIN
_sql_string= 'SELECT EXISTS(SELECT 1 FROM ' || _schema_name || '.' || _table_name || ' WHERE ' || _field_name || '=' || _field_value || ');';
RETURN BOOLEAN EXECUTE _sql_string;
--RETURN TABLE EXECUTE _sql_string; doesn't work
END
$$;
The following should work, but I keep getting ERROR: syntax error at or near "EXECUTE"
Please let me know the error of my ways.
also, your dynamic string is bad, better use:
select format('select exists from %I.%I where %I = %L',schema_name,table_name,field_name, field_value) into _sql_string;
also, you realize your _field_value has no check for data type?..
so in short, it could be smth similar to:
db=# CREATE or replace FUNCTION records_exist(schema_name VARCHAR(255), table_name VARCHAR(255), field_name VARCHAR(255), field_value VARCHAR(255))
RETURNS BOOLEAN
LANGUAGE plpgsql
AS $$
DECLARE
_sql text;
_b boolean;
BEGIN
_sql := format('select exists (select null from %I.%I where %I = %L)',schema_name,table_name,field_name, field_value);
execute _sql into _b;
return _b;
END
$$;
CREATE FUNCTION
Time: 10.680 ms
db=# select * from records_exist('pg_catalog','pg_database','datname','postgres'); records_exist
---------------
t
(1 row)
Time: 59.472 ms

Simplify my function to postgres sql query

Can I make this function simple, using only sql in Postgres? (without delete, forget it)
I need only select and insert queries.
I have tried with "with recursive" no success.
CREATE OR REPLACE FUNCTION mt_CriarRotaExecutada(id_rota integer)
RETURNS void AS
$$
DECLARE
searchsql text := '';
searchsqlId text := '';
var_match RECORD;
BEGIN
EXECUTE('delete from rota_executada where id_rota = ' || CAST( id_rota As text));
searchsql := 'select (ST_DumpPoints(the_geom)).geom as the_geom,
id_destino
from (select id, the_geom, id_destino
from rota_data
where num =' || CAST( id_rota As text) ||
' order by id) a';
FOR var_match IN EXECUTE(searchsql)
LOOP
EXECUTE 'insert into rota_executada(id_rota, id_rua, id_destino, the_geom, visitado)
select $1, id, $3, $2, $4
from ruas r
ORDER BY r.the_geom <#> $2
LIMIT 1'
USING id_rota, var_match.the_geom, var_match.id_destino, 'N';
END LOOP;
END;
$$
LANGUAGE 'plpgsql';
Thanks.
You dont need EXECUTE just do the sql statement
DELETE from rota_executada where id_rota = id_rota::text;
AND
INSERT INTO rota_executada(id_rota, id_rua, id_destino, the_geom, visitado)
SELECT id_rota, id, var_match.id_destino, var_match.the_geom, 'N'
FROM ruas r
ORDER BY r.the_geom <#> var_match.the_geom
LIMIT 1