Let's say we have the following table structures:
documents docmentStatusHistory status
+---------+ +--------------------+ +----------+
| docId | | docStatusHistoryId | | statusId |
+---------+ +--------------------+ +----------+
| ... | | docId | | ... |
+---------+ | statusId | +----------+
| ... |
+--------------------+
It may be obvious, but it's worth mentioning, that the current status of a document is the last Status History entered.
The system was slowly but surely degrading in performance and I suggested changing the above structure to:
documents docmentStatusHistory status
+--------------+ +--------------------+ +----------+
| docId | | docStatusHistoryId | | statusId |
+--------------+ +--------------------+ +----------+
| currStatusId | | docId | | ... |
| ... | | statusId | +----------+
+--------------+ | ... |
+--------------------+
This way we'd have the current status of a document right where it should be.
Because the way the legacy applications were built I could not change the code on legacy applications to update the current status on the document table.
In this case I had to open an exception to my rule to avoid triggers at all costs, simply because I don't have access to the legacy applications code.
I created a trigger that updates the current status of a document every time a new status is added to the status history, and it works like a charm.
However, in an obscure and rarely used situation there is a need to DELETE the last status history, instead of simply adding a new one. So, I created the following trigger:
create or replace trigger trgD_History
after delete on documentStatusHistory
for each row
currentStatusId number;
begin
select statusId
into currentStatusId
from documentStatusHistory
where docStatusHistoryId = (select max(docStatusHistoryId)
from documentStatusHistory
where docId = :old.docId);
update documentos
set currStatusId = currentStatusId
where docId = :old.docId;
end;
And thats where I got the infamous error ORA-04091.
I understand WHY I'm getting this error, even though I configured the trigger as an AFTER trigger.
The thing is that I can't see a way around this error. I have searched the net for a while and couldn't find anything helpful so far.
In time, we're using Oracle 9i.
The standard workaround to a mutating table error is to create
A package with a collection of keys (i.e. docId's in this case). A temporary table would also work
A before statement trigger that initializes the collection
A row-level trigger that populates the collection with each docId that has changed
An after statement trigger that iterates over the collection and does the actual UPDATE
So something like
CREATE OR REPLACE PACKAGE pkg_document_status
AS
TYPE typ_changed_docids IS TABLE OF documentos.docId%type;
changed_docids typ_changed_docids := new typ_changed_docids ();
<<other methods>>
END;
CREATE OR REPLACE TRIGGER trg_init_collection
BEFORE DELETE ON documentStatusHistory
BEGIN
pkg_document_status.changed_docids.delete();
END;
CREATE OR REPLACE TRIGGER trg_populate_collection
BEFORE DELETE ON documentStatusHistory
FOR EACH ROW
BEGIN
pkg_document_status.changed_docids.extend();
pkg_document_status.changed_docids( pkg_document_status.changed_docids.count() ) := :old.docId;
END;
CREATE OR REPLACE TRIGGER trg_use_collection
AFTER DELETE ON documentStatusHistory
BEGIN
FOR i IN 1 .. pkg_document_status.changed_docids.count()
LOOP
<<fix the current status for pkg_document_status.changed_docids(i) >>
END LOOP;
pkg_document_status.changed_docids.delete();
END;
seems to be a duplicate of this question
check out Tom Kyte's take on that
Related
I have tables that looks like this:
tbl1
+---------+
|c_no |
+---------+
|1 |
+---------+
tbl2
+----------+---------+
|tbl1_c_no |s_name |
+----------+---------+
|1 |A |
|1 |D |
+----------+---------+
My form:
◘ The 1st block's base table usage is tbl1.
◘ C_NO field is auto generated using sequence. (required).
◘ S_GR is just an unbound item. (not required).
◘ The 2nd block's base table usage is tbl2 and is multiple row.
◘ S_NAME. (required)
◘ 1st block is like the parent of 2nd block.
◘ 1st and 2nd block is linked using c_no and tbl1_c_no
For example if I wanted to add some data, it's like this:
Then press F10 for saving:
tbl1 will be:
+---------+
|c_no |
+---------+
|1 |
|2 |
+---------+
tbl2 will be:
+----------+---------+
|tbl1_c_no |s_name |
+----------+---------+
|1 |A |
|1 |D |
|2 |B |
|2 |C |
|2 |E |
+----------+---------+
And my problem is that I wanted to fetch s_names from my 3rd table into 2nd block.
tbl3
+----------+---------+
|s_gr |s_name |
+----------+---------+
|80 |F |
|85 |G |
|84 |H |
|84 |I |
|80 |J |
+----------+---------+
Like this:
then after leaving S_GR field, it will fetch S_NAME from tbl3 that S_GR = 80 into the 2nd block
You can create two blocks :
for the 1st one, to have a block with no base table, create manually just by touching Data Blocks node
with mouse's cursor and then toggling the create icon (a green plus
sign ) and type a name blk_no. And add a field s_no on the canvas.
for the 2nd one use Data Block Wizard and choose Table or View type
for the type of the block. There select the table(tbl1)'s both columns
(s_no and name) as Database Items.
And then, the forms must invoke
Layout Wizard automatically as default, where choose only name column
as displayed and leave s_no hidden as to be . Name the block as blk_names. This is a base-table block, and Data Source Name of the block blk_names is the table tbl1.
By the way, set Number of Records Displayed property to 10 as an example, and convert the name of the field name to snames as in your question.
Set block's WHERE Clause (in Database node) as s_no = :blk_no.s_no at the Property Palette. After
all, create a KEY-NEXT-ITEM trigger on s_no field with the inline
code :
go_block('blk_names');
execute_query;
At the runtime you can enter an integer value( let's give 1 as an example ) for s_no and populate the names field by pressing enter key ( the records with A and D will appear )
A button might be added with WHEN-BUTTON-PRESSED trigger having the code :
go_block('blk_names');
delete tbl2;
first_record;
while :blk_names.s_no is not null
loop
insert into tbl2 values(:snames);
next_record;
end loop;
commit;
to populate and re-populate the table tbl2( in this case tbl2 is populated with the records A and D ).
P.S. To suppress the message
FRM-40352: Last Record of Query retrieved
add an ON-MESSAGE trigger at the forms level wtih the code :
if message_code = 40352 then
null;
end if;
I have a table like this:
+------------+
| EMP_CODE |
+------------+
|CODEA |
|CODEA1 |
|CODEA2 |
|CODEB |
|CODEC |
|CODEC2 |
|CODED |
|CODED1 |
|CODEE |
|CODEE1 |
|CODEE2 |
+------------+
My multi-row block in forms:
What I wanted is if I add for example the EMP_CODE CODEE, it will automatically add EMP_CODE(s) with CODEE on the next row and so on. Like this:
Just tell me if I missed out something or if there's something unclear on my explanation. Thank you!
Will try to explain one approach, perhaps there would be better ways to achieve the desired functionality.
The below is pseudo code, improvise and change as per your request.
KEY-NEXT-ITEM
BEGIN
SELECT emp INTO :your_block_name.column_name
FROM table_name
WHERE column_name = :block_name.emp;
EXCEPTION
WHEN OTHERS THEN
// consider raising or do as per logic
END;
Having answered this, I believe the table design is not correct for which perhaps instigate a separate discussion to dissect.
every month I do a simple update statement on my oracle database. But, since monday it takes very long. The table grows every month by 5 percent. Now there are 8 million records stored.
The Statement:
update /*+ parallel(destination_tab, 4) */ destination_tab dest
set (full_name, state) =
(select /*+ parallel(source_tab, 4) */ dest.name, src.state
from source_tab src
where src.city = dest.city);
In real there are 20 fields to update, not only two... but so it looks easier to descripe the problem.
explain plan:
-----------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------------------
| 0 | update statement | | 8517K| 3167M| 579M (50)|999:59:59 |
| 1 | update | destination_tab | | | | |
| 2 | PX COORDINATOR | | | | | |
| 3 | PX SEND QC (RANDOM) | :TQ10000 | 8517K| 3167M| 6198 (1)| 00:01:27 |
| 4 | px block iterator | | 8517K| 3167M| 6198 (1)| 00:01:27 |
| 5 | table access full | DESTINATION_TAB | 8517K| 3167M| 6198 (1)| 00:01:27 |
| 6 | table access by index rowid| SOURCE_TAB | 1 | 56 | 1 (0)| 00:00:01 |
|* 7 | index unique scan | CITY_PK | 1 | | 1 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------------
Could anyone descripe to me, how this can be? The plan looks very bad! Thank you very very much.
You didn't say how long is too long. You are joining an 8 million row table. Not sure how many rows are in source_tab.
I noticed the execution plan indicates a full table scan of destination_tab. Is the city column on the destination_tab table indexed? If not, try adding an index. If it is, Oracle may be ignoring it because it knows it needs to return every value anyway and destination_tab is the driving table.
No matter how you optimize it, this will always degrade in performance as the tables grow because you are updating every row by fetching a value from the same table joined to another. That is, you are always doing N operations where N is the number of rows in destination_tab.
High-level questions/suggestions:
Do you need to update every row every time? Are only certain rows likely to have changed values? If so, can you somehow predict which rows you need to update and limit your updates to it.
Why are the hints there? If performance changes, I would experiment with dropping hints. It's the optimizer's job to find the best plan for you. By using hints, you are telling the optimizer how to do its job. You'd better be right.
You are updating the full_name column on destination_tab to the name column of the same row. But you are obtaining the name column through a join to the table. It may be quicker to take that out of your select and use something like below. This is a guess. It may not matter.
update destination_tab dest
set full_name = name,
state =
(select src.state
from source_tab src
where src.city = dest.city);
Try the following.
merge
into destination_tab d
using source_tab s
on (d.city = d.city)
when matched then
update
set d.state = s.state
where decode(d.state, s.state, 1, 0) = 0;
If this is a data warehouse, I wouldn't do updates, especially not every row in a large table. I'd probably create a materialized view combining the pieces from various base tables, and do a full refresh when needed (non-atomic: truncate + insert append).
Edit:
As for WHY the current update approach is taking much longer than usual, my guess is that in previous runs Oracle found a good number of blocks needed for the update in buffer cache, and lately Oracle has had to pull a lot from disk into buffer first. You can look into consistent gets and db block gets (logical io) vs physical io (disk).
I understand the comments about the sense of a data warehouse and so on. However, I have to do this update in this kind. The update is part of an ETL workflow. I have to copy every month the complete 8 million records of the table "destination". After this step I have to do the UPDATE which makes problems.
I do not understand the problem, that the performance is so bad day-to-day. Usually, the update runs 45 minutes. Now, it runs about 4 hours. But why? There is no sorting necessary, so the famous reason "sorting on disc instead on main memory" is not possible. What is the problem in my case?
Could there be an difference about the performance between normal update (how I do it) and the merge-update?
customers:
+------------+--------------+
| cid | Name |
+------------+--------------+
| 1 | Bob |
| 2 | John |
| 3 | Jane |
+------------+--------------+
accounts:
+------------+--------------+
| aid | type |
+------------+--------------+
| 1 | Checking |
| 2 | Saving |
| 3 | Checking |
+------------+--------------+
transactions:
+------------+--------------+--------------+--------------+
| tid | cid | aid | type |
+------------+--------------+--------------+--------------+
| 1 | 1 | 1 | Open |
| 2 | 2 | 3 | Open |
| 3 | 1 | 2 | Open |
| 4 | 2 | 3 | Deposit |
+------------+--------------+--------------+--------------+
I am trying to write a trigger that writes to a logs table when a new account is successfully opened.
Right now I have this:
CREATE OR REPLACE TRIGGER acc_opened
BEFORE INSERT ON transactions
FOR EACH ROW
DECLARE
c_name customers.name%TYPE;
BEGIN
IF :new.type = 'Open' THEN
SELECT name into c_name
FROM customers c
WHERE c.cid = :new.cid;
INSERT INTO logs (who, what) VALUES (c_name, 'An account has been opened');
END;
/
The code that I have doesn't work and don't know where to go from here.
The trigger completes, but when it fires, I get this error message:
PLS-00103: Encountered the symbol "END" when expecting one of the following: (begin case declare exit for goto if loop mod null pragma raise return select update while with << continue close current delete fetch lock insert open rollback savepoint set sql execut commit forall merge pipe purge
As with your previous question, if you want to refer to a particular column of the new row of data, you need to use the :new pseudo-record. So, at a minimum,
SELECT cid
INTO c_id
FROM transactions t
WHERE t.aid = aid;
would need to be
SELECT cid
INTO c_id
FROM transactions t
WHERE t.aid = :new.aid;
Beyond that, are you sure that the row exists in the transactions table before the row is inserted into the accounts tale? Assuming that you have normal foreign key constraints, I would generally expect that you would insert a row into the accounts table before inserting the row into the transactions table.
The name transactions also seems pretty odd. If that is really just mapping the customer ID to the account ID, transactions seems like a rather poor name. If that table actually stores transactions, I'm not sure why it would have a customer ID. But if it does store transactions, there must be some other table that maps customers to accounts.
In your updated trigger, you are missing the END IF statement
CREATE OR REPLACE TRIGGER acc_opened
BEFORE INSERT ON transactions
FOR EACH ROW
DECLARE
c_name customers.name%TYPE;
BEGIN
IF :new.type = 'Open'
THEN
SELECT name
into c_name
FROM customers c
WHERE c.cid = :new.cid;
INSERT INTO logs (who, what)
VALUES (c_name, 'An account has been opened');
END IF;
END;
I have a table like this:
myTable (id, group_id, run_date, table2_id, description)
I also have a index like this:
index myTable_grp_i on myTable (group_id)
I used to run a query like this:
select * from myTable t where t.group_id=3 and t.run_date='20120512';
and it worked fine and everyone was happy.
Until I added another index:
index myTable_tab2_i on myTable (table2_id)
My life became miserable... it's taking almost as 5 times longer to run !!!
execution plan looks the same (with or without the new index):
--------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost
--------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 220 | 17019
|* 1 | TABLE ACCESS BY INDEX ROWID| MYTABLE | 1 | 220 | 17019
|* 2 | INDEX RANGE SCAN | MYTABLE_GRP_I | 17056 | | 61
--------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("T"."RUN_DATE"='20120512')
2 - access("T"."GROUP_ID"=3)
I have almost no hair left on my head, why should another index which is not used, on a column which is not in the where clause make a difference ...
I will update the things I checked:
a. I removed the new index and it run faster
b. I added the new index in 2 more different environments and the same thing happen
c. I changed MYTABLE_GRP_I to be on columns run_date and group_id - this made it run fast as a lightning !!
But still why does it happen ?