Can Coldfusion ORM access an Oracle trigger - oracle

I have ORM setup and working with Oracle on an existing database and have been able to get inserts to work when I access the sequence but because triggers were used in the original application the sequence skips a number.
Is there a way to get ORM to use the trigger?
Disabling the trigger is not an option since it is used by the existing app and cannot be disabled during migration.
component persistent="true" table="table_name" schema="schema_name" {
property name="table_id" column="table_id" fieldtype="id" generator="sequence" sequence="schema_name.sequence_name";
...
}

Triggers are not accessible program units. The only way to "call" a trigger is to execute the appropriate DML against the owning table.
There are two possible resolutions to your problem.
Rewrite the trigger. You say another application still needs the trigger to populate the ID, but you could change the trigger's logic with a conditional....
if :new.id is null then
:new.id := whatever_seq.nextval; --11g syntax for brevity
end if;
This will populate the ID when the other application insert into the table but won't overwrite your value.
Stop worrying. Sequences are merely generators of unique identifiers. The numbers ascend but it really doesn't matter if there are gaps. Unless you are handling billions of rows it is extremely unlikely your sequence will run out of numbers before your applications get retired.

Do you mean that the DB normally assigns an ID, using an insert trigger? That would explain why you're skipping a number. You could try generator="select" which will get hibernate to read the ID back after the insert has occurred (and the trigger has been fired). It's there to handle exactly the situation I think you're describing.

Related

Sequences (using as ID) issue in Oracle SQL Developer

I am using sequences to create IDs, so while executing insert stored procedure it will create unique value for ID. But after some time it is losing the definition for the sequence.
Not sure why this is happening again and again and how to solve the problem?
I am using Oracle SQL Developer and in the edit table property there is 'Identity Column' setting. See below:
Next step is setting up trigger and sequence:
It was working fine for some time until this property defaulted. Now it is not there anymore:
Still have this trigger and sequence object in the schema and able to setup again but it will break later.
How to avoid this problem in future?
I think it is just a bug/limitation in your client software, Oracle SQL Developer. The "Identity Column" tab is a handy way to create the corresponding sequence and trigger but it doesn't seem to recognise existing elements. I've just verified my own system and that's exactly what happens.
It makes sense, because adding a new sequence and trigger is a pretty straightforward task (all you need is a template) but displaying current sequence is hard given that a trigger can implement any conceivable logic. Surely it could be done but the cost-benefit ratio probably left things this way.
In short, your app is not broken so nothing needs to be fixed on your side.
This is what I received from IT support regarding the issue:
A few possibilities that might cause this:
1 - Another user with limited privileges might be editing the table using SQL Developer. In this case, if this user's privilege is not enough to obtain the sequence and/or trigger information from the database, the tool might leave the fields blank and disable it when table changes are saved.
2 - The objects are being changed or removed outside of SQL Developer, causing it to lose the information. In my tests I noticed that dropping the trigger and recreating it with the same name caused the identity property information to be lost on SQL Developer.
Even being the trigger enabled, and working for inserts it could not retrieve the information.
Then, if I run an alter trigger to enable it (even tough dba_trigger is reporting it as already enabled), SQL Developer will list the information again:
ALTER TRIGGER "AWS"."TABLE1_TRG" ENABLE;
So it looks like there are some issues with the SQL Developer, that is causing this behavior.
Next time it happen, please check if the trigger still exist on the database and is enabled with the query below:
select owner, trigger_name, TRIGGER_TYPE, TRIGGERING_EVENT, TABLE_OWNER, TABLE_NAME, STATUS
from dba_triggers
where trigger_name = 'ENTER_YOUR_TRG_NAME'; --Just change the trigger name in WHERE

Consecutive application threads and uncommitted data in Oracle

Our application reads a record from an Oracle 'Event' table. When the event record exists we update the 'count' field of that record. If the record doesn't exist we insert it. So we want only 1 record for a particular event in the table.
The problem with this is probably quite predictable: one application thread will read the table, see the event is not there, insert the new event and commit. But before it commits a second thread will also read the table and see the event is not there. And then both threads will insert the event and we end up with 2 records for the same event.
I guess synchronizing access to this particular method in our application will prevent this problem, but what is the best option in Oracle to prevent this? Will MERGE for example always prevent this problem?
Serialising access to the procedure that implements this functionality would be trivial to implement, using DBMS_LOCK to define and take an exclusive lock.
Serialising through SQL based methods is practically impossible, due to the read consistency model.
CREATE TABLE EVENTS (ID NUMBER PRIMARY KEY, COUNTER NUMBER NOT NULL);
MERGE INTO EVENTS
USING (SELECT ID, COUNTER FROM DUAL LEFT JOIN EVENTS ON EVENTS.ID = :EVENT_ID) SRC
ON (EVENTS.ID = SRC.ID)
WHEN MATCHED THEN UPDATE SET COUNTER = SRC.COUNTER + 1
WHEN NOT MATCHED THEN INSERT (ID, COUNTER) VALUES (:EVENT_ID, 1);
Simple SQL securing single record for each ID and consistently increasing the counter no matter what application fires it or number of concurrent thread. You don't need to code anything at all and it's very lightweight as well.
It also doesn't produce any exception related to data consistency so you don't need any special handling.
UPDATE: It actually produces unique violation exception if both threads are inserting. I thought the second merge would switch to update, but it doesn't.
UPDATE: Just tested the same case on SQL Server and when executing in parallel and the record doesn't exist one MERGE inserts and the second updates.

"who_called_me" equivalent for Hibernate

We know that it is possible to dynamically figure out the name of the procedure or package that is currently executing as explained here and here. This generally applies to statements being executed from other stored procedures (compiled) in the database.
The problem:
We have been trying to log all UPDATE activity on a specific column (called STATE) by placing a trigger on the table and invoking who_called_me from within the trigger. The purpose of doing this is apparently as per the application design the column STATE could get updated by multiple pieces of code (residing in the database) based on certain business conditions. In addition to that, the column could also get updated by the application which is a hibernate based application and at times when the update happens by a hibernate query the who_called_me function returns nothing. There are multiple parts in the application that could also UPDATE the column STATE based on certain conditions.
The who_called_me strategy is working well for us in cases where a stored procedure (which resides in the database) issues the UPDATE statement and who_called_me is successfully capturing the corresponding owner, name, line no. etc. of the stored procedure. But in case the UPDATE happens from hibernate, the function captures no details.
Is there a way to capture which hibernate query UPDATEd the row through the trigger? Or is there any other way?
Note: The trigger code is similar to the answer posted on this question.
you can track the query with ora_sql_text function, e.g. this is the function I use for that:
-- getting sql code, which is calling the current event, as clob
function getEventSQLtext
return clob
is
sqllob clob;
sql_text ora_name_list_t;
dummy integer;
begin
dummy := ora_sql_txt(sql_text);
dbms_lob.createtemporary(sqllob,false);
for i in 1..sql_text.count loop
dbms_lob.writeappend(sqllob,length(sql_text(i)),sql_text(i));
end loop;
return sqllob;
if dummy is null then null; end if; -- removing warning of non-used variable :)
end;
This will be a query which is generated by hibernate and this is the only information you can get because this should be the only thing hibernate can do with DB.
It turns out, the who_called_me approach works better for stored procedure calls where the stack trace can point exactly which line invoked a DML. In, case of hibernate it is possible that the code may not call a stored procedure but in-turn may have individual DMLs which get invoked based on certain conditions. As opposed to other answer given by #simon, the ora_sql_txt function may only work in system event triggers or I may be wrong, but either way it is not capable of capturing the SQL Statement issued by Hibernate (tested that it does not works and retunrs a NULL value).
So at the end of the day, to find what SQL Hibernate is using, DB Trace files and Hibernate debug level logs is the only way for now.

Oracle: difference between max(id)+1 and sequence.nextval

I am using Oracle
What is difference when we create ID using max(id)+1 and using sequance.nexval,where to use and when?
Like:
insert into student (id,name) values (select max(id)+1 from student, 'abc');
and
insert into student (id,name) values (SQ_STUDENT.nextval, 'abc');
SQ_STUDENT.nextval sometime gives error that duplicate record...
please help me on this doubt
With the select max(id) + 1 approach, two sessions inserting simultaneously will see the same current max ID from the table, and both insert the same new ID value. The only way to use this safely is to lock the table before starting the transaction, which is painful and serialises the transactions. (And as Stijn points out, values can be reused if the highest record is deleted). Basically, never use this approach. (There may very occasionally be a compelling reason to do so, but I'm not sure I've ever seen one).
The sequence guarantees that the two sessions will get different values, and no serialisation is needed. It will perform better and be safer, easier to code and easier to maintain.
The only way you can get duplicate errors using the sequence is if records already exist in the table with IDs above the sequence value, or if something is still inserting records without using the sequence. So if you had an existing table with manually entered IDs, say 1 to 10, and you created a sequence with a default start-with value of 1, the first insert using the sequence would try to insert an ID of 1 - which already exists. After trying that 10 times the sequence would give you 11, which would work. If you then used the max-ID approach to do the next insert that would use 12, but the sequence would still be on 11 and would also give you 12 next time you called nextval.
The sequence and table are not related. The sequence is not automatically updated if a manually-generated ID value is inserted into the table, so the two approaches don't mix. (Among other things, the same sequence can be used to generate IDs for multiple tables, as mentioned in the docs).
If you're changing from a manual approach to a sequence approach, you need to make sure the sequence is created with a start-with value that is higher than all existing IDs in the table, and that everything that does an insert uses the sequence only in the future.
Using a sequence works if you intend to have multiple users. Using a max does not.
If you do a max(id) + 1 and you allow multiple users, then multiple sessions that are both operating at the same time will regularly see the same max and, thus, will generate the same new key. Assuming you've configured your constraints correctly, that will generate an error that you'll have to handle. You'll handle it by retrying the INSERT which may fail again and again if other sessions block you before your session retries but that's a lot of extra code for every INSERT operation.
It will also serialize your code. If I insert a new row in my session and go off to lunch before I remember to commit (or my client application crashes before I can commit), every other user will be prevented from inserting a new row until I get back and commit or the DBA kills my session, forcing a reboot.
To add to the other answers, a couple of issues.
Your max(id)+1 syntax will also fail if there are no rows in the table already, so use:
Coalesce(Max(id),0) + 1
There's nothing wrong with this technique if you only have a single process that inserts into the table, as might be the case with a data warehouse load, and if max(id) is fast (which it probably is).
It also avoids the need for code to synchronise values between tables and sequences if you are moving restoring data to a test system, for example.
You can extend this method to multirow insert by using:
Coalesce(max(id),0) + rownum
I expect that might serialise a parallel insert, though.
Some techniques don't work well with these methods. They rely of course on being able to issue the select statement, so SQL*Loader might be ruled out. However SQL*Loader has support for this technique in general through the SEQUENCE parameter of the column specification: http://docs.oracle.com/cd/E11882_01/server.112/e22490/ldr_field_list.htm#i1008234
Assuming MAX(ID) is actually fast enough, wouldn't it be possible to:
First get MAX(ID)+1
Then get NEXTVAL
Compare those two and increase sequence in case NEXTVAL is smaller then MAX(ID)+1
Use NEXTVAL in INSERT statement
In that case I would have a fully stable procedure and manual inserts would also be allowed without worrying about updating the sequence

Oracle Apex - Updating a view with instead-of trigger

Apex beginner here. I have a view in my Oracle database of the form:
create or replace view vw_awkward_view as
select unique tab1.some_column1,
tab2.some_column1,
tab2.some_column2,
tab2.some_column3
from table_1 tab1,
table_2 tab2
WHERE ....
I need the 'unique' clause on 'tab1.some_column1' because it has many entries in its underlying table. I also need to include 'tab1.some_column1' in my view because the rest of the data doesn't make much sense without it.
In Apex, I want to create a report on this view with a form for editing it (update only). I do NOT need to edit tab1.some_column1. Only the other columns in the view need to be editable. I can normally achieve this using an 'instead-of' trigger, but this doesn't look possible when the view contains a 'distinct', 'unique' or 'group by' clause.
If I try to update a row on this view I get the following error:
ORA-02014: cannot select FOR UPDATE from view with DISTINCT, GROUP BY, etc.
How can I avoid this error? I want my 'instead-of' trigger to kick in and perform the update and I don't need to edit the column which has the 'unique' clause, so I think it should be possible to do this.
I think that you should be able to remove the "unique".
if tab2.some_column1, tab2.some_column2, tab2.some_column3 are not unique, then how do you want to update them ?
if they are unique then the whole result: tab1.some_column1, tab2.some_column1, tab2.some_column2, tab2.some_column3 is unique.
When you state in a sql query "unique" or "distinct" it's for all columns not only 'tab1.some_column1'
Hope i'm in the correct direction of your question here ;)
Your query could be achieved by doing something like:
select a.some_column1, tab2.some_column1, tab2.some_column2, tab2.some_column3
from table_2 tab2
join (select distinct some_column1 from table_1) a
on tab2.column_in_tab1 = a.some_column1
The reason you get the ORA-02014 error is because of the automatically generated ApplyMRU process. This process will attempt to lock a (the) changed row(s):
begin
for r in (select ...
from vw_awkward_view
where <your first defined PK column>= 'value for PK1'
for update nowait)
loop
null;
end loop;
end;
That's a bummer, and means you won't be able to use the generated process. You'll have to write your own process which does the updating.
For this, you'll have to use the F## arrays in apex_application.
If this sounds totally unfamiliar, take a look at:
Custom submit process, and on using the apex_application arrays.
Also, here is a how-to for apex from 2004 from Oracle itself. It still uses lots of htmldb references, but the gist of it is there.
(it might be a good idea to use the apex_item interface to build up your form, and have control over what is generated and what array it takes.)
What it comes down to is: loop over the array containing your items and do an UPDATE on your view with the submitted values.
Of course, you don't have locking this way, nor a way to prevent unnecessary updates.
Locking you can do yourself, with for example using the select for update method. You'd have to lock the correct rows in the table(s) you want to alter, before you update them. If the locking fails, then your process should fail.
As for the 'lost update' story: here you'd need to check the MD5-checksums. A checksum is generated from the editable columns in your form and put in the html-code. On submit, this checksum is then compared to a newly generated checksum from those same columns, but with values from the database at that time of submit. If the checksums differ, it means the record has changed between the page load and the page submit. Your process should fail because the record has been altered, and you don't want to have those overwritten. (if you go the apex_item way, then don't forget to include an MD5_CHECKSUM call (or MD5_HIDDEN).
Important note though: checksums generated by either using apex_item or simply the standard form functionality build up a string to be hashed. As you can see in apex_item.md5_hidden, checksums are generated using DBMS_OBFUSCATION_TOOLKIT.MD5.
You can get the checksum of the values in the DB in 2 ways: wwv_flow_item.md5 or using dbms_obfuscation.
However, what the documentation fails to mention is this: OTN Apex discussion on MD5 checksums. Pipes are added in the generated checksums! Don't forget this, or it'll blow up in your face and you'll be left wondering for days what the hell is wrong with it.
Example:
select utl_raw.cast_to_raw(dbms_obfuscation_toolkit.md5(input_string=>
"COLUMN1" ||'|'||
"COLUMN2" ||'|'||
"COLUMN5" ||'|'||
"COLUMN7" ||'|'||
"COLUMN10" ||'|'||
"COLUMN12" ||'|'||
"COLUMN14" ||
'|||||||||||||||||||||||||||||||||||||||||||'
)) md5
from some_table
To get the checksum of a row of the some_table table, where columns 1,2,5,7,10,12,14 are editable!
In the end, this is how it should be structured:
loop over array
generate a checksum for the current value of the editable columns
from the database
compare this checksum with the submitted checksum
(apex_application.g_fcs if generated) if the checksums match,
proceed with update. If not, fail process here.
lock the correct records for updating. Specify nowait, and it
locking fails, fail the process
update your view with the submitted values. Your instead-of trigger
will fire. Be sure you use correct values for your update statement so that only this one record will be updated
Don't commit inbetween. It's either all or nothing.
I almost feel like i went overboard, and it might feel like it is all a bit much, but when you know the pitfalls it's actually not so hard to pull this custom process off! It was very knowledgable for me to play with it :p
The answer by Tom is a correct way of dealing with ths issue but I think overkill for your requirements if I understand correctly.
The easiest way may be to create a form on the table you want to edit. Then have the report edit link take the user to this form which will only update the needed columns from the one table. If you need the value of the column from the other table displayed it is simple when you create the link to pass this value to the form which can contain a display only item to show this.

Resources