Creating parameterized cursors in DB2 - sql

I'm Facing below error:
An unexpected token "(" was found following " CURSOR ". Expected tokens may include: "CURSOR". SQLSTATE=42601
And I'm just trying to create a simple cursor, actually the example one found here in IBM documentation.
Cursor declaration looks something like:
DECLARE
CURSOR c1 (max_wage NUMBER) IS
SELECT * FROM emp WHERE sal < max_wage;
Not sure if this is do to the version of DB2 being used or not. Can anyone suggest maybe an alternative to creating a parameterized cursor?

You are trying to use PL/SQL syntax in DB2. This requires changes to the server environment. If you want to support the Oracle datatypes as well, the database must be created with the right settings, too. See this article for more details. The summary of that article is:
Open a DB2 Command Window (in Administrator mode)
Run db2start
Run db2set DB2_COMPATIBILITY_VECTOR=ORA
Run db2set DB2_DEFERRED_PREPARE_SEMANTICS=YES
Run db2stop
Run db2start
Execute your PL/SQL statements, e.g. in a DB2 CLP (run db2 -tv) command window.
Note that you should run
SET SQLCOMPAT PLSQL; in your DB2 CLP before trying PL/SQL. This enables using a forward slash (/) as a PL/SQL statement terminator. You should then obviously also then actually terminate your command with a forward slash :)
Here's an example taken from your link, modified to work with the default SAMPLE database in DB2:
SET SQLCOMPAT PLSQL;
DECLARE
my_record emp%ROWTYPE;
CURSOR c1 (max_wage integer) IS
SELECT * FROM employee WHERE salary < max_wage;
BEGIN
OPEN c1(40000);
LOOP
FETCH c1 INTO my_record;
EXIT WHEN c1%NOTFOUND;
DBMS_OUTPUT.PUT_LINE('Name = ' || my_record.firstnme || ', salary = '
|| my_record.salary);
END LOOP;
CLOSE c1;
END;
/
If you don't want to do all the above, then use the standard DB2 cursor syntax:
DECLARE [cursor name] CURSOR FOR [...]
...but that doesn't support parameterized cursors. To do so, I'd recommend creating a stored procedure taking the parameter. This stored procedure can then create a cursor using that parameter directly in the SQL.

Related

Unable to create DB2 Procedure through command line

I am trying to create a procedure to do a blanket revoking of executeauth for procedures from a schema. This is in line with trying to secure a non-restricted database.
CREATE PROCEDURE PROC_REV
LANGUAGE SQL
MODIFIES SQL DATA
BEGIN
DECLARE v_NAME VARCHAR(400);
FOR v1 AS
c1 CURSOR FOR
select specificname from SYSCAT.ROUTINEAUTH where grantee='PUBLIC' and schema='SYSPROC' and routinetype='P'
DO
SET v_NAME = specificname;
SET v_GrantQuery = 'revoke execute on specific procedure '|| v_NAME ||' from PUBLIC';
EXECUTE IMMEDIATE v_GrantQuery;
END FOR;
END#
and this is the command I run to process the file with the code above.
db2 -td# -svf RoutineAuthRevoke.db2
However, I keep running into this error
SQL0553N An object cannot be created with the schema name "SYSFUN ". LINE NUMBER=1. SQLSTATE 42939
I'm fairly new to DB2 and this is my first foray into writing db2 procedure scripts. Would anyone be able to spot the "SYSFUN " because I sure as hell can't. The only other way I can revoke 310 entries from SYSPROC for PUBLIC is through a batch file and I figured, procedures might be a cleaner way of achieving this. I would really appreciate any help with either this error or with the code itself.
A number of problems.
You can't create a routine in the SYSFUN schema as the error message shows. You get this message because the statement VALUES CURRENT SCHEMA returns SYSFUN in your session. You must either run the SET SCHEMA SOME_VALID_SCHEMA_NAME statement before CREATE or use fully qualified routine name like SOME_VALID_SCHEMA_NAME.PROC_REV.
Variable v_GrantQuery is not defined in the routine.
According to the syntax of REVOKE (routine privileges) statement, you should generate the REVOKE statement using fully qualified routine name and with RESTRICT clause at the end. The easiest way to do it with a compound statement (you don't need to create a routine for that):
BEGIN
FOR v1 AS
select 'REVOKE EXECUTE ON SPECIFIC PROCEDURE "' || schema ||'"."'|| specificname || '" FROM PUBLIC RESTRICT' AS STMT
from SYSCAT.ROUTINEAUTH
where grantee='PUBLIC' and schema='SYSPROC' and routinetype='P'
DO
EXECUTE IMMEDIATE v1.STMT;
END FOR;
END#

How to use SET OPTION within a DB2 stored procedure

I read (and tried) that I cannot use WITH UR in DB2 stored procedures. I am told that I can use SET OPTION to achieve the same. However, when I implement it in my stored procedure, it fails to compile (I moved around its location same error). My questions are:
Can I really not use WITH UR after my SELECT statements within a procedure?
Why is my stored procedure failing to compile with the below error
message?
Here is a simplified version of my code:
CREATE OR REPLACE PROCEDURE MySchema.MySampleProcedure()
DYNAMIC RESULT SETS 1
LANGUAGE SQL
SET OPTION COMMIT=*CHG
BEGIN
DECLARE GLOBAL TEMPORARY TABLE TEMP_TABLE AS (
SELECT 'testValue' as "Col Name"
) WITH DATA
BEGIN
DECLARE exitCursor CURSOR WITH RETURN FOR
SELECT *
FROM SESSION.TEMP_TABLE;
OPEN exitCursor;
END;
END
#
Error Message:
SQL0104N An unexpected token "SET OPTION COMMIT=*CHG" was found
following " LANGUAGE SQL
Here is code/error when I use WITH UR
CREATE OR REPLACE PROCEDURE MySchema.MySampleProcedure()
LANGUAGE SQL
DYNAMIC RESULT SETS 1
--#SET TERMINATOR #
BEGIN
DECLARE GLOBAL TEMPORARY TABLE TEMP_TABLE AS (
SELECT UTI AS "Trade ID" FROM XYZ WITH UR
) WITH DATA;
BEGIN
DECLARE exitCursor CURSOR WITH RETURN FOR
SELECT *
FROM SESSION.TEMP_TABLE;
OPEN exitCursor;
END;
END
#
line 9 is where the DECLARE GLOBAL TEMPORARY ... is
DB21034E The command was processed as an SQL statement because it was
not a valid Command Line Processor command. During SQL processing it
returned: SQL0109N The statement or command was not processed because
the following clause is not supported in the context where it is
used: "WITH ISOLATION USE AND KEEP". LINE NUMBER=9. SQLSTATE=42601
Specifying the isolation level:
For static SQL:
If an isolation-clause is specified in the statement, the value of that clause is used.
If an isolation-clause is not specified in the statement, the isolation level that was specified for the package when the package was bound to the database is used.
You need to bind the routine package with UR, since your DECLARE GTT statement is static. Before CREATE OR REPLACE use the following in the same session:
CALL SET_ROUTINE_OPTS('ISOLATION UR')
P.S.: If you want to run your routine not only 1 time in the same session without an error, use additional WITH REPLACE option of DECLARE.
If your Db2 server runs on Linux/Unix/Windows (Db2-LUW), then there is no such statement as SET OPTION COMMIT=*CHG , and so Db2 will throw an exception for that invalid syntax.
It is important to only use the matching Db2 Knowledge Centre for your Db2 platform and your Db2-version. Don't use Db2-Z/OS documentation for Db2-LUW development. The syntax and functionalities differ per platform and per version.
A Db2-LUW SQL PL procedure can use with ur in its internal queries, and if you are getting an error then something else is wrong. You have to use with ur in the correct syntax however, i.e in a statement that supports this clause. For your example you get the error because the clause does not appear to be valid in the depicted context. You can achieve the desired result in other ways, one of them being to populate the table in a separate statement from the declaration (e.g insert into session.temp_table("Trade ID") select uti from xyz with ur; ) and other ways are also possible.
One reason to use the online Db2 Knowledge Cenbtre documentation is that it includes sample programs, including sample SQL PL procedures, which are also available in source code form in the sample directory of your DB2-LUW server, in addition to being available on github. It is wise to study these, and get them working for you.

Run an oracle SQL script twice with different parameters

I have an SQL statement in Oracle SQL developer that has some variables:
DEFINE custom_date = "'22-JUL-2016'" --run1
DEFINE custom_date = "'25-JUL-2016'" --run2
SELECT * FROM TABLE WHERE date=&custom_date
The real query is much more complicated and has many more variables and new tables are created from the results of the query. How can I create a script so that the query is executed twice, the first time with the custom date set as in the first line and the second time as in the second line.
In Oracle, the &variable is a "substitution variable" and is not part of SQL; it is part of the SQL*Plus scripting language (understood by SQL Developer, Toad etc.)
The better option, and what you are asking about, is BIND VARIABLES. The notation is :variable (with a colon : instead of &), for example :custom_date.
The difference is that a substitution variable is replaced by its value in the front-end application (SQL Developer in your case) before the query is ever sent to the Oracle engine proper. A bind variable is substituted at runtime. This has several benefits; discussing them is outside the scope of your question.
When you execute a query with bind variables in SQL Developer, the program will open a window where you enter the desired values for the bind variables. You will have to experiment with that a little bit till you can make it work (for example I never remember if a date must be entered with the single quotes or without). Good luck!
Define is used in TRANSACT SQL. To do this Oracle way, You can create anonymus PL/SQL block, similar to this:
DECLARE
p_param1 DATE;
p_param2 NUMBER;
CURSOR c_cur1(cp_param1 DATE,cp_param2 NUMBER)
IS
SELECT * FROM table WHERE date = cp_param1
;
BEGIN
-- Execute it first time
p_param1 := TO_DATE('2016-09-01','YYYY-MM-DD');
FOR r IN c_cur1(p_param1)
LOOP
NULL;
END LOOP;
-- Execute it second time
p_param1 := TO_DATE('2015-10-11','YYYY-MM-DD');
FOR r IN c_cur1(p_param1)
LOOP
NULL;
END LOOP;
END;
And in it, You create cursor with parameters and execute it twice with different parameter.
I do not know why You want to execute this query twice, so the script abowe does nothing with results, but it certainly should execute Your query twice, with different params.

Ad hoc querying Oracle PL/SQL - for SQL Server developer

I used to do Oracle development many many years ago. I have spent most of the past 15 years doing mainly SQL Server, and where I have done Oracle work, been insulated from the workings by Web services etc.
My question seems to have been asked on the web a few times, but it seems difficult somehow to communicate - at least judging by the answers. I very much appreciate that tools and functionality differ, know I have to learn new things, but this is so simple, yet seems so hard to do.
I am looking to do some ad-hoc queries on the database. At the moment we are using SQL Navigator - I am open to using other tools...
In SQL Server Management Studio, if you open a query window, type a bit of SQL that retuns a value or a set, you get a nice display of the rows or values in a results window.
I've gathered that with Oracle PL/SQL things are a bit different, worked out that I need to return a cursor - but how do I get a tool to display the results?
I started simple:
declare
my_id number := 356655;
cursor c1 is select my_id from dual;
begin
open c1;
end;
This runs fine - but how do I see the results? Are there any tools that deal with this as 'nicely' as SSMS? I am used to being able to do a lot of this, including stuff like
(perhaps not exactly the right syntax? but you get the idea...)
declare
my_id number := 356655;
cursor c1 is select name from my_table where id = my_id;
begin
open c1;
And having the results displayed to me as text/grid. Ideally that there is a nice solution. Some spiffy new tool, maybe?
With SQL Developer or SQL*Plus you can use a bind variable declared before the PL/SQL block:
variable rc refcursor;
declare
my_id number := 356655;
begin
open :rc for select my_id from dual;
end;
/
print rc
RC
-------------------------------
356655
You can also use a bind variable within the query, which can be useful:
variable my_id number;
variable rc refcursor;
execute :my_id := 356655;
begin
open :rc for select :my_id from dual;
end;
/
print rc
The variable and print commands are covered in the SQL*Plus documentation, which largely applies to SQL Developer as well - that has its own documentation, including the commands that are carried over from SQL*Plus.
If you have a function that returns a ref cursor then you can call that in a query, as select func(val) from dual, and then the results can go in a grid; or you can call the function (or procedure) with the same :rc bind variable and print it. But I'm not sure either is helpful if you are only doing ad hoc queries.
On the other hand, using a PL/SQL block for an ad hoc query seems a little heavy-handed, even if your queries are complicated. You'd need a good reason to open a cursor for a select statement from within a block, rather than just running the select directly. (Not sure if that's a SQL Server thing or if you actually have a real need to do this!). If you're just running a query inside the block, you don't need the block, even if you want to keep a bind variable for the values you're using in the query:
variable my_id number;
execute :my_id := 356655;
select :my_id from dual;
:MY_ID
----------
356655
I use Oracle SQL Developer.
Anyway, this should work in any oracle sql client:
If you just want to see your results, you can use
dbms_output.put_line('Foo' || somevar || ' bar');
Before this, run
SET SERVEROUTPUT ON
Check the examples at docs.oracle.com
I would suggest using sql developer available free from the oracle website. There is a button which allows you to run sql as a script which will get back what you want. SSMS doesn't work with pl/sql.

ORACLE Batching DDL statements within a Execute Immediate

I'm trying to run multiple ddl statements within one Execute Immediate statement.
i thought this would be pretty straight forward but it seems i'm mistaken.
The idea is this:
declare v_cnt number;
begin
select count(*) into v_cnt from all_tables where table_name='TABLE1' and owner = 'AMS';
if v_cnt = 0 then
execute immediate 'CREATE TABLE TABLE1(VALUE VARCHAR2(50) NOT NULL) ALTER TABLE TABLE1 ADD (MYVAL2 NVARCHAR2(10))';
end if;
end;
however this results in an error
ORA-00911: invalid character
ORA-06512: at line 10
Each of the statements within the batch run fine if i execute them by themselves. and if i take this statement and execute it, it will run fine (with the ; between the 2 statements). If i remove the ; between statements i get a different error about invalid option
the plan is that i'll be able to build a table, export the table schema for this table including all it's alter statements, and then run the batch against another system as part of an install/update process.
So, how do i batch these DDL statements within a single execute immediate? Or is there a better way to do what i'm needing?
I'm a bit of a Oracle newb, i must admit. Thank you all for your patience.
The semicolon is not part of Oracle's SQL syntax. SQL*Plus and other client side tools use semicolon to signal the end of a SQL Statement, but the server doesn't see it.
We can force SQL*Plus to pass the semicolon to the DB:
SQL> set sqlterminator off
SQL> select * from user_tables;
2 /
select * from user_tables;
*
ERROR at line 1:
ORA-00911: invalid character
If i take this statement and execute it, it will run fine (with the ; between the 2 statements) The client tool you are using, breaks it into two calls to the DB.
So, I don't think it is possible to pass multiple statements inside an execute immediate.
I suppose one could call execute immediate with a string containing an anonymous PL/SQL block, with individual calls to execute immediate inside it ... and I don't know what the point of doing that would be. ;)
Why do you need a single EXECUTE IMMEDIATE call? Surely just do it as 2 calls?
Bear in mind that each DDL statement contains an implicit COMMIT, so there's no concurency benefit to doing it as a single call.
Also, why not just set up the table correctly in the first call? You could do...
CREATE TABLE TABLE1(VALUE VARCHAR2(50) NOT NULL, MYVAL2 NVARCHAR2(10))
...instead of needing 2 calls.
Also, have you looked at DBMS_METADATA... it can generate DDL for objects such as tables for you.