Not use to use dbms_lob.compare, so the update works fine, but when it reach the IF statement look like only do the same thing when it suppost to alternate like u can see bellow
My PL/SQL code:
CREATE OR REPLACE PROCEDURE teste
IS
aux CLOB;
cnt NUMBER := 0;
cnt1 NUMBER := 0;
BEGIN
FOR rec IN (SELECT xxxx)
LOOP
aux := rec.VALUE;
UPDATE db
SET VALUE = TO_CLOB(deletexml(
xmltype(VALUE),
'//*:getPaymentDetailsResponse/*:Payment/*:childs[./*:status[text()="Failed"]]'
))
WHERE id=rec.gb_ID;
--Teste
IF DBMS_LOB.compare(rec.VALUE, aux) = 0 THEN
DBMS_OUTPUT.put_line('### ORDERS NOT CHANGED ###');
cnt1 := cnt1 + 1; --count orders without any change
DBMS_OUTPUT.put_line(cnt1 || '- ' || rec.ORDER_PUBLIC_ID);
ELSE
DBMS_OUTPUT.put_line('### ORDERS CHANGED ###');
cnt := cnt +1; --count changed orders
DBMS_OUTPUT.put_line(cnt || '- ' || rec.ORDER_PUBLIC_ID);
END IF;
END LOOP;
-- Print count results
DBMS_OUTPUT.put_line('Orders without changing: '|| cnt1 || ' orders.');
DBMS_OUTPUT.put_line('Orders updated: '|| cnt || ' orders.');
END;
/
This is what i am currently getting:
ORDERS NOT CHANGED
1- 160000
ORDERS NOT CHANGED
2- 160000
ORDERS NOT CHANGED
3- 160313
ORDERS NOT CHANGED
4- 160313
What I want to happen:
ORDERS CHANGED
1- 160000
ORDERS NOT CHANGED
2- 160000
ORDERS CHANGED
3- 160313
ORDERS NOT CHANGED
4- 160313
When you create a cursor it is a snapshot of the data as it exists at the time you run the select. Any table changes that are made during the execution of the cursor do not get reflected in the cursor. see Do database cursors pick up changes to the underlying data? for more details.
So you update the base table for your cursor (I assume as you don't show the actual select)
UPDATE db
SET VALUE = TO_CLOB(deletexml(
xmltype(VALUE),
'//*:getPaymentDetailsResponse/*:Payment/*:childs[./*:status[text()="Failed"]]'
))
WHERE id=rec.gb_ID;
Then you do a compare using the details in your cursor
IF DBMS_LOB.compare(rec.VALUE, aux) = 0 THEN
As the cursor is a snapshot this will always come back as Not Changed, you would have to re-select the value from your base table to or use SQL%ROWCOUNT to check if your update statment affected any rows (see https://community.oracle.com/thread/2370954?start=0&tstart=0) for details of how to use this.
Related
My requirement is that I have a table where there are 4 columns(val1,val1_date,new_val1,new_val1_date) in it.
The issue is that when new_val1 gets updated along with new_val1_date,I need to update columns val1,val1_date accordingly.
For that I have created a function.
Now when new_val1_date is equal to sysdate then I want to execute the trigger.
My trigger is not giving me proper result.
Can anyone help me in that.
create or replace TRIGGER MY_Trigger
AFTER INSERT OR UPDATE ON my_table
FOR EACH ROW
WHEN (TO_DATE(NEW.new_val1_date,'dd/mon/yyyy')<=TO_DATE(sysdate,'dd/mon/yyyy'))
declare
V_CD_ERROR NUMBER;
V_DS_ERROR VARCHAR2(500);
begin
My_UpdateProc(V_CD_ERROR,V_DS_ERROR);
DBMS_OUTPUT.PUT_LINE('V_CD_ERROR: ' || V_CD_ERROR || ' V_DS_ERROR ' || V_DS_ERROR);
end;
Oracle Date columns store the timestamp, so unless your inserted value matches sysdate down to the second, it won't have the expected result.
You need to "trunc" the dates so the timestamp is set to midnight for both values. Then it will evaluate based on the date.
Also, the evaluation should be done inside the begin/end block of the trigger.
create or replace TRIGGER MY_Trigger
AFTER INSERT OR UPDATE ON my_table
FOR EACH ROW
declare
V_CD_ERROR NUMBER;
V_DS_ERROR VARCHAR2(500);
begin
if (trunc(NEW.new_val1_date) = trunc(sysdate)) then
My_UpdateProc(V_CD_ERROR,V_DS_ERROR);
DBMS_OUTPUT.PUT_LINE('V_CD_ERROR: ' || V_CD_ERROR || ' V_DS_ERROR ' || V_DS_ERROR);
end if;
end;
I'm currently working on modeling a data warehouse using Data Vault modeling.
My problem is the following: I want to define for each satellite the last state according to a business key.
Here are the elements and the algorithm that I want to put in place:
I have two tables.
The first table (SAT) contains a history of events whose primary key is composed of a business key and a date of update of this business key. This table is powered in real time.
The second table (LAST_SAT) contains the last state of each event whose primary key consists only of a business key. The update date is the maximum update date of the first table. This table is fed once a day.
Here is the algorithm that I want implemented in PL / SQL:
For each satellite pair (SAT), and last state satellite (LAST_SAT):
Identify the last update date of LAST SAT (if the return is zero because the table is empty, then value 01/01/1900): MAX_DATE
Select the data in the SAT table updated after MAX_DATE.
Compare selected data (2) with LAST_SAT data to update LAST_SAT with new and updated data.
3.a. Delete data from LAST_SAT present in SAT (according to business key)
3.b. Insert the selected data (2) in LAST_SAT in an orderly fashion.
Here is the program I started writing in PL / SQL, but my level is too low to achieve this.
/* Formatted on 29/07/2019 15:25:07 (QP5 v5.185.11230.41888) */
DECLARE
v_maxVal VARCHAR2 (200);
requete VARCHAR2 (200);
TYPE TabCur IS REF CURSOR;
v_tab_cursor TabCur;
BEGIN
FOR sat IN (SELECT table_name, column_name
FROM USER_TAB_COLUMNS
WHERE column_name LIKE '%UPDATE%' AND table_name = 'S_SALE'
)
LOOP
EXECUTE IMMEDIATE 'SELECT MAX(' ||sat.column_name || ') FROM ' || 'LS_'|| substr(sat.table_name,1,29) INTO v_maxVal;
requete := 'SELECT * FROM '|| sat.table_name || ' WHERE '|| sat.column_name || ' >= ''' || v_maxVal || '''';
OPEN v_tab_cursor FOR requete;
LOOP
FETCH v_tab_cursor INTO rec;
EXIT WHEN v_tab_cursor%NOTFOUND;
DBMS_OUTPUT.PUT_LINE(rec.CONT_CONT_CONTRAT_REF);
END LOOP;
CLOSE v_tab_cursor;
--DBMS_OUTPUT.put_line (sat.table_name || ' -> ' || sat.column_name || ' : ' || v_maxVal);
--DBMS_OUTPUT.put_line (sat.table_name || ' : ' || v_maxVal);
END LOOP;
END;
Can you help me in my goal?
Thank you in advance,
Guillaume.
Im trying to learn PL/SQL and I was given an assignment which I am not sure how to tackle.
I am given a list of orders. I want to check my ORDER table for each of them in the following way:
Check if order exists, if no create a record
Check if order fullfilled (0 or 1)
If order is not fullfilled (0), update to 1
I put together a script which I think can do this for one order, but I'm sure it's not very good:
DECLARE
tmp NUMBER;
tmp2 NUMBER;
o_id NUMBER := 999;
BEGIN
/*Checking if order exists */
SELECT COUNT (*)
INTO tmp
FROM ORDERS
WHERE ORDERID = o_id;
IF ( tmp = 0 ) THEN
/* INSERT HERE */
END IF;
SELECT FULLFILLED INTO tmp2
FROM ORDERS
WHERE ORDERID = o_id;
IF (tmp2 = 0) THEN
/* UPDATE... */
END IF;
end;
I would appreciate any advice, what should I look into to make this script efficient? Thank you.
MERGE statement is what you need. It is based on SELECT statement and let's you UPDATE or INSERT data using it's WHEN (NOT) MATCHED THEN clauses. Here's a good explanation with some examples: Oracle Base MERGE Statement.
Here's also some code snippet you might find useful:
DECLARE
o_id NUMBER := 999;
BEGIN
MERGE INTO ORDERS o
USING
(SELECT o_id AS orderid FROM dual) o_id
ON
(o.orderid = o_id.orderid)
WHEN MATCHED THEN
UPDATE SET
o.fulfilled = CASE WHEN o.fulfilled = 0 THEN 1 ELSE o.fulfilled END
WHEN NOT MATCHED THEN
INSERT (fulfilled, <some_other_columns>)
VALUES (1, <values_for_other_columns>);
END;
/
Please read up on the merge statement: https://docs.oracle.com/cd/B28359_01/server.111/b28286/statements_9016.htm
Also called an "upsert". Basically if the row does not exist, insert. If it does, update.
It does what you are trying to do in one statement.
I have this trigger:
create or replace TRIGGER TR14_2
BEFORE INSERT OR UPDATE OF CANTIDAD ON DISTRIBUCION
FOR EACH ROW
DECLARE
total_cars NUMBER;
total_cars_potential NUMBER;
BEGIN
SELECT sum(cantidad) into total_cars
FROM DISTRIBUCION
WHERE cifc = :NEW.cifc;
total_cars_potential := total_cars + :NEW.cantidad;
IF INSERTING THEN
IF(total_cars_potential > 40) THEN
raise_application_error(-20005, 'Dealer [' || :NEW.cifc || '] already has 40 cars stocked');
END IF;
END IF;
IF UPDATING THEN
IF(total_cars_potential - :OLD.cantidad > 40) THEN
raise_application_error(-20006, 'That update of CANTIDAD makes the dealer exceeds the limit of 40 cars stocked');
END IF;
END IF;
END;
It gets a mutating table error, and I have checked that is because of the UPDATING block of code, the INSERTING goes ok; but why? And how can I fix it?
Just to clarify, I want that each dealer can have at maximum 40 cars stocked. So, if I add a row to DISTRIBUCION ("distribution") with cantidad ("quantity") that will make the dealer exceed its maximum stock, I will raise an error.
But, if I update a quantity of cars of a type, stocked already in the database, and I exceed 40 cars, I want also a exception to be thrown.
Thing is, I am not seeing the mutatig table error on the UPDATING block.
1st: The reason you get a mutating table syndrome is that you're reading from the table that is updated in the trigger (selecting the total cars).
2nd: Solution: I'd probably create 2 triggers: A for-each-row trigger as you did that collects the updated rows in a package variable
CREATE OR REPLACE PACKAGE PCK_TR14_2 IS
TYPE changed_row IS RECORD (
cantidad DISTRIBUCION.cantidad%TYPE,
);
TYPE changed_row_table IS TABLE OF changed_rows INDEX BY binary_integer;
changed_rows changed_row_table;
cnt_changed_rows BINARY_INTEGER DEFAULT 1;
END PCK_TR14_2;
/
The for-each-row trigger would look something like this (now an after insert!)
create or replace TRIGGER TR14_2
AFTER INSERT OR UPDATE OF CANTIDAD ON DISTRIBUCION
FOR EACH ROW
DECLARE
BEGIN
PCK_TR14_2.changed_rows(PCK_TR14_2.cnt_changed_rows).cantidad := :old.cantidad;
PCK_TR14_2.cnt_changed_rows := PCK_TR14_2.cnt_changed_rows + 1;
END;
/
Then in a after statement trigger implement your logic:
CREATE OR REPLACE TRIGGER TR14_2_S
AFTER INSERT OR UPDATE OF CANTIDAD ON DISTRIBUCION
BEGIN
FOR i IN PCK_TR14_2.changed_rows.FIRST..PCK_TR14_2.changed_rows.LAST LOOP
-- YOUR LOGIC HERE
null;
END LOOP;
END;
/
Access the candidat in the "virtuell" table as needed ( PCK_TR14_2.changed_rows(i). CANTIDAD)
A trigger can not change a table that it has read from. This is the mutating table error issue.
You can see the solutions provided in the below link
Mutating table error
avoiding_mutating_table_error
I need to run some SQL blocks to test them, is there an online app where I can insert the code and see what outcome it triggers?
Thanks a lot!
More specific question below:
<<block1>>
DECLARE
var NUMBER;
BEGIN
var := 3;
DBMS_OUTPUT.PUT_LINE(var);
<<block2>>
DECLARE
var NUMBER;
BEGIN
var := 200;
DBMS_OUTPUT.PUT_LINE(block1.var);
END block2;
DBMS_OUTPUT.PUT_LINE(var);
END block1;
Is the output:
3
3
200
or is it:
3
3
3
I read that the variable's value is the value received in the most recent block so is the second answer the good one? I'd love to test these online somewhere if there is a possibility.
Also, is <<block2>> really the correct way to name a block??
Later edit:
I tried this with SQL Fiddle, but I get a "Please build schema" error message:
Thank you very much, Dave! Any idea why this happens?
create table log_table
( message varchar2(200)
)
<<block1>>
DECLARE
var NUMBER;
BEGIN
var := 3;
insert into log_table(message) values (var)
select * from log_table
<<block2>>
DECLARE
var NUMBER;
BEGIN
var := 200;
insert into log_table(message) values (block1.var || ' 2nd')
select * from log_table
END block2;
insert into log_table(message) values (var || ' 3rd')
select * from log_table
END block1;
In answer to your three questions.
You can use SQL Fiddle with Oracle 11g R2: http://www.sqlfiddle.com/#!4. However, this does not allow you to use dbms_output. You will have to insert into / select from tables to see the results of your PL/SQL scripts.
The answer is 3 3 3. Once the inner block is END-ed the variables no longer exist/have scope. You cannot access them any further.
The block naming is correct, however, you aren't required to name blocks, they can be completely anonymous.
EDIT:
So after playing with SQL Fiddle a bit, it seems like it doesn't actually support named blocks (although I have an actual Oracle database to confirm what I said earlier).
You can, however, basically demonstrate the way variable scope works using stored procedures and inner procedures (which are incidentally two very important PL/SQL features).
Before I get to that, I noticed three issues with you code:
You need to terminate the insert statements with a semi-colon.
You need to commit the the transactions after the third insert.
In PL/SQL you can't simply do a select statement and get a result, you need to select into some variable. This would be a simple change, but because we can't use dbms_output to view the variable it doesn't help us. Instead do the inserts, then commit and afterwards select from the table.
In the left hand pane of SQL Fiddle set the query terminator to '//' then paste in the below and 'build schema':
create table log_table
( message varchar2(200)
)
//
create or replace procedure proc1 as
var NUMBER;
procedure proc2 as
var number;
begin
var := 200;
insert into log_table(message) values (proc1.var || ' 2nd');
end;
begin
var := 3;
insert into log_table(message) values (var || ' 1st');
proc2;
insert into log_table(message) values (var || ' 3rd');
commit;
end;
//
begin
proc1;
end;
//
Then in the right hand panel run this SQL:
select * from log_table
You can see that proc2.var has no scope outside of proc2. Furthermore, if you were to explicitly try to utilize proc2.var outside of proc2 you would raise an exception because it is out-of-scope.