Oracle Insert, Update, Delete Trigger with Join - oracle

I'm trying to implement Oracle triggers for child views but I need to be able to join the child views to their parents in order to do a role permission check.
In SQL Server I'm able to stuff like this:
ALTER TRIGGER [dbo].[ASetTrt_I] ON [dbo].[UCV_ASet_TRT]
INSTEAD OF Insert AS
BEGIN
SET NOCOUNT ON;
IF EXISTS (SELECT 1 from INSERTED i INNER JOIN [Analysis_Sets] p on i.[key] = p.[ID]
WHERE ([dbo].IsMemberOf(p.[UpdateRole]) <> 1 and [dbo].IsMemberOf('db_owner') <> 1))
RAISERROR ('Update failed due to insufficient permission',11,1)
INSERT INTO [Set_Trts] ( [ID], [Name], [key], [f_lTreatmentKey], [f_lOrder] )
SELECT
inserted.[ID], inserted.[Name], inserted.[key], inserted.[f_lTreatmentKey], inserted.[f_lOrder]
FROM inserted
INNER JOIN [Sets] parentT
on inserted.[key] = parentT.[ID]
WHERE (([dbo].IsMemberOf('db_owner')=1) or ([dbo].IsMemberOf(parentT.[UpdateRole])=1))
END
Is there anything I can do in Oracle to replicate the join functionality?
I've tried selecting from :New the way that SS selects from inserted but that doesn't seem to work..
Thanks.

You don't need to join. The:new pseudorow is just available and can be referenced like a record type. It isn't a table-like structure, and is only available in a for each row trigger. So your insert would be something like:
INSERT INTO Analysis_Set_Trts ( ID, Name, f_lAnalysisSetKey, f_lTreatmentKey,
f_lOrder )
SELECT :new.ID, :new.Name, :new.f_lAnalysisSetKey, :new.f_lTreatmentKey,
:new.f_lOrder
FROM Analysis_Sets parentT
WHERE parentT.ID = :new.f_lAnalysisSetKey
AND ((IsMemberOf('db_owner')=1) or (IsMemberOf(parentT.UpdateRole)=1));
... although not quite sure what the last line is doing or what the equivalent is.

Related

Comparing two tables, if rows are different, run query in Oracle

Think of my two tables have the same columns. One column is the ID, and the other one is the text. Is it possible to implement the following pseudo code in PLSQL?
Compare each row (They will have the same ID)
If anything is different about them
Run a couple of queries: an Update, and an Insert
ElseIf they are the same
Do nothing
Else the row does not exist
So add the row to the table compared on
Is it easy to do this using PLSQL or should I create a standalone application to do do this logic.
As your table have the same columns, by using NATURAL JOIN you can easily check if two corresponding rows are identical -- without need to update your code if a column is added to your table.
In addition, using OUTER JOIN allow you to find the rows present in one table but not in the other.
So, you can use something like that to achieve your purpose:
for rec in (
SELECT T.ID ID1,
U.ID ID2,
V.EQ
FROM T
FULL OUTER JOIN U ON T.ID = U.ID
FULL OUTER JOIN (SELECT ID, 1 EQ FROM T NATURAL JOIN U) V ON U.ID = V.ID)
loop
if rec.id1 is null
then
-- row in U but not in T
elsif rec.id2 is null
then
-- row in T but not in U
elsif rec.eq is null
-- row present in both tables
-- but content mismatch
end if
end loop
Else the row does not exist
So add the row to the table compared on
Is this condition means that rows can be missed in both tables? If only in one, then:
insert into t1 (id, text)
select id, text
from t2
minus
select id, text
from t1;
If missed records can be in both tables, you need the same query that inserts into table t2 rows from t1.
If anything is different about them
If you need one action for any amount of different rows, then use something like this:
select count(*)
into a
from t1, t2
where t1.id = t2.id and t1.text <> t2.text;
if a > 0 then
...
otherwise:
for i in (
select *
from t1, t2
where t1.id = t2.id and t1.text <> t2.text) loop
<do something>
end loop;
A 'merge' statement is what u needed.
Here is the syntax:
MERGE INTO TARGET_TABLE
USING SOURCE_TABLE
ON (CONDITION)
WHEN MATCHED THEN
UPDATE SET (DO YOUR UPDATES)
WHEN NOT MATCHED THEN
(INSERT YOUR NEW ROWS)
Google MERGE syntax for more about the statement.
Just use MINUS.
query_1
MINUS
query_2
In your case, if you really want to use PL/SQL, then select count into a local variable. Write a logic, if count > 0 then do other stuff.

Materialized View involving UNION ALL operator in FROM with ON COMMIT

I have two tables 'survey' & 'survey_processed' and both are basically similar tables. I have a working query which uses UNION ALL operator between two tables & am getting some counts. When I try to translate it in to a Materialized View, I get the error related to ON COMMIT. Check out the MV DDL & Error below.
CREATE MATERIALIZED VIEW vwm_survey_records_count
REFRESH FAST ON COMMIT
AS
SELECT
survey_combined.survey_header_id,
COUNT(*) AS count_total,
COUNT(CASE WHEN survey_combined.processed_flag = 'Y' THEN 1 ELSE NULL END) AS count_a,
COUNT(CASE WHEN survey_combined.approved_flag IS NULL THEN 1 ELSE NULL END) AS count_b,
COUNT(CASE WHEN survey_combined.processed_flag = 'N' AND survey_combined.approved_flag = 'Y' THEN 1 ELSE NULL END) AS count_c,
COUNT(CASE WHEN survey_combined.approved_flag = 'N' THEN 1 ELSE NULL END) AS count_d
FROM
(
SELECT survey_header_id, 'N' AS processed_flag, approved_flag FROM survey
UNION ALL
SELECT survey_header_id, 'Y' AS processed_flag, approved_flag FROM survey_processed) survey_combined
INNER JOIN survey_header ON survey_combined.survey_header_id = survey_header.id
GROUP BY survey_combined.survey_header_id;
Error I get if i run the above command:
'SQL Error: ORA-12054: cannot set the ON COMMIT refresh attribute for the materialized view' .
But, if i use the 'REFRESH COMPLETE ON DEMAND' it works. I know am obviously breaking some restrictions for the ON COMMIT attribute, but can't figure out which. Can someone let me know what am doing wrong on the above query? Also, Is there any better approach for the query as such to make it efficient & work with 'REFRESH FAST ON COMMIT', while creating the MV.
Note: I have the MV Log created for both the tables using rowid on the selected columns.
Let me know if someone has any questions.
Thanks in advance.
Here's the DDL for MV Log as requested by 'jonearles'
CREATE MATERIALIZED VIEW LOG ON survey WITH SEQUENCE,ROWID (id, survey_header_id, approved_flag, processed_flag) INCLUDING NEW VALUES;
CREATE MATERIALIZED VIEW LOG ON survey_processed WITH SEQUENCE,ROWID (id, survey_header_id, approved_flag) INCLUDING NEW VALUES;
CREATE MATERIALIZED VIEW LOG ON survey_header WITH SEQUENCE,ROWID (id) INCLUDING NEW VALUES;
Note: The column 'processed_flag' in the 'survey' table will be dropped later on. Technically, the two tables were recently split based on the value of the 'processed_flag' column. So, the 'survey' table has all the un-processed records (processed_flag = 'N') & 'survey_processed' has the processed records (processed_flag = 'Y'). After the split, the column is irrelevant.
I think you're out of luck here. From Restrictions on Fast Refresh on Materialized Views with UNION ALL:
The defining query must have the UNION ALL operator at the top level.
And replacing the UNION ALL with outer joins doesn't work either. Aggregation and outer joins don't appear to work together. The code below is not equivalent to yours, but it demonstrates that even a much simplified version of your query won't work:
CREATE MATERIALIZED VIEW vwm_survey_records_count
REFRESH COMPLETE ON DEMAND AS
SELECT survey_header.id AS survey_header_id, COUNT(*) AS count_total
FROM survey_header, survey, survey_processed
WHERE survey_header.id = survey.survey_header_id(+)
GROUP BY survey_header.id;
delete from MV_CAPABILITIES_TABLE;
begin
DBMS_MVIEW.EXPLAIN_MVIEW ('VWM_SURVEY_RECORDS_COUNT');
end;
/
select possible, msgno, msgtxt
from MV_CAPABILITIES_TABLE
where capability_name = 'REFRESH_FAST_AFTER_INSERT';
POSSIBLE MSGNO MSGTXT
-------- ----- ------
N 2048 outer join in mv

Finding sequences and triggers associated with an Oracle table

I have used this query to fetch the list of sequences belonging to an Oracle database user:
SELECT * FROM all_sequences x,all_tables B
WHERE x.sequence_owner=B.owner AND B.TABLE_NAME='my_table';
But that database user is having many more sequence also, so the query returns me all the sequence of the database user. Can anybody help me to find the particular sequence of my_table using query so that I can get the auto increment id in my application.
i want the query which fetch list of table of my database user with the sequence and triggers used in the table
You can get the triggers associated with your tables from the user_triggers view. You can then look for any dependencies recorded for those triggers in user_dependencies, which may include objects other than sequences (packages etc.), so joining those dependencies to the user_sequences view will only show you the ones you are interested in.
Something like this, assuming you are looking at your own schema, and you're only interesting in triggers that references sequences (which aren't necessarily doing 'auto increment', but are likely to be):
select tabs.table_name,
trigs.trigger_name,
seqs.sequence_name
from user_tables tabs
join user_triggers trigs
on trigs.table_name = tabs.table_name
join user_dependencies deps
on deps.name = trigs.trigger_name
join user_sequences seqs
on seqs.sequence_name = deps.referenced_name;
SQL Fiddle demo.
If you're actually looking at a different schema then you'll need to use all_tables etc. and filter and join on the owner column for the user you're looking for. And if you want to include tables which don't have triggers, or triggers which don't refer to sequences, you can use outer joins.
Version looking for a different schema, though this assumes you have the privs necessary to access the data dictionary information - that the tables etc. are visible to you, which they may not be:
select tabs.table_name,
trigs.trigger_name,
seqs.sequence_name
from all_tables tabs
join all_triggers trigs
on trigs.table_owner = tabs.owner
and trigs.table_name = tabs.table_name
join all_dependencies deps
on deps.owner = trigs.owner
and deps.name = trigs.trigger_name
join all_sequences seqs
on seqs.sequence_owner = deps.referenced_owner
and seqs.sequence_name = deps.referenced_name
where tabs.owner = '<owner>';
If that can't see them then you might need to look at the DBA views, again if you have sufficient privs:
select tabs.table_name,
trigs.trigger_name,
seqs.sequence_name
from dba_tables tabs
join dba_triggers trigs
on trigs.table_owner = tabs.owner
and trigs.table_name = tabs.table_name
join dba_dependencies deps
on deps.owner = trigs.owner
and deps.name = trigs.trigger_name
join dba_sequences seqs
on seqs.sequence_owner = deps.referenced_owner
and seqs.sequence_name = deps.referenced_name
where tabs.owner = '<owner>';
One way would be to run these queries to check if there are any sequence's Pseudocolumns (NEXTVAL and CURRVAL ) used in your functions , procedures, packages, Triggers or PL/SQL JAVA SOURCE.
select * from user_source where
UPPER(TEXT) LIKE '%NEXTVAL%';
select * from all_source where
UPPER(TEXT) LIKE '%NEXTVAL%';
Then go to the specific Procedure, Function or Trigger to check which column/table gets populated by a sequence.
The query could also be used with '%CURRVAL%'
This might not help if you are running inserts from JDBC or other external applications using a sequence.
Oracle 12c introduced the IDENTITY columns, using which you could create a table with an identity column, which is generated by default.
CREATE TABLE t1 (c1 NUMBER GENERATED BY DEFAULT ON NULL AS IDENTITY,
c2 VARCHAR2(10));
This will internally create a sequence that auto-generates the value for the table's column.So, If you would like to know which sequence generates the value for which table, you may query the all_tab_columns
SELECT data_default AS sequence_val
,table_name
,column_name
FROM all_tab_columns
WHERE OWNER = 'HR'
AND identity_column = 'YES';
SEQUENCE_VAL |TABLE_NAME |COLUMN_NAME
-----------------------------------------|-------------------------------------
"HR"."ISEQ$$_78160".nextval |T1 |C1
I found a solution to this problem to guess the sequence of a particular sequence
select * from SYS.ALL_SEQUENCES where SEQUENCE_OWNER='OWNER_NAME' and LAST_NUMBER between (select max(FIELD_NAME) from TABLE_NAME) and (select max(FIELD_NAME)+40 from TABLE_NAME);
This query will guess by search the LAST_NUMBER of the sequence value between MAX value of the field using sequence and Max value + 40 (in my case cache value is 20, so I put 40)
select SEQUENCE_NAME from sys.ALL_TAB_IDENTITY_COLS where owner = 'SCHEMA_NAME' and table_name = 'TABLE_NAME';

Calling function in select vs calling temp table

Just looking if there is any way to have the following behaviuour.
Currently I'm doing
select * from table_1 t1 where function_call(t1.col1, t1.col2) <> 0;
The reason why I'm calling the function function_call is , The function has the table table_2 which I don't have access. The only way I can access is through one package.
Is there any way changing the above behaviour to, having the table information in the from clause instead of calling the function_call in where clause.
like ,
select * from table_1 t1, package_x.get_table_2() where t1.col1 = 1 and t1.col2 = 1 and t1.col3 = t2.col3
I agree that calling a function from the WHERE clause may result in slow performance as this will very likely result in a full table scan of TABLE_1, with the function being called once for every row in TABLE_1. I can think of several things your site can do:
Grant the necessary level of access to TABLE_1.
Create a view on TABLE_1 which restricts the data which can be seen, and grant appropriate access on the view, or
Live with the slow access times.
Share and enjoy.

Rownum in the join condition

Recently I fixed the some bug: there was rownum in the join condition.
Something like this: left join t1 on t1.id=t2.id and rownum<2. So it was supposed to return only one row regardless of the “left join”.
When I looked further into this, I realized that I don’t understand how Oracle evaluates rownum in the "left join" condition.
Let’s create two sampe tables: master and detail.
create table MASTER
(
ID NUMBER not null,
NAME VARCHAR2(100)
)
;
alter table MASTER
add constraint PK_MASTER primary key (ID);
prompt Creating DETAIL...
create table DETAIL
(
ID NUMBER not null,
REF_MASTER_ID NUMBER,
NAME VARCHAR2(100)
)
;
alter table DETAIL
add constraint PK_DETAIL primary key (ID);
alter table DETAIL
add constraint FK_DETAIL_MASTER foreign key (REF_MASTER_ID)
references MASTER (ID);
prompt Disabling foreign key constraints for DETAIL...
alter table DETAIL disable constraint FK_DETAIL_MASTER;
prompt Loading MASTER...
insert into MASTER (ID, NAME)
values (1, 'First');
insert into MASTER (ID, NAME)
values (2, 'Second');
commit;
prompt 2 records loaded
prompt Loading DETAIL...
insert into DETAIL (ID, REF_MASTER_ID, NAME)
values (1, 1, 'REF_FIRST1');
insert into DETAIL (ID, REF_MASTER_ID, NAME)
values (2, 1, 'REF_FIRST2');
insert into DETAIL (ID, REF_MASTER_ID, NAME)
values (3, 1, 'REF_FIRST3');
commit;
prompt 3 records loaded
prompt Enabling foreign key constraints for DETAIL...
alter table DETAIL enable constraint FK_DETAIL_MASTER;
set feedback on
set define on
prompt Done.
Then we have this query :
select * from master t
left join detail d on d.ref_master_id=t.id
The result set is predictable: we have all the rows from the master table and 3 rows from the detail table that matched this condition d.ref_master_id=t.id.
Result Set
Then I added “rownum=1” to the join condition and the result was the same
select * from master t
left join detail d on d.ref_master_id=t.id and rownum=1
The most interesting thing is that I set “rownum<-666” and got the same result again!
select * from master t
left join detail d on d.ref_master_id=t.id and rownum<-666.
Due to the result set we can say that this condition was evaluated as “True” for 3 rows in the detail table. But if I use “inner join” everything goes as supposed to be.
select * from master t
join detail d on d.ref_master_id=t.id and rownum<-666.
This query doesn’t return any row,because I can't imagine rownum to be less then -666 :-)
Moreover, if I use oracle syntax for outer join, using “(+)” everything goes well too.
select * from master m ,detail t
where m.id=t.ref_master_id(+) and rownum<-666.
This query doesn’t return any row too.
Can anyone tell me, what I misunderstand with outer join and rownum?
ROWNUM is a pseudo-attribute of result sets, not of base tables. ROWNUM is defined after rows are selected, but before they're sorted by an ORDER BY clause.
edit: I was mistaken in my previous writeup of ROWNUM, so here's new information:
You can use ROWNUM in a limited way in the WHERE clause, for testing if it's less than a positive integer only. See ROWNUM Pseudocolumn for more details.
SELECT ... WHERE ROWNUM < 10
It's not clear what value ROWNUM has in the context of a JOIN clause, so the results may be undefined. There seems to be some special-case handling of expressions with ROWNUM, for instance WHERE ROWNUM > 10 always returns false. I don't know how ROWNUM<-666 works in your JOIN clause, but it's not meaningful so I would not recommend using it.
In any case, this doesn't help you to fetch the first detail row for each given master row.
To solve this you can use analytic functions and PARTITION, and combine it with Common Table Expressions so you can access the row-number column in a further WHERE condition.
WITH numbered_cte AS (
SELECT *, ROW_NUMBER() OVER (PARTITION BY t.id ORDER BY d.something) AS rn
FROM master t LEFT OUTER JOIN detail d ON d.ref_master_id = t.id
)
SELECT *
FROM numbered_cte
WHERE rn = 1;
if you want to get the first three values from the join condition change the select statement like this.
select *
from (select *
from master t left join detail d on d.ref_master_id=t.id)
where rownum<3;
You will get the required output. Take care on unambigiously defined column names when using *
Let me give an absolute answer which u can run directly with out making any changes to the code.
select *
from (select t.id,t.name,d.id,d.ref_master_id,d.name
from master t left join detail d on d.ref_master_id=t.id)
where rownum<3;
A ROWNUM filter doesn't make any sense in a join, but it isn't being rejected as invalid.
The explain plan will either include the ROWNUM filter or exclude it. If it includes it, it will apply the filter to the detail table after applying the other join condition(s). So if you put in ROWNUM=100 (which will never be satisfied) all the detail rows are excluded and then the outer join kicks in.
If you put in ROWNUM=1 it seems to drop the filter.
And if you query
with
a as (select rownum a_val from dual connect by level < 10),
b as (select rownum*2 b_val from dual connect by level < 10)
select * from a left join b on a_val < b_val and rownum in (1,3);
you get something totally weird.
It probably should be rejected as an error, so expect nonsensical things to happen

Resources