Materialized View involving UNION ALL operator in FROM with ON COMMIT - oracle

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

Related

Fast Refreshable MView creation ORA-12015: cannot create a fast refresh materialized view from a complex query

I want to create m-view which keeps the aggregates of cash from transaction history.
The query is as follows.
CREATE MATERIALIZED VIEW MV_CASH_STATS
BUILD IMMEDIATE REFRESH FAST ON DEMAND
AS
( -- Total aggregates
SELECT
'total' AS CODE
, COUNT(T.CASH_IN) AS DEPOSITS
, COUNT(T.CASH_OUT) AS WITHDRAWS
, SUM(T.CASH_IN) AS DEPOSIT
, SUM(T.CASH_OUT) AS WITHDRAW
FROM
TB_CASH_LOG T
)
UNION ALL
( -- Aggregates by account grades
SELECT
'grade-' || T1.GRADE AS CODE
, COUNT(T.CASH_IN) AS DEPOSITS
, COUNT(T.CASH_OUT) AS WITHDRAWS
, SUM(T.CASH_IN) AS DEPOSIT
, SUM(T.CASH_OUT) AS WITHDRAW
FROM
TB_CASH_LOG T
INNER JOIN TB_ACCOUNT T1 ON
T.ACCOUNT_ID = T1.ID
GROUP BY T1.GRADE
)
When i execute this, the following error occurs.
> ORA-12015: cannot create a fast refresh materialized view from a complex query
(I created m-view log tables for both TB_CASH_LOG and TB_ACCOUNT)
The Total aggregates part is ok, but the later part - Aggregates by account grades - makes the error.
I've read Oracle basic materialized view docs, and thinking that INNER JOIN or GROUP BY is the error point.
If it is true, fast refresh m-view with inner join and group by is impossible?
What is wrong with my m-view declaration sql?
p.s. I'm working on oracle 18c

How to retrieve data from 3 tables using sub query oracle SQL

I want to retrieve users name and there responsibility_key where there end_date is null and i want to convert it to (sysdate+1) using nvl but i am only able to retrieve the responsibility_key not the name please help.
The error in the image says "column ambiguously defined". Take a close look. Your last END_DATE could refer to either the u alias or the table from the subquery. Change it to match the rest of your subquery (FIND_USER_GROUPS_DIRECT.END_DATE)
EDIT
Your query is
select u.USER_NAME, d.responsibility_key from FND_USER u,FND_RESPONSIBILITY_VL d
where responsibility_id in(
select responsibility_id from
FND_USER_RESP_GROUPS_DIRECT WHERE END_USER_RESP_GROUPS_DIRECT.END_DATE=nvl(END_DATE,sysdate+1)) and
u.END_DATE=nvl(END_DATE,SYSDATE + 1)
;
The query isn't formatted, which makes it hard to read.
Not all columns are qualified with table name (or aliases), as mentioned in the comments.
The query currently uses an implicit join.
The query is impossible to understand without seeing the table definitions (desc [table_name]).
For points 1 and 2, a properly formatted query will look something like
select u.user_name, d.responsibility_key
from
fnd_user u,
fnd_responsibility_vl d
where
d.responsibility_id in (
select urgd.responsibility_id
from
fnd_user_resp_groups_direct urgd
where
urgd.end_date = nvl(u.end_date, sysdate+1)
) and
u.end_date = nvl(urgd.end_date, sysdate + 1)
;
This makes it easier to read and in addition to this, you can see that without table definitions I guessed (see point 4) as to which tables the end_date column belongs in your query. If I had to guess, so does Oracle. That means you have an ambiguity problem. To fix it, take a close look at the end_date column as it appears in your original query and where you do not prefix it with anything, you need to prefix it with the appropriate alias (after you have aliased all your tables).
For point 3, you can write your query more clearly with an explicit join and by using aliases for all columns. As for the explicit join I have no idea what your tables look like but one possibility is something like
select u.user_name, d.responsibility_key
from fnd_user u
join fnd_responsibility_vl d
on u.id = d.user_id
where
d.responsibility_id in (
select responsibility_id
from fnd_user_resp_groups_direct urgd
where
urgd.end_date = nvl(u.end_date, sysdate+1)
) and
u.end_date = nvl(urgd.end_date, sysdate+1)
;
If you follow these points you will get to the root of the error.

can i set up an SSRS report where users input parameters to a table

I have an oracle query that uses a created table as part of the code. Every time I need to run a report I delete current data and import the new data I receive. This is one column of id's. I need to create a report on SSRS in which the user can input this data into said table as a parameter. I have designed a simple report that they can enter some of the id's into a parameter, but there may be times when they need to enter in a few thousand id's, and the report already runs long. Here is what the SSRS code currently says:
select distinct n.id, n.notes
from notes n
join (
select max(seq_num) as seqnum, id from notes group by id) maxresults
on n.id = maxresults.ID
where n.seq_num = maxresults.seqnum
and n.id in (#MyParam)
Is there a way to have MyParam insert data into a table I would join called My_ID, joining as Join My_Id id on n.id = id.id
I do not have permissions to create functions or procedures in the database.
Thank you
You may try the trick with MATERIALIZE hint which normally forces Oracle to create a temporary table :
WITH cte1 AS
( SELECT /*+ MATERIALIZE */ 1 as id FROM DUAL
UNION ALL
SELECT 2 DUAL
)
SELECT a.*
FROM table1 a
INNER JOIN cte1 b ON b.id = a.id

Oracle Insert, Update, Delete Trigger with Join

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.

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