DB2oC (DB2 on Cloud) : Facing "attempted to modify data but was not defined as MODIFIES SQL DATA" error - db2

I have created very simple function in DB2oC as below, which has one UPDATE sql statement and one SELECT sql statement along with MODIFIES SQL DATA. But still I get the below error, though I have specified MODIFIES SQL DATA. I did GRANT ALL on that TEST table to my user id and also did GRANT EXECUTE ON FUNCTION to my user id on safe side. Can you please help to explain on what could be the issue?
I have simply invoked the function using SELECT statement like below:
SELECT TESTSCHEMA.MAIN_FUNCTION() FROM TABLE(VALUES(1));
SQL Error [38002]: User defined routine "TESTSCHEMA.MAIN_FUNCTION"
(specific name "SQL201211013006981") attempted to modify data but was
not defined as MODIFIES SQL DATA.. SQLCODE=-577, SQLSTATE=38002,
DRIVER=4.27.25
CREATE OR REPLACE FUNCTION MAIN_FUNCTION()
RETURNS VARCHAR(20)
LANGUAGE SQL
MODIFIES SQL DATA
BEGIN
DECLARE val VARCHAR(20);
UPDATE TEST t SET t.CONTENT_TEXT = 'test value' WHERE t.ID = 1;
select CONTENT_TEXT into val from TEST where ID = 1;
return val;
end;
Appreciate your help.

For the modifies SQL data clause , the usage of the function is restricted on Db2-LUW.
These restrictions do not apply for user defined functions that do not modify data.
For your specific example, that UDF will operate when used as the sole expression on the right hand side of an assignment statement in a compound-SQL compiled statemnent.
For example:
create or replace variable my_result varchar(20) default null;
begin
set my_result = main_function();
end#
Consider using stored procedures to modify table contents, instead of user defined functions.

You could avoid using a function, and just use a single "change data statement"
SELECT CONTENT_TEXT
FROM NEW TABLE(
UPDATE TEST t
SET t.CONTENT_TEXT = 'test value'
WHERE t.ID = 1
)

Related

How to return result set from PostgreSQL stored procedure?

PostgreSQL supports stored procedure (not function) since version 11. I created a stored procedure like this:
CREATE OR REPLACE PROCEDURE get_user_list ()
LANGUAGE SQL
SECURITY DEFINER
AS $$
SELECT "id",
"username",
"display_name"
FROM "user"
ORDER BY "created_at" ASC;
$$;
But when I am trying to execute this stored procedure, it does not return any data:
postgres=# CALL get_user_list();
CALL
postgres=# SELECT * FROM get_user_list();
ERROR: get_user_list() is a procedure
LINE 1: SELECT * FROM get_user_list();
^
HINT: To call a procedure, use CALL.
So the question is, how can a stored procedure returns its result set in PostgreSQL 11+?
Following the docs on Postgres 11 (bold emphasis mine):
A procedure does not have a return value. A procedure can therefore end without a RETURN statement. If a RETURN statement is desired to exit the code early, then NULL must be returned. Returning any other value will result in an error.
You could mark your parameters as output though, so that they would behave like variables.
On a side note there's normally a distinction within other DBMS that functions can only call SELECT statements and should not modify data while procedures should be handling the data manipulation and data definition languages (DML, DDL). This concludes (in my opinion) that creating a procedure to simply perform a stable (*) select statement is not the desired technique.
(*) Read more on function volatility here.
In case if you have one select in procedure to get result set you can create view like this:
CREATE OR REPLACE VIEW as get_user_list
SELECT "id",
"username",
"display_name"
FROM "user"
ORDER BY "created_at" ASC;
and then select * from get_user_list to get result set.

Temp table for user defined functions

I am trying to use temp table in user defined function for DB2. I am trying to do this in data studio but the code below is not working. How can I make this work?
Thanks.
CREATE FUNCTION BELSIZE.TEST (aSTRING VARCHAR(50))
RETURNS TABLE(
column1 INTEGER
)
F1: BEGIN ATOMIC
DECLARE c1 CURSOR WITH RETURN TO CLIENT FOR stmt;
SET v_dynStmt = 'SELECT 1 column1 from sysibm.sysdummy1';
PREPARE stmt FROM v_dynStmt;
OPEN c1;
RETURN
END
You have syntax errors in your code, more details below.
Apart from syntax errors, your title mentions temp table but your code does not, so your question is poor.
Never write "...is not working", instead write the exact SQLCODE and SQLSTATE and message that you see.
When asking for help with Db2, always write in the question the Db2-version and the operating-system (Z/OS, i-Series, Linux/Unix/Windows) on which the Db2-server runs, because the answer can depend on those facts. Different versions of Db2 for different operating systems have different capabilities and different syntax.
If you want to use cursors for result-sets then use SQL PL stored-procedures, because there are fewer restrictions.
SQL table functions are suitable when you don't need to declare a cursor for result-set.
Db2-LUW prevents you from declaring a cursor in an SQL table function when you use BEGIN ATOMIC.
If you are not using BEGIN ATOMIC, Db2-LUW (current versions, i.e. v11.1) lets you declare a cursor in an SQL UDF but you cannot use that cursor directly to return the result-set, as you can do inside SQL PL stored procedures.
For your example, the syntax below is valid and also useless, so consider using an SQL PL procedure instead:
--#SET TERMINATOR #
CREATE or replace FUNCTION BELSIZE.TEST (aSTRING VARCHAR(50))
RETURNS TABLE( column1 INTEGER)
language sql
specific belsize.test
BEGIN atomic
RETURN select 1 as column1 from sysibm.sysdummy1 ;
END
#
select * from table(belsize.test('a')) as t
#

Execute triggers function of another schema on the actual chema

my problem is easy to explain with an example: I have a 'common' schema (the public one?) where I store common data between a clustered application.
For every instance of my application, I have a role (used as the application user).
And i have a common role, app_users, with read-only privileges on the common schema, and every application role is a member of app_users.
Now my problem is: how can i set a trigger on the app_a scheme that execute a function (procedure) in the common scheme, but affect the (and only the) app_a tables?
I mean:
// common_scheme, dummy function to emulate the mysql on update = now()
CREATE OR REPLACEFUNCTION update_etime() RETURNS TRIGGER AS $$
BEGIN
NEW.etime = date_part('epoch'::text, now())::int;
RETURN NEW;
END;
$$ language plpgsql;
// now, in the app_foo scheme, i have the table:
CREATE TABLE foo_table (fid serial not null primary key unique, label char(25));
// and the trigger:
CREATE TRIGGER foo_table_update_etime BEFORE UPDATE ON foo_talbe FOR EACH ROW EXECUTE PROCEDURE update_etime();
// ERROR: function update_etime() does not exist
CREATE TRIGGER foo_table_update_etime BEFORE UPDATE ON foo_talbe FOR EACH ROW EXECUTE PROCEDURE common_scheme.update_etime();
// ERROR: function common_scheme.update_etime() does not exist
The user that will access app_foo has the execute privilege on update_etime() function in common_schema.
Any idea?
I've googled around but the only solution I fount to call functions from other schemas is something like execute 'select * from ' || schema_name || '.table_name'; but i dont think this will do the trick in my case, becose the function must work with the 'local' scheme.
Your second set of syntax should work... the one with "EXECUTE PROCEDURE common_scheme.update_etime();"
If it isn't finding the function, I'd guess that you either have created it in a different schema than you think it is in, or you haven't created it at all (and note, your example create syntax has a bug, no space between "replace" and "function", which would cause an error when trying to create the function. Try doing a:
\df *.update_etime
As superuser to verify the function exists and is in the location you think it is in. HTH.

the max function requires 1 argument(s)

I wrote this very simple SP in SQL 2008:
Create procedure dbo.GetNextID
(
#TableName nvarchar(50),
#FieldName nvarchar(50)
)
AS
BEGIN
exec('select isnull(max('+#FieldName+'),0)+1 as NewGeneratedID from '+ #TableName);
END
When I execute this procedure in Visual Studio SQL Express and pass a table name and a field name, it works fine. But when I try to add this SP as a query in a QueryTableAdapter in my ADO DataSet, I receive this error before clicking on Finish button:
the max function requires 1 argument(s)
can anyone help me with this?
I guess that VS tries to determine a field list by executing the SP. But as it does not know what to pass to the SP, it uses empty parameters. Now, of course, your select statement fails.
You could try adding the following to your SP:
IF ISNULL(#TableName,'') = '' SET #TableName = '<Name of a test table>';
IF ISNULL(#FieldName,'') = '' SET #FieldName = '<Name of some field>';
Use the names of some field and table that do exist here (for example names that you'd use from your application, too).
Alternatively you could add the following above the exec:
IF (ISNULL(#TableName, '') = '') OR (ISNULL(#FieldName, '') = '')
BEGIN
SELECT -1 AS NewGeneratedId
RETURN 0
END
EDIT
On a side note, I'd like to warn you about concurrency issues that I see coming up from what your code does. If this code is supposed to return a unique ID for a new record in some table, I'd redesign this as follows:
Create a table NumberSeries where each row contains a unique name, a possible range for IDs and the current ID value.
Create a stored procedure that uses UPDATE ... OUTPUT to update the current ID for a number series and retrieve it in one step.
That way you can make sure that creating a new ID is a single operation that does not cause concurrency problems.

Oracle error ORA-22905: cannot access rows from a non-nested table item

here's the stored procedure i wrote.In this proc "p_subjectid" is an array of numbers passed from the front end.
PROCEDURE getsubjects(p_subjectid subjectid_tab,p_subjects out refCursor)
as
BEGIN
open p_subjects for select * from empsubject where subject_id in
(select column_value from table(p_subjectid));
--select * from table(cast(p_subjectid as packg.subjectid_tab))
END getsubjects;
This is the error i am getting.
Oracle error ORA-22905: cannot access rows from a non-nested table item OR
as i have seen in different post,i tried casting "cast(p_subjectid as packg.subjectid_tab)" inside table function as given in the comment below.But i am getting another error: ORA-00902: invalid datatype.
And this is the definition of the "subjectid_tab".
type subjectid_tab is table of number index by binary_integer;
Can anyone please tell me what's the error.Is anything wrong with the my procedure.
You have to declare the type on "the database level" as ammoQ suggested:
CREATE TYPE subjectid_tab AS TABLE OF NUMBER INDEX BY binary_integer;
instead of declaring the type within PL/SQL. If you declare the type just in the PL/SQL block, it won't be available to the SQL "engine".
Oracle has two execution scopes: SQL and PL/SQL. When you use a SELECT/INSERT/UPDATE (etc) statement you are working in the SQL scope and, in Oracle 11g and below, you cannot reference types that are defined in the PL/SQL scope. (Note: Oracle 12 changed this so you can reference PL/SQL types.)
TYPE subjectid_tab IS TABLE OF NUMBER INDEX BY BINARY_INTEGER;
Is an associative array and can only be defined in the PL/SQL scope so cannot be used in SQL statements.
What you want is to define a collection (not an associative array) in the SQL scope using:
CREATE TYPE subjectid_tab IS TABLE OF NUMBER;
(Note: you do not need the INDEX BY clause for a collection.)
Then you can do:
OPEN p_subjects FOR
SELECT *
FROM empsubject
WHERE subject_id MEMBER OF p_subjectid;
or
OPEN p_subjects FOR
SELECT *
FROM empsubject
WHERE subject_id IN ( SELECT COLUMN_VALUE FROM TABLE( p_subjectid ) );
This is the good solution.
You cannot use a table(cast()) if the type that you cast is in the DECLARE part of the pl/sql block.
You REALLY need to use CREATE TYPE my_type [...]. Otherwise, it will throw the "cannot fetch row[...]" exception.
I just had this problem yesterday.
DECLARE
TYPE number_table IS TABLE OF NUMBER;
result_ids number_table := number_table();
BEGIN
/* .. bunch of code that uses my type successfully */
OPEN ? AS
SELECT *
FROM TABLE(CAST(result_ids AS number_table)); /* BOOM! */
END;
This fails in both of the ways you described earlier when called from a java routine. I discovered this was due to the fact that the type number_table is not defined in an exportable manner than can be shipped off the database. The type works great internally to the routine. But as soon as you try to execute a returnable recordset that references it in any way (including IN clauses?!?) you get a datatype not defined.
So the solution really is CREATE TYPE myschema.number_table IS TABLE OF NUMBER; Then drop the type declaration from your block and use the schema level declaration. Use the schema qualifier to reference the type just to be sure you are using the right one.
you have to cast the results of the pipelined query so:
If your pipelined function returns a rowtype of varchar2 then define a type (for example )
CREATE OR REPLACE TYPE char_array_t is VARRAY(32) of varchar2(255);
select * from table(cast(fn(x) as user_type_t ) );
will now work.