I have the following table structure that holds error codes and related error messages:
ERR_CODE ERR_MESSAGE
CN001 Invalid Username :USERNM
CN002 Invalid Password :PWD
In my PLSQL code, i would like to dynamically substitute the values for username and password.
I understand that EXECUTE IMMEDIATE can do substitutions by the USING clause but the query has to be static in that case.
I am looking for a flavor similar to execute immediate wherein can do this:
SIMILAR_EXECUTE_IMMDIATE q'{select ERR_MESSAGE from ERROR_MESSAGES where ERR_CODE = 'CN001'}' INTO l_err_msg USING l_curr_user;
OR maybe i can break it down in to 2 steps:
select ERR_MESSAGE into err_msg_var from ERROR_MESSAGES where ERR_CODE='CN001';
EXECUTE_IMMDIATE err_msg_var INTO l_err_msg USING l_curr_user;
Basically, I am trying to reduce the number of steps involved or maybe get a better performing query or approach.
Thanks in advance !
No, execute immediate statement will be of no help in this situation. Besides there is really no need to use it - everything(select list, table names) is known at compile time, it only comes down to the string substitution. Use static query, not dynamic. In order to do the substitution(or string formatting) you have at least two choices:
Simply use replace() function:
set serveroutput on;
clear screen;
declare
l_result varchar2(50);
begin
select err_message
into l_result
from error_messages
where err_code = 'CN001';
dbms_output.put_line(replace(l_result, ':USERNM', 'new value'));
end;
Result:
Invalid Username new value
If it possible, update err_message column of your error_messages table by replacing :USERNM and :PWD and other similar sub-strings thta denote placeholders with %s(placeholder for a character literal) or %d (placeholder for an integer literal if you have any) and use utl_lms package and specifically format_message() function:
set serveroutput on;
clear screen;
declare
l_result varchar2(50);
begin
select err_message
into l_result
from error_messages
where err_code = 'CN001';
dbms_output.put_line(
utl_lms.format_message(l_result, 'new_value_goes_here')
);
end;
Result:
Invalid Username new_value_goes_here
You can just get the ERR_MESSAGE from your table via SELECT and append userName or PWD to it. Below is a snippet.
select ERR_MESSAGE into err_msg_var from ERROR_MESSAGES where ERR_CODE='CN001';
dbms_output.put_line(err_msg_var||l_curr_user)
You may return value of dbms_output if you want to define this as a function.
Related
Very simple question, but one I can't seem to find an answer to.
I have a variable calculated at the start of an Oracle SQL script (in TOAD, if that makes a difference), and I hoped to reuse it later in an UPDATE statement.
Variable is declared and then set here:
DECLARE
v_months number;
v_check number;
BEGIN
v_check := '1'
SELECT (total_due/monthly_amount) INTO v_months FROM TABLE1 WHERE ...
and will return a numeric value of, say, 20.
and I want to use that figure here:
IF(v_check = 1)
update TABLE2 set paid = 'YES' where sequence between v_months and 48;
This doesn't seem to be possible as the variable is flagged up as an invalid identifier, but is there a way round this?
DECLARE the variable at the start of the PL/SQL block and then just re-use it in the same PL/SQL block and it works:
DECLARE
v_months PLS_INTEGER;
BEGIN
SELECT (total_due/monthly_amount) INTO v_months FROM TABLE1;
update TABLE2 set paid = 'YES' where sequence between v_months and 48;
END;
/
If you are trying to re-use it between different PL/SQL blocks then it will not work using a PL/SQL variable.
db<>fiddle here
If you want to use a variable in multiple statements then you can use bind variables:
VARIABLE v_months NUMBER
BEGIN
SELECT (total_due/monthly_amount) INTO :v_months FROM TABLE1;
END;
/
update TABLE2 set paid = 'YES' where sequence between :v_months and 48;
My test function is this
CREATE OR REPLACE FUNCTION MULTI_VAL
(MYNAME OUT EMP2017.ENAME%TYPE)
RETURN NUMBER AS
MYSAL EMP2017.SAL%TYPE;
BEGIN
SELECT SAL, ENAME INTO MYSAL, MYNAME FROM EMP2017 ;
RETURN MYSAL;
END;
/
When I run it like
variable mynm varchar2(20)
SELECT MULTI_VAL(:mynm) FROM dual;
it gives this error
ERROR at line 1:
ORA-06553: PLS-561: character set mismatch on value for parameter 'MYNAME'
The error you get now indicates a datatype mismatch.
However there is a fundamental problem with your code. We cannot use functions which have OUT parameters in SQL. So once you have fixed the datatype issue you will get this error: ORA-06572: Function MULTI_VAL has out arguments.
You can run it like this:
declare
n varchar2(20);
x number;
begin
x := multi_val(n);
end;
/
Generally, functions with OUT parameters are considered bad practice. The syntax allows them, but the usage is hard to understand. It's better to use a procedure with two OUT parameters (because we can only call the program in PL/SQL anyway) or else have the function return a user-defined type.
CREATE TABLE EMP2017(ENAME VARCHAR2(10),SAL NUMBER);
INSERT INTO EMP2017 VALUES ('SMITH',5000);
INSERT INTO EMP2017 VALUES ('JOHNS',1000);
COMMIT;
CREATE TYPE RET_MULT AS OBJECT
(ENAME VARCHAR2(10),SAL NUMBER);
CREATE TYPE T_RET_MULT AS TABLE OF RET_MULT;
CREATE OR REPLACE FUNCTION MULTI_VAL RETURN T_RET_MULT PIPELINED IS
MYSAL RET_MULT;
BEGIN
FOR I IN(SELECT SAL, ENAME FROM EMP2017) LOOP
MYSAL := RET_MULT(I.ENAME,I.SAL);
PIPE ROW(MYSAL);
END LOOP ;
RETURN ;
END;
SELECT * FROM TABLE(MULTI_VAL());
I think this question can be solved without using pipeline functions. Just like this. All pre required data as described #Sedat.Turan except function. Sorry for copy/past.
CREATE TABLE EMP2017(ENAME VARCHAR2(10),SAL NUMBER);
INSERT INTO EMP2017 VALUES ('SMITH',5000);
INSERT INTO EMP2017 VALUES ('JOHNS',1000);
COMMIT;
CREATE TYPE RET_MULT AS OBJECT
(ENAME VARCHAR2(10),SAL NUMBER);
CREATE TYPE T_RET_MULT AS TABLE OF RET_MULT;
create or replace function MULTI_VAL return T_RET_MULT is
RET_SET T_RET_MULT;
begin
select RET_MULT(ENAME, SAL) bulk collect into RET_SET from EMP2017;
return RET_SET;
end;
In SQL Server we can use this:
DECLARE #variable INT;
SELECT #variable= mycolumn from myTable;
How can I do the same in Oracle? I'm currently attempting the following:
DECLARE COMPID VARCHAR2(20);
SELECT companyid INTO COMPID from app where appid='90' and rownum=1;
Why this is not working?
SELECT INTO
DECLARE
the_variable NUMBER;
BEGIN
SELECT my_column INTO the_variable FROM my_table;
END;
Make sure that the query only returns a single row:
By default, a SELECT INTO statement must return only one row. Otherwise, PL/SQL raises the predefined exception TOO_MANY_ROWS and the values of the variables in the INTO clause are undefined. Make sure your WHERE clause is specific enough to only match one row
If no rows are returned, PL/SQL raises NO_DATA_FOUND. You can guard against this exception by selecting the result of an aggregate function, such as COUNT(*) or AVG(), where practical. These functions are guaranteed to return a single value, even if no rows match the condition.
A SELECT ... BULK COLLECT INTO statement can return multiple rows. You must set up collection variables to hold the results. You can declare associative arrays or nested tables that grow as needed to hold the entire result set.
The implicit cursor SQL and its attributes %NOTFOUND, %FOUND, %ROWCOUNT, and %ISOPEN provide information about the execution of a SELECT INTO statement.
Not entirely sure what you are after but in PL/SQL you would simply
DECLARE
v_variable INTEGER;
BEGIN
SELECT mycolumn
INTO v_variable
FROM myTable;
END;
Ollie.
One Additional point:
When you are converting from tsql to plsql you have to worry about no_data_found exception
DECLARE
v_var NUMBER;
BEGIN
SELECT clmn INTO v_var FROM tbl;
Exception when no_data_found then v_var := null; --what ever handle the exception.
END;
In tsql if no data found then the variable will be null but no exception
ORA-01422: exact fetch returns more than requested number of rows
if you don't specify the exact record by using where condition, you will get the above exception
DECLARE
ID NUMBER;
BEGIN
select eid into id from employee where salary=26500;
DBMS_OUTPUT.PUT_LINE(ID);
END;
For storing a single row output into a variable from the select into query :
declare v_username varchare(20);
SELECT username into v_username FROM users WHERE user_id = '7';
this will store the value of a single record into the variable v_username.
For storing multiple rows output into a variable from the select into query :
you have to use listagg function. listagg concatenate the resultant rows of a coloumn into a single coloumn and also to differentiate them you can use a special symbol.
use the query as below
SELECT listagg(username || ',' ) within group (order by username) into v_username FROM users;
I have a question about "dynamic using clause" in execute immediate statement. I need to set dynamically the "execute immediate statement" and the using clause as well. I don't know the table structure, but I know only the name of the table, and I need to do an operation update on it.
So I wrote a function (through user_tab_columns and user user_constraints tables) to set a variable with the update statement and the bind_variable but now I need to set the using clause with the list of variable.
Example:
CREATE TABLE table1
(
rec1 VARCHAR2(10 BYTE) NULL,
rec2 DATE NULL,
rec3 number(9) not null
);
declare
TYPE cur_type IS REF CURSOR;
cur cur_type;
table_list table1%ROWTYPE;
sqlstring varchar2(400);
begin
OPEN cur FOR sqlstring;
LOOP
FETCH cur INTO table_list;
EXIT WHEN cur%NOTFOUND;
sqlstring:=function1('table1');
-- that returns sqlstring:='update table1 set rec1=:1 , rec2=:2 , rec3=:3 where rec_id=:c4';
execute immediate sqlstring using table_list.rec1, table_list.rec2, table_list.rec3, table_list.rec_id;
END LOOP;
close cur;
end;
I need to implement dynamically the list of variables of the cursor table_list.
"execute immediate sqlstring using table_list.rec1, table_list.rec2, table_list.rec3, table_list.rec_id"
Does anybody know how to solve this problem?
Thanks a lot for your replies.
The problem is that I'm assuming I don't know the table's structure and so the list of variables of the cursor table_list table1%ROWTYPE.
So I can't explicit table_list.rec1, table_list.rec2 ... in the using clause.
If I use only table_list as variable
begin
OPEN cur FOR sqlstring;
LOOP
FETCH cur INTO table_list;
EXIT WHEN cur%NOTFOUND;
sqlstring:=function1('table1');
execute immediate sqlstring using table_list;
END LOOP;
close cur;
I got the error:" 00457 Expressions have to be of SQL types"
http://psoug.org/oraerror/PLS-00457.htm
Error Cause:
An expression of wrong type is in USING or dynamic RETURNING clause. In USING or dynamic RETURNING clause, an expression cannot be of non-SQL types such as BOOLEAN, INDEX TABLE, and record.
I need a way to retrive not only the values but also the list of variables of the cursor table_list first.
But maybe it's impossible and I have to find a work around.
If I will find something interesting I will post.
Thankyou.
Try to replace your execute immediate to full use of dbms_sql.
http://docs.oracle.com/cd/B28359_01/appdev.111/b28419/d_sql.htm#i996891
And usefull for you will be bind_array function from this package.
Use dynamic PL/SQL, unless you can re-factor the original statement and just plug the values into it.
declare
v_string constant varchar2(32767) := 'update test1 set a = :1, b = :2';
v_using_string varchar2(32767);
begin
--Create dynamic using string.
--For example, let's say you want to pass in the values "1" for each NUMBER column.
select listagg(1, ',') within group (order by null)
into v_using_string
from user_tab_columns
where table_name = 'TEST1'
and data_type = 'NUMBER';
--Execute the original dynamic SQL, adding the USING string.
execute immediate '
begin
execute immediate '''||v_string||''' using '||v_using_string||';
end;
';
end;
/
You can either use DBMS_SQL package:
open a cursor using dbms_sql.open_cursor
parse the statement using dbms_sql.parse
bind variables in a loop using dbms_sql.bind_variable
execute the statement using dbms_sql.execute
and finally close the cursor using dbms_sql.close_cursor
Or EXECUTE IMMEDIATE of anonymous PL/SQL block, which performs a dynamically created EXECUTE IMMEDIATE (this approach is not suitable for returning data). See Answer of #JonHeller.
Bear with me a bit
I want to run a select statement and the result should look exactly as if I ran that select myself result should be a datagrid, not a dbms_output?
DECLARE
sql_stmt VARCHAR2(200);
sql_stmt2 VARCHAR2(200);
ids VARCHAR2(200);
BEGIN
ids := 5;
sql_stmt:='select query from query_table where id = :id';
EXECUTE IMMEDIATE sql_stmt using ids into sql_stmt2;
EXECUTE IMMEDIATE sql_stmt2;
END;
but this gives me a
ORA-06502: PL/SQL: numeric or value error
ORA-06512: at line 8
line 8 is
EXECUTE IMMEDIATE sql_stmt2
By my count line 8 is EXECUTE IMMEDIATE sql_stmt using ids into sql_stmt2;.
sql_stmt2 is defined as VARCHAR2(200).
I'd say your query returns a string with more than 200 characters.
1) You don't need dynamic SQL to execute your first statement - all identifiers are known at the time of execution. Static SQL like
select query from query_table where id = p_ds;
where p_id is a parameter is the relevant choice.
2) Line 8 points the expression
EXECUTE IMMEDIATE sql_stmt using ids into sql_stmt2;
which has the wrong syntax, INTO clause has to be the first one:
EXECUTE IMMEDIATE sql_stmt INTO sql_stmt2 USING ids;
See Oracle documentation please:
http://docs.oracle.com/cd/B13789_01/appdev.101/b10807/13_elems017.htm
To get the complete answer in your question please provide the real code and data example in your table.
Can you try
ids number;
or
ids :='5';