Update with "with data as" clause and regex do not commit. Why? - oracle

Sorry for the delay! I've taken a workload greater then i can handle these couple weeks.
Ok, lets clarify things up!
There is a proprietary software running on top of it and
I do not have de ability to change this software! Actually it allows me to do a few things.
In this specific case I can not create a relation 1>N, I can not create a new table! What I do can is create fields.
So, how do i customize things? Through the database! Using triggers, functions and procedures. I know its a bad "work around" but it's what i got and works flawlessly 99% of times. Any exception my code throws the application shows on the screen!
The problem is actually the update not working.
You ask:
Why you're looping when you're forcing there to only be one iteration (unless P_QTDLINHAS can be < 1 I suppose?)
R: Couse the user can select multiple lines in the application but I dont want them to do it. So a throw an error on the screen.
Why you have nested begin/end blocks.
R: Couse I may have to throw exceptions, I got used to write begin Statements Exception some message end.
Sample data:
CREATE TABLE SAMPLE_DATA
(
PKNUMBER NUMBER NOT NULL
, DESCRIPTION VARCHAR2(20)
, GROUPTOCHANGE VARCHAR2(100)
, STATUS VARCHAR2(1 BYTE)
, CONSTRAINT SAMPLE_DATA_PK PRIMARY KEY
(
PKNUMBER
)
ENABLE
);
INSERT INTO sample_data VALUES (1000,'ORDER1',NULL,NULL);
INSERT INTO sample_data VALUES (2000,'ORDER2',NULL,NULL);
INSERT INTO sample_data VALUES (3000,'ORDER3',NULL,NULL);
INSERT INTO sample_data VALUES (4000,'ORDER4','1000,2000,30001',NULL);
In this case the field GROUPTOCHANGE will be filled by the user like this "2108,8090,8843". Each number represents a PKNUMBER in same table "SAMPLE_DATA".
yes i know! the user can type something wrong.. let's ignore this for now!
The field STATUS will eventually be updated to 'C','L','R' OR NULL. When this happens I Need this logic to be executed:
IF OLD.STATUS <> NEW.STATUS AND GROUPTOCHANGE IS NOT NULL THEN
UPDATE SAMPLE_DATA SP
SET SP.STATUS = :NEW.STATUS
WHERE SP.PKNUMBER IN (:NEW.GROUPTOCHAGE)
AND PS.GROUPTOCHANGE IS NULL;
END IF;
Dispite the bad design is it possible to do?
Thank for any help!!
Here what I've done so far:
create or replace PROCEDURE "AD_LIBERA_FRETES_FILHOS"(
P_CODUSU NUMBER,
P_IDSESSAO VARCHAR2,
P_QTDLINHAS NUMBER,
P_MENSAGEM OUT VARCHAR2)
AS
P_NUNOTA NUMBER(10);
P_CONTROLE VARCHAR(100);
P_PEDIDOS VARCHAR(100);
P_STATUS VARCHAR(100);
BEGIN
-- avoid more than 1 at a time
IF (P_QTDLINHAS > 1) THEN
RAISE_APPLICATION_ERROR(-20000, 'SELECIONE APENAS UM PEDIDO PARA EXECUTAR ESTA AÇÃO.');
END IF;
FOR I IN 1..P_QTDLINHAS LOOP
--extract param from session
P_NUNOTA := ACT_INT_FIELD(P_IDSESSAO, I, 'PKNUMBER');
P_STATUS := ACT_TXT_FIELD(P_IDSESSAO, I, 'STATUS');
--verify typed text should be "84090,89830,83393..."
BEGIN
SELECT REGEXP_REPLACE(CAB.GROUPTOCHANGE, '[0-9-, ]', ''),
CAB.GROUPTOCHANGE
INTO P_CONTROLE,
P_PEDIDOS
FROM SAMPLE_DATA CAB
WHERE CAB.PKNUMBER = P_NUNOTA;
END;
IF (P_CONTROLE IS NOT NULL) THEN
RAISE_APPLICATION_ERROR(-20000, '<B> SOMETHING WRONG !</B>');
ELSE
--perform de update (not working)
BEGIN
UPDATE SAMPLE_DATA C
SET C.STATUS = P_STATUS
WHERE
C.GROUPTOCHANGE IS NULL AND
C.PKNUMBER IN
(WITH DATA AS
(SELECT CAB.GROUPTOCHANGE STR
FROM SAMPLE_DATA CAB
WHERE CAB.PKNUMBER = P_NUNOTA )
SELECT TRIM(REGEXP_SUBSTR(STR, '[^,]+', 1, LEVEL)) STR
FROM DATA CONNECT BY INSTR(STR, ',', 1, LEVEL - 1) > 0);
END;
END IF;
END LOOP;
--mgs to show
P_MENSAGEM := 'DONE!! CHECK -> '||P_PEDIDOS;
END;

Related

Making a trigger with RAISERROR [duplicate]

Hello fellow programmers and happy new year to you all!
I have few university tasks for winter break and one of them is to create trigger on table:
PERSON(ID, Name, Surname, Age);
Trigger is supposed to inform user when they have inserted row with invalid ID. Vadility criteria is that ID is 11 digits long.
I tried to write solution like this:
CREATE OR REPLACE TRIGGER person_id_trigg
AFTER INSERT
ON person
DECLARE
idNew VARCHAR(50);
lengthException EXCEPTION;
BEGIN
SELECT id INTO idNew FROM INSERTED;
IF LENGTH(idNew) <> 11 THEN
RAISE lengthException;
END IF;
EXCEPTION
WHEN lengthException THEN
dbms_output.put_line('ID for new person is INVALID. It must be 11 digits long!');
END;
Then I realized that INSERTED exists only in sqlserver and not in oracle.
What would you suggest I could do to fix that?
Thanks in advance!
Do you want to raise an exception (which would prevent the insert from succeeding)? Or do you want to allow the insert to succeed and write a string to the dbms_output buffer that may or may not exist and may or may not be shown to a human running the insert?
In either case, you'll want this to be a row-level trigger, not a statement-level trigger, so you'll need to add the for each row clause.
CREATE OR REPLACE TRIGGER person_id_trigg
AFTER INSERT
ON person
FOR EACH ROW
If you want to raise an exception
BEGIN
IF( length( :new.id ) <> 11 )
THEN
RAISE_APPLICATION_ERROR( -20001,
'The new ID value must have a length of 11' );
END IF;
END;
If you want to potentially print output but allow the insert to succeed
BEGIN
IF( length( :new.id ) <> 11 )
THEN
dbms_output.put_line( 'The new ID value must have a length of 11' );
END IF;
END;
Of course, in reality, you would never use a trigger for this sort of thing. In the real world, you would use a constraint.

Why am I getting this error: "ORA-00922: missing or invalid option" after running a PL/SQL procedure?

I created a table Patient with some attributes, like p_name, p_surname, p_number... I would like to create a procedure to transfer a patient from this table Patient to another table (Patient_backup), case his "p_number" attribute has received an input, deleting it from the first table and remaining only in the second. The second table has the same structure of the first one. I have coded the procedure like that.
CREATE TABLE patient (
p_number VARCHAR2(10) NOT NULL,
p_name VARCHAR2(15),
p_surname VARCHAR2(15),
p_street VARCHAR2(20),
p_city VARCHAR2(15)
);
CREATE TABLE patient_backup (
p_number VARCHAR2(10) NOT NULL,
p_name VARCHAR2(15),
p_surname VARCHAR2(15),
p_street VARCHAR2(20),
p_city VARCHAR2(15)
);
CREATE [OR REPLACE] PROCEDURE transfer (p_number VARCHAR2)
AS
CURSOR k1 IS SELECT p_number FROM patient;
BEGIN
OPEN k1;
LOOP
FETCH k1 INTO p_number;
IF p_number IS NULL THEN
dbms_output.put_line('empty');
ELSE
INSERT INTO patient_backup (SELECT * FROM patient);
Execute Immediate 'Drop Table patient;';
END IF;
END LOOP;
CLOSE k1;
END transfer;
But when I run it,I get the error "ORA-00922: missing or invalid option". Could you help me with that? I wonder if the code is correct. I have read a material about PL/SQL, but the concepts were not connected to each other, so I just tried to gather everything together, and I hope it is correct. Could you help me to correct this code and make it work?
It's hard to tell where exactly the error is, but my guess is: remove the ; from inside the string for execute immediate.
But I think you want do not want to DROP the table - that removes the table completely from the database including all rows and its definition. It won't be accessible after that.
I think what you really want is to DELETE a row from that table, not remove the table completely.
Also: the whole loop is completely unnecessary (and inefficient). You can do that with two simple SQL statements:
insert into patient_backup
select *
from patient
where p_number = 42; --<< to pick one patient
delete from patient
where p_number = 42;
Putting that into a procedure:
CREATE PROCEDURE transfer (p_number_to_delete VARCHAR2)
AS
BEGIN
insert into patient_backup
select *
from patient
where p_number = p_number_to_delete;
delete from patient
where p_number = p_number_to_delete;
END transfer;
It's highly recommended to not use the name of a column as the name of a parameter. That's why I named the parameter p_number_to_delete (but p_number is a bad name for a column that isn't a number to begin with - but that's a different discussion)
I think you need to DECLARE your cursor before you define it.
In Your procedure code have some error
1.P_NUMBER input parameter cannot be used into statment
2.don't use semicolon inside the EXECUTE IMMEDIATE string
3. in loop statement you should use exit otherwise it will run
continuously
Here the code
CREATE OR REPLACE PROCEDURE TRANSFER (P_NUMBER IN VARCHAR2) AS
CURSOR K1 IS
SELECT P_NUMBER FROM PATIENT;
P_NUM PLS_INTEGER;
BEGIN
OPEN K1;
LOOP
FETCH K1 INTO P_NUM;
IF P_NUM IS NULL THEN
DBMS_OUTPUT.PUT_LINE('EMPTY');
ELSE
INSERT INTO PATIENT_BACKUP (SELECT * FROM PATIENT);
DELETE FROM PATIENT;
END IF;
EXIT WHEN P_NUM IS NULL;
END LOOP;
CLOSE K1;
END TRANSFER;

With cluse in insert

Odd things going on with procedure below.
When I run procedure it insert into table only 125 records. When I insert data without procedure with the same parameters it gives me 15000 records. Do you have any ideas what could it be?
create or replace procedure calc_p(p_startdate number,
p_end_date number,
p_id number) is
begin
dbms_output.put_line('Start: ' || substr(localtimestamp, 1, 17));
delete rep_pd
where b_id = p_id
and rep_date between p_startdate and p_end_date;
-- commit;
insert into rep_pd
with cont
(rep_date, b_id, con_number, okres_zap) as
(select /*+ materialize*/
rep_date,
b_id,
con_number,
nvl(maturity_date - origin_date, 0) as okres_zap
from con#sg_al
where description not in ('OVER', 'Overn')
and b_id = p_id
and rep_date between p_startdate
and p_end_date)
select mr.suffix1,
mr.data_danych,
ma.rep_date,
b_id,
mr.k_symbol,
konto_nazwa,
case
when mr.k_nazwa likr '%EXT%' then
'04'
when mr.k_symbol like '3731%' then
'12'
else
mr.typ_k_symbol
end as cl_t,
ma.okres_zap,
mr.rezydent_symbol,
mr.w_symbol as cu,
mr.sal_ma_pl as outstanding,
get_pd_account(k_symbol) AS depo substr(to_number(to_char(data_danych, 'yyyymmdd')), 7, 2) as dzien
from abc.a_mr mr
left join cont ma
on ma.b_id = p_id
and to_date(ma.rep_date, 'YY/MM/DD') = mr.data_danych
and ma.con_number = mr.suffix1
where sal_ma_pl <> 0
and get_pd_account(k_symbol) is not null
and nvl(typ_k_symbol, '20') not in ('01', '02', '02A')
and mr.data_danych between to_date(p_startdate, 'YY/MM/DD') and
to_date(p_end_date, 'YY/MM/DD');
-- commit;
dbms_output.put_line('Koniec: ' || substr(localtimestamp, 1, 17));
end calc_p;
There are some errors or unadvised uses in your code:
When I copy-paste your code in my editor, I see an error. There is , missing between your depo and dzien columns in select.
Before anything else, double check that you gave us a correct code and that you haven't erased one part of the code. Also, double check that you haven't missed the same part of the code when transferring it in or out of the procedure.
As Littlefoot stated in the comment to your question, there should be no difference between INSERT stetements inside and outside stored procedures, if they are just being encapsulated with BEGIN END. There is one difference however, and that is the way you're defining your input parameters.
I see that you are defining your p_startdate and p_end_date as NUMBER parameters. Possible difference is that you're really expecting dates. The difference between the results of your insert inside and outside the procedure can easily be due to defining your p_startdate and p_end_date parameters as numbers.
If after these corrections you still get a difference in results, please provide us with some sample table data that is getting you that difference.

How to handle exception within trigger

I want to add exception handling within trigger.
I created Trigger as below.
I want to add exception handling within that. So that my trigger never failed if there are any invalid condition.
CREATE OR REPLACE TRIGGER system_notification_audit
AFTER
INSERT OR
UPDATE
on system_notification
FOR EACH ROW
begin
insert into system_notification_log
select :NEW.ID , :NEW.NAME, :NEW.Description, :NEW.PREFERENCE, :NEW.FREQUENCY,
:NEW.IS_HIGH, :NEW.IS_REQUIRED, :NEW.UPDATED_BY, :NEW.UPDATED_DATE
from dual
where :OLD.PREFERENCE <> :NEW.PREFERENCE
OR :OLD.FREQUENCY <> :NEW.FREQUENCY OR :NEW.IS_HIGH <> :OLD.IS_HIGH OR :NEW.IS_REQUIRED <> :OLD.IS_REQUIRED;
END;
Simply Ignoring an Exception isn't always a good way to design your program. At least log the errors somewhere for later observation.
Note that your INSERT statement can be rewritten using a simple insert ( without a select ) and an IF condition.
CREATE OR REPLACE TRIGGER system_notification_audit AFTER
INSERT OR UPDATE ON system_notification
FOR EACH ROW
BEGIN
IF
:old.preference <>:new.preference OR :old.frequency <>:new.frequency
OR :new.is_high <>:old.is_high OR :new.is_required <>:old.is_required
THEN
INSERT INTO system_notification_log (
id,
name,
description,
preference,
frequency,
is_high,
is_required,
updated_by,
updated_date
) VALUES (
:new.id,
:new.name,
:new.description,
:new.preference,
:new.frequency,
:new.is_high,
:new.is_required,
:new.updated_by,
:new.updated_date
);
END IF;
EXCEPTION WHEN OTHERS THEN
pr_trigger_logs(trig_name => 'system_notification_audit',
err_msg => DBMS_UTILITY.FORMAT_ERROR_BACKTRACE());
-- calling an error logging procedure.
END;
/
I won't give you the definition of procedure pr_trigger_logs. As an exercise, I'll let you come up with it!.

Optimizing a simple procedure

Below is my procedure, takes 51 sec to execute, I want to return cursor only if one count is found, in case anything else will return message and cursor as null. In case cursor is found message as null..
I am first taking count by query and populating data by the same query later but only in case of count being one.
is their anyway in which this could be optimized in terms of time.?
create or replace PROCEDURE sp_cp_getcrnnofrmmobdob(P_MobileNo IN VARCHAR2,
P_Dob IN VARCHAR2,
p_Output out SYS_REFCURSOR,
p_Message OUT VARCHAR2) IS
vCRN Varchar2(50) := '';
vCustid varchar2(50) := '';
vMobno varchar2(50) := '';
vCustname varchar2(400) := '';
vCustDob varchar2(50) := '';
vcount int := 0;
BEGIN
p_Message := '';
OPEN p_Output FOR
select 1 from dual;
Select count(*)
into vcount
FROM (select distinct(C.fw_customer_id_c) crn,
C.Cust_Id_n custid,
c.customername custname,
c.dob custdob,
A.MOBILE mobileno
from FCH_CASTRANSACTION.NBFC_CUSTOMER_M C,
FCH_CASMASTER.nbfc_address_m A
where A.BPID = C.Cust_Id_n and
A.mobile = P_MobileNo and
TO_CHAR(TO_DATE(C.DOB, 'DD-MON-YY'),'DD-MON-YY')=TO_CHAR(TO_DATE(P_Dob,'DD/MM/YYYY'),'DD-MON-YY'));
if (vcount = 1) then
select B.crn,
B.custid,
B.mobileno,
B.custname,
B.custdob
into vCRN, vCustid, vMobno, vCustname, vCustDob
from (select distinct(C.fw_customer_id_c) crn,
C.Cust_Id_n custid,
c.customername custname,
c.dob custdob,
A.MOBILE mobileno
from FCH_CASTRANSACTION.NBFC_CUSTOMER_M C,
FCH_CASMASTER.nbfc_address_m A
where A.BPID = C.Cust_Id_n and
A.mobile = P_MobileNo and
TO_CHAR(TO_DATE(C.DOB, 'DD-MON-YY'),'DD-MON-YY')=TO_CHAR(TO_DATE(P_Dob,'DD/MM/YYYY'),'DD-MON-YY')) B;
if ((vCRN = '') OR (vCRN IS Null)) then
p_Message := 'No data found for entered details';
else
if ((vMobno <> P_MobileNo) OR (vMobno IS Null)) then
p_Message := 'Entered mobile number is not registered with us.Please contact customer care.';
else
if ((vCustDob <> TO_CHAR(TO_DATE(P_Dob,'DD/MM/YYYY'),'DD-MON-YY')) OR (vCustDob IS Null)) then
p_Message := 'Entered date of birth is not registered with us.Please contact customer care.';
else
OPEN p_Output FOR
select vCRN as "CrnNum", vCustid as "CustId", vMobno as "MobNo", vCustname as "CustName", vCustDob as "CustDob"
from dual;
End if;
End if;
End if;
else
p_Message := 'Inconsistent details for entered data found. Please contact customer care';
End if;
EXCEPTION
WHEN NO_DATA_FOUND THEN
p_Message := 'Unable to process your request.Please contact customer care.';
OPEN p_Output FOR
SELECT 1 FROM dual;
END;
Would really appreciate if someone can help.
You can just use one SELECT ... INTO ... and catch the exception TOO_MANY_ROWS.
'' and NULL are the same thing.
It will not return a row if the mobile number does not match (or is null) so that check is redundant.
Same for the date of birth check.
DISTINCT is NOT a function - it is a keyword that applies to all the rows.
You assigning a cursor to p_output twice. Also, some systems may not like that the function can return different numbers of columns to your cursor.
So, something like this:
create or replace PROCEDURE sp_cp_getcrnnofrmmobdob(
P_MobileNo IN VARCHAR2,
P_Dob IN VARCHAR2,
p_Output out SYS_REFCURSOR,
p_Message OUT VARCHAR2
)
IS
v_dob DATE := TO_DATE( p_dob, 'DD/MM/YYYY' );
vCRN FCH_CASTRANSACTION.NBFC_CUSTOMER_M.fw_customer_id_c%TYPE;
vCustid FCH_CASTRANSACTION.NBFC_CUSTOMER_M.Cust_Id_n%TYPE;
vMobno FCH_CASMASTER.nbfc_address_m.MOBILE%TYPE;
vCustname FCH_CASTRANSACTION.NBFC_CUSTOMER_M.customername%TYPE;
vCustDob FCH_CASTRANSACTION.NBFC_CUSTOMER_M.dob%TYPE;
BEGIN
p_Message := '';
select distinct
C.fw_customer_id_c,
C.Cust_Id_n,
c.customername,
c.dob,
A.MOBILE
into vCRN, vCustid, vMobno, vCustname, vCustDob
from FCH_CASTRANSACTION.NBFC_CUSTOMER_M C
INNER JOIN FCH_CASMASTER.nbfc_address_m A
ON ( A.BPID = C.Cust_Id_n )
WHERE A.mobile = P_MobileNo
AND TO_DATE( C.DOB, 'DD-MON-YY') = v_dob;
IF vCRN IS NULL THEN
p_Message := 'No data found for entered details';
OPEN p_Output FOR
select 1 from dual;
RETURN;
END IF;
OPEN p_Output FOR
select vCRN as "CrnNum", vCustid as "CustId", vMobno as "MobNo", vCustname as "CustName", vCustDob as "CustDob"
from dual;
EXCEPTION
WHEN NO_DATA_FOUND THEN
p_Message := 'Unable to process your request.Please contact customer care.';
OPEN p_Output FOR
SELECT 1 FROM dual;
WHEN TOO_MANY_ROWS THEN
p_Message := 'Inconsistent details for entered data found. Please contact customer care';
OPEN p_Output FOR
SELECT 1 FROM dual;
END;
Looking at TO_CHAR(TO_DATE(C.DOB, 'DD-MON-YY'),'DD-MON-YY')=TO_CHAR(TO_DATE(P_Dob,'DD/MM/YYYY'),'DD-MON-YY')) in both of your queries, I suggest.
Based on this logic, you are storing the date of birth (dob) as a string. It is a shame if you do, it should be in the database as a DATE.
You are converting the strings to a date and then back to a string. In Oracle, you can compare dates, so only convert from column to DATE, not again back to a string. Such as TO_DATE(column,'column format')=TO_DATE(variable,'variable format')
Or, better yet, for your data model, consider just converting the variable input date string to match the column string format. Like column = TO_CHAR(TO_DATE(variable, 'variable format'),'column format'). There are two possible advantages here. First, the conversion will only happen once for the supplied value, but the query never has to perform a function on the column value. If the table is big. Also, since there is no function to be performed on the column value, IF there is an index on this value, the optimizer can use it (although this may not help your example based on my guess on your data model). Performance improvement here would depend on how the dob is used by your query. If Oracle is lookup up records by mobile number, then filter by dob, it shouldn't make much difference, but if it goes the other way around, looking up by dob then filtering on mobile number, this could help immensely.
tl;dr Store dates as dates, compare dates as dates, avoid functions on the column values when possible

Resources