create or replace PROCEDURE newprocedur(outname OUT VARCHAR2,outroll OUT NUMBER) AS
CURSOR c1 IS
select Name,Rollno,Section
from emp;
BEGIN
Open c1;
fetch c1 into outname,outroll;
Here out of 3 columns in cursor is it possible to fetch only two columns using FETCH like i did above??
You're on 11g. You don't need to define variables or a cursor. Let Oracle do the heavy lifting for you.
create or replace PROCEDURE newprocedur(outname OUT VARCHAR2,outroll OUT NUMBER)
AS
BEGIN
select Name,Rollno
into outname,outroll
from emp;
END;
This will raise an error if EMP has more than one row. There are various ways of fixing that. For instance, using ROWNUM to artificially limit the result set. Or taking EMP_ID as an input parameter to select the desired record. It depends on whether this is a toy example or the start of a real world API.
"how to use ROWNUM in above eg?"
ROWNUM is a pseudo-column, and is covered in the documentation. However, for the sake of completeness, we can use it to restrict a result set like this:
select Name,Rollno
from emp
where rownum <= 1;
Related
I have to generate a table (contains two columns) of random data from a database table through oracle procedure. The user can indicate the number of data required and we have to use the table data with ID values from 1001 to 1060. I am trying to use cursor loop and not sure dbms_random method dhould I use.
I am using the following code to create procedure
create or replace procedure a05_random_plant(p_count in number)
as
v_count number := p_count;
cursor c is
select plant_id, common_name
from ppl_plants
where rownum = v_count
order by dbms_random.value;
begin
delete from a05_random_plants_table;
for c_table in c
loop
insert into a05_random_plants_table(plant_id, plant_name)
values (c_table.plant_id, c_table.common_name);
end loop;
end;
/
it complied successfully. Then I executed with the following code
set serveroutput on
exec a05_random_plant(5);
it shows anonymous block completed
but when run the following code, I do not get any records
select * from a05_random_plants_table;
The rownum=value would not work for a value greater than 1
hence try the below
create or replace procedure a05_random_plant(p_count in number)
as
v_count number := p_count;
cursor c is
select plant_id, common_name
from ppl_plants
where rownum <= v_count
order by dbms_random.value;
begin
delete from a05_random_plants_table;
for c_table in c
loop
insert into a05_random_plants_table(plant_id, plant_name)
values (c_table.plant_id, c_table.common_name);
end loop;
end;
/
Query by Tom Kyte - will generate almost 75K of rows:
select trunc(sysdate,'year')+mod(rownum,365) TRANS_DATE,
mod(rownum,100) CUST_ID,
abs(dbms_random.random)/100 SALES_AMOUNT
from all_objects
/
You can use this example to write your query and add where clause to it - where id between 1001 and 1060, for example.
I don't think you should use a cursor (which is slow naturally) but do a direct insert from a select:
insert into table (col1, col2)
select colx, coly from other_table...
And, isn't missing a COMMIT on the end of your procedure?
So, all code in your procedure would be a DELETE, a INSERT WITH that SELECT and then a COMMIT.
create or replace
trigger addpagamento
after insert on marcacoes_refeicoes
for each row
declare
nmarcacaoa number;
ncartaoa number;
begin
select nmarcacao into nmarcacaoa from marcacoes_refeicoes where rownum < (select count(*) from marcacoes_refeicoes);
select ncartao into ncartaoa from marcacoes_refeicoes where rownum < (select count(*) from marcacoes_refeicoes);
insert_pagamentos(nmarcacaoa, ncartaoa); --this is a procedure
exception when others then
raise_application_error(-20001, 'Error in Trigger!!!');
end addpagamento;
when i try to run the insert statement to the table "marcacoes_refeicoes" this procedure gives error: like the table is mutating
create or replace
procedure insert_pagamentos
(nmarcacaoa in number, ncartaoa in number)
AS
BEGIN
insert into pagamentos (nmarcacao, datapagamento, ncartao) values (nmarcacaoa, sysdate, ncartaoa);
commit;
END INSERT_PAGAMENTOS;
Short (oversimplified) answer:
You can't modify a table in a trigger that changes the table.
Long answer:
http://www.oracle-base.com/articles/9i/mutating-table-exceptions.php has a more in-depth explanation, including suggestions how to work around the problem.
You're hitting the mutating-table problem because you're selecting from the same table the trigger is on, but what you seem to be trying to do doesn't make sense. Your queries to get a value for nmarcacaoa and ncartaoa will return a no_data_found or too_many_rows error unless the table had exactly 2 rows in it before your insert:
select nmarcacao from marcacoes_refeicoes
where rownum < (select count(*) from marcacoes_refeicoes);
will return all rows except one; and the one that's excluded will be kind of random as you have no ordering. Though the state is undetermined within the trigger, so you can't really how many rows there are, and it won't let you do this query anyway. You won't normally be able to get a single value, anyway, and it's not obvious which value you actually want.
I can only imagine that you're trying to use the values from the row you are currently inserting, and putting them in your separate payments (pagamentos) table. If so there is a built-in mechanism to do that using correlation names, which lets you refer to the newly inserted row as :new (by default):
create or replace
trigger addpagamento
after insert on marcacoes_refeicoes
for each row
begin
insert_pagamentos(:new.nmarcacaoa, :new.ncartaoa);
end addpagamento;
The :new is referring to the current row, so :new.nmarcacaoa is the nmarcacaoa being inserted. You don't need to (and can't) get that value from the table itself. (Even with the suggested autonomous pragma, that would be a separate transaction and would not be able to see your newly inserted and uncommitted data).
Or you can just do the insert directly; not sure what the procedure is adding here really:
create or replace
trigger addpagamento
after insert on marcacoes_refeicoes
for each row
begin
insert into pagamentos(nmarcacao, datapagamento, ncartao)
values (:new.nmarcacaoa, sysdate, :new.ncartaoa);
end addpagamento;
I've removed the exception handler as all it was doing was masking the real error, which is rather unhelpful.
Editing this answer in view of the comments below:
You can use PRAGMA AUTONOMOUS_TRANSACTION to get rid of the error, but DONOT USE it as it will NOT solve any purpose.
Use PRAGMA AUTONOMOUS_TRANSACTION:
create or replace
trigger addpagamento
after insert on marcacoes_refeicoes
for each row
declare
PRAGMA AUTONOMOUS_TRANSACTION;
nmarcacaoa number;
ncartaoa number;
begin
select nmarcacao into nmarcacaoa from marcacoes_refeicoes where rownum < (select count(*) from marcacoes_refeicoes);
select ncartao into ncartaoa from marcacoes_refeicoes where rownum < (select count(*) from marcacoes_refeicoes);
insert_pagamentos(nmarcacaoa, ncartaoa); --this is a procedure
exception when others then
raise_application_error(-20001, 'Error in Trigger!!!');
end addpagamento;
However in this case , select count(*) from marcacoes_refeicoes will give you the new count after the current insertion into the table.
I can't able to create a select statement in oracle procedure. Please help me to create this.
Now I create the insert,update.delete statement in a procedure but i can't create a select statement. Please help me to create the select statement using cursor.
c_dbuser OUT SYS_REFCURSOR
ELSIF (TYPE_ =1) THEN
OPEN c_dbuser FOR
SELECT * FROM tbl_discount_master ;
CLOSE c_dbuser;
END IF;
call procedure_name(xx,xx,xx,1);
how can i get the selected value using call procedure statement.
In addition to the other suggestion, you have this solution when you are getting exactly one row.
DECLARE
myvar1 mytable.mycolumn1%TYPE;
myvar2 mytable.mycolumn2%TYPE;
BEGIN
SELECT mycolumn1, mycolumn2
INTO myvar1, myvar2
FROM mytable
WHERE …;
END;
This will throw an exception if there is no selected row (NO_DATA_FOUND) or if there is more than one row (TOO_MANY_ROWS).
The difference between select and the insert/update/delete is that you need to select into some structure, either one or more variables or a rowtype variable.
Avoid explicit cursors whenever possible in favour of the faster, less verbose and less error prone implicit cursor.
eg.
for cur_my_query in
select column1,
column2,
...
from ...
where ...
loop
refer here to cur_my_query or my_query.column1 etc
end loop
My situation:
I have a table named Table1. It has lots of columns, one of them is Column1. I don't know the other columns, they may even change sometimes.
There is a strongly typed ref cursor type which returns Table1%rowtype, named cur_Table1.
I have a stored procedure named SP1 which has an out parameter of type cur_Table1. I'm calling this SP1 stored procedure from another database that only sees this stored procedure, but not the table or the type itself.
How do I select only Column1 from the returned cursor?
I know I can fetch into a record or as many variables as the cursor has columns, but I only know of one column's existence so I can't declare the complete record or correct number of variables.
You can do this with DBMS_SQL, but it ain't pretty.
Table and sample data (COLUMN1 has the numbers 1 - 10):
create table table1(column1 number, column2 date, column3 varchar2(1000), column4 clob);
insert into table1
select level, sysdate, level, level from dual connect by level <= 10;
commit;
Package with a procedure that opens a ref cursor and selects everything:
create or replace package test_pkg is
type cur_Table1 is ref cursor return table1%rowtype;
procedure sp1(p_cursor in out cur_table1);
end;
/
create or replace package body test_pkg is
procedure sp1(p_cursor in out cur_table1) is
begin
open p_cursor for select column1, column2, column3, column4 from table1;
end;
end;
/
PL/SQL block that reads COLUMN1 data from the ref cursor:
--Basic steps are: call procedure, convert cursor, describe and find columns,
--then fetch rows and retrieve column values.
--
--Each possible data type for COLUMN1 needs to be added here.
--Currently only NUMBER is supported.
declare
v_cursor sys_refcursor;
v_cursor_number number;
v_columns number;
v_desc_tab dbms_sql.desc_tab;
v_position number;
v_typecode number;
v_number_value number;
begin
--Call procedure to open cursor
test_pkg.sp1(v_cursor);
--Convert cursor to DBMS_SQL cursor
v_cursor_number := dbms_sql.to_cursor_number(rc => v_cursor);
--Get information on the columns
dbms_sql.describe_columns(v_cursor_number, v_columns, v_desc_tab);
--Loop through all the columns, find COLUMN1 position and type
for i in 1 .. v_desc_tab.count loop
if v_desc_tab(i).col_name = 'COLUMN1' then
v_position := i;
v_typecode := v_desc_tab(i).col_type;
--Pick COLUMN1 to be selected.
if v_typecode = dbms_types.typecode_number then
dbms_sql.define_column(v_cursor_number, i, v_number_value);
--...repeat for every possible type.
end if;
end if;
end loop;
--Fetch all the rows, then get the relevant column value and print it
while dbms_sql.fetch_rows(v_cursor_number) > 0 loop
if v_typecode = dbms_types.typecode_number then
dbms_sql.column_value(v_cursor_number, v_position, v_number_value);
dbms_output.put_line('Value: '||v_number_value);
--...repeat for every possible type
end if;
end loop;
end;
/
Given the original question, jonearles's answer is still correct, so I'll leave it marked as such, but I ended up doing something completely different and much better.
The problem was/is that I have no control over SP1's database, I just have to call it from somewhere else as a 3rd party client. Now I managed to get permission to see not only SP, but also the type of the cursor. I still don't see the table but now there is a much cleaner solution:
In the other database I have been granted access to see this type now:
type cur_Table1 is ref cursor return Table1%rowtype;
So in my database I can do this now:
mycursor OtherDB.cur_Table1;
myrecord mycursor%rowtype;
...
OtherDB.SP1(mycursor);
fetch mycursor into myrecord;
dbms_output.put_line(myrecord.Column1);
See, I still don't need any access to the table, I see the cursor only. The key is that the magical %rowtype works for cursors as well, not just tables. It doesn't work on a sys_refcursor, but it does on a strongly typed one. Given this code, I don't have to care if anything changes on the other side, I don't have to define all the columns or records at all, I just specify the one column I'm interested in.
I really love this OOP attitude about Oracle.
Don't know if it's an option or not, but wouldn't a better solution be to create a function that returns the specific value you're looking for? That avoids the overhead of sending the extra data. Alternatively, you could define a cursor with a set of known fields in it that both parties know about.
I just want to SELECT values into variables from inside a procedure.
SELECT blah1,blah2 INTO var1_,var2_
FROM ...
Sometimes a large complex query will have no rows sometimes it will have more than one -- both cases lead to exceptions. I would love to replace the exception behavior with implicit behavior similiar to:
No rows = no value change, Multiple rows = use last
I can constrain the result set easily enough for the "multiple rows" case but "no rows" is much more difficult for situations where you can't use an aggregate function in the SELECT.
Is there any special workarounds or suggestions? Looking to avoid significantly rewriting queries or executing twice to get a rowcount before executing SELECT INTO.
Whats wrong with using an exception block?
create or replace
procedure p(v_job VARCHAR2) IS
v_ename VARCHAR2(255);
begin
select ename into v_ename
from (
select ename
from scott.emp
where job = v_job
order by v_ename desc )
where rownum = 1;
DBMS_OUTPUT.PUT_LINE('Found Rows Logic Here -> Found ' || v_ename);
EXCEPTION WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('No Rows found logic here');
end;
SQL> begin
p('FOO');
p('CLERK');
end; 2 3 4
5 /
No Rows found logic here
Found Rows Logic Here -> Found SMITH
PL/SQL procedure successfully completed.
SQL>
You could use a for loop. A for loop would do nothing for no rows returned and would be applied to every row returned if there where multiples. You could adjust your select so that it only returns the last row.
begin
for ARow in (select *
from tableA ta
Where ta.value = ???) loop
-- do something to ARow
end loop;
end;