How to write a Procedure with multiple operations like select and update - oracle

Table
Id
Count
I want to write a procedure to find 'Count' in the table with 'Id' as key.After getting 'count' i have to increment it and update back in the table for that 'Id'.How can I write this with procedure without using cursors.
I want a simple procedure like below, BUT ITS NOT EXECUTING.IT SAYS PROCEDURE SUCCESSFUL WITH COMPILATION ERRORS.Help me out.
create or replace PROCEDURE newpro( inId IN NUMBER, outcount OUT NUMBER) is
select COUNT into outcount from Table1 WHERE ID= inId ;
BEGIN
outcount := outcount +1;
update Table1 set COUNT = outcount WHERE ID = inId ;
END;

UPDATE tableName
SET "Count" = "Count" + 1
WHERE ID = valueHere
SEE SQLFiddle Demo

try this one
create or replace Procedure Newpro
(
Inid in number,
Outcount out number
) is
begin
select count + 1
into Outcount
from Table1
where Id = Inid;
update Table1
set count = Outcount
where Id = Inid;
end;

Related

how can i put a query in decode function?

create table ss( no number, filepath varchar2(300) )
I want to have 5 or less duplicate values of 'no' in this table
when select count(no) from ss where no=#{no} <5, insert into ss values({no},{filepath})
so duplicate values of 'no' can't be over 5.
how can i do this?
You could create a similar trigger to implement this logic:
CREATE OR REPLACE TRIGGER set_no_ss_tbl_trg
BEFORE INSERT ON ss_tbl
FOR EACH ROW
BEGIN
DECLARE
l_cnt_no NUMBER;
BEGIN
SELECT COUNT(1)
INTO l_exceeding
FROM g_piece
WHERE refdoss = :new.no;
IF l_cnt_no > 5 THEN
SELECT MIN(no)
INTO :new.no
FROM (SELECT COUNT(1), no
FROM ss_tbl
GROUP BY no
HAVING COUNT(1) + 1 <= 5);
END IF;
END;
END;

Oracle SQL%ROWCOUNT statement always return 1

I have a simple table USERS in Oracle with 2 columns ID and USERNAME.
ID is the primary key and auto incremented using a trigger.
I am inserting or updating records using a procedure like this
CREATE OR REPLACE PROCEDURE SaveUser(UID NUMBER, UN VARCHAR2, Row_Count OUT NUMBER) IS
BEGIN
IF(UID > 0) THEN
UPDATE USERS SET USERNAME = UN WHERE ID = UID;
ELSE
INSERT INTO USERS(USERNAME) VALUES(UN);
END IF;
Row_Count := SQL%ROWCOUNT;
END;
I am calling the procedure like this:
VARIABLE Row_Count NUMBER;
EXEC SaveUser(50, 'Username_1', Row_Count);
PRINT Row_Count;
The issue is I am passing 50 as the first parameter, but in the table there is no row with ID 50. But I am getting 1 as the result. Even if the row is not updated, SQL%ROWCOUNT statement returns 1. Can anyone help me to fix this?
The above code is simplified one and the exact code is here
CREATE OR REPLACE PROCEDURE SaveEmployee(ID NUMBER, User_Name VARCHAR2, Emp_Password VARCHAR2, Emp_Full_Name VARCHAR2, Emp_Date_Of_Birth DATE, Emp_Gender_ID NUMBER, Emp_Work_Type_ID NUMBER, Emp_Salary FLOAT, Emp_Email VARCHAR2, Row_Count OUT NUMBER) IS
Username_Row_Count NUMBER := 0;
Email_Row_Count NUMBER := 0;
BEGIN
IF(ID > 0) THEN
SELECT COUNT(1) INTO Username_Row_Count FROM EMPLOYEES WHERE LOWER(USERNAME) = LOWER(User_Name) AND EMPLOYEE_ID <> ID;
SELECT COUNT(1) INTO Email_Row_Count FROM EMPLOYEES WHERE LOWER(EMAIL) = LOWER(Emp_Email) AND EMPLOYEE_ID <> ID;
IF(Username_Row_Count = 0 AND Email_Row_Count = 0) THEN
UPDATE EMPLOYEES
SET USERNAME = LOWER(User_Name), PASSWORD = Emp_Password, FULL_NAME = Emp_Full_Name, DATE_OF_BIRTH = Emp_Date_Of_Birth, GENDER_ID = Emp_Gender_ID, WORK_TYPE_ID = Emp_Work_Type_ID, SALARY = Emp_Salary, EMAIL = LOWER(Emp_Email)
WHERE EMPLOYEE_ID = ID;
END IF;
ELSE
SELECT COUNT(1) INTO Username_Row_Count FROM EMPLOYEES WHERE LOWER(USERNAME) = LOWER(User_Name);
SELECT COUNT(1) INTO Email_Row_Count FROM EMPLOYEES WHERE LOWER(EMAIL) = LOWER(Emp_Email);
IF(Username_Row_Count = 0 AND Email_Row_Count = 0) THEN
INSERT INTO EMPLOYEES(USERNAME, PASSWORD, FULL_NAME, DATE_OF_BIRTH, GENDER_ID, WORK_TYPE_ID, SALARY, EMAIL, LOGIN_ATTEMPTS)
VALUES(LOWER(User_Name), Emp_Password, Emp_Full_Name, Emp_Date_Of_Birth, Emp_Gender_ID, Emp_Work_Type_ID, Emp_Salary, LOWER(Emp_Email), 0);
END IF;
END IF;
Row_Count := SQL%ROWCOUNT;
END;
I can't reproduce what you are saying:
SQL> select * from users;
no rows selected
SQL> declare
2 uid number := 50;
3 begin
4 if uid > 0 then
5 update users set username = '&&un' where id = uid;
6 else
7 insert into users (username) values ('&&un');
8 end if;
9 dbms_output.put_line(sql%rowcount);
10 end;
11 /
Enter value for un: 50
0 --> this is SQL%ROWCOUNT
PL/SQL procedure successfully completed.
SQL>
Code you posted is invalid (there's no NUBER datatype). It would help if you posted something that actually works, because - the way you put it - we can't be sure that what you claim to be true really is true. Please, copy/paste your own SQL*Plus session (just like I did) so that we'd see what you really have and how Oracle responded.
Add SQL%ROWCOUNT after the update to get the update count
UPDATE EMPLOYEES
SET USERNAME = LOWER(User_Name), PASSWORD = Emp_Password, FULL_NAME = Emp_Full_Name, DATE_OF_BIRTH = Emp_Date_Of_Birth, GENDER_ID = Emp_Gender_ID, WORK_TYPE_ID = Emp_Work_Type_ID, SALARY = Emp_Salary, EMAIL = LOWER(Emp_Email)
WHERE EMPLOYEE_ID = ID;
Row_Count := SQL%ROWCOUNT;
Now it's more clear.
This code:
SELECT COUNT(1) INTO Username_Row_Count FROM EMPLOYEES WHERE LOWER(USERNAME) = LOWER(User_Name) AND EMPLOYEE_ID <> ID;
SELECT COUNT(1) INTO Email_Row_Count FROM EMPLOYEES WHERE LOWER(EMAIL) = LOWER(Emp_Email) AND EMPLOYEE_ID <> ID;
must update the values Username_Row_Count and Email_Row_Count, so that the condition
IF(Username_Row_Count = 0 AND Email_Row_Count = 0) THEN
is always false. And the update is never executed. As the result, in your Row_Count variable you get the result of the last select, which is 1 row.

Oracle PL/SQL: Returning specific selected columns from procedure

What is the best method for returning specific column values from a Procedure. For example, the below code doesn't work
/*DECLARATION*/
TYPE t_data IS TABLE OF Table1%ROWTYPE;
PROCEDURE get_values(data OUT t_data) AS
BEGIN
SELECT a.object_id, a.num, b.descrip
BULK COLLECT INTO data
FROM Table1 a INNER JOIN
Table2 b ON (a.id = b.id)
WHERE a.date IS NULL;
END get_values;
In the same scenario if I use a SELECT *, it works...
I assume this is an exercise on how to use a procedure to get a list of structured values; I would never recommend such an approach to get data from a table, preferring, if possible, a pure SQL method.
You seem to have tables like these:
create table table1(id ,object_id, num, "date") as (
select 1, 1, 100, sysdate from dual union all
select 2, 2, 200, null from dual
);
create table table2(id, descrip) as (
select 1, 'desc1' from dual union all
select 2, 'desc2' from dual
);
You're trying to create a procedure that return a set of rows, where each row contains elements from both tables; to do so, you need to build a type that matches the result of your select query.
You may want to define your package like this:
CREATE OR REPLACE PACKAGE yourPackage AS
TYPE tRec IS RECORD /* made to match the columns you want to extract in your query */
(
object_id NUMBER,
num NUMBER,
descrip VARCHAR2(100)
);
TYPE tTab IS TABLE OF tRec;
PROCEDURE get_values(data OUT tTab);
END yourPackage;
create or replace package body yourPackage as
PROCEDURE get_values(data OUT tTab) AS
BEGIN
SELECT a.object_id, a.num, b.descrip
BULK COLLECT INTO data
FROM Table1 a INNER JOIN
Table2 b ON (a.id = b.id)
WHERE a."date" IS NULL;
END get_values;
end yourPackage ;
You can call the procedure in the package this way:
declare
someVar yourPackage.tTab;
begin
yourPackage.get_values(someVar);
--
if someVar.first is not null then
for i in someVar.first .. someVar.last loop
dbms_output.put_line(someVar(i).object_id || ' - ' || someVar(i).num || ' - ' || someVar(i).descrip);
end loop;
end if;
end;
and this is the result you get:
2 - 200 - desc2
Firstly you cannot create a Type of any table%rowtype outside a PLSQL block. You need to create table as object and then have to create a type of that object. Then you can use it.
See below:
CREATE OR REPLACE TYPE Table11 AS OBJECT
(
id NUMBER,
num NUMBER,
description VARCHAR2 (20)
)
CREATE OR REPLACE TYPE t_data IS TABLE OF Table11;
CREATE OR REPLACE PROCEDURE get_values (v_data OUT t_data)
AS
BEGIN
SELECT Table11 (a.row_id, 222, 'hello')
BULK COLLECT INTO v_data
FROM Table1 a INNER JOIN Table2 b
ON (a.row_id = b.appid)
WHERE a.date IS NULL;
END get_values;
execution:
DECLARE
v_var t_data;
BEGIN
get_values (v_var);
FOR i IN 1 .. v_var.COUNT
LOOP
DBMS_OUTPUT.put_line (v_var (i).id ||' ' ||v_var(i).num ||' ' || v_var(i).description );
END LOOP;
END;
Output:
SQL> /
1 222 hello
2 222 hello
PL/SQL procedure successfully completed.

Oracle pl/sql script which increments number

Looking for a way to create an Oracle script using the equivalent of java ++ syntax to increment a variable.
Ie:
int id=10
DELETE MYTABLE;
INSERT INTO MYTABLE(ID, VALUE) VALUES (id++, 'a value');
INSERT INTO MYTABLE(ID, VALUE) VALUES (id++, 'another value');
...
Trying to use a variable and not a sequence so I can rerun this multiple times with the same results.
PL/SQL doesn't have the ++ syntactic sugar. You'd need to explicitly change the value of the variable.
DECLARE
id integer := 10;
BEGIN
DELETE FROM myTable;
INSERT INTO myTable( id, value ) VALUES( id, 'a value' );
id := id + 1;
INSERT INTO myTable( id, value ) VALUES( id, 'another value' );
id := id + 1;
...
END;
At that point, and since you want to ensure consistency, you may be better off hard-coding the id values just like you are hard-coding the value values, i.e.
BEGIN
DELETE FROM myTable;
INSERT INTO myTable( id, value ) VALUES( 10, 'a value' );
INSERT INTO myTable( id, value ) VALUES( 11, 'another value' );
...
END;
You can create a SEQUENCE to increment a number.
----CREATING SEQUENCE:
SQL> create sequence seq_name
2 start with 1
3 increment by 1
4 NOCACHE
5 NOCYCLE
6 ;
Sequence created.
----EXECUTION:
SQL> select seq_name.nextval from dual;
NEXTVAL
1
SQL> select seq_name.nextval from dual;
NEXTVAL
2
Also you can create a function that can be called anywhere:
----CREATING FUNCTION:
create or replace function sequence_func(a_number IN Number)
RETURN Number
AS
id Number;
Begin
select seq_name.nextval into id from dual;
Return id;
end;
/
Function created.
----EXECUTION:
SQL> select sequence_func(1) seq from dual;
seq
1
P.S :
startwith and increment by values below can be set as per your requirement.

SQL Table is mutating... Error

I'm attempting to write a trigger that will disallow any room in a hospital to have more than 3 services. The table RoomServices has a room number and a service that it has. So the only way to determine this is to group the rooms by room number and count the services. I have tried the code:
CREATE TRIGGER RoomServiceLimit
BEFORE INSERT OR UPDATE ON RoomServices
FOR EACH ROW
DECLARE
numService NUMBER;
CURSOR C1 IS SELECT count(*) AS RoomCount FROM RoomServices WHERE roomNumber = :new.roomNumber;
BEGIN
IF(inserting) THEN
SELECT count(*) into numService FROM RoomServices WHERE roomNumber = :new.roomNumber;
if(numService > 2) THEN
RAISE_APPLICATION_ERROR(-20001,'Room ' || :new.roomNumber || ' will have more than 3 services.');
END IF;
END IF;
IF(updating) THEN
FOR rec IN C1 LOOP
IF(rec.RoomCount > 2) THEN
RAISE_APPLICATION_ERROR(-20001,'Room ' || :new.roomNumber || ' will have more than 3 services.');
END IF;
END LOOP;
END IF;
END;
/
I've tried running each method separately with insert and update, and inserting always works and updating will always give me the mutating table error. I don't know how else to go about solving this problem, so any advice would be greatly appreciated.
Thanks!
There is no reliable way to enforce this kind of constraint using triggers. One possible approach is to use a materialized view that automatically refreshes on commit and has a check constraint enforcing your business rule:
create table roomservices (
pk number not null primary key,
roomnumber number);
create materialized view mv_roomservices
refresh on commit as
select
pk,
roomnumber,
count(*) over (partition by roomnumber) as cnt
from roomservices;
alter table mv_roomservices add constraint
chk_max_2_services_per_room check (cnt <= 2);
Now, whenever you add more than two services for a room and try to commit your transaction, you will get a ORA-12008 exception (error in materialized view refresh path).
I assume that RoomServices:
a) is a small table that is not intensively modified
b) there will never exist a room with more than 3 services
Note: you say "more than 3 services" but your code says "more than 2 services". So I will use "more than 2 services".
Then, what about using a statement trigger?
CREATE OR REPLACE TRIGGER RoomServiceLimit
AFTER INSERT OR UPDATE ON RoomServices
DECLARE
badRoomsCount NUMBER;
badRoomsList VARCHAR2(32767); -- adjust the varchar2 size according to your requirements
BEGIN
SELECT COUNT(*), LISTAGG(roomNumber, ', ') WITHIN GROUP (ORDER BY 1)
INTO badRoomsCount, badRoomsList
FROM (SELECT roomNumber FROM RoomServices GROUP BY roomNumber HAVING COUNT(*) > 2);
IF (badRoomsCount > 0) THEN
RAISE_APPLICATION_ERROR(-20001,'Room/s '||badRoomsList||' will have more than 2 services.');
END IF;
END;
/
If RoomServices is small but have too many changes (inserts or updates) then you may consider create an index on RoomNumber.
If my assumptions are false try something like:
CREATE GLOBAL TEMPORARY TABLE RoomServicesAux as SELECT roomNumber FROM RoomServices WHERE 1=0;
/
CREATE OR REPLACE TRIGGER PreRoomServiceLimit
BEFORE INSERT OR UPDATE ON RoomServices
BEGIN
DELETE FROM RoomServicesAux;
END;
/
CREATE OR REPLACE TRIGGER RowRoomServiceLimit
BEFORE INSERT OR UPDATE OF roomNumber ON RoomServices FOR EACH ROW
BEGIN
INSERT INTO RoomServicesAux VALUES (:NEW.roomNumber);
END;
/
CREATE OR REPLACE TRIGGER RoomServiceLimit
AFTER INSERT OR UPDATE ON RoomServices
DECLARE
badRoomsCount NUMBER;
badRoomsList VARCHAR2(32767); -- adjust the varchar2 size according to your requirements
BEGIN
SELECT COUNT(*), LISTAGG(roomNumber, ', ') WITHIN GROUP (ORDER BY 1)
INTO badRoomsCount, badRoomsList
FROM (
SELECT roomNumber
FROM RoomServices
WHERE roomNumber in (SELECT roomNumber FROM RoomServicesAux)
GROUP BY roomNumber
HAVING COUNT(*) > 2
);
DELETE FROM RoomServicesAux;
IF (badRoomsCount > 0) THEN
RAISE_APPLICATION_ERROR(-20001,'Room/s '||badRoomsList||' will have more than 2 services.');
END IF;
END;
/
Or if you have Oracle 11g or greater then you can use a compound trigger:
CREATE OR REPLACE TYPE RoomsListType IS TABLE OF INTEGER; -- change to the type of RoomServices.rowNumber
/
CREATE OR REPLACE TRIGGER RoomServiceLimit
FOR INSERT OR UPDATE OF roomNumber ON RoomServices
COMPOUND TRIGGER
RoomsList RoomsListType := RoomsListType();
badRoomsCount NUMBER;
badRoomsList VARCHAR2(32767); -- adjust the varchar2 size according to your requirements
AFTER EACH ROW IS
BEGIN
RoomsList.EXTEND;
RoomsList(RoomsList.COUNT) := :NEW.roomNumber;
END AFTER EACH ROW;
AFTER STATEMENT IS
BEGIN
SELECT COUNT(*), LISTAGG(roomNumber, ', ') WITHIN GROUP (ORDER BY 1)
INTO badRoomsCount, badRoomsList
FROM (
SELECT roomNumber
FROM RoomServices
WHERE roomNumber in (SELECT * FROM table(RoomsList))
GROUP BY roomNumber
HAVING COUNT(*) > 2
);
IF (badRoomsCount > 0) THEN
RAISE_APPLICATION_ERROR(-20001,'Room/s '||badRoomsList||' will have more than 2 services.');
END IF;
END AFTER STATEMENT;
END;
/
Seems you cannot solve this issue without some workarounds. If there is nothing better you can find, check this out:
I guess you have table Room, otherwise create one:
alter table Room add (
servicesCount integer default 0 not null check (servicesCount <= 3)
);
Then update this number with current values (not sure if the statement is valid, it is not the key point here)
update Room r
set servicesCount = (select count(*)
from RoomServices s
where r.roomNumber = s.roomNumber);
then in your trigger
create trigger RoomServiceLimit
before insert or update on RoomServices
for each row
begin
update Room
set servicesCount = servicesCount + 1
where roomNumber = :new.roomNumber;
end;
Looks quite ugly, but, as I've told, I am not sure you can find anything better with trigger.
EDIT
The complete working example
drop table Room;
drop table RoomServices;
create table Room (
roomNumber integer primary key,
servicesCount integer default 0 not null check (servicesCount <= 3)
);
create table RoomServices (
roomNumber integer,
service varchar2(100),
comments varchar2(4000)
);
create trigger RoomServiceLimit
before insert or update or delete on RoomServices
for each row
begin
if inserting then
update Room
set servicesCount = servicesCount + 1
where roomNumber = :new.roomNumber;
elsif updating and :old.roomNumber != :new.roomNumber then
update Room
set servicesCount = servicesCount + 1
where roomNumber = :new.roomNumber;
update Room
set servicesCount = servicesCount - 1
where roomNumber = :old.roomNumber;
elsif deleting then
update Room
set servicesCount = servicesCount - 1
where roomNumber = :old.roomNumber;
end if;
end;
/
insert into Room(roomNumber) values (1);
insert into Room(roomNumber) values (2);
insert into RoomServices(roomNumber,service,comments) values (1,'cleaning','first');
insert into RoomServices(roomNumber,service,comments) values (1,'drying','second');
insert into RoomServices(roomNumber,service,comments) values (1,'watering','third');
insert into RoomServices(roomNumber,service,comments) values (1,'something','third'); -- error
select * from room;
insert into RoomServices(roomNumber,service,comments) values (2,'something','2: first');
update RoomServices
set comments = null
where roomNumber = 2;
select * from room;
update RoomServices -- error
set roomNumber = 1
where roomNumber = 2;
select * from room;
delete from RoomServices where roomNumber = 1;
select * from room;

Resources