how can i put a query in decode function? - spring

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;

Related

If exist then update in oracle forms 11g

I am trying to write a code block where record insert if record already exist then update
table. i am trying If (sql%rowcount = 0) then but it's not working in cursor and more one records.
What I tried so far as the code block is
declare
remp_id varchar2(60);
remp_name varchar2(100);
rdesig varchar2(100);
rdept_no number;
rdesig_no number;
rdept_name varchar2(60);
cursor alfa is
select emp_code, emp_name, desig, dept_name, dept_no, desig_no
from emp
where emp_code between :first_code and :second_code;
begin
open alfa;
loop
fetch alfa
into remp_id, remp_name, rdesig, rdept_name, rdept_no, rdesig_no;
exit when alfa%notfound;
update att_reg_mo
set emp_code = remp_id,
emp_name = remp_name,
desig = rdesig,
dept_name = rdept_name,
dept_no = rdept_no,
desig_no = rdesig_no,
att_date = :att_date,
emp_att = :emp_att,
att_type = 'MA',
reg_date = :reg_date
where emp_code between :first_code and :second_code
and reg_date = :reg_date
and att_date = :att_date;
commit;
if (sql%rowcount = 0) then
insert into att_reg_mo
(emp_code,
emp_name,
desig,
dept_name,
att_date,
emp_att,
att_type,
reg_date,
dept_no,
desig_no)
values
(remp_id,
remp_name,
rdesig,
rdept_name,
:att_date,
:emp_att,
'MA',
:reg_date,
rdept_no,
rdesig_no);
end if;
commit;
end loop;
close alfa;
end;
when i am fire the trigger then record is insert but where need to update record it's update with null values
Or you could use something like that:
DECLARE
cursor test is
select 1 as v from dual
union
select 2 as v from dual;
n_var NUMBER;
BEGIN
for rec in test loop
BEGIN
select 1 into n_var from dual where rec.v=2;
DBMS_OUTPUT.PUT_LINE('Please Update Any Table');
EXCEPTION
WHEN no_data_found THEN
DBMS_OUTPUT.PUT_LINE('Please Insert Any Table');
END;
end loop;
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE ('Unexpected error');
END;
SQL%attribute always refers to the most recently run SELECT or DML statement. It refreshes as to start from zero after any of transaction statement such as COMMIT, ROLLBACK or SAVEPOINT is issued, for a session in which AUTOCOMMIT is presumed to be OFF by default. Therefore, you always get zero from SQL%ROWCOUNT which is just before the INSERT statement, and keeping inserting to the concerned table during every run of code block.
So, remove the f i r s t COMMIT, removing also keeps the atomicity of the whole transaction, from your code block.
Demo

Oracle Loop Rule 4809

I need to write a procedure that will insert thousands of rows in a table and use the auto generated id resulted from these rows and use it in other inserts.
I used a for loop in which I save the sequence id in a variable then use it in my inserts.
declare
first_id integer;
BEGIN
FOR texts in (select distinct text from table_texts )
LOOP
first_id := SEQ_IDS_OBJECTID.NEXTVAL;
INSERT INTO table_1(id,some_fields)
VALUES (first_id, 'blablabla');
insert into table_2 (id,text_field)
VALUES (first_id, texts.text);
END LOOP;
commit;
END;
I think that this is not the ideal way to achieve what I need. Also when I enter the code in TOAD , I get the following warning :
Rule 4809 (A loop that contains DML statements should be refactored to use BULK COLLECT and FORALL)
Is there better way to do it?
EDIT:
the above code was simplified. But I think I have to expose more of it to explain the case :
declare
first_id integer;
second_id integer;
BEGIN
FOR texts in (select distinct text1 , text2 from mdf )
LOOP
first_id := XAKTA.SEQ_IDS_OBJECTID.NEXTVAL;
select id_1 into second_id from table_3 where field_1 =texts.text1 ;
INSERT INTO table_1(id_1,id_2,some_fields)
VALUES (first_id ,second_id ,'blablabla');
insert into table_2 (id,text1,text2)
VALUES (first_id, texts.text1,texts.text2);
END LOOP;
commit;
END;
You can use FORALL to insert batches of items from your cursor:
DECLARE
TYPE texts_tab IS TABLE OF table_texts.text%TYPE;
TYPE ids_tab IS TABLE OF table_2.id%TYPE;
p_texts texts_tab;
p_ids ids_tab;
CURSOR c IS
SELECT DISTINCT text FROM table_texts;
BEGIN
OPEN c;
LOOP
FETCH c BULK COLLECT INTO p_texts LIMIT 100;
FORALL i IN 1 .. p_texts.COUNT
INSERT INTO table_2 ( id, text_field )
VALUES ( SEQ_IDS_OBJECTID.NEXTVAL, p_texts(i) )
RETURNING id BULK COLLECT INTO p_ids;
FORALL i IN 1 .. p_ids.COUNT
INSERT INTO table_1( id, some_fields )
VALUES ( p_ids(i), 'blablabla' );
EXIT WHEN c%NOTFOUND;
END LOOP;
CLOSE c;
COMMIT;
END;
/
db<>fiddle here

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: Trying to loop thru insert statement using dynamic list of table names

I'm not quite understanding the solution found here:
Selecting Values from Oracle Table Variable / Array?
I have a list of table names. I would like to loop thru them as an array, using their values as tables to search from.
TMP_DORMANT_FILTERS physical table of table names. The array below is the same list.
LM_DORMANT_EMAIL is a list of email addresses.
I want to check the existence of the dormant email addresses in the list of tables. I realize I could write the same query 12 times to search each table. But that's not going to improve my SQL skills.
Here is my array attempt. In this attempt, Oracle doesn't like the way I'm calling the array value in my where not exists sql:
DECLARE
TYPE array_t IS VARRAY(12) OF VARCHAR2(25);
ARRAY array_t := array_t('BT_ABANDONED_HIST', 'BT_BROWSED_HIST', 'BT_PURCHASED_HIST', 'CM_ABANDONED_HIST', 'CM_BROWSED_HIST', 'CM_PURCHASED_HIST', 'CM_PAGE_VIEWS_HIST', 'MB_ABANDONED_HIST', 'MB_BROWSED_HIST', 'MB_CARTED_HIST', 'MB_PAGE_VIEWS_HIST', 'MB_PURCHASED_HIST');
BEGIN
FOR i IN 1..array.count LOOP
INSERT INTO TMP1_DORMANT_EMAIL
SELECT feed.EMAIL_ADDRESS
FROM LM_DORMANT_EMAIL feed
WHERE NOT EXISTS (
SELECT 1 FROM array(i) hist
WHERE ACTIVITY_DATE >= TRUNC(SYSDATE - 90)
AND hist.EMAIL = feed.EMAIL_ADDRESS
);
COMMIT;
END LOOP;
END;
/
Or using the solution found at the link above, I tried. Oracle doesn't recognize my inserting into dormant_filters under the begin part. It's telling me my physical table TMP_DORMANT_FILTERS does not exist:
CREATE GLOBAL TEMPORARY TABLE dormant_filters
( filters varchar2(100)
)
ON COMMIT DELETE ROWS;
BEGIN
INSERT INTO dormant_filters
( filters )
( SELECT TABLE_NAMES
FROM TMP_DORMANT_FILTERS
);
FOR j IN ( SELECT filters FROM dormant_filters ) LOOP
INSERT INTO TMP1_DORMANT_EMAIL
SELECT feed.EMAIL_ADDRESS, j as DORMANT_SOURCE
FROM LM_DORMANT_EMAIL feed
WHERE NOT EXISTS (
SELECT 1 FROM j hist
WHERE feed.ACTIVITY_DATE >= TRUNC(SYSDATE - 90)
AND hist.EMAIL = feed.EMAIL_ADDRESS
);
NULL;
END LOOP;
COMMIT;
END;
/
This problem requires dynamic SQL. Bind variables can be used for values but not for objects.
declare
type array_t is varray(12) of varchar2(25);
array array_t := array_t('BT_ABANDONED_HIST', 'BT_BROWSED_HIST', 'BT_PURCHASED_HIST', 'CM_ABANDONED_HIST', 'CM_BROWSED_HIST', 'CM_PURCHASED_HIST', 'CM_PAGE_VIEWS_HIST', 'MB_ABANDONED_HIST', 'MB_BROWSED_HIST', 'MB_CARTED_HIST', 'MB_PAGE_VIEWS_HIST', 'MB_PURCHASED_HIST');
begin
for i in 1 .. array.count loop
execute immediate '
INSERT INTO TMP1_DORMANT_EMAIL
SELECT feed.EMAIL_ADDRESS
FROM LM_DORMANT_EMAIL feed
WHERE NOT EXISTS (
SELECT 1 FROM '||array(i)||' hist
WHERE ACTIVITY_DATE >= TRUNC(SYSDATE - 90)
AND hist.EMAIL = feed.EMAIL_ADDRESS
)
';
commit;
end loop;
end;
/
UPDATE
If the column names are different for each table you can use the data dictionary to pick the correct column name.
declare
type array_t is varray(12) of varchar2(25);
array array_t := array_t('BT_ABANDONED_HIST', 'BT_BROWSED_HIST', 'BT_PURCHASED_HIST', 'CM_ABANDONED_HIST', 'CM_BROWSED_HIST', 'CM_PURCHASED_HIST', 'CM_PAGE_VIEWS_HIST', 'MB_ABANDONED_HIST', 'MB_BROWSED_HIST', 'MB_CARTED_HIST', 'MB_PAGE_VIEWS_HIST', 'MB_PURCHASED_HIST');
v_column_name varchar2(30);
begin
for i in 1 .. array.count loop
select column_name
into v_column_name
from all_tab_columns
where owner = 'SCHEMA NAME'
and table_name = array(i)
and column_name in ('ACTIVITY_TIME','DATE_ABANDONED');
execute immediate '
INSERT INTO TMP1_DORMANT_EMAIL
SELECT feed.EMAIL_ADDRESS
FROM LM_DORMANT_EMAIL feed
WHERE NOT EXISTS (
SELECT 1 FROM '||array(i)||' hist
WHERE '||v_column_name||' >= TRUNC(SYSDATE - 90)
AND hist.EMAIL = feed.EMAIL_ADDRESS
)
';
commit;
end loop;
end;
/

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