So I have a table that for some design reasons cannot use a foreign key to map to other entities. So I am working on a function that is called to safely delete entries from the media table. As it stands at the moment I have written the function to have basic functionality using this resource as my guide on how to dynamically insert a table name into a query (http://www.postgresql.org/docs/current/static/plpgsql-statements.html#PLPGSQL-STATEMENTS-EXECUTING-DYN). As it stands my query is written as follows
DECLARE
rows_returned numeric;
is_media boolean;
BEGIN
DELETE FROM solo_media WHERE entity_id = row_to_delete;
EXECUTE "DELETE FROM $1 WHERE id = $2;" INTO rows_returned USING table_name, row_to_delete;
END;
And when it is ran (with table_name and row_to_delete being parameters being passed in) I get the error
ERROR: column "DELETE FROM $1 WHERE id = $2;" does not exist
LINE 1: SELECT "DELETE FROM $1 WHERE id = $2;"
^
QUERY: SELECT "DELETE FROM $1 WHERE id = $2;"
CONTEXT: PL/pgSQL function "safe_del" line 7 at EXECUTE statement
when calling it with
SELECT safe_del(tableName, rowNumber);
As Milen A. Radev pointed out I was using the wrong characters to identify my string, I switched it over to dollar escaped strings, and resolved the issues.
Related
I tried to run this script with the parameters in place but it keeps throwing syntax errors.Is there anything wrong with the syntax.Also what is the correct way to call this function.I require an output that tells me the update statement was executed successfully. I tried "select function_name(schema_name.TABLE_NAME);".Let me add that I am a beginner and am open to any kind of feedback. will also provide more details if necessary.
CREATE OR REPLACE FUNCTION function_name (TABLE_NAME IN character varying)
RETURNS text AS $SQLQuery$
DECLARE SQLQuery text;
BEGIN
SQLQuery =
' UPDATE '|| TABLE_NAME || ' SET column1=''0''
WHERE column1 is null;' ||
' UPDATE '|| TABLE_NAME || ' SET column2='value'
WHERE column2=''different value'';' ||
--multiple update statements later
Execute SQLQuery;
Return SQLQuery;
END;
$SQLQuery$
LANGUAGE plpgsql;
Update:
this is the error i am getting when i call the test function
ERROR: missing FROM-clause entry for table "schema_name"
LINE 2: select test_function(schema_name.TABLE_NAME);
^
********** Error **********
ERROR: missing FROM-clause entry for table "schema_name"
SQL state: 42P01
it is reading the function as a table?
I have also received syntax errors saying
EXECUTE column does not exist or that the function does not exist
even though i just declared it.
To use single quotes inside a siting constant, you must escape them by doubling them.
Instead of
' SET column1='0''
you'll have to write
' SET column1=''0'''
smth like:
CREATE OR REPLACE FUNCTION function_name (schema_name text,TABLE_NAME IN character varying)
RETURNS text AS $SQLQuery$
DECLARE
c int;
rtn text :='';
BEGIN
execute format(' UPDATE %I.%I SET column1=''0'' WHERE column1 is null;',schema_name,TABLE_NAME);
get diagnostics c = row_count;
raise info '%', 'affected: '||c;
rtn = rtn + 'affected: '||c||chr(10);
--repeat above construct for multiple update statement
return rtn;
END;
$SQLQuery$
LANGUAGE plpgsql;
and advises. I'm novice like you, but I learned to follow several rules, that help me:
with dynamic sql use format to avoid sql injection
don't overcomplicate things (eg the functionality you are looking for is inside UPDATE statement already - check the output. If you want to check the resulting row use, UPDATE ... RETURNING * construct.
practice is good, but reading concepts is precious.
In your POST select function_name(schema_name.TABLE_NAME); would not work, because you use schema_name.TABLE_NAME without quotes, but even if you put them, your function is vulnerable - what will happen if you run select function_name(';drop sometable;--');?..
You are trying to pass SQL Identifier, but your function takes string as parameter instead. You should change it to something like:
select test_function('schema_name.TABLE_NAME');
You can try that function below as base for whatever you are trying to do.
/* You need to split table and schema name
or you might get errors when using names that aren't lower case.
This: 'public.TEST1' would be translated to: "public.TEST1"
that is different table from public.test1
*/
CREATE OR REPLACE FUNCTION multi_update_stuff(schema_name varchar, table_name varchar)
/* We will return set of multiple columns. One possible method is to return table.
First column shows executed query, second if it returned no errors (true)
*/
RETURNS TABLE(SQLQuery text, result boolean)
AS $body$
DECLARE
/* Declare arroy of queries that we will iterate and execute later.
We use format() to build query from template and fill it with values.
%1$I can be described as "put first value here and treat it as object identifier"
%3$L can be described as "put third value here and treat it as SQL literal"
*/
SQLQueries text[] := array[
/* First query */
format('UPDATE %1$I.%2$I SET column1 = %3$L WHERE column1 is null;',
schema_name, table_name, '0'),
/* Second query */
format('UPDATE %1$I.%2$I SET column2 = %3$L WHERE column2 = %4$L;',
schema_name, table_name, 'value', 'different value'),
/* Third query, to see error free result */
'SELECT 1'];
BEGIN
/* Iterate our array */
FOREACH SQLQuery IN ARRAY SQLQueries
LOOP
/* Start transaction block */
BEGIN
EXECUTE SQLQuery;
result := true;
/* Catch error if any */
EXCEPTION
WHEN others THEN
result := false;
END;
/* Return row with whatever is assigned to variables listed in RETURNS.
In this case SQLQuery was already assigned by FOREACH.
*/
RETURN NEXT;
END LOOP;
END;
$body$
LANGUAGE plpgsql;
SELECT * FROM multi_update_stuff('schema_name', 'TABLE_NAME')
I have created the following stored procedure, which basically receives a name of table, and a prefix. The function then finds all columns that share this prefix and returns as an output a 'select' query command ('myoneliner').
as follows:
CREATE OR REPLACE FUNCTION mytext (mytable text, myprefix text)
RETURNS text AS $myoneliner$
declare
myoneliner text;
BEGIN
SELECT 'SELECT ' || substr(cols,2,length(cols)-2) ||' FROM '||mytable
INTO myoneliner
FROM (
SELECT array(
SELECT DISTINCT quote_ident(column_name::text)
FROM information_schema.columns
WHERE table_name = mytable
AND column_name LIKE myprefix||'%'
order by quote_ident
)::text cols
) sub;
RETURN myoneliner;
END;
$myoneliner$ LANGUAGE plpgsql;
Call:
select mytext('dkj_p_k27ac','enri');
As a result of running this stored procedure and the 'select' that is following it, I get the following output at the Data Output window (all within one cell, named "mytext text"):
'SELECT enrich_d_dkj_p_k27ac,enrich_lr_dkj_p_k27ac,enrich_r_dkj_p_k27ac
FROM dkj_p_k27ac'
I would like to basically be able to take the output command line that I received as an output and execute it. In other words, I would like to be able and execute the output of my stored procedure.
How can I do so?
I tried the following:
CREATE OR REPLACE FUNCTION mytext (mytable text, myprefix text)
RETURNS SETOF RECORD AS $$
declare
smalltext text;
myoneliner text;
BEGIN
SELECT 'SELECT ' || substr(cols,2,length(cols)-2) ||' FROM '||mytable
INTO myoneliner
FROM (
SELECT array(
SELECT DISTINCT quote_ident(column_name::text)
FROM information_schema.columns
WHERE table_name = mytable
AND column_name LIKE myprefix||'%'
order by quote_ident
)::text cols
) sub;
smalltext=lower(myoneliner);
raise notice '%','my additional text '||smalltext;
RETURN QUERY EXECUTE smalltext;
END;
$$ LANGUAGE plpgsql;
Call function:
SELECT * from mytext('dkj_p_k27ac','enri');
But I'm getting the following error message, could you please advise what should I change in order for it to execute?:
ERROR: a column definition list is required for functions returning "record"
LINE 26: SELECT * from mytext('dkj_p_k27ac','enri');
********** Error **********
ERROR: a column definition list is required for functions returning "record"
SQL state: 42601
Character: 728
Your first problem was solved by using dynamic SQL with EXECUTE like Craig advised.
But the rabbit hole goes deeper:
CREATE OR REPLACE FUNCTION myresult(mytable text, myprefix text)
RETURNS SETOF RECORD AS
$func$
DECLARE
smalltext text;
myoneliner text;
BEGIN
SELECT INTO myoneliner
'SELECT '
|| string_agg(quote_ident(column_name::text), ',' ORDER BY column_name)
|| ' FROM ' || quote_ident(mytable)
FROM information_schema.columns
WHERE table_name = mytable
AND column_name LIKE myprefix||'%'
AND table_schema = 'public'; -- schema name; might be another param
smalltext := lower(myoneliner); -- nonsense
RAISE NOTICE 'My additional text: %', myoneliner;
RETURN QUERY EXECUTE myoneliner;
END
$func$ LANGUAGE plpgsql;
Major points
Don't cast the whole statement to lower case. Column names might be double-quoted with upper case letters, which are case-sensitive in this case (no pun intended).
You don't need DISTINCT in the query on information_schema.columns. Column names are unique per table.
You do need to specify the schema, though (or use another way to single out one schema), or you might be mixing column names from multiple tables of the same name in multiple schemas, resulting in nonsense.
You must sanitize all identifiers in dynamic code - including table names: quote_ident(mytable). Be aware that your text parameter to the function is case sensitive! The query on information_schema.columns requires that, too.
I untangled your whole construct to build the list of column names with string_agg() instead of the array constructor. Related answer:
Update multiple columns that start with a specific string
The assignment operator in plpgsql is :=.
Simplified syntax of RAISE NOTICE.
Core problem impossible to solve
All of this still doesn't solve your main problem: SQL demands a definition of the columns to be returned. You can circumvent this by returning anonymous records like you tried. But that's just postponing the inevitable. Now you have to provide a column definition list at call time, just like your error message tells you. But you just don't know which columns are going to be returned. Catch 22.
Your call would work like this:
SELECT *
FROM myresult('dkj_p_k27ac','enri') AS f (
enrich_d_dkj_p_k27ac text -- replace with actual column types
, enrich_lr_dkj_p_k27ac text
, enrich_r_dkj_p_k27ac text);
But you don't know number, names (optional) and data types of returned columns, not at creation time of the function and not even at call time. It's impossible to do exactly that in a single call. You need two separate queries to the database.
You could return all columns of any given table dynamically with a function using polymorphic types, because there is a well defined type for the whole table. Last chapter of this related answer:
Refactor a PL/pgSQL function to return the output of various SELECT queries
I've got stored procedures in my PostgreSQL environment. One of these procedures stores user defined data.
In plpgsql-language, I would like to realize the following processing:
Because there are unique constraints in the destination-table, I first check, if the affected attributes are already in this table. If so, I want the stored procedure to return these affected attributes, so the user knows: "Uh, some of my data already exists, let's try something different!"
For example a part of the affected table:
CREATE TABLE XYZ (
Name varchar UNIQUE
...
)
Now I want to add a new row to XYZ. If the function notices that the name already exists it should return a message with the offending name. There can be multiple duplicates and the function should return all of them. How can this be implemented?
I first thought to check every attribute individually, but that's very slow:
-- Check, if abbreviation or name already exist.
SELECT EXISTS(
SELECT
1
FROM
"sample-scheme"."sample-table" AS tableName
WHERE
tableName.abbreviation = argAbbreviation
OR
tableName.name = argName
)
INTO
varEntryExists;
-- If abbreviation or name exists, return error message.
IF varEntryExists THEN
RETURN "Already exists.";
END IF;
This sample also doesn't return the offending attribute.
All you need is this simple query:
SELECT name
FROM XYZ
JOIN unnest ('{name1, name2, name3}'::varchar[]) AS n(name) USING (name);
Or use the "is contained by" operator <# (faster for small arrays):
SELECT name
FROM XYZ
WHERE name <# '{name1, name2, name3}'::varchar[]
Wrapped into a function, to return multiple finds, you can either return an array or RETURNS SETOF ... or RETURNS TABLE(...) to return a set. Demonstrating a plpgsql function (could also just be SQL), with a convenient VARIADIC parameter:
CREATE OR REPLACE FUNCTION f_name_dupes(VARIADIC _names varchar[])
RETURNS SETOF varchar AS
$func$
BEGIN
SELECT name
FROM XYZ x
WHERE name <# $1;
END
$func$ LANGUAGE sql;
Call:
SELECT * FROM f_name_dupes('name1', 'name2', 'name3');
Returns all names that already exist in table XYZ. More details for VARIADIC:
How to do WHERE x IN (val1, val2,…) in plpgsql
I have a postgresql function / stored proc that does the following:
1. calls another function and saves the value into a variable.
2. executes another sql statement using the value I got from step one as an argument.
My problem is that the query is not returning any data. No errors are returned either.
I'm just new to postgresql so I don't know the best way to debug... but I added a RAISE NOTICE command right after step 1, like so:
SELECT INTO active_id get_widget_id(widget_desc);
RAISE NOTICE 'Active ID is:(%)', active_id;
In the "Messages" section of the pgadmin3 screen, I see the debug message with the data:
NOTICE: Active ID is:(2)
I'm wondering whether or not the brackets are causing the problem for me.
Here's the sql I'm trying to run in step 2:
SELECT d.id, d.contact_id, d.priority, cp.contact
FROM widget_details d, contact_profile cp, contact_type ct
WHERE d.rule_id=active_id
AND d.active_yn = 't'
AND cp.id=d.contact_id
AND cp.contact_type_id=ct.id
AND ct.name = 'email'
Order by d.priority ASC
You'll notice that in my where clause I am referencing the variable "active_id".
I know that this query should return at least one row because when i run a straight sql select (vs using this function) and substitute the value 2 for the variable "active_id", I get back the data I'm looking for.
Any suggetions would be appreciated.
Thanks.
EDIT 1:
Here's the full function definition:
CREATE TYPE custom_return_type AS (
widgetnum integer,
contactid integer,
priority integer,
contactdetails character varying
);
CREATE OR REPLACE FUNCTION test(widget_desc integer)
RETURNS SETOF custom_return_type AS
$BODY$
DECLARE
active_id integer;
rec custom_return_type ;
BEGIN
SELECT INTO active_id get_widget_id(widget_desc);
RAISE NOTICE 'Active ID is:(%)', active_id;
FOR rec IN
SELECT d.id, d.contact_id, d.priority, cp.contact
FROM widget_details d, contact_profile cp, contact_type ct
WHERE d.rule_id=active_id
AND d.active_yn = 't'
AND cp.id=d.contact_id
AND cp.contact_type_id=ct.id
AND ct.name = 'email'
Order by d.priority ASC
LOOP
RETURN NEXT rec;
END LOOP;
END
$BODY$
That's several levels of too-complicated (edit: as it turns out that Erwin already explained to you last time you posted the same thing). Start by using RETURNS TABLE and RETURN QUERY:
CREATE OR REPLACE FUNCTION test(fmfm_number integer)
RETURNS TABLE (
widgetnum integer,
contactid integer,
priority integer,
contactdetails character varying
) AS
$BODY$
BEGIN
RETURN QUERY SELECT d.id, d.contact_id, d.priority, cp.contact
FROM widget_details d, contact_profile cp, contact_type ct
WHERE d.rule_id = get_widget_id(widget_desc)
AND d.active_yn = 't'
AND cp.id=d.contact_id
AND cp.contact_type_id=ct.id
AND ct.name = 'email'
Order by d.priority ASC;
END
$BODY$ LANGUAGE plpgsql;
at which point it's probably simple enough to be turned into a trivial SQL function or even a view. Hard to be sure, since the function doesn't make tons of sense as written:
You never use the parameter fmfm_number anywhere; and
widget_desc is never defined
so this function could never run. Clearly you haven't shown us the real source code, but some kind of "simplified" code that doesn't match the code you're really having issues with.
There is a difference between:
SELECT INTO ...
[http://www.postgresql.org/docs/current/interactive/sql-selectinto.html]
and
SELECT select_expressions INTO [STRICT] target FROM ...;
[http://www.postgresql.org/docs/current/interactive/plpgsql-statements.html#PLPGSQL-STATEMENTS-SQL-ONEROW]
I think you want:
SELECT get_widget_id(widget_desc) INTO active_id;
I want to get the SQL to drop a function in PostgreSQL. I write DROP FUNCTION and a get function name from pg_proc. That is not problem. However if I leave blank parameters it will not drop the function.
I checked the manual and there is written then I have to identify the function with its parameters to drop it, eg DROP FUNCTION some_func(text,integer) not just DROP FUNCTION some_func.
Where can I find the parameters? In the function's row on in the pg_proc table there is no parameters. So how can I get the SQL to drop the function?
Postgres has a dedicated function for that purpose. Introduced with Postgres 8.4. The manual:
pg_get_function_identity_arguments(func_oid) ... get argument list to identify a function (without default values) ...
pg_get_function_identity_arguments returns the argument list
necessary to identify a function, in the form it would need to appear
in within ALTER FUNCTION, for instance. This form omits default values.
Using that (and format(), introduced with Postgres 9.1), the following query generates DDL statements to drop functions matching your search terms:
SELECT format('DROP %s %I.%I(%s);'
, CASE WHEN p.proisagg THEN 'AGGREGATE' ELSE 'FUNCTION' END
, n.nspname
, p.proname
, pg_catalog.pg_get_function_identity_arguments(p.oid)
) AS stmt
FROM pg_catalog.pg_proc p
JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace
WHERE p.proname = 'dblink' -- function name
-- AND n.nspname = 'public' -- schema name (optional)
-- AND pg_catalog.pg_function_is_visible(p.oid) -- function visible to user
ORDER BY 1;
The system catalog pg_proc changed in Postgres 11. proisagg was replaced by prokind, true stored procedures were added. You need to adapt. See:
How to drop all of my functions in PostgreSQL?
Returns:
stmt
---------------------------------------------------
DROP FUNCTION public.dblink(text);
DROP FUNCTION public.dblink(text, boolean);
DROP FUNCTION public.dblink(text, text);
DROP FUNCTION public.dblink(text, text, boolean);
Found four matches in the example because dblink uses overloaded functions.
Run DROP statements selectively!
Alternatively, you can use the convenient cast to the object identifier type regprocedure which returns a complete function signature including argument types:
-- SET LOCAL search_path = ''; -- optional, to get all names schema-qualified
SELECT format('DROP %s %s;'
, CASE WHEN proisagg THEN 'AGGREGATE' ELSE 'FUNCTION' END
, oid::regprocedure
) AS stmt
FROM pg_catalog.pg_proc
WHERE proname = 'dblink' -- function name
ORDER BY 1;
In Postgres 10, you can delete a function without knowing the list of parameters, as long as it is unique in its schema.
drop function if exists some_func;
See the docs.
Of course, if you have overloaded the function (or are trying to delete over multiple schemas), you will still need the above answers.
use pgadminIII and direct access to function list and right click it then select delete
If you are working on an old previous version of postgres, for which pg_get_function_identity_arguments(func_oid) doesn't exist, I create my own function get the parameters from the function, you only need to pass the oid for the function, you need to deploy the function below to your postgres db.
CREATE OR REPLACE FUNCTION public.getFunctionParameter(functionOid oid)
RETURNS text AS
$BODY$
declare
t_paras text;
paras oid[];
res text :='(';
begin
select proargtypes into t_paras from pg_proc where oid=functionOid;
if t_paras is null or t_paras='' then
return '()';
else
paras:=string_to_array(t_paras,' ');
for i in array_lower(paras,1) .. array_upper(paras,1)
loop
raise notice 'para is %',paras[i];
select format_type(paras[i]::oid,NULL) into t_paras;
res:=res||t_paras||',';
end loop;
res:=substring(res from 1 for char_length(res)-1);
res:=res||')';
return res;
end if;
end
$BODY$
LANGUAGE plpgsql ;
The function below will list the function name and parameters, change the schema name if you want to get function under some other schema, I am using public for example
SELECT n.nspname||'.'||p.proname||public.getFunctionParameter(p.oid)
FROM pg_proc p JOIN pg_namespace n ON n.oid = p.pronamespace
WHERE n.nspname='public'
You will the result like below
1 "public.getfunctionparameter(integer,text)"
2 "public.getfunctionparameter(oid)"