I have an Oracle function that has 3 parameters and uses parameters to set where clause values in multiple select statements that are union'd together. Here is the pseudo code:
create or replace function fn_newfunction
(IN_1_id in VARCHAR2, IN_2 in VARCHAR2, IN_3 in VARCHAR2)
RETURN T_varchar_table AS
v_tab T_varchar_table;
begin
select
cast(multiset (
--add users
select * from table1 opt where opt.col2 = IN_2 and opt.col3 = IN_3 and opt.col1 = IN_1_id
union
...
<insert 10+ select statements here with same values>
) as T_varchar_table)
END
into v_tab
from dual;
return v_tab;
end;
A use case has come up to pass blank values into the function for any of the IN parameters and have it select ANY value in the where clause where the parameter is blank. An example is if IN_1_id is passed a blank value, the where clause in the first select statement would show where ANY value (even null) is in the opt.col1. How can I make this happen? Thank you!
The logic I most frequently use, albeit not in Oracle, is the following. I've written it pseudo, simply because as I mentioned, I believe this more methodological question, as opposed to syntax.
The Logic
Function (#Parameter1, #Parameter2)
SELECT * FROM MyTable
WHERE
--Parameter1
(#Parameter1 IS NULL OR MyTable.Parameter1 = #Parameter1)
AND
--Parameter2
(#Parameter2 IS NULL OR MyTable.Parameter2 = #Parameter2)
Why It works
If you don't pass #Parameter1:
--This evaluates to TRUE for every single row, because the first condition has been met
(#Parameter1 IS NULL OR MyTable.Parameter1 = #Parameter1)
If you do pass #Parameter1:
--The first condition will never be met, because #Parameter1 is NOT NULL.
--The second condition will only be met for rows that match the parameter.
(#Parameter1 IS NULL OR MyTable.Parameter1 = #Parameter1)
Using this method, you can conditionally add fields to your WHERE clause.
You can use dynamic sql to get your issue addressed
if IN_1_id is not null then
lv_where := ' and opt.col1 =IN_1_id1 ';
else
lv_where :=' ';
end if;
EXECUTE IMMEDIATE 'select * from table1 opt where opt.col2 = IN_2 and opt.col3 = IN_3 ' ||lv_where ;
Related
I have a query that requires strings from tables to be stripped of special characters before they are compared against each other. I created a function that takes in a string and removes certain special characters from the string before returning it. The problem is I found myself using the function many times due to the query doing a lot of comparisons. This significantly slowed down the performance after adding the functionality.
So I have this function I created:
create or replace FUNCTION F_REMOVE_SPECIAL_CHARACTERS
(
IN_PARAM_EMAIL_NAME IN VARCHAR2,
IN_PARAM_NUMBER_FLAG IN VARCHAR2 DEFAULT 'N'
) RETURN VARCHAR2 AS
BEGIN
/* If flag is Y then remove all numbers too. Otherwise, keep numbers in the string */
IF IN_PARAM_NUMBER_FLAG = 'Y' THEN
RETURN replace(regexp_replace(IN_PARAM_EMAIL_NAME, '[-,._0-9]', ''), ' ', '');
ELSE
RETURN replace(regexp_replace(IN_PARAM_EMAIL_NAME, '[-,._]', ''), ' ', '');
END IF;
END F_REMOVE_SPECIAL_CHARACTERS;
I also have a query that goes like this:
SELECT a.ID, LISTAGG(b.BUSINESS_EMAIL) WITHIN GROUP (ORDER BY a.ID)
FROM tableA a, tableB b
WHERE UPPER(F_REMOVE_SPECIAL_CHARACTERS(b.LAST_NAME)) IN (
(SELECT UPPER(F_REMOVE_SPECIAL_CHARACTERS(a.NICK_NAME)) FROM tableC c
WHERE UPPER(F_REMOVE_SPECIAL_CHARACTERS(a.NICK_NAME)) IN (
(SELECT UPPER(F_REMOVE_SPECIAL_CHARACTERS(c.NAME)) FROM tableC c
WHERE UPPER(F_REMOVE_SPECIAL_CHARACTERS(a.NICK_NAME)) = UPPER(F_REMOVE_SPECIAL_CHARACTERS(a.LAST_NAME))
)
)
)
)
The actual query is bigger and more complicated but the point is that I need to remove special characters from certain column values which happens to be repeated multiple times in the query. This means I need to use the function multiple times but this causes significant slowdown in performance.
Does anyone have an idea on how to reduce the performance slowdown when using multiple function calls in a query? Thanks.
Assuming you need this as a function (because you use it in many places), you could clean it up and simplify it (and make it more efficient) like so:
create or replace function f_remove_special_characters
(
in_param_email_name in varchar2,
in_param_number_flag in varchar2 default 'N'
)
return varchar2
deterministic
as
pragma udf; -- if on Oracle 12.1 or higher, and function is only for SQL use
/* If flag is Y then remove all numbers too.
Otherwise, keep numbers in the string
*/
chars_to_remove varchar2(16) := 'z-,._ ' ||
case in_param_number_flag when 'Y' then '0123456789' end;
begin
return translate(in_param_email_name, chars_to_remove, 'z');
end f_remove_special_characters;
/
The silly trick with the 'z' in translate (in the second and third arguments) is due to Oracle's odd treatment of null. In translate, if any of the arguments is null the result is null, in contrast with Oracle's treatment of null in other string operations.
If you are on 12c or above, then as a quick fix you can use WITH FUNCTION clause
As I remember this eliminates PL/SQL<->SQL Context switches so you query shoul perform better.
I've never tested that, but it is very likely that it will be faster even 30-50 times.
Let me know how fast it will be, because I'm curious
WITH FUNCTION F_REMOVE_SPECIAL_CHARACTERS
(
IN_PARAM_EMAIL_NAME IN VARCHAR2,
IN_PARAM_NUMBER_FLAG IN VARCHAR2 DEFAULT 'N'
) RETURN VARCHAR2 AS
BEGIN
/* If flag is Y then remove all numbers too. Otherwise, keep numbers in the string */
IF IN_PARAM_NUMBER_FLAG = 'Y' THEN
RETURN replace(regexp_replace(IN_PARAM_EMAIL_NAME, '[-,._0-9]', ''), ' ', '');
ELSE
RETURN replace(regexp_replace(IN_PARAM_EMAIL_NAME, '[-,._]', ''), ' ', '');
END IF;
END;
SELECT a.ID, LISTAGG(b.BUSINESS_EMAIL) WITHIN GROUP (ORDER BY a.ID)
FROM tableA a, tableB b
WHERE UPPER(F_REMOVE_SPECIAL_CHARACTERS(b.LAST_NAME)) IN (
(SELECT UPPER(F_REMOVE_SPECIAL_CHARACTERS(a.NICK_NAME)) FROM tableC c
WHERE UPPER(F_REMOVE_SPECIAL_CHARACTERS(a.NICK_NAME)) IN (
(SELECT UPPER(F_REMOVE_SPECIAL_CHARACTERS(c.NAME)) FROM tableC c
WHERE UPPER(F_REMOVE_SPECIAL_CHARACTERS(a.NICK_NAME)) = UPPER(F_REMOVE_SPECIAL_CHARACTERS(a.LAST_NAME))
)
)
)
)
SELECT DISTINCT L.* FROM LABALES L , MATCHES M
WHERE M.LIST LIKE '%ENG'
ORDER BY L.ID
I need to create function with this select, I tried this but it doesn't work.
CREATE OR REPLACE FUNCTION getSoccerLists
RETURN varchar2 IS
list varchar2(2000);
BEGIN
SELECT DISTINCT L.* FROM LABALES L , MATCHES M
WHERE M.LIST LIKE '%ENG'
ORDER BY L.ID
return list;
END;
How will I create function that returns all from table L.
Thanks
You may use implicit result using DBMS_SQL.RETURN_RESULT(Oracle12c and above) in a procedure using a cursor to your query.
CREATE OR REPLACE PROCEDURE getSoccerLists
AS
x SYS_REFCURSOR;
BEGIN
OPEN x FOR SELECT DISTINCT L.* FROM LABALES L
JOIN MATCHES M ON ( 1=1 ) -- join condition
WHERE M.LIST LIKE '%ENG'
ORDER BY L.ID;
DBMS_SQL.RETURN_RESULT(x);
END;
/
then simply call the procedure
EXEC getSoccerLists;
For lower versions(Oracle 11g) , you may use a print command to display the cursor's o/p passing ref cursor as out parameter.
CREATE OR REPLACE PROCEDURE getSoccerLists (x OUT SYS_REFCURSOR)
AS
BEGIN
OPEN x FOR SELECT DISTINCT L.* FROM LABALES L
JOIN MATCHES M ON ( 1=1 ) -- join condition
WHERE M.LIST LIKE '%ENG'
ORDER BY L.ID;
END;
/
Then, in SQL* Plus or running as script in SQL developer and Toad, you may get the results using this.
VARIABLE r REFCURSOR;
EXEC getSoccerLists (:r);
PRINT r;
Another option is to use TABLE function by defining a collection of the record type of the result within a package.
Refer Create an Oracle function that returns a table
I guess this questions is a repetition of the your previously asked question, where you wanted to get all the columns of tables but into separate column. I already answered in stating this you cannot do if you call your function via a SELECT statement. If you call your function in a Anoymous block you can display it in separate columns.
Here Oracle function returning all columns from tables
Alternatively, you can get the results separated by a comma(,) or pipe (|) as below:
CREATE OR REPLACE
FUNCTION getSoccerLists
RETURN VARCHAR2
IS
list VARCHAR2(2000);
BEGIN
SELECT col1
||','
||col2
||','
||col2
INTO LIST
FROM SOCCER_PREMATCH_LISTS L ,
SOCCER_PREMATCH_MATCHES M
WHERE M.LIST LIKE '%' || (L.SUB_LIST) || '%'
AND (TO_TIMESTAMP((M.M_DATE || ' ' || M.M_TIME), 'DD.MM.YYYY HH24:MI') >
(SELECT SYSTIMESTAMP AT TIME ZONE 'CET' FROM DUAL
))
ORDER BY L.ID");
Return list;
End;
Note here if the column size increased 2000 chars then again you will lose the data.
Edit:
From your comments
I want it to return a table set of results.
You then need to create a table of varchar and then return it from the function. See below:
CREATE TYPE var IS TABLE OF VARCHAR2(2000);
/
CREATE OR REPLACE
FUNCTION getSoccerLists
RETURN var
IS
--Initialization
list VAR :=var();
BEGIN
SELECT NSO ||',' ||NAME BULK COLLECT INTO LIST FROM TEST;
RETURN list;
END;
Execution:
select * from table(getSoccerLists);
Note: Here in the function i have used a table called test and its column. You replace your table with its columnname.
Edit 2:
--Create a object with columns same as your select statement
CREATE TYPE v_var IS OBJECT
(
col1 NUMBER,
col2 VARCHAR2(10)
)
/
--Create a table of your object
CREATE OR REPLACE TYPE var IS TABLE OF v_var;
/
CREATE OR REPLACE FUNCTION getSoccerLists
RETURN var
IS
--Initialization
list VAR :=var();
BEGIN
--You above object should have same columns with same data type as you are selecting here
SELECT v_var( NSO ,NAME) BULK COLLECT INTO LIST FROM TEST;
RETURN list;
END;
Execution:
select * from table(getSoccerLists);
This is not an answer on how to build a function for this, as I'd recommend to make this a view instead:
CREATE OR REPLACE VIEW view_soccer_list AS
SELECT *
FROM soccer_prematch_lists l
WHERE EXISTS
(
SELECT *
FROM soccer_prematch_matches m
WHERE m.list LIKE '%' || (l.sub_list) || '%'
AND TO_TIMESTAMP((m.m_date || ' ' || m.m_time), 'DD.MM.YYYY HH24:MI') >
(SELECT SYSTIMESTAMP AT TIME ZONE 'CET' FROM DUAL)
);
Then call it in a query:
SELECT * FROM view_soccer_list ORDER BY id;
(It makes no sense to put an ORDER BY clause in a view, because you access the view like a table, and table data is considered unordered, so you could not rely on that order. The same is true for a pipelined function youd access with FROM TABLE (getSoccerLists). Always put the ORDER BY clause in your final queries instead.)
i need to pick the count based on given parameter value in a function.function parameter value can be 'I' or 'D' or 'ALL'. 'ALL' means I and D both.
for ex:
create or replace function test1( FLAG in varchar2) return varchar2
as
b varchar2(20);
c varchar2(20);
begin
if flag='ALL'
then
c:='I','D';
else
c:=FLAG;
end if;
select count(*) into b from test where id=c;
return b;
end;
if i pass I or D its working fine.I want to pass 'ALL' as in parameter to pick all the count for both (I,D) but I am facing error.
Let me know if any other info required at my end.
There are a few ways of doing this, all over complicated for your scenario. It'd be easier if you used the logic from your IF statement in your SQL:
select count(*) into b from test where flag = 'ALL' or id = flag
Thus, if you pass in the FLAG ALL then you get everything in the table, otherwise if FLAG is not ALL then you restrict it to the specific value. If you want to restrict the ID to only the 2 values mentioned then you could do this:
select count(*) into b
from test
where ( flag = 'ALL' and id in ('I','D') )
or id = flag
If else block needs to be changed to meet your requirements. Updated code is listed below.
create or replace function test1( flag_var in varchar2) return varchar2
as
count_num NUMBER(20);
begin
if flag_var='ALL'
then
select count(*) into count_num from TEST where ID in ('I' , 'D');
else
select count(*) into count_num from TEST where ID = flag_var;
end if;
return count_num;
end;
CREATE OR REPLACE PROCEDURE test_max_rows (
max_rows IN NUMBER DEFAULT 1000
)
IS
CURSOR cur_test ( max_rows IN number ) IS
SELECT id FROM test_table
WHERE user_id = 'ABC'
AND ROWNUM <= max_rows;
id test_table.id%TYPE;
BEGIN
OPEN cur_test(max_rows) ;
LOOP
FETCH cur_test INTO id;
EXIT WHEN cur_test%NOTFOUND;
DBMS_OUTPUT.PUT_LINE('ID:' || id);
END LOOP;
END;
My requirement is to modify the above code so that when I pass -1 for max_rows, the proc should return all the rows returned by the query. Otherwise, it should limit the rows as per max_rows.
For example:
EXECUTE test_max_rows(-1);
This command should return all the rows returned by the SELECT statement above.
EXECUTE test_max_rows(10);
This command should return only 10 rows.
You can do this with a OR clause; change:
AND ROWNUM <= max_rows;
to:
AND (max_rows < 1 OR ROWNUM <= max_rows);
Then passing zero, -1, or any negative number will fetch all rows, and any positive number will return a restricted list. You could also replace the default 1000 clause with default null, and then test for null instead, which might be a bit more obvious:
AND (max_rows is null OR ROWNUM <= max_rows);
Note that which rows you get with a passed value will be indeterminate because you don't have an order by clause at the moment.
Doing this in a procedure also seems a bit odd, and you're assuming whoever calls it will be able to see the output - i.e. will have done set serveroutput on or the equivalent for their client - which is not a very safe assumption. An alternative, if you can't specify the row limit in a simple query, might be to use a pipelined function instead - you could at least then call that from plain SQL.
CREATE OR REPLACE FUNCTION test_max_rows (max_rows IN NUMBER DEFAULT NULL)
RETURN sys.odcinumberlist PIPELINED
AS
BEGIN
FOR r IN (
SELECT id FROM test_table
WHERE user_id = 'ABC'
AND (max_rows IS NULL OR ROWNUM <= max_rows)
) LOOP
PIPE ROW (r.id);
END LOOP;
END;
/
And then call it as:
SELECT * FROM TABLE(test_max_rows);
or
SELECT * FROM TABLE(test_max_rows(10));
Here's a quick SQL Fiddle demo. But you should still consider if you can do the whole thing in plain SQL and PL/SQL altogether.
I would like to have a query to find if a unique record exist or not in the database. For instance:
SELECT externaleventid
FROM event
WHERE externaleventid = "XYZ".
If "XYZ" doesn't exist, it should return as false and if exist it should return as true. I am using sql developer and eclipse. I tried using
if exists (select externaleventid from event where externaleventid='XYZ')
select 'True'
else
select 'False'
return
but it is giving me an Syntax error "and/or" expected in sql developer.
It's not clear whether you are asking for a SQL statement that returns the string 'True' or 'False' or whether you are asking for a PL/SQL function that returns that same string or whether you are asking for a PL/SQL function that returns an actual boolean. The boolean data type didn't exist in SQL prior to 12.1 so I'm guessing that you're not asking for a SQL statement that returns a boolean.
If you want a single SQL statement, you could do something like
SELECT (CASE WHEN cnt >= 1
THEN 'True'
ELSE 'False'
END) result_str
FROM( SELECT COUNT(*) cnt
FROM event
WHERE externaleventid = 'XYZ' )
If you want a PL/SQL function, you'd probably do something like
CREATE OR REPLACE FUNCTION does_exist(
p_externaleventid IN event.externaleventid%type
)
RETURN VARCHAR2
IS
l_cnt PLS_INTEGER;
BEGIN
SELECT COUNT(*)
INTO l_cnt
FROM event
WHERE externaleventid = p_externaleventid;
IF( l_cnt > 0 )
THEN
RETURN 'True';
ELSE
RETURN 'False';
END IF;
END;