Combining Multiple Statements in a Stored Procedure - oracle

I'm new to Oracle, what I'm trying to achieve I could in SQL but I'm having a tough time in doing it in Oracle.
So within a Stored Procedure, I'm trying to truncate a table, then insert values, lastly run a SELECT statement against the table.
Here is what I have but it doesn't work, when I run this script, it runs with no error but it seems it only goes through the first (TRUNCATE) statement and that's it.
I would like it create the Store Procedure (which it does) and then show me the contents of the table from the SELECT statement.
CREATE OR REPLACE procedure MYSTOREDPROCEDURE is
BEGIN
EXECUTE IMMEDIATE 'TRUNCATE TABLE MYTABLE';
INSERT INTO MYTABLE
(COL1,
COL2,
COL3)
SELECT COL1, COL2, COL3 FROM MYOTHERTABLE;
end ;
/
SELECT * FROM MYTABLE
END MYSTOREDPROCEDURE;

For clarification SQL is a language implmemented by many RDBMS including SQL Server, PostgreSQL etc. If you're doing this in Oracle you are using SQL. However, most RDBMS have also added a procedural extension to SQL such as T-SQL (SQL Server), pgPL/SQL (PostgreSQL) and PL/SQL (Oracle). In this case you're attempting to use PL/SQL.
From what you're attempting to do I assume you're used to SQL Server and temporary tables. It is less common and less necessary to use temporary tables in Oracle.
Firstly, EXECUTE IMMEDIATE 'TRUNCATE TABLE MYTABLE';. It should rarely be necessary to perform DDL in a stored procedure in Oracle; it would normally be indicative of a flaw in the data-model. In this case it appears as though you're using an actual table as a temporary table. I wouldn't do this. If you need a temporary table use a global temporary table.
Secondly, SELECT * FROM MYTABLE. You can't do this in PL/SQL. If you need to select some data you have to use SELECT <columns> INTO <variables> FROM .... If you do this it won't show you the contents of the table.
From your description of what you're attempting you only need to do the following:
SELECT COL1, COL2, COL3 FROM MYOTHERTABLE;
There is no need for PL/SQL (stored procedures) at all.

I suspect you intended to execute the SELECT * FROM MYTABLE after the procedure was compiled and executed. The above could be restrucured as:
CREATE OR REPLACE procedure MYSTOREDPROCEDURE is
BEGIN
EXECUTE IMMEDIATE 'TRUNCATE TABLE MYTABLE';
INSERT INTO MYTABLE (COL1, COL2, COL3)
SELECT COL1, COL2, COL3
FROM MYOTHERTABLE;
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('Error in MYSTOREDPROCEDURE : ' || SQLCODE || ' - ' || SQLERRM);
RAISE;
END MYSTOREDPROCEDURE;
/
EXECUTE MYSTOREDPROCEDURE
/
SELECT * FROM MYTABLE
/
Share and enjoy.

My DBA won't let me have TRUNCATE privileges even though truncate removes high water marks compared to delete MYTABLE; Your code will fail without these privileges. Both #Ben and #Bob Jarvis provide sound advice with the missing INTO and nicer form. One addition to Bob Jarvis' answer is that you need to start the command with SET SERVEROUTPUT ON SIZE 100000;. Otherwise, his nicely crafted error message will not be displayed. The serveroutput command is used for DBMS_OUTPUT and only lasts as long as your database session lasts.

Related

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.

Oracle 10g Create a View passing in a parameter

I am have been searching but I have not been able to find a satisfactory solution where I can pass a parameter to a view.
What I am trying to do is to call a view saved in Oracle 10g passing in a date via NHibernate, which sounds simple enough, but I am reading that passing parameters to view is not so. So, I am unless I am being misled, can someone please advise me whether this is possible and how; or should I do this as a Stored Procedure?
CREATE OR REPLACE aView AS VIEW
SELECT col1,
col2,
col3,
FROM someTable
WHERE col4 <= TO_DATE('somePassInDate', 'dd/mm/yyyy');
The above is the sort of query I want to run. I don't hibernate to create this query.
Thanks
There is a problem with pipelined-table-functions as they are
inefficient and could take a while to return any rows. This query is
part of a housekeeping query where every night it will return
thousands of rows. This is why I have taken it away from nhibernate as
it is inefficient
This is wrong. Pipelined functions are more efficient than table functions. The reason being:
Pipelining allows rows to be passed out of table functions as they are
produced, rather than waiting for whole collections to be produced
before the results are returned. The outcome is a reduction in the
time taken for the first rows to be produced and a reduction in the
total amount of memory consumed by the table function.
You can read an elaborative example here Pipelined Functions
However not sure what is your need, but you can create a dynamic block or Procedure to get your requirement. On execution of the below block it would prompt for a input date.
DECLARE
v_sql VARCHAR2 (1000);
v_date DATE := :v_date;
BEGIN
v_sql := q'[CREATE OR REPLACE aView AS VIEW
SELECT col1,
col2,
col3,
FROM someTable
WHERE col4 <= TO_DATE(':somePassInDate', 'dd/mm/yyyy')]';
EXECUTE IMMEDIATE v_sql USING v_date;
END;
The only way how to "customize" output from a via is to use system context. It is something like a global variable in a SQL engine.
https://docs.oracle.com/cd/B19306_01/server.102/b14200/statements_5002.htm
CREATE OR REPLACE aView AS VIEW
SELECT col1,
col2,
col3,
FROM someTable
WHERE col4 <= TO_DATE(SYS_CONTEXT ('MY_CONTEXT', 'MY_CTX_VARIABLE') , 'dd/mm/yyyy');
Where you will find HOWTO set value for a context variable:
https://dba.stackexchange.com/questions/114252/oracle-how-do-i-set-a-context-variable
IMHO it is not worth of trying, you will need privilege granted "CREATE ANY SYSTEM CONTEXT". If you use hibernate, you should not combine it with advanced Oracle stuff.

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.

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

Dropping multiple columns: PLSQL and user_tab_cols

I have a table TABLE_X and it has multiple columns beginning with M_ characters which needs to be dropped. I decided to use the following PLSQL code to drop almost 100 columns beginning with M_ characters. Is it a good employment of dynamic sql and cursors? Can it be better? I didn't know more simple way since ALTER TABLE ... DROP COLUMN doesn't allow subquery to specify multiple column names.
declare
rcur sys_refcursor;
cn user_tab_cols.column_name%type;
begin
open rcur for select column_name from user_tab_cols where table_name='TABLE_X' and column_name LIKE 'M_%';
loop
fetch rcur into cn;
exit when rcur%NOTFOUND;
execute immediate 'alter table TABLE_X drop column '||cn;--works great
execute immediate 'alter table TABLE_X drop column :col'using cn;--error
end loop;
close rcur;
end;
Also. Why is it impossible to use 'using cn'?
This is a reasonable use of dynamic SQL. I would seriously question an underlying data model that has hundreds of columns in a single table that start with the same prefix and all need to be dropped. That implies to me that the data model itself is likely to be highly problematic.
Even using dynamic SQL, you cannot use bind variables for column names, table names, schema names, etc. Oracle needs to know at parse time what objects and columns are involved in a SQL statement. Since bind variables are supplied after the parse phase, however, you cannot specify a bind variable that changes what objects and/or columns a SQL statement is affecting.
The syntax for dropping multiple columns in a single alter statement is this:
SQL> desc t42
Name Null? Type
----------------------------------------- -------- ----------------------
COL1 NUMBER
COL2 DATE
COL3 VARCHAR2(30)
COL4 NUMBER
SQL> alter table t42 drop (col2, col3)
2 /
Table altered.
SQL> desc t42
Name Null? Type
----------------------------------------- -------- ----------------------
COL1 NUMBER
COL4 NUMBER
SQL>
So, if you really need to optimize the operation, you'll need to build up the statement incrementally - or use a string aggregation technique.
However, I would question whether you ought to be running a statement like this often enough to need to optimize it.

Resources