Oracle: What does 'execute immediate' mean? - oracle

What is the significance of execute immediate in PL/SQL when used with DML and DDL statements? Does execute immediate implicitly commit to a database like DDL statements do ?
The following is an example I have encountered:
SELECT 'TRUNCATE TABLE BD_BIDS_APPR_DET' INTO V_SQL1 FROM DUAL;
EXECUTE IMMEDIATE V_SQL1;
SELECT 'TRUNCATE TABLE BD_BIDS_SYS_DET' INTO V_SQL1 FROM DUAL;
EXECUTE IMMEDIATE V_SQL1;
SELECT 'TRUNCATE TABLE BD_BIDS_EXT_DET' INTO V_SQL1 FROM DUAL;
EXECUTE IMMEDIATE V_SQL1;

It is for running native dynamic SQL.
For DML you'd use it when running statements that you don't have available at compile time, e.g. if the column list is based on a selection from the user.
In your case it's being used because DDL cannot be run as static SQL from within PL/SQL. Only certain query, DML and TCL commands are valid. Anything else has to be treated as dynamic.
I'd say it's rare to need to use DDL from a PL/SQL block. TRUNCATE might be reasonable; if you find anything creating or dropping objects on the fly then that might be more of a concern as it can suggest a suboptimal data model.
EXECUTE IMMEDIATE itself does not automatically commit; but if you execute DDL then that will behave the same as if you ran it outside PL/SQL, so it will commit in your case, yes.
Incidentally, I'm not sure why your code is using an intermediate variable to hold the statement; that's useful if you want to display what it's going to run maybe, but you don't seem to be doing that. What you have could be done as:
EXECUTE IMMEDIATE 'TRUNCATE TABLE BD_BIDS_EXT_DET';
i.e. without using V_SQL_1 at all.

It provides end-to-end support when executing a dynamic SQL statement or an anonymous PL/SQL block. It substitutes the unknown values, executes the statement, returns the data, and then releases the resources.
EXECUTE IMMEDIATE [dynamic SQL string statement without terminator]
[INTO {define_variable [, define_variable] ... | record}]
[USING [IN|OUT|IN OUT] bind_argument [, [IN|OUT|IN OUT] bind_arguments] ];
define_variable is a PL/SQL variable or record that captures the result set of the SELECT query
Bind arguments are the actual values or local variables which would replace the bind variables in the SQL string statement.

Related

Is SQL statement in EXECUTE IMMEDIATE parsed at run time?

Considering below procedure. For processing, it has to go through all the processing phases anyway. So my question is: Is it at this parse phase 'Select job_id from asfd' still treated as string and while the execution phase of EXECUTE IMMEDIATE the statement 'Select job_id from asfd' is parsed again now as SQL statement?
CREATE OR REPLACE PROCEDURE test_ei
IS
BEGIN
EXECUTE IMMEDIATE ‘select job_id from asfd’; //line 3
END;
One explanation I found in here is the asdf table does not exist but gut successfully compiled, so 'Select job_id from asfd' is still treated as string at this stage. Any corrections please.
As matter of fact even when EXECUTE IMMEDIATE 'select * asfd'; is replace in line 3 it still complies successfully. So I guess above explanation is correct.
Yes. At EXECUTE IMMEDIATE, the runtime engine must first parse the statement.And i.e the reason why you cannot bind in the names of schema elements (tables, columns, etc.)
More details:
The parse phase is what guarantees that the SQL statement is properly defined. PL/SQL can tell that the following statement is valid without having to know the value of :xyz.
'UPDATE emp SET sal = :xyz'
But how can PL/SQL know if the following statement is well formed? We can't, and so the restriction.
'UPDATE emp SET :col_name = :xyz'

Dynamic Procedure Execution

Hi I have to execute a remote call ...
DBMS_STATS.FLUSH_DATABASE_MONITORING_INFO#DB_LINK()
... from a stored procedure.
As the value of DB_LINK is to be obtained during runtime. It should be executed dynamically. I tried to use
EXECUTE IMMEDIATE ' DBMS_STATS.FLUSH_DATABASE_MONITORING_INFO#'||DB_LINK||'()';
But I get the following error
ORA-00900: invalid SQL statement
Can anyone advise me on how to execute the Stored Procedure dynamically?
The clue lies in the precise wording of the error message: invalid SQL statement. As the PL/SQL manual says, EXECUTE IMMEDIATE is for executing dynamic SQL statements.
You are executing a procedure call i.e. PL/SQL not SQL. So you need to pass an anonymous block to EXECUTE IMMEDIATE:
EXECUTE IMMEDIATE
'begin DBMS_STATS.FLUSH_DATABASE_MONITORING_INFO#'||DB_LINK||'() ; end;'
;
DBMS_STATS.FLUSH_DATABASE_MONITORING_INFO takes no parameters, so the empty brackets are optional. By all means include them if they make you feel happier.
EXECUTE IMMEDIATE 'DBMS_STATS.FLUSH_DATABASE_MONITORING_INFO#'||DB_LINK();
should work if DB_LINK is a global function returning a VARCHAR2 string.

Why cannot I use bind variables in DDL/SCL statements in dynamic SQL?

I am trying to execute an SQL command within dynamic SQL with bind variables:
-- this procedure is a part of PL/SQL package Test_Pkg
PROCEDURE Set_Nls_Calendar(calendar_ IN VARCHAR2)
IS
BEGIN
EXECUTE IMMEDIATE
'ALTER SESSION
SET NLS_CALENDAR = :cal'
USING IN calendar_;
END Set_Nls_Calendar;
Then on the client side, I am trying to invoke the procedure:
Test_Pkg.Set_Nls_Calendar('Thai Buddha');
But this get's me ORA-02248: invalid option for ALTER SESSION.
And my question is: Why cannot I use bind variables in DDL/SCL statements in dynamic SQL?
Bind variables are not allowed in DDL statements. So following statements will cause errors:
Example #1: DDL statement. Will cause ORA-01027: bind variables not allowed for data definition operations
EXECUTE IMMEDIATE
'CREATE TABLE dummy_table ( dummy_column NUMBER DEFAULT :def_val )'
USING 42;
Example #2: DDL statement. Will cause ORA-00904: : invalid identifier
EXECUTE IMMEDIATE
'CREATE TABLE dummy_table ( :col_name NUMBER )'
USING var_col_name;
Example #3: SCL statement. Will cause ORA-02248: invalid option for ALTER SESSION
EXECUTE IMMEDIATE
'ALTER SESSION SET NLS_CALENDAR = :cal'
USING var_calendar_option;
Problem
To understand why this happens, we need to look at How Dynamic SQL Statements Are Processed.
Typically, an application program prompts the user for the text of a SQL statement and the values of host variables used in the statement. Then Oracle parses the SQL statement. That is, Oracle examines the SQL statement to make sure it follows syntax rules and refers to valid database objects. Parsing also involves checking database access rights1, reserving needed resources, and finding the optimal access path.
1 Emphasis added by answerer
Note that parsing step happens before binding any variables to the dynamic statement. If you examine the above four examples, you will realize that there is no way for the parser to guarantee the syntactical validity of these dynamic SQL statements without knowing the values for bind variables.
Example #1: Parser cannot tell if the bind value will be valid. What if instead of USING 42, programmer wrote USING 'forty-two'?
Example #2: Parser cannot tell if :col_name would be a valid column name. What if the bound column name was 'identifier_that_well_exceeds_thirty_character_identifier_limit'?
Example #3: Values for NLS_CALENDAR are built in constants (for a given Oracle version?). Parser cannot tell if the bound variable will have a valid value.
So the answer is that you cannot bind schema elements such as table names, column names in dynamic SQL. Nor you can bind built in constants.
Solution
The only way to achieve referencing schema elements/constants dynamically is to use string concatenation in dynamic SQL statements.
Example #1:
EXECUTE IMMEDIATE
'CREATE TABLE dummy_table ( dummy_column NUMBER DEFAULT ' || to_char(42) || ')';
Example #2:
EXECUTE IMMEDIATE
'CREATE TABLE dummy_table (' || var_col_name || ' NUMBER )';
Example #3:
EXECUTE IMMEDIATE
'ALTER SESSION SET NLS_CALENDAR = ''' || var_calendar_option || '''';

DDL statements in PL/SQL?

I am trying the code below to create a table in PL/SQL:
DECLARE
V_NAME VARCHAR2(20);
BEGIN
EXECUTE IMMEDIATE 'CREATE TABLE TEMP(NAME VARCHAR(20))';
EXECUTE IMMEDIATE 'INSERT INTO TEMP VALUES(''XYZ'')';
SELECT NAME INTO V_NAME FROM TEMP;
END;
/
The SELECT statement fails with this error:
PL/SQL: ORA-00942: table or view does not exist
Is it possible to CREATE, INSERT and SELECT all in a single PL/SQL Block one after other?
I assume you're doing something like the following:
declare
v_temp varchar2(20);
begin
execute immediate 'create table temp(name varchar(20))';
execute immediate 'insert into temp values(''XYZ'')';
select name into v_name from temp;
end;
At compile time the table, TEMP, does not exist. It hasn't been created yet. As it doesn't exist you can't select from it; you therefore also have to do the SELECT dynamically. There isn't actually any need to do a SELECT in this particular situation though you can use the returning into syntax.
declare
v_temp varchar2(20)
begin
execute immediate 'create table temp(name varchar2(20))';
execute immediate 'insert into temp
values(''XYZ'')
returning name into :1'
returning into v_temp;
end;
However, needing to dynamically create tables is normally an indication of a badly designed schema. It shouldn't really be necessary.
I can recommend René Nyffenegger's post "Why is dynamic SQL bad?" for reasons why you should avoid dynamic SQL, if at all possible, from a performance standpoint. Please also be aware that you are much more open to SQL injection and should use bind variables and DBMS_ASSERT to help guard against it.
If you run the program multiple time you will get an error even after modifying the program to run the select statement as dynamic SQL or using a returning into clause.
Because when you run the program first time it will create the table without any issue but when you run it next time as the table already created first time and you don't have a drop statement it will cause an error: "Table already exists in the Database".
So my suggestion is before creating a table in a pl/sql program always check if there is any table with the same name already exists in the database or not. You can do this check using a Data dictionary views /system tables which store the metadata depending on your database type.
For Example in Oracle you can use following views to decide if a tables needs to be created or not:
DBA_TABLES ,
ALL_TABLES,
USER_TABLES

Truncating a table in a stored procedure

When I run the following in an Oracle shell it works fine
truncate table table_name
But when I try to put it in a stored procedure
CREATE OR REPLACE PROCEDURE test IS
BEGIN
truncate table table_name;
END test;
/
it fails with
ERROR line 3, col 14, ending_line 3, ending_col 18, Found 'table', Expecting: # ROW or ( or . or ; :=
Why?
All DDL statements in Oracle PL/SQL should use Execute Immediate before the statement. Hence you should use:
execute immediate 'truncate table schema.tablename';
As well as execute immediate you can also use
DBMS_UTILITY.EXEC_DDL_STATEMENT('TRUNCATE TABLE tablename;');
The statement fails because the stored proc is executing DDL and some instances of DDL could invalidate the stored proc. By using the execute immediate or exec_ddl approaches the DDL is implemented through unparsed code.
When doing this you neeed to look out for the fact that DDL issues an implicit commit both before and after execution.
try the below code
execute immediate 'truncate table tablename' ;
You should know that it is not possible to directly run a DDL statement like you do for DML from a PL/SQL block because PL/SQL does not support late binding directly it only support compile time binding which is fine for DML. hence to overcome this type of problem oracle has provided a dynamic SQL approach which can be used to execute the DDL statements.The dynamic sql approach is about parsing and binding of sql string at the runtime.
Also you should rememder that DDL statements are by default auto commit hence you should be careful about any of the DDL statement using the dynamic SQL approach incase if you have some DML (which needs to be commited explicitly using TCL) before executing the DDL in the stored proc/function.
You can use any of the following dynamic sql approach to execute a DDL statement from a pl/sql block.
1) Execute immediate
2) DBMS_SQL package
3) DBMS_UTILITY.EXEC_DDL_STATEMENT (parse_string IN VARCHAR2);
Hope this answers your question with explanation.

Resources