Writing Oracle Stored Procedure - oracle

I haven't written many stored procedures with oracle. I read through some tutotorials (for example: http://plsql-tutorial.com/plsql-procedures.htm) and tried to model my sp after what I saw, but I am still encountering an error. Here's is a small sample procedure and error:
create or replace
PROCEDURE TEST_SP()
BEGIN
insert into tablespace.tablename
select * from testtable;
END TEST_SP;
PLS-00103: Encountered the symbol ")" when expecting one of the following:
<an identifier> <a double-quoted delimited-identifier>
I get the impression that I am missing the declaration section, but I do not understand what I am supposed to be declaring :-/
Any help would be appreciated.
Followed Justin's advice from first response, now getting different error:
create or replace
PROCEDURE TEST_SP
AS
BEGIN
insert into tablespace.tablename (col1, col2)
select (col1, col2) from testtable;
END TEST_SP;
PLS-00103: Encountered the symbol "AS" when expecting one of the following:
. , # in <an identifier> <a double-quoted delimited-identifier> partition subpartition

It sounds like you're after something like this. You don't want to have parenthesis after the name of the procedure if you are not declaring any parameters. And you need the keyword AS (or IS) before your BEGIN even if you're not going to declare any local variables.
create or replace PROCEDURE TEST_SP
AS
BEGIN
insert into tablespace.tablename
select * from testtable;
END TEST_SP;
Generally, however, it's a bad idea to write code like this that omits the list of columns. That assumes that the two tables have exactly the same columns defined in exactly the same order so if someone decides to add another column to one of the tables, your code will break. It also creates the possibility that you're inadvertently copying data from the wrong column. It is generally more robust to write something like
create or replace PROCEDURE TEST_SP
AS
BEGIN
insert into tablespace.tablename( <<list of columns>> )
select <<list of columns>>
from testtable;
END TEST_SP;
As an example
SQL> create table foo( col1 number );
Table created.
SQL> create table foo_cpy( col1 number );
Table created.
SQL> ed
Wrote file afiedt.buf
1 create or replace procedure test_sp
2 as
3 begin
4 insert into foo( col1 )
5 select col1
6 from foo_cpy;
7* end test_sp;
SQL> /
Procedure created.

Related

Procedure select 1 and delete

I am trying to create a procedure that
1. Selects the first record.
2. Deletes the record and returns it to the caller.
I have the following code (mind you I am fairly new to PLSQL).
I got it from looking at other SO questions and the oracle docs.
CREATE PROCEDURE TAKE_1_DELETE_1
AS
BEGIN
DELETE FROM my_table
where rownum = 1
RETURNING *
INTO v_event;
END TAKE_1_DELETE_1;
I get the following error:
[Error] PLS-00103 (7: 3): PLS-00103: Encountered the symbol "end-of-file" when expecting one of the following:
; <an identifier> <a double-quoted delimited-identifier>
current delete exists prior <a single-quoted SQL st
I have tried numerous things, but none have worked thus far.
Our Data-Warehouse people are saying "We don't know what is wrong".
Dear SO gods... what do I need to do, to get my procedure to do what I want? :)
There's a couple of errors here:
You haven't declared v_event so you're trying to return data into a uninstantiated variable/out parameter
You can't "return" * you have to explicitly state all columns - this is the actual error you're getting
Assuming a table that looks like this
create table my_table (a number, b number);
Your procedure would look like the following:
create or replace procedure take_1_delete_1 is
v_event my_table%rowtype;
begin
delete from my_table
where rownum = 1
returning a, b
into v_event;
end take_1_delete_1;
Or, if you wanted to use the returning value outside the procedure you can use an OUT parameter rather than declaring a local variable
create or replace procedure take_1_delete_1 (p_event out my_table%rowtype) is
begin
delete from my_table
where rownum = 1
returning a, b
into p_event;
end take_1_delete_1;

Oracle: Choose user-selected column in procedure

I'm trying to write a procedure that can take a column name as an input parameter and select that column from a table.
I know you can do this in a regular query by simply creating a string variable and referencing the column like such:
DEFINE mycol = 'column1'
SELECT a.&mycol FROM table1 a
This does however not work within a procedure. Variables in procedures don't appear to be able to be referenced by the '&' symbol, and as such when I attempt to pull a column using an input parameter it tells me that it's an invalid identifier.
Searching around on the internet, I can't find an example of someone doing this without dynamic SQL, when inside a proc. I would prefer not to use dynamic SQL if possible.
Does anyone know a workaround for this, or is dynamic SQL required in procs for this to occur?
Cheers,
Ollie
Dynamic SQL in named block is inevitable as ampersand construct are not compliant with SQL standard. It's only feature of SQL Plus engine.
I have tested the scenarios as described.
CREATE TABLE SCOTT.COLUMN_LIST
(
COLUMN_ID NUMBER,
COLUMN_NAME VARCHAR2(200),
TABLE_NAME VARCHAR2(50 )
);
Insert into SCOTT.COLUMN_LIST
(COLUMN_ID, COLUMN_NAME, TABLE_NAME)
Values
(1, 'DEPTNO', 'DEPT');
COMMIT;
CREATE TABLE SCOTT.TEST1
(
A VARCHAR2(300 BYTE)
);
CREATE OR REPLACE PROCEDURE PROC1
IS
v_Select_Column VARCHAR2(200);
v_Table_Name VARCHAR2(50 );
V_SQL VARCHAR2(300);
BEGIN
SELECT COLUMN_NAME,TABLE_NAME
INTO v_Select_Column,v_Table_Name
FROM COLUMN_LIST
WHERE COLUMN_ID=1;
V_SQL:= 'SELECT '||v_Select_Column||
' FROM '||v_Table_Name;
EXECUTE IMMEDIATE V_SQL;
INSERT INTO TEST1 values(V_SQL);
COMMIT;
END PROC1;
/
Please test at your end. You can see the output on test1 table.

Declare Table Variable in Oracle Procedure

I'm having a heck of a time trying to find an example of this being done. I have a procedure, and as part of that procedure I want to store the results of a SELECT statement so that I can work against that set, and then use it as a reference to update the original records when it's all done.
The difficulty I'm having is in declaring the temporary table variable. Here's an example of what I'm trying to do:
PROCEDURE my_procedure
IS
output_text clob;
temp_table IS TABLE OF MY_TABLE%ROWTYPE; -- Error on this line
BEGIN
SELECT * BULK COLLECT INTO temp_table FROM MY_TABLE WHERE SOME_DATE IS NULL;
-- Correlate results into the clob for sending to email (working)
-- Set the SOME_DATE value of the original record set where record is in temp_table
I get an error on the second occurrence of IS, saying that it is an unexpected symbol. This suggests to me that my table variable declaration is either wrong, or in the wrong place. I've tried putting it into a DECLARE block after BEGIN, but I just get another error.
Where should this declaration go? Alternatively, if there is a better solution I'll take that too!
CREATE OR REPLACE PROCEDURE PROCEDURE1 AS
output_text clob;
type temp_table_type IS TABLE OF MY_TABLE%ROWTYPE;
temp_table temp_table_type;
BEGIN
SELECT * BULK COLLECT INTO temp_table FROM MY_TABLE;
END PROCEDURE1;
or
CREATE OR REPLACE PROCEDURE PROCEDURE1 ( output_text OUT clob ) IS
type temp_table_type IS TABLE OF MY_TABLE%ROWTYPE
INDEX BY BINARY_INTEGER;
temp_table temp_table_type;
BEGIN
SELECT * BULK COLLECT INTO temp_table FROM MY_TABLE;
FOR indx IN 1 .. temp_table.COUNT
LOOP
something := temp_table(indx).col_name;
END LOOP;
END PROCEDURE1;
I had a similiar problem and found this:
Selecting Values from Oracle Table Variable / Array?
The global temporary table can be used like a regular table, but its content is only temporary (deleted at end of session/transaction) and each session has its own table content.
If you don't need dynamic SQL this can be used as good solution:
CREATE GLOBAL TEMPORARY TABLE temp_table
(
column1 NUMBER,
column2 NUMBER
)
ON COMMIT DELETE ROWS;
PROCEDURE my_procedure
IS
output_text clob;
BEGIN
-- Clear temporary table for this session (to be sure)
DELETE FROM temp_table;
-- Insert data into temporary table (only for this session)
INSERT INTO temp_table SELECT * FROM MY_TABLE WHERE SOME_DATE IS NULL;
-- ...
END;
The only disadvantages are, in my opinion, that you got another table and that the temporary table is not dynamic.

FORALL+ EXECUTE IMMEDIATE + INSERT Into tbl SELECT

I have got stuck in below and getting syntax error - Please help.
Basically I am using a collection to store few department ids and then would like to use these department ids as a filter condition while inserting data into emp table in FORALL statement.
Below is sample code:
while compiling this code i am getting error, my requirement is to use INSERT INTO table select * from table and cannot avoid it so please suggest.
create or replace Procedure abc(dblink VARCHAR2)
CURSOR dept_id is select dept_ids from dept;
TYPE nt_dept_detail IS TABLE OF VARCHAR2(25);
l_dept_array nt_dept_detail;
Begin
OPEN dept_id;
FETCH dept_id BULK COLLECT INTO l_dept_array;
IF l_dept_array.COUNT() > 0 THEN
FORALL i IN 1..l_dept_array.COUNT SAVE EXCEPTIONS
EXECUTE IMMEDIATE 'INSERT INTO stg_emp SELECT
Dept,''DEPT_10'' FROM dept_emp'||dblink||' WHERE
dept_id = '||l_dept_array(i)||'';
COMMIT;
END IF;
CLOSE dept_id;
end abc;
Why are you bothering to use cursors, arrays etc in the first place? Why can't you just do a simple insert as select?
Problems with your procedure as listed above:
You don't declare procedures like Procedure abc () - for a standalone procedure, you would do create or replace procedure abc as, or in a package: procedure abc is
You reference a variable called "dblink" that isn't declared anywhere.
You didn't put end abc; at the end of your procedure (I hope that was just a mis-c&p?)
You're effectively doing a simple insert as select, but you're way over-complicating it, plus you're making your code less performant.
You've not listed the column names that you're trying to insert into; if stg_emp has more than two columns or ends up having columns added, your code is going to fail.
Assuming your dblink name isn't known until runtime, then here's something that would do what you're after:
create Procedure abc (dblink in varchar2)
is
begin
execute immediate 'insert into stg_emp select dept, ''DEPT_10'' from dept_emp#'||dblink||
' where dept_id in (select dept_ids from dept)';
commit;
end abc;
/
If, however, you do know the dblink name, then you'd just get rid of the execute immediate and do:
create Procedure abc (dblink in varchar2)
is
begin
insert into stg_emp -- best to list the column names you're inserting into here
select dept, 'DEPT_10'
from dept_emp#dblink
where dept_id in (select dept_ids from dept);
commit;
end abc;
/
There appears te be a lot wrong with this code.
1) why the execute immediate? Is there any explicit requirement for that? No, than don't use it
2) where is the dblink variable declared?
3) as Boneist already stated, why not a simple subselect in the insert statement?
INSERT INTO stg_emp SELECT
Dept,'DEPT_10' FROM dept_emp#dblink WHERE
dept_id in (select dept_ids from dept );
For one, it would make the code actually readable ;)

Oracle PL/SQL: Forwarding whole row to procedure from a trigger

In have an Oracle (10i) PL/SQL Row-Level trigger which is responsible for three independent tasks. As the trigger is relatively cluttered that way, I want to export these three tasks into three stored procedures.
I was thinking of using a my_table%ROWTYPE parameter or maybe a collection type for the procedures, but my main concern is how to fill these parameters.
Is there a way to put the whole :NEW row of a trigger into a single variable easily?
So far the only way I could find out was assigning each field separately to the variable which is not quite satisfying, looking at code maintenance etc.
Something like
SELECT :NEW.* INTO <variable> FROM dual;
would be preferred. (I haven't tried that actually but I suppose it wouldn't work)
In the vast majority of cases, the only way to assign the new values in the row to a %ROWTYPE variable would be to explicitly assign each column. Something like
CREATE OR REPLACE TRIGGER some_trigger_name
BEFORE INSERT OR UPDATE ON some_table
FOR EACH ROW
DECLARE
l_row some_table%rowtype;
BEGIN
l_row.column1 := :NEW.column1;
l_row.column2 := :NEW.column2;
...
l_row.columnN := :NEW.columnN;
procedure1( l_row );
procedure2( l_row );
procedure3( l_row );
END;
If your table happens to be declared based on an object, :NEW will be an object of that type. So if you have a table like
CREATE OR REPLACE TYPE obj_foo
AS OBJECT (
column1 NUMBER,
column2 NUMBER,
...
columnN NUMBER );
CREATE TABLE foo OF obj_foo;
then you could declare procedures that accept input parameters of type OBJ_FOO and call those directly from your trigger.
The suggestion in the other thread about selecting the row from the table in an AFTER INSERT/ UPDATE thread, unfortunately, does not generally work. That will generally lead to a mutating table exception.
1 create table foo (
2 col1 number,
3 col2 number
4* )
SQL> /
Table created.
SQL> create procedure foo_proc( p_foo in foo%rowtype )
2 as
3 begin
4 dbms_output.put_line( 'In foo_proc' );
5 end;
6 /
Procedure created.
SQL> create or replace trigger trg_foo
2 after insert or update on foo
3 for each row
4 declare
5 l_row foo%rowtype;
6 begin
7 select *
8 into l_row
9 from foo
10 where col1 = :new.col1;
11 foo_proc( l_row );
12 end;
13 /
Trigger created.
SQL> insert into foo values( 1, 2 );
insert into foo values( 1, 2 )
*
ERROR at line 1:
ORA-04091: table SCOTT.FOO is mutating, trigger/function may not see it
ORA-06512: at "SCOTT.TRG_FOO", line 4
ORA-04088: error during execution of trigger 'SCOTT.TRG_FOO'
It's not possible that way.
Maybe my answer to another question can help.
Use SQL to generate the SQL;
select ' row_field.'||COLUMN_NAME||' := :new.'||COLUMN_NAME||';' from
ALL_TAB_COLUMNS cols
where
cols.TABLE_NAME = 'yourTableName'
order by cols.column_name
Then copy and paste output.
This is similar to Justins solution but a little bit shorter (no typing of left part of each assignment) :
-- use instead of the assignments in Justins example:
select :new.column1,
:new.column2,
...
:new.columnN,
into l_row from dual;

Resources