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.
Related
I recently started working on a number of large Oracle PL/SQL stored procedures with Toad for Oracle. Number of these procedures updates and inserts stuff into tables. My question is, is there a way to "safely" execute PL/SQL procedures without permanently modifying any of the tables ? Also, how do I safely modify and execute stored procedures for experimentation without actually making changes to the database ?
Doesn't matter if you have Toad or SQ*Plus or anything really - it's all about the code.
First - does your program have any commits or rollbacks IN the stored procedures?
Second - does your program do any DDL work: create a table? That will do an implicit COMMIT. Mind you, if your program calls another program and THAT program has a COMMIT or DDL - you're COMMITTED as its' all in one session.
Third - when you go to execute your stored procedure, does your anonymous block have a COMMIT or ROLLBACK there?
Your tool comes into play for the third bit. Inspect the code behind the 'execute' button.
In SQL Developer (similar to Toad in this regard)...
In this case my SP has a commit in the code - so barring an exception before that line...it's a permanent change.
In the generated anonymous block, there's a ROLLBACK, but it's commented out. When you hit the execute button in your GUI, look at the code there. Change it if necessary.
You can create a copy of your database, then play there. Other thing is, you can create a copy of the procedures/functions, packages and tables involve and play with it.
Let's you have this procedure,
CREATE PROCEDURE proc1
IS
BEGIN
INSERT INTO table1
(col1, col2)
VALUES
('actual data', 'hello');
UPDATE table2
SET col1 = 'actual'
WHERE col2 = 1;
COMMIT;
END;
You will create new procedure with same logic inside it.
CREATE PROCEDURE proc1_test
IS
BEGIN
INSERT INTO table1_test
(col1, col2)
VALUES ('test', 'hello');
UPDATE table2_test
SET col1 = 'test2'
WHERE col2 = 1;
COMMIT;
END;
/
Doing this, it will let you compare your actual data to your test data.
You can put rollback at the end of the procedure and comment any Commits/DDL statements. Also you need to be careful for the Pragma statements if any.
I have the function in which I need to drop and create tables. In the example below I try to create the table but it fails
CREATE OR REPLACE FUNCTION DEVTEST
RETURN NUMBER
IS
COMMAND VARCHAR2(256);
ID VARCHAR2(128);
NAME VARCHAR2(128);
TMP_LIST VARCHAR2(128);
BEGIN
ID := '12345';
NAME := 'ABCdef';
TMP_LIST := 'tmpTest';
command := 'create table ' || TMP_LIST || ' ( USER_ID VARCHAR2(11), USER_NAME VARCHAR2(36))';
DBMS_OUTPUT.PUT_LINE('command = ' || command);
EXECUTE IMMEDIATE command;
return 0;
END;
I call the function:
select NSB_DEVTEST() from dual
And get the error:
ORA-14552: cannot perform a DDL, commit or rollback inside a query or DML ORA-06512: at "DEV1_SERVER.DEVTEST", line 15
How do I correct this to create/drop a table inside a function?
My server details:
Oracle Database 10g Enterprise Edition Release 10.2.0.5.0 - 64bi
PL/SQL Release 10.2.0.5.0 - Production
CORE 10.2.0.5.0 Production
TNS for Solaris: Version 10.2.0.5.0 - Production
NLSRTL Version 10.2.0.5.0 - Production
The problem is not with the function but with it being called from a SQL statement rather than from a pl/sql block.
A SELECT statement in SQL is equivalent to a READ operation that comes with read consistency. It cannot make any changes to the database. The database should always be the same before and after the "READ" operation completed, otherwise it would be a WRITE operation and the entire database consistency would go havoc.
Also, like the error says, DDL operations do a COMMIT behind the scenes before they start. Any read consistent operation should never do any COMMITS and write to the database without the user knowing.
You can instead call the function from pl/sql like this -
DECLARE
l_result NUBMER;
BEGIN
l_result := DEVTEST;
DBMS_OUTPUT.PUT_LINE(l_number);
END;
Still I would prefer writing a procedure for this, so others don't get confused by why this can't be called from SQL. The general rule that I follow for myself is that - Functions "get" things and Procedures "do" things (like DML).
The answer to your question is: don't. Production code, on the whole, shouldn't be creating tables on the fly. If you need a table to hold data temporarily, then create a Global Temporary Table (GTT) once and have your code refer to it.
The reason why you're getting that error (apart from it being self-evident from the error message) is because you're calling the function from within a SQL statement. You can't do that; you'd have to call it directly in PL/SQL.
I'm curious as to why you think this approach is a good, feasible approach, and also what you're going to be doing with the table once you've created it.
Your code is perfect no problem in the code . the problem is while you try to execute
you can only execute a pure function in select statement, which means a function without ddl & dml . ( if you use pragma autonomous_transaction while performing dml inside a function then you can use it in select statement ). When function has DDL command you can never ever execute it in select statement , but instead you can only execute it in PLSQL block like this
declare
a number;
begin
a:= devtest;
end;
/
and you can check your table
select * from tmptest;
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.
I'm trying to execute two DML statements in DB2 using JDBC and I keep getting a syntax error:
ALTER TABLE "TEST" ALTER COLUMN "COL1" SET DATA TYPE INT;
ALTER TABLE "TEST" ALTER COLUMN "COL1" SET NOT NULL;
If I execute them one by one everything is OK.
I've tried with no semicolons, only one semicolon on the first statement. I also tried with several \r\n and I really can't understand what the problem is.
The exception is the following:
Caused by: com.ibm.db2.jcc.am.SqlSyntaxErrorException: DB2 SQL Error: SQLCODE=-104, SQLSTATE=42601, SQLERRMC=;
ALTER TABLE "TEST";1" SET DATA TYPE INT;<space>, DRIVER=3.65.77
Does anyone know what might be the problem?
Obviously, the execute() method accepts only a single statement, not multiple statements delimited by whatever you might try. It's clearly stated in the API documentation: "Executes the given SQL statement".
You can wrap multiple statements into a (single) compound statement:
begin execute immediate 'alter ...'; execute immediate 'alter ...'; end
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