Cursor locking the table - sql

declare
CURSOR C1
IS select tgt.exp_date ,(src.eff_date - 1/(24*60*60))eff_date
from mira_rate tgt,mira_rate_dummy src
where src.tc_code = tgt.tc_code and src.carrier_code = tgt.carrier_code and tgt.exp_date is null for update of tgt.exp_date;
v_a date;
v_b date;
i number:=0;
begin
open c1;
loop
fetch c1 into v_a, v_b;
exit when c1%notfound;
update mira_rate
set exp_date =v_b where current of c1;
i:=i+1;
end loop;
dbms_output.put_line(i||' rows updated');
close c1;
commit;
end;
After i excecute the query it is locking the table says
ORA-00054: resource busy and acquire with NOWAIT specified
Also pls tell me how to remove the lock i tried killing the sesssion it is not happening.still it says the same
Affter removing the lock. Pls clear me this requirement
select tgt.exp_date ,(src.eff_date - 1/(24*60*60))eff_date
from mira_rate tgt,mira_rate_dummy src
where src.tc_code = tgt.tc_code and src.carrier_code = tgt.carrier_code and tgt.exp_date is null;
it ill return rows I need to goto the mira_rate table need to update exp_date=eff_date.
Please suggest me how to do i m using Oracle 9i so merge without not matched is working

At first sight, there is no commit in the code.
The code with commit wil be ok. Commit will release the locks(Oracle cursor examples/expl)
But better you would:
MERGE INTO mira_rate tgt
USING mira_rate_dummy src
ON (src.tc_code = tgt.tc_code and src.carrier_code = tgt.carrier_code)
WHEN MATCHED THEN UPDATE
SET exp_date= src.eff_date - 1/(24*60*60) --or just src.eff_date
WHERE tgt.exp_date is null;
This is what you want to do as far as I understand.
As a rule: What you can do in SQL, do in SQL, not PL/SQL.

Take out the 'FOR UPDATE'.
You need to be very clear in your mind why you need it and in my experience you generally don't.
Between us I think we are saying this should be your approach
begin
UPDATE mira_rate
SET exp_date= src.eff_date - 1/(24*60*60)
WHERE exp_date is null;
DBMS_OUTPUT.PUT_LINE
(TO_CHAR(SQL%ROWCOUNT) || ' Rows Updated);
end;
No need for locks and no need for cursors.
Hope that helps.
Edit - still not entirely sure what your requirement is but the following sql may be what you are looking for.
UPDATE MIRA_RATE TGT
SET EXP_DATE =
(
SELECT SRC.EFF_DATE - 1/86400
FROM MIRA_RATE_DUMMY SRC
WHERE
SRC.TC_CODE = TGT.TC_CODE AND
SRC.CARRIER_CODE = TGT.CARRIER_CODE
)
WHERE
TGT.EXP_DATE IS NULL;

#Satheesh, Updatable select will work only for primary key columns. See if the select fetches the primary key and also useins it in where clause. Else the update will throw error.
There is something to check for
cannot modify a column which maps to a non key-preserved table
You can have join but the update needs primary key to update in the base table.

Related

I must not add row with 2 same values in columns

Can I enter a rule when creating a table so that I, as an author, can't add a review to a product I've already reviewed?
I've been thinking about triggers, but I don't know exactly how to set it. In the workbench I can check it via this code:
declare
pocet number := 0;
begin
SELECT COUNT(a."id_recenze")
INTO pocet
FROM "recenze" a
INNER JOIN (SELECT "id_komponenty", "id_autora"
FROM "recenze"
GROUP BY "id_komponenty", "id_autora"
HAVING COUNT(*) > 1) b
ON a."id_komponenty" = b."id_komponenty" AND a."id_autora" = b."id_autora";
if pocet > 2 then
DBMS_OUTPUT.put_line('Nesmite vytvaret recenzi na komponentu, u ktere jste uz recenzoval');
else
DBMS_OUTPUT.put_line('Vysledek je v poradku');
end if;
end;
But I don't want to be able to create these records.
Can someone help me, how i can do it?
I use APEX by Oracle.
EDIT: (24.4. 10:35)
In a nutshell, i don't want records, where id_autora & id_komponenty are more times. For example i don't want this:
id_recenze(PK) id_autora id_komponenty
1 2 3
2 2 3
After your explanation I see you could still use a unique index but you want to create it on id_komponenty and id_autora. That would throw an error if you tried to add a duplicate.
But I see from your code that you are trying to update with the most recent values if it's duplicated. In that case I would abandon the idea of the index and the trigger, and I would replace the INSERT statement (not shown) with Oracle's MERGE statement. This allows a simultaneous logic for insert and update, plus you get to define the criteria when you do either. It would look something like:
MERGE INTO recenze r
USING (Select <newid_komponenty> AS newk
,<newid_autora AS newa> from Dual) n
ON (r.id_komponenty=n.newk And r.id_autora=n.newa)
WHEN MATCHED THEN UPDATE SET {your update logic here}
WHEN NOT MATCHED THEN INSERT {your insert logic here}
Personally, I try to stay away from triggers when there are other solutions available. By altering your Insert statement to this Merge you get the same effect with one less DB object to keep track of and maintain.
I get it.
CREATE TRIGGER "nesmiPridat" BEFORE INSERT ON "recenze"
FOR EACH ROW BEGIN
DECLARE pocet INT(2);
DECLARE smazat INT(2);
SET pocet := (SELECT COUNT("id_recenze") FROM "recenze" WHERE (NEW."id_autora" = "id_autora") AND (NEW."id_komponenty" = "id_komponenty"));
SET smazat :=(SELECT "id_recenze" FROM "recenze" WHERE (NEW."id_autora" = "id_autora") AND (NEW."id_komponenty" = "id_komponenty"));
IF (pocet > 0) THEN
DELETE FROM "recenze" WHERE smazat."id_recenze" = NEW."id_recenze";
END IF;
END;

Matching and updating two table

I am going to fetch every row from one table and find the equivalent in another table. Then i am going to update the rows of the second table by using the id which i have already gotten.
I tried to run my script but i had some problems.
I actually tried to make a loop and then put the id of every row in a variable to use them for my update statement but Pl shows me an error which tells me "not data found"
My unfinished script
DECLARE
tbl1Count number(4);
counter number(4);
MyO66ID number(8);
Begin
select Count(*) INTO tbl1Count from crbank ;
<<my_loop>>
For counter IN 1..tbl1Count-1 Loop
select O66ID INTO MyO66ID from crbank where rownum=counter;
End loop my_loop;
End;
You have written a strange logic in this scenario
This should work:
DECLARE
tbl1Count number(4) :=0;
MyO66ID number(8);
Begin
-- select Count(*) INTO tbl1Count from crbank; -- not needed at all
For myItems IN (select O66ID, ROWNUM, whatever_columns_you_need from crbank) Loop
MyO66ID := myItems.O66ID;
tbl1Count := tbl1Count + 1; -- this will serve you better than the first select if you are concerned of the number of rows you have.
/*
Do your logic here for the values you have in the myItems object
EX: update yourTable set yourColumn = myItems.otherColumn where id= myItems.something
You dont need variables to be defined if you noticed as in the above example.
*/
End loop;
End;
Hints:
You are getting the count, then looping on the count you get and matching it with rownum!, which is not a best practice; hitting your database twice, for count and for select, although you can do it in once loop, and no need for the first select
rownum will be different for each select statement, depending on the order you specified, so is it wise to use it?
You have mentioned in your question
I am going to fetch every row from one table and find the equivalent in another table
Oracle just have a workaround for this type of conditions. MERGE statement is very useful in these typical scenarios. Consider the below illustrated snippet. Let me know if this helps.
Whenever it is possible try to use pure SQL over PL/SQL
MERGE INTO <Update_table> USING <LOOKUP_TABLE>
ON
(UPDATE_TABLE.COLUMN_NAME = LOOKUP_TABLE.COLUMN_NAME)
WHEN MATCHED THEN
UPDATE SET
<UPDATE_TABLE.COLUMN_NAME> = <Update_value>
;
Try this one using cursor in sql.
Declare #id bigint
DECLARE CUR CURSOR FOR
select data from table1
open CUR
Fetch next from cur into #id
while ##FETCH_STATUS=0
begin
update table2 set columnname=value where id=#id
Fetch next from cur into #id
end
CLOSE CUR
DEALLOCATE CUR

Error while working with dates in pl/sql?

This is how it's supposed to work:
I have a table called Pro2 with a column called finall that is a date and another one called validade. If finall has already happened or is happening validade is set to 0.
BEGIN
FOR validade in (SELECT * FROM PRO2 WHERE TRUNC(finall)<= TRNCU(SYSDATE))
LOOP
SET validade = 0
END LOOP;
END;
I'm new in PL/SQL please help!
You have an error in your second TRUNC command.
You aren't supposed to set validade (which is a CURSOR) to any value.
Assignment operator is := and not SET in PL/SQL.
Try this:
BEGIN
FOR r_record IN (SELECT *
FROM PRO2
WHERE TRUNC(finall) <= TRUNC(SYSDATE))
LOOP
UPDATE PRO2 t1
SET t1.validade = 0
WHERE t1.id = r_record.id;
END LOOP;
END;
You shouldn't use PL/SQL for such thing!
Use SQL when it is possible!
UPDATE PRO2
SET validade = 0
WHERE TRUNC(finall) <= TRUNC(SYSDATE);
Every well-designed Oracle table has a primary key, often called id or ending with it. That is what Alex meant by t1.id. You should ask for the column(-combination) that makes the primary key for PRO2 and substitute that.
DML code like the shown UPDATE can be included in PLSQL and is really the best solution:
BEGIN
-- other things here ....
UPDATE pro2
SET validade = 0
WHERE TRUNC( finall ) <= TRUNC( SYSDATE );
-- and here ...
END;

For loop update better alternative

In Oracle 11g, I am using the following in a procedure.. can someone please provide a better solution to achieve the same results.
FOR REC IN
(SELECT E.EMP FROM EMPLOYEE E
JOIN
COMPANY C ON E.EMP=C.EMP
WHERE C.FLAG='Y')
LOOP
UPDATE EMPLOYEE SET FLAG='Y' WHERE EMP=REC.EMP;
END LOOP;
Is there a more efficient/better way to do this? I feel as if this method will run one update statement for each record found (Please correct me if I am wrong).
Here's the is actual code in full:
create or replace
PROCEDURE ACTION_MSC AS
BEGIN
-- ALL MIGRATED CONTACTS, CANDIDATES, COMPANIES, JOBS
-- ALL MIGRATED CANDIDATES, CONTACTS
FOR REC IN (SELECT DISTINCT AC.PEOPLE_HEX
FROM ACTION AC JOIN PEOPLE P ON AC.PEOPLE_HEX=P.PEOPLE_HEX
WHERE P.TO_MIGRATE='Y')
LOOP
UPDATE ACTION SET TO_MIGRATE='Y' WHERE PEOPLE_HEX=REC.PEOPLE_HEX;
END LOOP;
-- ALL MIGRATED COMPANIES
FOR REC IN (SELECT DISTINCT AC.COMPANY_HEX
FROM ACTION AC JOIN COMPANY CM ON AC.COMPANY_HEX=CM.COMPANY_HEX
WHERE CM.TO_MIGRATE='Y')
LOOP
UPDATE ACTION SET TO_MIGRATE='Y' WHERE COMPANY_HEX=REC.COMPANY_HEX;
END LOOP;
-- ALL MIGRATED JOBS
FOR REC IN (SELECT DISTINCT AC.JOB_HEX
FROM ACTION AC JOIN "JOB" J ON AC.JOB_HEX=J.JOB_HEX
WHERE J.TO_MIGRATE='Y')
LOOP
UPDATE ACTION SET TO_MIGRATE='Y' WHERE JOB_HEX=REC.JOB_HEX;
END LOOP;
COMMIT;
END ACTION_MSC;
You're right, it will do one update for each record found. Looks like you could just do:
UPDATE EMPLOYEE SET FLAG = 'Y'
WHERE EMP IN (SELECT EMP FROM COMPANY WHERE FLAG = 'Y')
AND FLAG != 'Y';
A single update will generally be faster and more efficient than multiple individual row updates in a loop; see this answer for another example. Apart from anything else, you're reducing the number of context switches between PL/SQL and SQL, which add up if you have a lot of rows. You could always benchmark this with your own data, of course.
I've added a check of the current flag state so you don't do a pointless update with no chamges.
It's fairly easy to compare the approaches to see that a single update is faster than one in a loop; with some contrived data:
create table people (id number, people_hex varchar2(16), to_migrate varchar2(1));
insert into people (id, people_hex, to_migrate)
select level, to_char(level - 1, 'xx'), 'Y'
from dual
connect by level <= 100;
create table action (id number, people_hex varchar2(16), to_migrate varchar2(1));
insert into action (id, people_hex, to_migrate)
select level, to_char(mod(level, 200), 'xx'), 'N'
from dual
connect by level <= 500000;
All of these will update half the rows in the action table. Updating in a loop:
begin
for rec in (select distinct ac.people_hex
from action ac join people p on ac.people_hex=p.people_hex
where p.to_migrate='Y')
loop
update action set to_migrate='Y' where people_hex=rec.people_hex;
end loop;
end;
/
Elapsed: 00:00:10.87
Single update (after rollback; I've left this in a block to mimic your procedure):
begin
update action set to_migrate = 'Y'
where people_hex in (select people_hex from people where to_migrate = 'Y');
end;
/
Elapsed: 00:00:07.14
Merge (after rollback):
begin
merge into action a
using (select people_hex, to_migrate from people where to_migrate = 'Y') p
on (a.people_hex = p.people_hex)
when matched then update set a.to_migrate = p.to_migrate;
end;
/
Elapsed: 00:00:07.00
There's some variation from repeated runs, particularly that update and merge are usually pretty close but sometimes swap which is faster in my environment; but both are always significantly faster than updating in a loop. You can repeat this in your own environment and with your own data spread and volumes, and you should if performance is that critical; but a single update is going to be faster than the loop. Whether you use update or merge isn't likely to make much difference.

BEFORE DELETE trigger

How to stop from deleting a row, that has PK in another table (without FK) with a trigger?
Is CALL cannot_delete_error would stop from deleting?
This is what I've got so far.
CREATE TRIGGER T1
BEFORE DELETE ON Clients
FOR EACH ROW
BEGIN
SELECT Client, Ref FROM Clients K, Invoice F
IF F.Client = K.Ref
CALL cannot_delete_error
END IF;
END
Use an 'INSTEAD OF DELETE' trigger.
Basically, you can evaluate whether or not you should the delete the item. In the trigger you can ultimately decide to delete the item like:
--test to see if you actually should delete it.
--if you do decide to delete it
DELETE FROM MyTable
WHERE ID IN (SELECT ID FROM deleted)
One side note, remember that the 'deleted' table may be for several rows.
Another side note, try to do this outside of the db if possible! Or with a preceding query. Triggers are downright difficult to maintain. A simple query, or function (e.g. dbo.udf_CanIDeleteThis()') can be much more versatile.
If you're using MySQL 5.5 or up you can use SIGNAL
DELIMITER //
CREATE TRIGGER tg_fk_check
BEFORE DELETE ON clients
FOR EACH ROW
BEGIN
IF EXISTS(SELECT *
FROM invoices
WHERE client_id = OLD.client_id) THEN
SIGNAL sqlstate '45000'
SET message_text = 'Cannot delete a parent row: child rows exist';
END IF;
END//
DELIMITER ;
Here is SQLFiddle demo. Uncomment the last delete and click Build Schema to see it in action.