Functional Where-In Clause - Oracle PL/SQL - oracle

I've got an association table that groups accounts together.
I'm trying to select a subset of table 'target'
p_group_id := 7;
select *
target t
where t.account_id in get_account_ids(p_group_id);
Is it possible to write a function that returns a list of account_ids (as some form of collection) that would facilitate the above code?
I've looked at pipelined functions, however I want to stay away from loops and cursors. Also, custom types / table also get into casting that I'd like to avoid.
For reference, here's some pseudocode for what the function 'get_account_ids' would do, hypothetically:
function get_account_ids(p_group_id)
insert into v_ret
select aa.account_id
from assoc_account aa
where aa.groupid = p_group_id;
return v_ret;

You simply need:
select *
from target t
where t.account_id in
( select aa.account_id
from assoc_account aa
where aa.groupid = 7;
)
The above will work, assuming that assoc_account.account_id is never NULL.

Related

Is there a hint to generate execution plan ignoring the existing one from shared pool?

Is there a hint to generate execution plan ignoring the existing one from the shared pool?
There is not a hint to create an execution plan that ignores plans in the shared pool. A more common way of phrasing this question is: how do I get Oracle to always perform a hard parse?
There are a few weird situations where this behavior is required. It would be helpful to fully explain your reason for needing this, as the solution varies depending why you need it.
Strange performance problem. Oracle performs some dynamic re-optimization of SQL statements after the first run, like adaptive cursor sharing and cardinality feedback. In the rare case when those features backfire you might want to disable them.
Dynamic query. You have a dynamic query that used Oracle data cartridge to fetch data in the parse step, but Oracle won't execute the parse step because the query looks static to Oracle.
Misunderstanding. Something has gone wrong and this is an XY problem.
Solutions
The simplest way to solve this problem are by using Thorsten Kettner's solution of changing the query each time.
If that's not an option, the second simplest solution is to flush the query from the shared pool, like this:
--This only works one node at a time.
begin
for statements in
(
select distinct address, hash_value
from gv$sql
where sql_id = '33t9pk44udr4x'
order by 1,2
) loop
sys.dbms_shared_pool.purge(statements.address||','||statements.hash_value, 'C');
end loop;
end;
/
If you have no control over the SQL, and need to fix the problem using a side-effect style solution, Jonathan Lewis and Randolf Geist have a solution using Virtual Private Database, that adds a unique predicate to each SQL statement on a specific table. You asked for something weird, here's a weird solution. Buckle up.
-- Create a random predicate for each query on a specific table.
create table hard_parse_test_rand as
select * from all_objects
where rownum <= 1000;
begin
dbms_stats.gather_table_stats(null, 'hard_parse_test_rand');
end;
/
create or replace package pkg_rls_force_hard_parse_rand is
function force_hard_parse (in_schema varchar2, in_object varchar2) return varchar2;
end pkg_rls_force_hard_parse_rand;
/
create or replace package body pkg_rls_force_hard_parse_rand is
function force_hard_parse (in_schema varchar2, in_object varchar2) return varchar2
is
s_predicate varchar2(100);
n_random pls_integer;
begin
n_random := round(dbms_random.value(1, 1000000));
-- s_predicate := '1 = 1';
s_predicate := to_char(n_random, 'TM') || ' = ' || to_char(n_random, 'TM');
-- s_predicate := 'object_type = ''TABLE''';
return s_predicate;
end force_hard_parse;
end pkg_rls_force_hard_parse_rand;
/
begin
DBMS_RLS.ADD_POLICY (USER, 'hard_parse_test_rand', 'hard_parse_policy', USER, 'pkg_rls_force_hard_parse_rand.force_hard_parse', 'select');
end;
/
alter system flush shared_pool;
You can see the hard-parsing in action by running the same query multiple times:
select * from hard_parse_test_rand;
select * from hard_parse_test_rand;
select * from hard_parse_test_rand;
select * from hard_parse_test_rand;
Now there are three entries in GV$SQL for each execution. There's some odd behavior in Virtual Private Database that parses the query multiple times, even though the final text looks the same.
select *
from gv$sql
where sql_text like '%hard_parse_test_rand%'
and sql_text not like '%quine%'
order by 1;
I think there is no hint indicating that Oracle shall find a new execution plan everytime it runs the query.
This is something we'd want for select * from mytable where is_active = :active, with is_active being 1 for very few rows and 0 for maybe billions of other rows. We'd want an index access for :active = 1 and a full table scan for :active = 0 then. Two different plans.
As far as I know, Oracle uses bind variable peeking in later versions, so with a look at the statistics it really comes up with different execution plans for different bind varibale content. But in older versions it did not, and thus we'd want some hint saying "make a new plan" there.
Oracle only re-used an execution plan for exactly the same query. It sufficed to add a mere blank to get a new plan. Hence a solution might be to generate the query everytime you want to run it with a random number included in a comment:
select /* 1234567 */ * from mytable where is_active = :active;
Or just don't use bind variables, if this is the problem you want to address:
select * from mytable where is_active = 0;
select * from mytable where is_active = 1;

Trying to return dataset from SQL statement with function

I've been digging on this for most of the day but have so far been unable to find the right answer. I'm trying to find a way to return the results of a SQL query in a custom function. All data in our system is both transaction and effective dated, and we are commonly called upon to compare data between two points of time. Right now I use a WITH clause to pull two different datasets ("Before" and "After"). The problem is that the queries used to create these datasets are very long, and each CTE is basically the same thing just with different effective dates. I'd like to find a way to create a function that I can pass the effective/transaction dates to for the comparison so I don't have so much redundant logic in my SQL. Here's the catch - I have read-only access and cannot create any objects in the DB. I've read that I can use a Declare statement to get around this but just haven't been able to get it right so far.
Here's an example of what I have now. I've simplified the query greatly so this isn't a complete mess.
WITH effective_date AS (
SELECT to_date(:EFFDT) AS effdt,
to_date(:REPORT_DATE_BEFORE) AS report_dt_before,
to_date(:REPORT_DATE_AFTER) AS report_dt_after
FROM dual),
election_data_before AS (
SELECT *
FROM effective_date efd
CROSS JOIN elections e
WHERE efd.effdt >= e.start_dt
AND efd.effdt < e.until_dt
AND efd.report_dt_before >= e.tran_start_dt
AND efd.report_dt_before < e.tran_until_dt),
election_data_after AS (
SELECT *
FROM effective_date efd
CROSS JOIN elections e
WHERE efd.effdt >= e.start_dt
AND efd.effdt < e.until_dt
AND efd.report_dt_after >= e.tran_start_dt
AND efd.report_dt_after < e.tran_until_dt)
SELECT ...
FROM election_data_before edb
INNER JOIN election_data_after eda
ON edb.employee_id = eda.employee_id
AND edb.benefit_type = eda.benefit_type
WHERE ...
This doesn't look so bad, but like I said this is extremely simplified. Here's what I'd like to be able to do. I know this is garbage code, just trying to illustrate what I'm picturing.
FUNCTION elections ( effdt date, report_dt date )
RETURN (
SELECT *
FROM elections e
WHERE effdt >= e.start_dt
AND effdt < e.until_dt
AND report_dt >= e.tran_start_dt
AND report_dt < e.tran_until_dt)
SELECT ...
FROM elections(:EFFDT, :REPORT_DT_BEFORE) edb
ON pp.employee_id = edb.employee_id
INNER JOIN elections(:EFFDT, :REPORT_DT_AFTER) eda
ON pp.employee_id = eda.employee_id
AND edb.benefit_type = eda.benefit_type
WHERE ...
I've been reading about pipelined functions and anonymous blocks all day but haven't been able to put it all together. If anyone can point me in the right direction or let me know if I'm better off just using two different CTEs I'd appreciate it. Thanks!
In order to create and use a pipelined table function, you must first create an object type.
create or replace type election_type as object
(
col1 varchar2(100),
col2 date,
col3 number -- Here you define the name and datatype of the columns you want
-- to return from the select query.
);
/
create or replace type election_type_tab as table of election_type;
-- You need a collection (nested table) type to return multiple records
-- of the type defined above from your function
Now, define your function using an implicit cursor for loop to extract and pass rows to the caller.
CREATE OR REPLACE FUNCTION fn_elections (
effdt DATE,
report_dt DATE
) RETURN election_type_tab
PIPELINED
AS
BEGIN
FOR rec IN (
SELECT * --This should return the same columns as that of election_type
FROM elections e --,some_othertable s
WHERE effdt >= e.start_dt AND
effdt < e.until_dt
AND report_dt >= e.tran_start_dt
AND report_dt < e.tran_until_dt
) LOOP
PIPE ROW ( election_type(rec.col1,rec.col2,rec.col3) );
END LOOP;
return;
END;
/
You may then call it like this.
select * from TABLE(fn_elections(sysdate,sysdate+1)); --or some other date argument
Demo

How to Have Function Definition and Subquery in With Clause of Oracle Select Statement?

I know the right syntax for having a function definition in the WITH clause. I know the right syntax for having a subquery in the WITH clause. But I have been unable to find an example of having a subquery and a function definition in the WITH clause of a SELECT statement.
If I have:
with totals as ( select colum_name from some_table )
select sum(column_name) from totals;
How do I add a function definition in the WITH clause?
Since you can't find much/anything about this from Oracle, I don't think it is a good idea to use it. Anyway, this works in 18.1:
WITH
FUNCTION with_plus(p IN NUMBER) RETURN NUMBER IS
BEGIN
RETURN p + 1;
END;
FUNCTION with_min(p IN NUMBER) RETURN NUMBER IS
BEGIN
RETURN p - 1;
END;
qry1 AS (
SELECT with_plus(10) plus
FROM DUAL
),
qry2 AS (
SELECT plus, with_min(10) min
FROM qry1
)
SELECT *
FROM qry2
;
/
So don't forget the slash / at the end.
If you ever find out how to put this whole block in a subquery, please let me know
I don't think there's any such restriction. However, I suspect that your problem has to do with column aliasing. Here's what worked for me:
with totals as (select sum(column_name) c1 from some_table)
select c1 from totals;
Oracle might have complained because you were trying to do something like:
with totals as (select sum(column_name) from some_table)
select sum(column_name) from totals;
Unfortunately, this is a consequence of name resolution. The subquery's column will get named "sum(column_name)". Since sum is a function, there's no way to reference that column name without Oracle thinking you're referencing the function. You have to give it another name in order to reference it anywhere else.
Edit: It seems that you want to define a function as if you would a view subquery. I don't think anything like this is possible. View subqueries really only perform textual substitution.
PL/SQL functions require a whole different parser, name resolution, compilation process, etc. Having them work in queries alone is hard enough.
Sorry to say, but you'd have to define your packages/procedures/functions normally.

Oracle: How to create a function returning values for a "SELECT * FROM tab WHERE name IN (function())"

I have a problem which I can't solve. Maybe you have an idea about how to solve it.
I do have a given parameter-table like this:
P_VALUE P_NAME
----------- ----------
X85 A_03
XH1 A_04
XH2 A_04
XH3 A_04
C84 A_05
As you can see there are parameters with multiple entries. At the moment this parameters are used in this way:
SELECT * FROM tablex
WHERE code IN (SELECT p_value
FROM parameter_table
WHERE p_name LIKE 'A_04');
As the query is very big these parameter sub-select are used very often. I was trying to implement a function in Oracle to get my parameters. This works very fine as long as there is just 1 row per parameter. When I want to use it in "IN-Statements", it won't work because functions just return a single value.
--WORKS
SELECT * FROM tablex
WHERE code = (f_get_param('A_03'));
--DOES NOT WORK
SELECT * FROM tablex
WHERE code IN (f_get_param('A_04'));
Please note that I need it for plain SQL statements, so procedures won't work as they are just good for PL/SQL.
I would be really thankful for good ideas or help!
Use collections. Here you have an example http://www.adp-gmbh.ch/ora/plsql/coll/return_table.html
Technically you can achieve using the function this way but doing this will cause index not to be used on code column on tablex and may affect performance .Using function index you can reduce performance impact
CREATE OR REPLACE FUNCTION f_get_param(p_value1 IN VARCHAR2,p_name1 in VARCHAR2) return NUMBER
DETERMINISTIC
IS
l_count NUMBER;
BEGIN
select count(1) into l_count from parameter_table where p_value =p_value1
and p_name=p_name1;
if l_count > 0
then
return 1;
else
return 0;
end if;
end f_get_param;
AND use the select statement like this
SELECT * FROM tablex
WHERE f_get_param(code,'A_04')=1;
EDIT 1:-
Also to reduce the performance impact in database 10.2 and greater If the parameter_table is static you can use the DETERMINISTIC clause in the Function to say that the function returns the same value if called with same parameters every time
Please find the link on the article about using functions in SELECT statement
--DOES NOT WORK
SELECT * FROM tablex
WHERE code IN (f_get_param('A_04'));
-- Try this
SELECT * FROM tablex
WHERE code IN (select * from TABLE(f_get_param('A_04')));
You have to "CAST" a collection onto SQL TABLE.
Also when you use cast you can also use inner joint:
SELECT * FROM tablex join TABLE(f_get_param('A_04') using (code);
I think - generally - your problem is called "Dynamic where clause". Try to search some articles about it on AskTom.
I think the actual solution to your problem is to simply join the two tables and create the appropriate indexes rather than invoking a PL/SQL function at all:
SELECT x.* FROM tablex x, parameter_table p
WHERE x.code = p.p_value
AND p.p_name LIKE '%A_04%';
Note that you also have a semantic error in your LIKE clause. You're not using the % sign therefore your LIKE 'A_04' is just the same as = 'A_04'

Sorting by value returned by a function in oracle

I have a function that returns a value and displays a similarity between tracks, i want the returned result to be ordered by this returned value, but i cannot figure out a way on how to do it, here is what i have already tried:
CREATE OR REPLACE PROCEDURE proc_list_similar_tracks(frstTrack IN tracks.track_id%TYPE)
AS
sim number;
res tracks%rowtype;
chosenTrack tracks%rowtype;
BEGIN
select * into chosenTrack from tracks where track_id = frstTrack;
dbms_output.put_line('similarity between');
FOR res IN (select * from tracks WHERE ROWNUM <= 10)LOOP
SELECT * INTO sim FROM ( SELECT func_similarity(frstTrack, res.track_id)from dual order by sim) order by sim; //that's where i am getting the value and where i am trying to order
dbms_output.put_line( chosenTrack.track_name || '(' ||frstTrack|| ') and ' || res.track_name || '(' ||res.track_id|| ') ---->' || sim);
END LOOP;
END proc_list_similar_tracks;
/
declare
begin
proc_list_similar_tracks(437830);
end;
/
no errors are given, the list is just presented unsorted, is it not possible to order by a value that was returned by a function? if so, how do i accomplish something like this? or am i just doing something horribly wrong?
Any help will be appreciated
In the interests of (over-)optimisation I would avoid ordering by a function if I could possibly avoid it; especially one that queries other tables. If you're querying a table you should be able to add that part to your current query, which enables you to use it normally.
However, let's look at your function:
There's no point using DBMS_OUTPUT for anything but debugging unless you're going to be there looking at exactly what is output every time the function is run; you could remove these lines.
The following is used only for a DBMS_OUTPUT and is therefore an unnecessary SELECT and can be removed:
select * into chosenTrack from tracks where track_id = frstTrack;
You're selecting a random 10 rows from the table TRACKS; why?
FOR res IN (select * from tracks WHERE ROWNUM <= 10)LOOP
Your ORDER BY, order by sim, is ordering by a non-existent column as the column SIM hasn't been declared within the scope of the SELECT
Your ORDER BY is asking for the least similar as the default sort order is ascending (this may be correct but it seems wrong?)
Your function is not a function, it's a procedure (one without an OUT parameter).
Your SELECT INTO is attempting to place multiple rows into a single-row variable.
Assuming your "function" is altered to provide the maximum similarity between the parameter and a random 10 TRACK_IDs it might look as follows:
create or replace function list_similar_tracks (
frstTrack in tracks.track_id%type
) return number is
sim number;
begin
select max(func_similarity(frstTrack, track_id)) into sim
from tracks
where rownum <= 10
;
return sim;
end list_similar_tracks;
/
However, the name of the function seems to preclude that this is what you're actually attempting to do.
From your comments, your question is actually:
I have the following code; how do I print the top 10 function results? The current results are returned unsorted.
declare
sim number;
begin
for res in ( select * from tracks ) loop
select * into sim
from ( select func_similarity(var1, var2)
from dual
order by sim
)
order by sim;
end loop;
end;
/
The problem with the above is firstly that you're ordering by the variable sim, which is NULL in the first instance but changes thereafter. However, the select from DUAL is only a single row, which means you're randomly ordering by a single row. This brings us back to my point at the top - use SQL where possible.
In this case you can simply SELECT from the table TRACKS and order by the function result. To do this you need to give the column created by your function result an alias (or order by the positional argument as already described in Emmanuel's answer).
For instance:
select func_similarity(var1, var2) as function_result
from dual
Putting this together the code becomes:
begin
for res in ( select *
from ( select func_similarity(variable, track_id) as f
from tracks
order by f desc
)
where rownum <= 10 ) loop
-- do something
end loop;
end;
/
You have a query using a function, let's say something like:
select t.field1, t.field2, ..., function1(t.field1), ...
from table1 t
where ...
Oracle supports order by clause with column indexes, i.e. if the field returned by the function is the nth one in the select (here, field1 is in position 1, field2 in position 2), you just have to add:
order by n
For instance:
select t.field1, function1(t.field1) c2
from table1 t
where ...
order by 2 /* 2 being the index of the column computed by the function */

Resources