PLSQL aggregation function using type object PARALLEL_ENABLE AGGREGATE - oracle

I have question. Already i have avg_new function, which include nulls (as 0) in the result. I have start from linkedin link.
Code:
select avg(a),avg_new(nvl(a,-9999)) from
(select 'test' h, 2 a from dual
union all
select 'test' h, null a from dual
union all
select 'test' h, 2 a from dual
union all
select 'test' h ,2 a from dual)
the results are
2; 1,5
I would like to extend avg_new function by adding denominator parameter ex.:
avg_new(nvl(a,-9999),10)
Te result should be then 0.6
Default value of the parameter would be null, then function works as previous example. If the parameter would be >0 then I would divide sum of 'a' by value of this parameter. How i could do this? I would like to pass this parameter to used type object and to perform further calculations there. Is it possible?
create or replace FUNCTION avg_new (input NUMBER , denominator NUMBER DEFAULT NULL) RETURN NUMBER
PARALLEL_ENABLE AGGREGATE USING T_avg_new;
Right now the type could proper read only the first parameter. After adding i have errors:
ORA-29925: cannot execute T_avg_new.ODCIAGGREGATEINITIALIZE
ORA-06553: PLS-306: wrong number or types of arguments in call to "ODCIAGGREGATEINITIALIZE"
00000 - "cannot execute %s"
*Cause: The specified function does not exist or does not have an
appropriate signature.
*Action: Implement the function with the appropriate signature.

Related

Input list to Snowflake SQL udf

I have created a Snowflake SQL udf I call with the following code:
select *
from table(drill_top_down('12345','XXX)) order by depth,path;
If I need to run the query for multiple items is it then possible to input a list or similar to the udf, and then loop through my input list?
Or can I somehow call my function in smarter way so I can get the result from multiple inputs?
You can provide a Snowflake Array, Object or Variant with your argument sets nested within, and use that as input to the table function.
Adapting your example, using array construct to provide two sets of arguments the input would look something like :
select *
from table(drill_top_down(
array_construct(
array_construct('12345','XXX'),
array_construct('67890','YYY')
)::array;
Or my preference is to use parse_json, as I find it easier to read
select *
from table(drill_top_down(parse_json('
[ ["12345","XXX"],
["67890","YYY"] ]')::array;
You will need to adapt your Table Function to unpack the Argument Sets using a common-table-expression (CTE) to tabularise the input arguments and then unnest them with Lateral Flatten.
Here's a trivial example:
CREATE OR REPLACE FUNCTION array_concat ( arr array)
RETURNS TABLE ( concatenated_string varchar )
AS
$$
With a as (Select arr)
Select listagg(value)
From a, table(flatten(input => arr))
$$
;
Here is a slightly more sophisticated example that performs an operation with each argument set, using row_number() to group them.
CREATE OR REPLACE FUNCTION array_calcs ( arg_list array)
RETURNS TABLE
( arg_id integer,
array_sz integer,
array_sum integer,
array_mean decimal(12,2) )
AS
$$
With
-- CTE containing the ARGS
arg_input as (select arg_list),
-- CTE un-nest (flatten) first level of args list to each args set
arg_sets as
(Select row_number() over (order by NULL desc) as arg_id, value as arg_set
From arg_input, lateral flatten(input => arg_list))
-- Do something with the Args. e.g. Perform some calculations with the Input arguments
Select arg_id , count(*) array_sz, sum(value)::integer array_sum, array_sum/array_sz::decimal(12,2) array_mean
From arg_sets, table(flatten(input => arg_set))
Where is_decimal( value ) or is_integer( value ) or is_double( value ) -- filter out non-numeric arguments i.e. validate inputs
Group By arg_id
$$;
This works if we provide the following input arguments
Select * from table(array_calcs(parse_json('[ [1],
[1,2],
[1,2,3],
[1,2,3,4],
["A","B"],
["A",1]
]')::array));
Producing the following:
ARG_ID
ARRAY_SZ
ARRAY_SUM
ARRAY_MEAN
1
1
1
1.0
2
2
3
1.5
3
3
6
2.0
4
4
10
2.5
6
1
1
1.0
But word of caution. If your aim was to build your arguments directly from your data, rather than hard-code them in the function call, you are more than likely to run into this issue:
Create or replace View V_array_calcs_input as
Select parse_json($1)::array arg_list
from (values ('[[1],[1,2],[1,2,3],[1,2,3,4],["A","B"], ["A",1]'));
Select *
from V_array_calcs_input,
table(array_calcs(arg_list));
SQL compilation error: Unsupported subquery type cannot be evaluated
A Stored Procedure, or JavaScript UDF/UDTF may be better options to resolve this, if you can build the functional logic you need in either of those.

ORACLE Obtain the greater and lower value in a varchar2 field range

I have a table with a field varchar2 type,
Suppose this values:
aaab
s123
2445
25
21000
2500000
1
10000790
1899
I need to obtain the greater value that begins with 2.
I was trying with:
Select TO_NUMBER(myfield) from Services where myfield like '2%';
I get
2445
25
21000
2500000
Now, I want to obtain, the greater and the smaller values 25 and 2500000.
I was trying with:
Select TO_NUMBER(myfield) from Services where myfield like '2%' ORDER BY myfield DESC;
and
Select MAX(TO_NUMBER(myfield)) from Services where myfield like '2%';
Select MIN(TO_NUMBER(myfield)) from Services where myfield like '2%';
I get:
01722. 00000 - "invalid number"
*Cause: The specified number was invalid.
*Action: Specify a valid number.
Oracle executes most function calls regardless if the row is part of the result set or not. To ensure that the function is only called for values of the result set put the where condition in a subquery:
Select TO_NUMBER(myfield)
from (SELECT * from Services where myfield like '2%')
ORDER BY 1 DESC
If you are not sure, that every value of your table is a number it would be better to write your own pl/sql function:
CREATE FUNCTION my_to_number(val IN VARCHAR2) RETURN NUMBER IS
BEGIN
RETURN TO_NUMBER(val);
EXCEPTION
WHEN VALUE_ERROR THEN
RETURN NULL;
END;
And then use this function to get your values:
Select MY_TO_NUMBER(myfield)
from Services where myfield like '2%'
ORDER BY 1 DESC

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 */

Expected CHAR got NUMBER

DB: Oracle 11g
Query:
SELECT CASE
WHEN rs.OPTION = '3'
THEN
(SELECT COUNT(DISTINCT ex.EXTS) AS TMPCOL0
FROM CRSIDM.SUB_OPTS ex
INNER JOIN CRSIDM.SUB_OPTS_GRP cg
ON cg.GROUP_ID = ex.GRP_ID
)
ELSE
(SELECT COUNT(DISTINCT ex.EXTS) AS TMPCOL0
FROM CRSIDM.SUB_OPTS ex
INNER JOIN CRSIDM.SUB_OPTS_POL cg
ON cg.GROUP_ID = ex.GRP_ID
)
END AS PROPTS
FROM PR_OPTS
I am getting error 'expected CHAR got NUMBER', here EXTS,GROUP_ID & GRP_ID are numeric. Then how there is a chance of expecting CHAR?
Generally when Oracle compares different datatypes such as a NUMBER with a CHARACTER, implicit conversion kicks in and all is well (provided the data can be converted.) For example, if you have a function that expects a CHARACTER value but you pass it a NUMBER, all is well - Oracle simply converts the NUMBER to character.
E.g. a function like this:
create or replace function get_something(p_id VARCHAR2) return number ...
works if you call it with this:
get_dno(10);
or this:
get_dno('10');
and in SQL:
select * from some_table where numeric_column = '10' -- no problem.
A popular place where you see this kind of error is with the return values in CASE statements. For instance, you'll get that error if you have something like this:
SQL> SELECT CASE WHEN 1 = 1 THEN '1' ELSE 2 END
2 FROM dual
3 ;
SELECT CASE WHEN 1 = 1 THEN '1' ELSE 2 END
*
ERROR at line 1:
ORA-00932: inconsistent datatypes: expected CHAR got NUMBER
(The datatype from the first WHEN clause is what it expects in the other WHEN/ELSE clauses that follow.)
But in your case the WHEN and THEN both return counts - the datatypes are consistent. So, I think you have a red-herring in there.
As Alex mentioned above, OPTION is a keyword and if you try and create a table with that as a column name, Oracle disagrees:
SQL> create table dummy
2 (option varchar2(10)
3 );
(option varchar2(10)
*
ERROR at line 2:
ORA-00904: : invalid identifier
This works:
SQL> create table dummy
2 (option_col varchar2(10)
3 );
Table created.
or you could do it with quotes:
SQL> create table dummy
2 ("option" varchar2(10));
Table created.
But now you're in a world of hurt - you need quotes from now on:
SQL> select option from dummy;
select option from dummy
*
ERROR at line 1:
ORA-00936: missing expression
SQL> select d.option from dummy d;
select d.option from dummy d
*
ERROR at line 1:
ORA-01747: invalid user.table.column, table.column, or column specification
With quotes:
SQL> select d."option" from dummy d;
no rows selected
So, if your query is really giving you "expected CHAR, got NUMBER", it looks to me like something is off.
Essentially, it means some of the fields you are using aren't compatible with each other. It's basically a "type mismatch". Just check to see if any types of CHAR are being used with types of NUMBER. Then you can either switch the type of one, or simply use a conversion as part of the query.
The issue is OPTION = '3', the quotation marks indicate that you're looking for a string containing the solitary character 3.
Try this instead:
SELECT CASE
WHEN rs.OPTION = 3
THEN
(SELECT COUNT(DISTINCT ex.EXTS) AS TMPCOL0
FROM CRSIDM.SUB_OPTS ex
INNER JOIN CRSIDM.SUB_OPTS_GRP cg
ON cg.GROUP_ID = ex.GRP_ID)
ELSE
(SELECT COUNT(DISTINCT ex.EXTS) AS TMPCOL0
FROM CRSIDM.SUB_OPTS ex
INNER JOIN CRSIDM.SUB_OPTS_POL cg
ON cg.GROUP_ID = ex.GRP_ID)
END AS PROPTS
FROM PR_OPTS

Is it possible to check whether a value is in the list of items in a single Oracle Decode Function?

I would like to know that if I can compare a value with a list of items in an decode function. Basically I want to know that if is it possible to make a decode statement's 'search' value a list. For example,
decode(task_id, (1,2,3), 3 * task_time)
This piece of code won't compile though. Is this the only option for this case then (without using case-when) or are there alternative ways of doing this?
decode(task_id, 1, 3 * task_time,
2, 3 * task_time,
3, 3 * task_time)
I am using Oracle 10gR2. Any help is much appreciated.
If a single list of values is sufficient, you can turn it into a CASE and IN clause:
case when task_id in (1, 2, 3) then 3 * task_time else null end
I don't think its possible to use a list with decode in this way. Per the docs:
DECODE compares expr to each search value one by one. If expr is equal
to a search, then Oracle Database returns the corresponding result. If
no match is found, then Oracle returns default
So task_id is compared with a search value one by one. If search value was a list, you couldn't compare with a single value.
I found a solution :)
select
decode(
task_id,
(select task_id from dual where task_id in (1,2,3)),
3*task_time)
decode ( (taskid-1)*(taskid-2)*(taskid-3), 0, 3 * tasktime ) could do what you want
Here's a working example:
with a as (
select 1 taskid, 11 tasktime from dual union all
select 2 taskid, 11 tasktime from dual union all
select 3 taskid, 11 tasktime from dual union all
select 4 taskid, 11 tasktime from dual
)
select
taskid,
decode (
(taskid-1) *
(taskid-2) *
(taskid-3) ,
0, 3 * tasktime
) decoded
from a;
you can use union all:
select 3 * task_time from your_table where task_id in (1,2,3)
union all
select task_time from your_table where task_id not in (1,2,3)
but why ?
In if condition :
IF vVal in (3,1,2) THEN
dbms_output.put_line('FOUND');
ELSE
dbms_output.put_line('NOT FOUND');
END IF;
I've seen instr(...) and static strings used as a way to quickly determine whether a value is one of multiple you're looking for as a condition for what value you return. You may need to choose a delimiter, but with limited datasets you can even omit it. It avoids using case-when, subqueries, and PL/SQL. As far as I know, there is no shorter way to do this:
decode(instr('123', taskid), 0, null, taskid * 3)
It's also very convenient when you want to set exceptions (for instance returning taskid without multiplication if it equals 1):
decode(instr('12345', taskid), 0, null, 1, taskid, taskid * 3)

Resources