DDL statements in PL/SQL? - oracle

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

Related

How to use this execute immediate create table to insert values in table

I am using execute immediate to create table and use this table into my code to insert some values when i run this code .it gives me error that table and view does not exist.why?. this code has ability to create table because when i use drop and create table command with existing table by using execute immediate then my code works fine and when i just remove drop table syntax, my code does not work, Please help me to clear my concept about dynamic SQL-----------
SET serveroutput ON
DECLARE
ddl_table VARCHAR2(200);
r_emp SYS.ODCINUMBERLIST := SYS.ODCINUMBERLIST();
v_array SYS.ODCIVARCHAR2LIST := SYS.ODCIVARCHAR2LIST('ACCT_ID',
'PARENT_ACCT_ID',
'CUST_ID',
'ACCT_NAME',
'BILLING_CYCLE_TYPE',
'PAID_FLAG',
'BILL_DELIVER_METHOD');
BEGIN
ddl_table := 'CREATE TABLE test123(
v_column VARCHAR2(50),
v_count NUMBER
)';
EXECUTE IMMEDIATE ddl_table;
DBMS_OUTPUT.ENABLE;
FOR i IN 1 .. v_array.COUNT LOOP
r_emp.EXTEND;
EXECUTE IMMEDIATE 'SELECT COUNT(*)
FROM account_profile
WHERE NOT REGEXP_LIKE(' ||v_array(i) || ',''[A-Za-z0-9.]'')'
INTO r_emp(i);
IF r_emp(i) <> 0 THEN
DBMS_OUTPUT.PUT_LINE(v_array(i) || ': ' || r_emp(i));
INSERT INTO test123 VALUES (v_array(i), r_emp(i));
END IF;
END LOOP;
END;
/
Error report -
ORA-06550: line 24, column 17:
PL/SQL: ORA-00942: table or view does not exist
Your problem is that the annonymous block is pre-validated (pre-compiled) before it is valid. Oracle will check all objects in use before executing it. Since you are creating test123 dynamically it doesn't exist so your insert statement fails.
You can instead, use an EXECUTE IMMEDIATE command to also insert the data on your test123 table.
The way you use the EXECUTE IMMEDIATE for an insert command is either concatenating the parameters or preparing them, I prefer the later. Like this
execute immediate 'insert into test123 values (:param1,:param2)' using v_array(i), r_emp(i);
Here is the official documentation for the EXECUTE IMMEDIATE command.
Though it shows how it works and explain the usage of it, it doesn't particularly answer you direct question on the comments.
So
can you explain :param1,:param2
Those are called "binding" parameters that will be replaced by the variables used after the using statement. It doesn't matter their name only the order in which they appear on the string. The number of parameters within the dynamic string needs to match the number of parameters after the using statement.
why we use it with : colon and what are these
The colon : there is just to make it easier for the parser to know what to replace and where when using the variables you provided after the using statement
Translating that execute immediate it would be something like this:
... values (:param1,:param2)' using v_array(i), r_emp(i)
Hey "execute immediate" whenever you see :param1 please replace it with the content of the variable I'm providing as v_array(i) and whenever you see :param2 please replace it with the content of the variable I'm providing as r_emp(i)

Trying to create a DML file of the owner inserts Oracle

I am trying to create a DML file that contains all the inserts to a database using only a script and asking only for the owner name, I found some documentation about the creation of files in Oracle and some other about how to get the insert statements.
This is the query that gets the inserts
SELECT /*insert*/ * FROM ALL_TAB_COLUMNS WHERE OWNER = 'OwnerName';
And this is what I`m trying to do in order to create the file with the selected rows from the query
DECLARE
F1 UTL_FILE.FILE_TYPE;
CURSOR C_TABLAS IS
SELECT /*insert*/ * FROM ALL_TAB_COLUMNS WHERE OWNER = 'BETA';
V_INSERT VARCHAR2(32767);
BEGIN
OPEN C_TABLAS;
LOOP
FETCH C_TABLAS INTO V_INSERT;
EXIT WHEN C_TABLAS%NOTFOUND;
F1 := UTL_FILE.FOPEN('D:\Desktop\CENFOTEC\4 Cuatrimestre\Programación de Bases de Datos\Proyecto\FileTests','TestUno.dml','W');
UTL_FILE.PUT_LINE(F1, V_INSERT);
UTL_FILE.FCLOSE (F1);
END LOOP;
CLOSE C_TABLAS;
END;
I'm having trouble with the fetch, I'm getting this error: wrong number of values in the INTO list of a FETCH statement
I know that it is a basic one, but I can't figure out how many columns I am getting from the query above
Although I'm trying this way i wouldn't mind changing it, I need to create a DML file of all the inserts needed to replicate the database of the given user. Thanks a lot
In SQL Developer, when you use:
SELECT /*insert*/ * FROM ALL_TAB_COLUMNS WHERE OWNER = 'OwnerName';
Then the /*insert*/ hint is processed by SQL Developer on the client-side and converts the returned result set into DML statements.
To quote #ThatJeffSmith in his answer where he gave the above solution:
here is a SQL Developer-specific solution
That behaviour is specific to the SQL Developer client application.
In the Oracle database, when you use:
SELECT /*insert*/ * FROM ALL_TAB_COLUMNS WHERE OWNER = 'OwnerName';
Then /*insert*/ is an inline comment and it is IGNORED and has zero effect on the output of the query.
Therefore, when you do:
DECLARE
F1 UTL_FILE.FILE_TYPE;
CURSOR C_TABLAS IS
SELECT /*insert*/ * FROM ALL_TAB_COLUMNS WHERE OWNER = 'BETA';
V_INSERT VARCHAR2(32767);
BEGIN
OPEN C_TABLAS;
LOOP
FETCH C_TABLAS INTO V_INSERT;
EXIT WHEN C_TABLAS%NOTFOUND;
F1 := UTL_FILE.FOPEN('D:\Desktop\CENFOTEC\4 Cuatrimestre\Programación de Bases de Datos\Proyecto\FileTests','TestUno.dml','W');
UTL_FILE.PUT_LINE(F1, V_INSERT);
UTL_FILE.FCLOSE (F1);
END LOOP;
CLOSE C_TABLAS;
END;
/
The PL/SQL anonymous block will be processed by the database's PL/SQL engine on the server-side and it will context-switch and pass the cursor's SQL to the database's SQL engine where it will be run and the /*insert*/ comment is ignored and it will return all the columns.
I can't figure out how many columns I am getting from the query above.
One column for every column in the ALL_TABS_COLUMNS table. You can use:
SELECT * FROM all_tabs_columns FETCH FIRST ROW ONLY
And then count the columns. I made it 37 columns (but might have miscounted).
However
Trying to generate INSERT statements that correspond to all the rows in the ALL_TAB_COLUMNS table so that you can recreate the database is WRONG. You need to generate the DDL statements for each table and not generate DML statements to try to modify a data dictionary table (which, likely as not, if you try to modify data dictionary tables will leave your database in an unusable state).
If you want to recreate the database then use the answers in this question or backup the database and then restore it to the new database.

Testing native dynamic SQL in Oracle before execution

I have implemented a certain feature in my application where the user can compose queries dynamically from the user interface by pushing around buttons and inserting some values here and there.
The user will not see the generated SQL statement at all.
I was wondering if there is a way to perhaps check the syntax and grammar (e.g. he opened a parantheses '(' and forgot to close it ) of the dynamically generated SQL to ensure that no run-time compilation errors would happen before actually executing the statement using EXECUTE IMMEDIATE.
You could use the dbms_sql.parse procedure to parse the SQL statement assuming the statement is DML not DDL. It would be rather unusual to parse a dynamic SQL statement using the dbms_sql package and then use EXECUTE IMMEDIATE to execute it rather than using dbms_sql.execute but nothing prevents you from mixing dbms_sql and execute immediate.
The code to just parse the SQL statement would be something like
DECLARE
l_cursor integer;
l_sql_stmt varchar2(1000) := <<some SQL statement>>;
BEGIN
l_cursor := dbms_sql.open_cursor;
dbms_sql.parse( l_cursor, l_sql_stmt, dbms_sql.native );
dbms_sql.close_cursor( l_cursor );
END;
You can also use explain plan, this is basically doing the first step of the execution.
SQL> explain plan for select * from dual;
Explained.
SQL is valid, you can also use the explain tables to get the tables, views etc, maybe even estimated run time ...
SQL> explain plan for select * from duall;
explain plan for select * from duall
*
ERROR at line 1:
ORA-00942: table or view does not exist
SQL is invalid, this is why ...
you can also use it in a dynamic statement
SQL> begin execute immediate 'explain plan for ' || ' select * from dual'; end;
2 /
PL/SQL procedure successfully completed.
As always use error-handling, e.g. write a little error log creater package or procedure what u call when it's needed.
DECLARE
my_stmt VARCHAR2(4000):='this will contain my statements';
BEGIN
'begin' || my_stmt || 'end';
EXCEPTION
WHEN OTHERS THEN
prc_my_error_log_creator();
END;
in the log_creator use for example the DBMS_UTILITY.FORMAT_ERROR_BACKTRACE() func to get the stack's contain.
Regards

How to create a table and insert in the same statement

I'm new to Oracle and I'm struggling with this:
DECLARE
cnt NUMBER;
BEGIN
SELECT COUNT(*) INTO cnt FROM all_tables WHERE table_name like 'Newtable';
IF(cnt=0) THEN
EXECUTE IMMEDIATE 'CREATE TABLE Newtable ....etc';
END IF;
COMMIT;
SELECT COUNT(*) INTO cnt FROM Newtable where id='something'
IF (cnt=0) THEN
EXECUTE IMMEDIATE 'INSERT INTO Newtable ....etc';
END IF;
END;
This keeps crashing and gives me the "PL/SQL: ORA-00942:table or view does not exist" on the insert-line. How can I avoid this? Or what am I doing wrong? I want these two statements (in reality it's a lot more of course) in a single transaction.
It isn't the insert that is the problem, it's the select two lines before. You have three statements within the block, not two. You're selecting from the same new table that doesn't exist yet. You've avoided that in the insert by making that dynamic, but you need to do the same for the select:
EXECUTE IMMEDIATE q'[SELECT COUNT(*) FROM Newtable where id='something']'
INTO cnt;
SQL Fiddle.
Creating a table at runtime seems wrong though. You said 'for safety issues the table can only exist if it's filled with the correct dataset', which doesn't entirely make sense to me - even if this block is creating and populating it in one go, anything that relies on it will fail or be invalidated until this runs. If this is part of the schema creation then making it dynamic doesn't seem to add much. You also said you wanted both to happen in one transaction, but the DDL will do an implicit commit, you can't roll back DDL, and your manual commit will start a new transaction for the insert(s) anyway. Perhaps you mean the inserts shouldn't happen if the table creation fails - but they would fail anyway, whether they're in the same block or not. It seems a bit odd, anyway.
Also, using all_tables for the check could still cause this to behave oddly. If that table exists in another schema, you create will be skipped, but you select and insert might still fail as they might not be able to see, or won't look for, the other schema version. Using user_tables or adding an owner check might be a bit safer.
Try the following approach, i.e. create and insert are in two different blocks
DECLARE
cnt NUMBER;
BEGIN
SELECT COUNT (*)
INTO cnt
FROM all_tables
WHERE table_name LIKE 'Newtable';
IF (cnt = 0)
THEN
EXECUTE IMMEDIATE 'CREATE TABLE Newtable(c1 varchar2(256))';
END IF;
END;
DECLARE
cnt2 NUMBER;
BEGIN
SELECT COUNT (*)
INTO cnt2
FROM newtable
WHERE c1 = 'jack';
IF (cnt2 = 0)
THEN
EXECUTE IMMEDIATE 'INSERT INTO Newtable values(''jill'')';
END IF;
END;
Oracle handles the execution of a block in two steps:
First it parses the block and compiles it in an internal representation (so called "P code")
It then runs the P code (it may be interpreted or compiled to machine code, depending on your architecture and Oracle version)
For compiling the code, Oracle must know the names (and the schema!) of the referenced tables. Your table doesn't exist yet, hence there is no schema and the code does not compile.
To your intention to create the tables in one big transaction: This will not work. Oracle always implicitly commits the current transaction before and after a DDL statement (create table, alter table, truncate table(!) etc.). So after each create table, Oracle will commit the current transaction and starts a new one.

Creating table before creating a cursor in Oracle

I have a PL/SQL procedure which creates a temporary table and then extracts the data from this temporary table using cursors, processes the data and then drops the temporary table. However Oracle doesn't allow the usage of cursor if the table doesn't exist in the database.
Please help me handle this.
Your statement is not quite correct. You can use a cursor for pretty much arbitrary queries. See below:
create or replace procedure fooproc
IS
type acursor is ref cursor;
mycur acursor;
mydate date;
BEGIN
execute immediate 'create global temporary table footmp (bar date) on commit delete rows';
execute immediate 'insert into footmp values (SYSDATE)';
open mycur for 'select * from footmp';
loop
fetch mycur into mydate;
exit when mycur%notfound;
dbms_output.put_line(mydate);
end loop;
close mycur;
execute immediate 'drop table footmp';
END fooproc;
/
(More details here - especially this short proc is not safe at all since the table name is fixed and not session-dependent).
It is (quite) a bit ugly, and I'm not suggesting you use that - rather, you should be thinking whether you need that procedure-specific temporary table at all.
See this other article:
DO NOT dynamically create them [temp tables], DO NOT dynamically create them, please -- do NOT dynamically create them.
Couldn't you use a global temporary table? Do you actually need a temporary table at all? (i.e. doesn't using a cursor on the select statement you'd use to fill that table work?)
Or, if you wish to avoid differences between global temporary tables and "regular" permanent tables you may be used to (see Oracle docs on temp table data availability, lifetime etc), simply create the table first (nologging). Assuming nobody else is using this table, your procedure could truncate before/after your processing.

Resources