I have to make modifications to an Oracle stored procedure that has the following lines.
InsStmt = 'INSERT INTO EMPLOYEE (Emp_cd, Emp_lst_nm, Emp_fst,nm) VALUES
(:Emp_cd, :Emp_lst_nm, :Emp_fst_nm);';
varExec :='
DECLARE
var1 VARCHAR2(100);
BEGIN
var1 := :Emp_cd||:Emp_lst_nm||:Emp_fst_nm;
'||InsStmt||'
END;';
EXECUTE IMMEDIATE varExec USING ip_param_cd, ip_param_lnm, ip_param_fnm;
I have only basic understanding of Oracle stored procedures. After some research I found out that the || operator is for concatenating strings.
But I'm still wondering what does the below statement mean
var1 := :Emp_cd||:Emp_lst_nm||:Emp_fst_nm;
'||InsStmt||'
I went through the tutorial at http://docs.oracle.com/cd/B28359_01/appdev.111/b28843/tdddg_procedures.htm#CIHGDECD but could not find any help.
Outside of the trigger context, the column : is used to bind variables within a statement.
For example:
EXECUTE IMMEDIATE 'UPDATE mytable SET age = 25 WHERE age = :1'
USING IN localVarAge;
In this case, the :1 value would be replaced by the value of localVarAge.
The order the ':' variables appear in the prepared statement matter, not their actual labels.
In your code there's clearly a piece missing, this part var1 := :Emp_cd||:Emp_lst_nm||:Emp_fst_nm; should be within quotes. That would make sense anyway since you have right after that a closing quote and a concatenation.
Related
In the Oracle PL/SQL, how to escape single quote in a string ? I tried this way, it doesn't work.
declare
stmt varchar2(2000);
begin
for i in 1021 .. 6020
loop
stmt := 'insert into MY_TBL (Col) values(\'ER0002\')';
dbms_output.put_line(stmt);
execute immediate stmt;
commit;
end loop;
exception
when others then
rollback;
dbms_output.put_line(sqlerrm);
end;
/
You can use literal quoting:
stmt := q'[insert into MY_TBL (Col) values('ER0002')]';
Documentation for literals can be found here.
Alternatively, you can use two quotes to denote a single quote:
stmt := 'insert into MY_TBL (Col) values(''ER0002'')';
The literal quoting mechanism with the Q syntax is more flexible and readable, IMO.
Here's a blog post that should help with escaping ticks in strings.
Here's the simplest method from said post:
The most simple and most used way is to use a single quotation mark with two single quotation marks in both sides.
SELECT 'test single quote''' from dual;
The output of the above statement would be:
test single quote'
Simply stating you require an additional single quote character to print a single quote character. That is if you put two single quote characters Oracle will print one. The first one acts like an escape character.
This is the simplest way to print single quotation marks in Oracle. But it will get complex when you have to print a set of quotation marks instead of just one. In this situation the following method works fine. But it requires some more typing labour.
In addition to DCookie's answer above, you can also use chr(39) for a single quote.
I find this particularly useful when I have to create a number of insert/update statements based on a large amount of existing data.
Here's a very quick example:
Lets say we have a very simple table, Customers, that has 2 columns, FirstName and LastName. We need to move the data into Customers2, so we need to generate a bunch of INSERT statements.
Select 'INSERT INTO Customers2 (FirstName, LastName) ' ||
'VALUES (' || chr(39) || FirstName || chr(39) ',' ||
chr(39) || LastName || chr(39) || ');' From Customers;
I've found this to be very useful when moving data from one environment to another, or when rebuilding an environment quickly.
In my case, I use like this:
stmt := q'!insert into MY_TBL (Col) values('ER0002')!';
Adding: q'! before and !' after string.
This is another reference: Alternative Quoting Mechanism (''Q'') for String Literals
EXECUTE IMMEDIATE 'insert into MY_TBL (Col) values(''ER0002'')'; worked for me.
closing the varchar/string with two pairs of single quotes did the trick. Other option could be to use using keyword, EXECUTE IMMEDIATE 'insert into MY_TBL (Col) values(:text_string)' using 'ER0002'; Remember using keyword will not work, if you are using EXECUTE IMMEDIATE to execute DDL's with parameters, however, using quotes will work for DDL's.
I need help in dynamic query for the following scenario.
I have a procedure A in which I am storing a string in an output variable which will be passed to a procedure B.
Procedure B receives tablename also as an input parameter but procedure A doesn't, however procedure A also uses the tablename variable.
I am thinking of how to use tablename variable in procedure A string such that when the string is passed to procedure B, its input variable value of tablename gets assigned to the tablename variable in the string of procedure A.
I will try to explain with some code sample. It's a sample and no actual code.
proc A
begin
--- string that uses tablename but has no variable input for tablename.
mystr:='AND DAY_OF_WEEK_ID IN (SELECT B.DAY_ID FROM DAY_OF_WEEK B
INNER JOIN CD.' || 'v_tableName' || ' CD
ON TRIM(TO_CHAR(TO_TIMESTAMP(CD.GMT_SEIZ_DT_TIME,''YYYYMMDDHH24MISS''), ''DAY'')) = B.NAME WHERE B.DAY_ID IN (1,7))';
end;
proc B
(v_tablename, mystr)
begin
mystr2:= 'insert into sometable
select ' || mystr || ' from ' || v_tablename
end;
so the mystr string already contains tablename variable for which I want the same value to be assigned as the variable v_table_name of procedure B.
I apologies if I have made the scenario too complex but I couldn't find a better way.
Regards.
In procedure B you need to substitute the placeholder in the passed string with the actual table name.
Your posted pseudo-code is a bit garbled, but it seems the placeholder in the string generated by procedure A is 'v_tablename' and the actual table name in procedure B is held in a variable called v_tablename. That being the case, this would work for you:
mystr := replace(mystr, 'v_tablename', v_tablename);
Newbie to PL/SQL. I have several questions, so here's an example of what I'm trying to do.
CREATE OR REPLACE PROCEDURE "my_procedure" (
"my_inparam1" IN VARCHAR2,
"my_inparam2" IN VARCHAR2,
"my_output" OUT SYS_REFCURSOR)
AS
sql_text VARCHAR2 (10000);
BEGIN
sql_text :=
'select something
from my_table
where 1 = 1';
IF '&my_inparam1' <> 'foo'
THEN
sql_text := sql_text || ' and something = 0';
END IF;
IF '&my_inparam1' = 'foo' and '&my_inparam2' = 'bar'
THEN
sql_text := sql_text || ' and somethingelse = 1';
ELSIF '&my_inparam1' = 'foo' AND '&my_inparam2' = 'baz'
THEN
sql_text := sql_text || ' and somethingelse = 0';
END IF;
OPEN my_output FOR sql_text; --ERROR PLS-00201 Identifier 'MY_OUTPUT' must be declared
END;
So obviously I'm trying to return a query result, optionally filtered by whatever parameters I pass in. I'm at a loss as to why the offending line returns an error - in an earlier iteration, I was able to return results, but now, mysteriously, it's stopped working.
1) Is there a better way to approach this?
2) Do I have to reference the input params with the '&my_inparam' syntax?
3) If I do approach this by creating the sql text first and then opening the ref cursor, is there a shortcut for concatening the strings, like
sql_text &= ' and another_condition = 1'
?
In reverse order... no, there is no shorthand for concatenation like &=. You could use the concat() function instead, but the || method is more common, and more convenient especially if you're sticking more than two things together - nested concat() calls aren't as easy to follow. I'd stick with what you're doing.
Secondly, no, you're confusing SQL*Plus substitution variables with PL/SQL variables. Your references to '&my_inparam1' should be my_inparam1, etc; no ampersand and no quotes.
Except for some reason you've decided to make life difficult for yourself and use case-sentisive procedure and variable names, so you have to refer to "my_inparam1", in double quotes, everywhere.
That's why you're getting the message PLS-00201 Identifier 'MY_OUTPUT' must be declared. You didn't quote my_output so by default it's looking for a case-insensitive variable called MY_OUTPUT, which does not exist. It would work if you did this instead:
OPEN "my_output" FOR sql_text;
Unless you have a really really good reason, really don't do that.
CREATE OR REPLACE PROCEDURE my_procedure (
my_inparam1 IN VARCHAR2,
my_inparam2 IN VARCHAR2,
my_output OUT SYS_REFCURSOR)
AS
sql_text VARCHAR2 (10000);
BEGIN
sql_text :=
'select something
from my_table
where 1 = 1';
IF my_inparam1 <> 'foo'
THEN
sql_text := sql_text || ' and something = 0';
END IF;
...
OPEN my_output FOR sql_text;
END;
For more information, refer to the naming rules:
Every database object has a name. In a SQL statement, you represent
the name of an object with a quoted identifier or a nonquoted
identifier.
A quoted identifier begins and ends with double quotation marks (").
If you name a schema object using a quoted identifier, then you must
use the double quotation marks whenever you refer to that object.
A nonquoted identifier is not surrounded by any punctuation.
And more importantly:
Note:
Oracle does not recommend using quoted identifiers for database object names. These quoted identifiers are accepted by
SQL*Plus, but they may not be valid when using other tools that manage
database objects.
You quoted procedure name falls into this category; so do the quoted variable names. They're all identifiers and the same advice applies.
I get an error when use named parameter to call PL/SQL block, when all named parameters are used only once, then my code works fine, but when I dupplicate the SQL marked with "// the SQL". then all named parameters (starts with colon, :q) are used twice, now I get a SQL Exception, it says: The number of parameter names does not match the number of registered praremeters.
It seems that JDBC driver or DB think there is 2 parameters, but only 1 parameters are registered? why we cannot use a named parameter multiple times? is JDBC driver don't required to support this case?
How I get an alternative (exeption rewriting PL/SQL block to stored procedure)?
My Oracle JDBC Driver is latest version 11.2.0.3.0.
Because my project have many PL/SQL block, I try my best to avoid rewrite SQL to a stored procedure, running raw PL/SQL block with named parameter (just treat it as a procedure) is preferred. I tested convert the PL/SQL block to a stored procedure, then only 1 parameters exported, but I don't want to rewrite all PL/SQL blocks, it takes more efforts.
thanks for any hints.
My Java code:
CallableStatement stmt = ...;
stmt.registerOutParameter("q", Types.VARCHAR);
stmt.execute();
String v1 = stmt.getString("q");
SQL as below:
BEGIN
select DUMMY into :q from dual where dummy = 'X';
select DUMMY into :q from dual where dummy = 'X';
EXCEPTION
WHEN NO_DATA_FOUND THEN
NULL;
END;
I just found that "Execute Immediate" can be used for my case, when the dynamic SQL is a PL/SQL block (quoted with begin and end), then the named parameters with a name can be referenced by all occurrences. i.e. SQL attached below, in this block, parameter 'q' is used only once,
but now I have another 2 questions,
Q1: I don't know if the parameter 'q' is IN, OUT or both IN and OUT. If I give wrong IN/OUT mode, error got, how we test if the parameter is IN/OUT or both of them? I want to scan the SQL for ':q :=' and 'into :q', it seems that it is not good method.
Q2: Why I can't fetch result of parameter 'q' when it is assigned IN OUT mode? only if it is OUT, I can get its value. when it is both IN OUT, I get NULL.
begin
execute immediate 'begin select dummy into :q from dual where :q is not null; end;'
using in out :q;
end;
Oh, I get a workaround for NULL when parameter is IN OUT mode, I just treat it is a bug of Oracle JDBC driver, I split IN/OUT role of the named parameter 'q' into 2 parts, first is IN, second is OUT, using a variable to keep its value returned by 'using in out :q' clause, and then assign variable to 2nd role, like below-attached, in JDBC we treat it both IN OUT, only use exact IN,OUT or IN OUT in USING clause after scanning ' :q := ' and ' into :q '.
declare
p varchar2(100);
q varchar2(100);
begin
p := ?;
q := ?;
execute immediate 'begin if :p is null then :p := ''X''; else :p := ''Y''; :q := ''Z''; end if; end;' using in out p, out q;
? := p;
? := q;
end;
You can't use one bind parameter multiple times in SQL statement. You must provide a value for each occurrence of parameter. This is because Oracle ignores bind parameter name and only a colon symbol is taken into account.
Oracle docs
I have a hefty SQL statement with unions where code keeps getting re-used. I was hoping to find out if there is a way to re-use a single bind variable without repeating the variable to for "USING" multiple times.
The code below returns "not all variables bound" until I change the "USING" line to "USING VAR1,VAR2,VAR1;"
I was hoping to avoid that as I'm referring to :1 in both instances - any ideas?
declare
var1 number :=1;
var2 number :=2;
begin
execute immediate '
select * from user_objects
where
rownum = :1
OR rownum = :2
OR rownum = :1 '
using var1,var2;
end;
/
EDIT: For additional info, I am using dynamic SQL as I also generate a bundle of where conditions.
I'm not great with SQL arrays (I am using a cursor in my code but I think that will overcomplicate the issue) but the pseudocode is:
v_where varchar2(100) :='';
FOR i in ('CAT','HAT','MAT') LOOP
v_where := v_where || ' OR OBJECT_NAME LIKE ''%' || i.string ||'%''
END;
v_where := ltrim(v_where, ' OR');
And then modifying the SQL above to something like :
execute immediate '
select * from user_objects
where
rownum = :1
OR rownum = :2
OR rownum = :1 AND ('||V_WHERE||')'
using var1,var2;
There are some options you might consider, although they may require changes, either to how you execute your SQL statement or to your SQL statement itself.
Use DBMS_SQL instead of EXECUTE IMMEDIATE -- DBMS_SQL (see http://docs.oracle.com/cd/B19306_01/appdev.102/b14258/d_sql.htm) is harder to use than EXECUTE IMMEDIATE, but gives you more control over the process -- including the ability (through DBMS_SQL.BIND_VARIABLE and DBMS_SQL.BIND_ARRAY) to bind by name instead of by position.
Use EXECUTE IMMEDIATE with a WITH clause -- You might be able restructure your query to use WITH clause that gathers your bind variables in subquery at the beginning, and then joins to the subquery (instead of referencing the bind variables directly) whenever it needs them. It might look something like this
with your_parameters as
(select :1 as p1, :2 as p2 from dual)
select *
from your_table, your_parameters
where your_table.some_column1 = your_parameters.p1
and your_table.some_column2 <= your_parameters.p1
and your_table.some_column3 = your_parameters.p2
This could affect the performance of your query, but it might be an acceptable compromise.
Don't use dynamic SQL -- Of course, if you don't need dynamic SQL, you don't need to use EXECUTE IMMEDIATE, so the "bind only by position" limitiation does not apply. Are you sure you really need to use dynamic SQL?
EDIT: If you're using dynamic SQL because you have a variable number of OR conditions like you posted in your edit, you might be able to avoid using dynamic SQL by doing one of the following:
If the OR criteria come from a table (or query) -- Join to that table (or query) instead of using a list of OR criteria. For example, if CAT, HAT, and MAT are listed in a column named YOUR_CRITERIA in a table named YOUR_CRITERIA_TABLE you might add YOUR_CRITERIA_TABLE to the FROM clause and replace the OBJECT_NAME LIKE '%CAT% OR OBJECT_NAME LIKE '%MAT% OR OBJECT_NAME LIKE '%HAT% OR OBJECT_NAME LIKE '%MAT% in the WHERE clause with something like OBJECT_NAME LIKE '%' || YOUR_CRITERIA_TABLE.YOUR_CRITERIA || '%'.
Otherwise, you might put the criteria in a global temporary table -- If your criteria don't come from a table (or query), you could (once, at design time, not at run time) create a global temporary table to hold them, and then at run time, insert the criteria into the global temporary table and then join to it as described in item 1.
Or, you might put the criteria in an nested table -- This is like item 2, except uses a nested table (one created using CREATE TYPE...IS TABLE OF) instead of a global temporary table. You could create or own nested table type, or use a built-in one like SYS.ODCIVARCHAR2LIST. In PL/SQL, you would populate an variable of this type, and then use it like a "real" table like in item 1.
An example of item 3 might look something like:
DECLARE
tblCriteria SYS.ODCIVARCHAR2LIST;
BEGIN
tblCriteria := SYS.ODCIVARCHAR2LIST();
-- In "real" code you might populate the nested table in a loop.
-- This example populates it explicitly so that it will compile. For the
-- purpose of the example, we could have populated the nested table in
-- a single statement:
-- tblCriteria := SYS.ODCIVARCHAR2LIST('CAT', 'HAT', 'MAT');
tblCriteria.EXTEND(1);
tblCriteria(tblCriteria.LAST) := 'CAT';
tblCriteria.EXTEND(1);
tblCriteria(tblCriteria.LAST) := 'HAT';
tblCriteria.EXTEND(1);
tblCriteria(tblCriteria.LAST) := 'MAT';
FOR rec IN
(
SELECT
USER_OBJECTS.*
FROM
USER_OBJECTS,
TABLE(tblCriteria) YOUR_NESTED_TABLE
WHERE
USER_OBJECTS.OBJECT_NAME LIKE '%' || YOUR_NESTED_TABLE.COLUMN_VALUE || '%'
)
LOOP
-- Do something. For example, print out the object name.
DBMS_OUTPUT.PUT_LINE(rec.OBJECT_NAME);
END LOOP;
END;
No, unfortunately, the bind variables for EXECUTE IMMEDIATE must be provided in the same order they appear in the statement, and the bind variable names are ignored. So you'll just have to have :1, :2 and :3 in your statement.