creating parameterized views in oracle11g - oracle

I have a big query with nesting and left join and Ineed to create a view out of it so as not to run it from the application. The issue is I need the date range and some other fields as input parameters since it will vary from the front end for each request.
I just looked up and saw some posts referring to using SYS_CONTEXT for parameterized views and need to know exactly how do I create the view for example with 2 parameters - fromdate, todate and how I invoke the view from the application.
Just for info I am using grails/groovy for developing the application.
and here is the query I want to create view out of..
select
d.dateInRange as dateval,
eventdesc,
nvl(td.dist_ucnt, 0) as dist_ucnt
from (
select
to_date(fromdate,'dd-mon-yyyy') + rownum - 1 as dateInRange
from all_objects
where rownum <= to_date(fromdate,'dd-mon-yyyy') - to_date(todate,'dd-mon-yyyy') + 1
) d
left join (
select
to_char(user_transaction.transdate,'dd-mon-yyyy') as currentdate,
count(distinct(grauser_id)) as dist_ucnt,
eventdesc
from
gratransaction, user_transaction
where gratransaction.id = user_transaction.trans_id and
user_transaction.transdate between to_date(fromdate,'dd-mon-yyyy') and to_date(todate,'dd-mon-yyyy')
group by to_char(user_transaction.transdate, 'dd-mon-yyyy'), eventdesc
) td on td.currentdate = d.dateInRange order by d.dateInRange asc

The context method is described here: http://docs.oracle.com/cd/B28359_01/network.111/b28531/app_context.htm
e.g. (example adapted from the above link)
CREATE CONTEXT dates_ctx USING set_dates_ctx_pkg;
CREATE OR REPLACE PACKAGE set_dates_ctx_pkg IS
PROCEDURE set(d1 in date, d2 in date);
END;
/
CREATE OR REPLACE PACKAGE BODY set_dates_ctx_pkg IS
PROCEDURE set(d1 in date, d2 in date) IS
BEGIN
DBMS_SESSION.SET_CONTEXT('dates_ctx', 'd1', TO_CHAR(d1,'DD-MON-YYYY'));
DBMS_SESSION.SET_CONTEXT('dates_ctx', 'd2', TO_CHAR(d2,'DD-MON-YYYY'));
END;
END;
/
Then, set the dates in your application with:
BEGIN set_dates_ctx_pkg.set(mydate1, mydate2); END;
/
Then, query the parameters with:
SELECT bla FROM mytable
WHERE mydate
BETWEEN TO_DATE(
SYS_CONTEXT('dates_ctx', 'd1')
,'DD-MON-YYYY')
AND TO_DATE(
SYS_CONTEXT('dates_ctx', 'd2')
,'DD-MON-YYYY');
The advantage of this approach is that it is very query-friendly; it involves no DDL or DML at runtime, and therefore there are no transactions to worry about; and it is very fast because it involves no SQL - PL/SQL context switch.
Alternatively:
If the context method and John's package variables method are not possible for you, another one is to insert the parameters into a table (e.g. a global temporary table, if you're running the query in the same session), then join to that table from the view. The downside is that you now have to make sure you run some DML to insert the parameters whenever you want to run the query.

I have just made a workaround for this annoying Oracle disadvantage. Like this
create or replace package pkg_utl
as
type test_record is record (field1 number, field2 number, ret_prm_date date);
type test_table is table of test_record;
function get_test_table(prm_date date) return test_table pipelined;
end;
/
create or replace package body pkg_utl
as
function get_test_table(prm_date date) return test_table pipelined
is
begin
for item in (
select 1, 2, prm_date
from dual
) loop
pipe row (item);
end loop;
return;
end get_test_table;
end;
/
it still requires a package, but at least i can use it in more convinient way:
select *
from table(pkg_utl.get_test_table(sysdate))
i am not sure about performance...

To use parameters in a view one way is to create a package which will set the values of your parameters and have functions that can be called to get those values. For example:
create or replace package MYVIEW_PKG as
procedure SET_VALUES(FROMDATE date, TODATE date);
function GET_FROMDATE
return date;
function GET_TODATE
return date;
end MYVIEW_PKG;
create or replace package body MYVIEW_PKG as
G_FROM_DATE date;
G_TO_DATE date;
procedure SET_VALUES(P_FROMDATE date, P_TODATE date) as
begin
G_FROM_DATE := P_FROMDATE;
G_TO_DATE := P_TODATE;
end;
function GET_FROMDATE
return date is
begin
return G_FROM_DATE;
end;
function GET_TODATE
return date is
begin
return G_TO_DATE;
end;
end MYVIEW_PKG;
Then your view can be created thus:
create or replace view myview as
select
d.dateInRange as dateval,
eventdesc,
nvl(td.dist_ucnt, 0) as dist_ucnt
from (
select
MYVIEW_PKG.GET_FROMDATE + rownum - 1 as dateInRange
from all_objects
where rownum <= MYVIEW_PKG.GET_FROMDATE - MYVIEW_PKG.GET_TODATE + 1
) d
left join (
select
to_char(user_transaction.transdate,'dd-mon-yyyy') as currentdate,
count(distinct(grauser_id)) as dist_ucnt,
eventdesc
from
gratransaction, user_transaction
where gratransaction.id = user_transaction.trans_id and
user_transaction.transdate between MYVIEW_PKG.GET_FROMDATE and MYVIEW_PKG.GET_TODATE
group by to_char(user_transaction.transdate, 'dd-mon-yyyy'), eventdesc
) td on td.currentdate = d.dateInRange order by d.dateInRange asc;
And to run it you must set the values first:
exec MYVIEW_PKG.SET_VALUES(trunc(sysdate)-1,trunc(sysdate));
And then calls to it will use these values:
select * from myview;

Related

PL/SQL - cannot access rows from a non-nested table item oracle

Can this package result be called from a procedure or function
the goal is to put that select into a procedure or function
so that when I call the procedure or function the result of the select is displayed.
This is the package i am using regarding this
create or replace PACKAGE get_cash_flow
IS
type type_rec
IS
record
(
asofdate VARCHAR2(30),
cfValue VARCHAR2(30));
type type_aa
IS
TABLE OF type_rec INDEX BY pls_integer;
FUNCTION func_aa RETURN type_aa;
END;
create or replace PACKAGE body get_cash_flow
IS
FUNCTION func_aa
RETURN type_aa
IS
l_aa_var1 get_cash_flow.type_aa;
BEGIN
FOR loop_aa IN
(
SELECT rownum rn,
asofdate, cash_inflow + TRANSFERS +ASSET_INFLOW + INTEREST_INCOME_LONG + DIVIDEND_INCOME - CASH_OUTFLOW
- ASSET_OUTFLOW-CASH_OUTFLOW -ASSET_OUTFLOW + INTEREST_INCOME_SHORT + MANAGEMENT_FEE + OTHER_FEES+EXPENSES cfValue
FROM eod_value_account_mtbl
order by asofdate
--where nf_account_id = '0208EA227E0B463ADA8BC413BA1A4D87'
--FETCH FIRST 1500000 rows only
FETCH FIRST 500 rows only
)
LOOP
l_aa_var1(loop_aa.rn).asofdate :=loop_aa.asofdate;
l_aa_var1(loop_aa.rn).cfValue:=loop_aa.cfValue;
END LOOP loop_aa;
RETURN l_aa_var1;
END func_aa;
END get_cash_flow;
here is the select I am trying to implement in a procedure or function
select *
from
get_cash_flow.func_aa();
declare
vcResult CLOB := NULL;
l_aa_var1 get_cash_flow.type_aa;
BEGIN
l_aa_var1:=get_cash_flow.func_aa;
SELECT JSON_ARRAYAGG(
JSON_OBJECT( KEY 'cfDate' VALUE asofdate,KEY 'cfValue' VALUE cfValue )
FORMAT JSON
RETURNING CLOB
) AS json
into vcResult
FROM (
SELECT asofdate,cfValue
FROM
TABLE(l_aa_var1)
);
dbms_output.put_line('vcResult: '|| vcResult);
END;
Expected result in one line data type in clob

How to prevent Oracle from calling a function for every attribute of the function result

I've created a package, containing a function that returns an object.
When retrieving the object details through sql, the function is called multiple times - once for every detail retrieved.
I believe it should be possible for it to just be called once instead.
Following is an example that demonstrates the issue:
CREATE OR REPLACE TYPE t_test AS OBJECT (
v1 VARCHAR2(10),
v2 VARCHAR2(10),
v3 VARCHAR2(10),
times_called NUMBER
);
/
CREATE OR REPLACE PACKAGE test_pkg AS
times_called NUMBER :=0;
FUNCTION test(something IN VARCHAR2) RETURN t_test;
PROCEDURE reset;
END test_pkg;
/
CREATE OR REPLACE PACKAGE BODY test_pkg IS
PROCEDURE reset IS
BEGIN
times_called := 0;
END;
FUNCTION test(something IN VARCHAR2) RETURN t_test IS
BEGIN
times_called := times_called + 1;
RETURN t_test('first', 'second', 'third', times_called);
END;
END test_pkg;
/
Here we can see that the function is invoked four times:
SQL> SELECT t.r.v1, t.r.v2, t.r.v3, t.r.times_called FROM (
2 SELECT test_pkg.test('x') r FROM DUAL
3 ) t;
R.V1 R.V2 R.V3 R.TIMES_CALLED
---------- ---------- ---------- --------------
first second third 4
SQL>
If we reset the counter, and only select two attributes, we can see it's called twice:
SQL> exec test_pkg.reset();
PL/SQL procedure successfully completed.
SQL> SELECT t.r.v1, t.r.times_called FROM (
2 SELECT test_pkg.test('x') r FROM DUAL
3 ) t;
R.V1 R.TIMES_CALLED
---------- --------------
first 2
SQL>
The actual stored procedure is more expensive, so I'd like to avoid re-calling it for every attribute listed.
The solution has to work on Oracle 10gr2
Oracle is not materializing the sub-query and is pushing the function calls to the outer query. You need to force the SQL engine to materialize the inner query either by:
Using a seemingly unnecessary ROWNUM > 0 filter:
SELECT t.r.v1, t.r.v2, t.r.v3, t.r.times_called
FROM (
SELECT test_pkg.test('x') r
FROM DUAL
WHERE ROWNUM > 0
) t;
or, you should be able to use the (undocumented) /*+ materialize */ hint but, for an unknown reason, it doesn't seem to want to materialize this particular query (although it does work for similar problems).
You can also (as pointed out in comments by William Robertson) use the /*+ NO_MERGE */ hint which "causes Oracle not to merge mergeable views":
SELECT t.r.v1, t.r.v2, t.r.v3, t.r.times_called
FROM (
SELECT /*+ no_merge */
test_pkg.test('x') r
FROM DUAL
) t;
db<>fiddle here

Can I create parameterized views in oracle

I have a query like this
SELECT ID,REF_ID,BATCHNO FROM reporttbl
where POSTING_DT >= '06/01/2020' and POSTING_DT <= '06/30/2020'
and I need it every month, so I would like to put it in a view, but as the date changes every month, it would be great to have a date parameter that I can pass to the view when calling it. Is there a way on how can i achieved this?
I'm new to oracle, appreciate every help. Thank youu.
From 19.6 you can create parameterized views using SQL macros.
create or replace function get_month (
tab dbms_tf.table_t, start_date date, end_date date
) return varchar2 sql_macro as
retval int;
begin
return 'select * from tab
where dt >= start_date and dt < end_date + 1';
end get_month;
/
create table t (
c1 int, dt date
);
insert into t
with rws as (
select level c1, add_months ( date'2019-12-25', level ) dt
from dual
connect by level <= 10
)
select * from rws;
select * from get_month (
t, date'2020-06-01', date'2020-07-01'
);
C1 DT
6 25-JUN-2020 00:00:00
select * from get_month (
t, date'2020-08-01', date'2020-09-01'
);
C1 DT
8 25-AUG-2020 00:00:00
This query return the data for the previous month, i.e. the month befort the current month at the time of the query (= sysdate).
You use the trunc with 'MM' to get the months first and the arithmetic with add_months
SELECT ID,REF_ID,BATCHNO FROM reporttbl
where POSTING_DT >= add_months(trunc(sysdate,'MM'),-1) and POSTING_DT < trunc(sysdate,'MM')
Another way to do this is using a function that retrieves the parameters from a table, thereby you don't need to manipulate any DDL. The idea here is
Using a table to store the parameters, basically you need parameter value and a parameter description.
Using a function to retrieve the value of that parameter when the input is the parameter name
Using the function call inside the view.
You can then manipulate the view automatically by modifying the values of the parameter table.
Table
create table my_param_table
( param_description varchar2(100) ,
param_value varchar2(100),
enabled varchar2(1)
) ;
Function
create or replace function f_retr_param ( p_value in varchar2 )
return varchar2
is
declare
v_value my_param_table.value_param%type;
begin
select value into v_value from my_table_of_parameters
where upper(value_param) = upper(p_value) ;
return v_value;
exception when others then raise;
end;
/
View
create or replace force view my_view as
SELECT ID,REF_ID,BATCHNO FROM reporttbl
where POSTING_DT >= f_retr_param ( p_value => 'p_start_date' );
and POSTING_DT <= f_retr_param ( p_value => 'p_end_date' );
There are ways to "parameterize" a view e.g. using Oracle contexts, but they aren't often useful and certainly not for your case.
If your query really just selects from one table with just the dates as predicates then a view doesn't add much value either. You could create a SQL script (in a file e.g. myquery.sql) using bind variables:
SELECT ID,REF_ID,BATCHNO FROM reporttbl
where POSTING_DT >= to_date(:from_date) and POSTING_DT <= to_date(:to_date);
Then every month you can just open the file and run it, and it will prompt you for the 2 dates. Or you can run as a script like this and it will also prompt you:
#myquery.sql
Or if you use substitution strings '&1.' and '&2.' instead:
SELECT ID,REF_ID,BATCHNO FROM reporttbl
where POSTING_DT >= to_date('&1.') and POSTING_DT <= to_date('&2.');
Then you can pass the dates in on the command line like this:
#myquery '06/01/2020' '06/30/2020'
(Because &1. means first parameter on command line, etc.)

Oracle lookup function in loop returns rowtype how to get fields from rowtype

I have a procedure that runs a select ( I tested that is good returns 56 records )
then when I run a cursor I want to pass 3 fields to a function ( see above ) that will
lookup/select a record from a table that contains 15 million records ( 10 years worth ).
It returns a rowtype that I want to then extract the fields from this rowtype record to
run an insert with both the records from the 1st select and the additional fields acquired
from the lookup function.
If I run the procedure the console prints out my test msgs but when I try to run
select * bulk collect into v_tab_proc_claim_recs from v_processed_claim;
it doesn't compile due to Error(97,65): PL/SQL: ORA-00942: table or view does not exist
as if either of these are not Tables.
Am I doing this right... how can I do it, why can't it see the table I'm trying to extract to ?
Should I do this some other way..
Thanks for any help/suggestions :)
The function is below....
create or replace function get_processed_claim_rec(
p_provider VARCHAR2,
p_rx VARCHAR2,
p_record_no NUMBER
)
return i_idb.processed_claim%rowtype
as
l_claim_record i_idb.processed_claim%rowtype;
begin
select * into l_claim_record from i_idb.processed_claim
where source_date = p_provider
AND rx = p_rx
AND rec_no = p_record_no;
return(l_claim_record);
end;
And the procedure is....
create or replace PROCEDURE import_mailer_data
AS
-------------------------------
/**
for the lookup table
**/
v_processed_claim i_idb.processed_claim%rowtype;
TYPE proc_claim_recs IS TABLE OF v_processed_claim%ROWTYPE INDEX BY PLS_INTEGER;
v_tab_proc_claim_recs proc_claim_recs;
--------------------------------
CURSOR myCursor
IS
SELECT *
from
(
SELECT
j.create_date as open_date,
case when (j.create_date < (sysdate - 20) )
then 'POD'
else 'REG'
end as priority,
c.division,
c.unit,
--p.refill as days_supply,
--p.din_name,
'CM_JOHN' as log_code,
c.first_name,
c.last_name,
--p.UNLISTED_compound,
--p.intervention_code,
--p.substitution,
--p.confirm,
c.PROVIDER,
c.rx,
c.DISPENSE_DATE,
c.DIN,
c.QTY,
c.DIN_COST_PAID,
c.DISP_FEE_PAID,
c.PAID_AMOUNT,
c.SOURCE_DATE,
c.RECORD_NO,
c.RELATIONSHIP,
c.INSURER_NO,
c.GROUP_NO,
c.CERTIFICATE,
c.BIRTH_DATE,
c.USER_ID,
--p.rej_code --v_seq_no
rank() over
(
partition by c.provider, c.rx, c.record_no Order by c.provider desc, c.rx desc
) as RNK
FROM AUDITCOLLECTIONS.MAILER_CLAIMS c,
AUDITCOLLECTIONS.MAILER_JOBS j
WHERE MAILER_JOB_DETAIL_ID IN
(SELECT MAILER_JOB_DETAIL_ID
FROM AUDITCOLLECTIONS.MAILER_JOB_DETAILS
WHERE MAILER_JOB_ID IN
( SELECT MAILER_JOB_ID FROM AUDITCOLLECTIONS.MAILER_JOBS
)
)
AND ( c.PROVIDER, c.rx, c.record_no ) NOT IN
( SELECT provider, rx, rec_no FROM AUDITCOLLECTIONS.COLLECTION_AUDIT_STAGING
)
AND j.create_date > (sysdate - 30)
AND c.provider = '2010500042'
) A_Latest
where A_Latest.RNK = 1;
BEGIN
v_report_id := audit_load.create_loaded_report(v_report_type_id);
FOR curRec IN myCursor
LOOP
BEGIN
dbms_output.put_line ('===>>>> PRINTING TEST1 = ');
v_processed_claim := get_processed_claim_rec(curRec.PROVIDER, curRec.RX, curRec.RECORD_NO);
select * bulk collect into v_tab_proc_claim_recs from v_processed_claim;
END LOOP;
audit_load.update_status_to_loaded(v_report_id);
END import_mailer_data;
You can do this:
FOR curRec IN myCursor
LOOP
v_processed_claim :=
get_processed_claim_rec(curRec.PROVIDER, curRec.RX, curRec.RECORD_NO);
v_tab_proc_claim_recs (v_tab_proc_claim_recs.COUNT+1) := v_processed_claim;
END LOOP;
Or simplify to:
FOR curRec IN myCursor
LOOP
v_tab_proc_claim_recs (v_tab_proc_claim_recs.COUNT+1) :=
get_processed_claim_rec(curRec.PROVIDER, curRec.RX, curRec.RECORD_NO);
END LOOP;

How to I create a view in Oracle where I can pass a parameter to it?

I created a view in Oracle
SELECT *
FROM CUSTOMER
ORDER BY CUSTOMER_ID
This works fine I can run select * from MyView.
How can I create a new view where I would pass a parameter to it?
Ex (Pseudo Code):
#LastName = 'Smith';
SELECT *
FROM CUSTOMER
WHERE LAST_NAME = #LastName
ORDER BY CUSTOMER_ID
You can't pass a parameter to a view.
Normally, it is enough to query a view and to let the optimizer handle pushing the predicate into the most appropriate place
create view customer_view
as
select *
from customer
select *
from customer_view
where last_name = :lastName
order by customer_id
You can write your view so that it references a value that is set in a package variable. That's a bit of a hack but it comes close to passing a parameter to a view
create or replace package my_pkg
as
g_last_name customer.last_name%type;
function get_last_name
return customer.last_name%type;
procedure set_last_name( p_last_name in customer.last_name%type );
end my_pkg;
create or replace package body my_pkg
as
procedure set_last_name( p_last_name in customer.last_name%type )
as
begin
g_last_name := p_last_name;
end;
function get_last_name
return customer.last_name%type
is
begin
return g_last_name;
end;
end;
create or replace view customer_view
as
select *
from customer
where last_name = my_pkg.get_last_name;
exec my_pkg.set_last_name( 'Smith' );
select *
from customer_view
order by customer_id;
Or you could define a pipelined table function that accepts a parameter and can be queried like a table
create type customer_obj
as object (
customer_id integer,
first_name varchar2(100),
last_name varchar2(100)
);
create type customer_nt
as table of customer_obj;
create or replace function my_pipeline_function( p_last_name in customer.last_name%type )
return customer_nt
pipelined
is
begin
for c in (select customer_obj( customer_id, first_name, last_name ) customer
from customer
where last_name = p_last_name)
loop
pipe row( c.customer );
end loop;
end;
select *
from table( my_pipeline_function( 'Smith' ))
order by customer_id;
If you join this pipelined table function to some other table, however, be aware that Oracle will not be able to push any predicates into the pipelined table function's query or perform other transforms. And the optimizer often has a hard time guessing how many rows a particular pipeline table function call will return which can cause it to choose less than optimal plans without some work. That can make it challenging to optimize queries that rely on a bunch of pipeline table functions.
If you can review this point of view with your architect! so, I suggest you to create a stored proc with an OUT parameter. This parameter will be a REF CURSOR. The SP would be something like this :
CREATE OR REPLACE yourSP (v_DataReturned OUT ref cursor, -- Data will be returned to your report.
v_last_name customer.last_name%type -- parameter used in the WHERE clause)
IS
BEGIN
OPEN v_DataReturned FOR
SELECT * FROM customer t -- replace* by fields you need to have
WHERE t.last_name = v_last_name;
END;
So, if you want to verify values returned by the procedure yourSP, you can execute this script on SQL Developer or SQL*Plus :
variable rc_returned ref cursor;
execute yourSP(:rc_returned,'John' -- or another last_name);
print rc_returned;

Resources