Oracle function to replace all names to their nick names? - sql

I have a table called nicknames
Names nickname
vikram vik
James Jim
Robert Bob
Charles Dick
Richard Dick
Rich Dick
Want to create an Oracle function to replace names with their nicknames from the table in an input string
func_nicknames('vikram, James, Rajesh, Robert') should return the value 'vik, Jim, Rajesh, Bob'
CREATE OR REPLACE FUNCTION func_nicknames( in_val varchar2)
RETURN VARCHAR2
IS
O_VAL VARCHAR2(100) := in_val;
BEGIN
SELECT REPLACE(O_VAL,t.name,t.nickname) INTO O_VAL FROM nicknames t;
RETURN(O_VAL);
END func_nicknames;
The above code is throwing an error.
In SQL Server, the below code works fine:
CREATE OR ALTER FUNCTION getNicknames(#val NVARCHAR(MAX))
RETURNS NVARCHAR(MAX) AS
BEGIN
DECLARE #result NVARCHAR(MAX);
SET #result = #val;
SELECT #result = REPLACE(#result, name, nickname)
FROM nicknames;
RETURN #result;
END;
Similar code I want to create in Oracle.
Working code for oracle:
CREATE OR REPLACE FUNCTION getNicknames(in_val VARCHAR) RETURN VARCHAR IS
ret VARCHAR(2000);
v VARCHAR(2000);
CURSOR cur IS SELECT SRC_VAL_STR, TGT_VAL_STR FROM nicknames;
BEGIN
ret := in_val;
FOR x IN cur
LOOP
SELECT REPLACE(ret, x.name, x.nickname) INTO v FROM DUAL;
ret := v;
END LOOP;
RETURN ret;
END;

First you have to convert the parameter to a table, the rest is very straightforward:
create or replace function getNicknames (names varchar2) return varchar2 is
ret varchar2 (32000);
begin
with names (name) as (
select trim (column_value)
from xmlTable (('"'||replace (names, ',', '","')||'"')) x
)
select
listagg (coalesce (nickname, na.name), ', ') within group (order by null) into ret
from names na
left join nicknames ni on ni.name=na.name;
return ret;
end;
/
Execution and outcome:
exec dbms_output.put_line ('result='||getNicknames ('vikram, James, Rajesh, Robert'));
result=vik, Jim, Rajesh, Bob
On db<>fiddle.

Your SQL returns more than one row. It is an error.
Try this:
create or replace function f_nicks (pNames in varchar2)
return varchar2
is
tab dbms_utility.uncl_array;
nCntElements number := 0;
vEl varchar2(32000);
o_nicks varchar2(1000);
BEGIN
dbms_utility.comma_to_table(pNames, nCntElements, tab);
select listagg (nvl( n.nickname, t.listname), ',') nicks into o_nicks
from (select trim(column_value) listname, rownum lp
from table(tab) ) t , nicknames n where n.name (+) = t.listname ;
return o_nicks;
END;
--- Test function
select f_nicks ('James, vikram, James,James, Rajesh, Robert') from dual;
db fiddle

Related

LISTAGG in Oracle gives NULL

If a column value if too long, then listagg gives NULL output in Oracle 12c. What is the cause of this and what is the solution?
SQL> select length(algo_desc) from r2_temp where DECL = '305';
32759
ALGO_DESC is very long, like 1045,2339,2389.......37364,58922,2389392
Now if I use LISTAGG then it gives NULL as follows:
SQL> select dump(listagg(algo_desc, ',') within group (order by algo_desc)) as algo_desc from r2_temp where DECL = '305';
NULL
max size of column ALGO_DESC is 32767, it's extended varchar2 in 12c
As a workaround you can use this function:
CREATE OR REPLACE TYPE VARCHAR_TABLE_TYPE AS TABLE OF VARCHAR2(32767);
-- or CREATE OR REPLACE TYPE VARCHAR_TABLE_TYPE AS TABLE OF NUMBER;
CREATE OR REPLACE FUNCTION JoinTable(TAB IN VARCHAR_TABLE_TYPE, Joiner IN VARCHAR2) RETURN CLOB IS
res CLOB;
BEGIN
IF TAB IS NULL THEN
RETURN NULL;
END IF;
IF TAB.COUNT = 0 THEN
RETURN NULL;
END IF;
DBMS_LOB.CREATETEMPORARY(res, FALSE, DBMS_LOB.CALL);
IF TAB(TAB.FIRST) IS NOT NULL THEN
DBMS_LOB.APPEND(res, TAB(TAB.FIRST));
END IF;
IF TAB.COUNT > 1 THEN
FOR i IN TAB.FIRST+1..TAB.LAST LOOP
DBMS_LOB.APPEND(res, Joiner||TAB(i));
END LOOP;
END IF;
RETURN res;
END JoinTable;
select JoinTable(CAST(COLLECT(algo_desc order by algo_desc) AS VARCHAR_TABLE_TYPE), ',') as algo_desc
from r2_temp
where DECL = '305';

I need to use variable with TABLE type in IN operator

I need help with PL/SQL. How can i use variable with TABLE data type (in my case this variable involve 3 VARCHAR2 elements) with IN operator, without use access by index?
Example
select field1
from dual
where field1 in (myTableVariable);
myTableVariable must returning from function.
Not finished function:
declare
v_string varchar2(100);
v_string2 varchar2(100);
TYPE V_ARRAY IS TABLE OF VARCHAR2(10)
INDEX BY BINARY_INTEGER;
result V_ARRAY;
n number;
begin
n := 1;
v_string := '13,15,02';
while v_string != ' ' loop
select regexp_substr(v_string, '^[a-z0-9]*', 1),
regexp_replace(v_string, '^[a-z0-9]*(,|$)', '')
into v_string2, v_string
from dual;
result(n) := v_string2;
n := n + 1;
dbms_output.put_line(v_string2);
end loop;
return result;
end;
First:
Declare your table data type on the schema level (i.e. not in a package).
Then:
select field1
from dual
where feld1 in (select column_value from table(myTableVariable));
Enjoy!

pl/sql function- pass multiple varchar2 values dynamically for SELECT ... NOT IN()

I have a table EMP with following definition:
EMP_ID NOT NULL NUMBER(6)
EMP_NAME NOT NULL VARCHAR2(25)
EMAIL NOT NULL VARCHAR2(25)
PHONE_NUMBER VARCHAR2(20)
HIRE_DATE NOT NULL DATE
JOB_ID NOT NULL VARCHAR2(10)
SALARY NUMBER(8,2)
If we want to count employees whose name not in 'King', 'Steve', 'John' we simply use this query :
SELECT count(*) FROM emp WHERE emp_name NOT IN('King','Steve','John');
Now Here is what I want above this:
What I want to do is, Create a PL/SQL Function which returns number of count according to the dynamic input, Like if we pass:
SELECT count_emp('King,Steve,John') FROM dual;
SELECT count_emp('William,Donald') FROM dual;
SELECT count_emp('Daniel') FROM dual;
needs to return appropriate count, how can I achieve this using PL/SQL FUNCTION
This is what I have tried and Needs guideline:
CREATE OR REPLACE FUNCTION count_emp(emp_nm IN varchar)
RETURN number
IS
cnt NUMBER;
BEGIN
SELECT count(*) INTO cnt FROM emp WHERE emp_name NOT IN(emp_nm);
RETURN cnt;
END;
it is giving result for single name, but how can I split/format multiple input(i.e. emp_nm) to pass in NOT IN()?
Try like this,
CREATE OR REPLACE
FUNCTION count_emp(emp_nm IN VARCHAR)
RETURN NUMBER
IS
cnt NUMBER;
BEGIN
SELECT count(*)
INTO cnt
FROM emp
WHERE ename NOT IN(
SELECT regexp_substr (emp_nm, '[^,]+', 1, ROWNUM)
FROM dual
CONNECT BY LEVEL <= LENGTH (regexp_replace (emp_nm, '[^,]+')) + 1);
RETURN cnt;
END;
You can try dynamic sql:
CREATE OR REPLACE FUNCTION count_emp(emp_nm IN varchar)
RETURN number
IS
cnt NUMBER;
BEGIN
Execute immediate 'SELECT count(*) FROM emp WHERE emp_name NOT IN(' || emp_nm || ')' returning into cnt;
RETURN cnt;
END;
You can also use MEMBER OF. Here is a snippet. Hope this helps!
-- Create a type in SQL
CREATE OR REPLACE TYPE t_emp_name AS TABLE OF VARCHAR2 (10);
-- use MEMBER OF to use your list as IN parameter
CREATE OR REPLACE FUNCTION count_emp (emp_nm IN t_emp_name)
RETURN NUMBER
IS
cnt NUMBER;
BEGIN
SELECT COUNT (*)
INTO cnt
FROM emp
WHERE emp_name NOT MEMBER OF (emp_nm);
RETURN cnt;
END;
-- Assign values to the list, you can do it dynamically as well. Call the function
DECLARE
l_emp_name_list t_emp_name;
lv_count NUMBER;
BEGIN
l_emp_name_list := t_emp_name ('King', 'Steve'); --add more names as needed
lv_count := count_emp (l_emp_name_list);
END;

Data masking in Oracle SQL select statement

Without using PL/SQL, is it possible to do data masking in SELECT statement?
For example:
(AS-IS) SELECT 'this is a string' from DUAL;
this is a string
(TO-BE) SELECT 'this is a string' from DUAL;
xxxx xx x xxxxxx
REGEXP_REPLACE can do this:
SELECT REGEXP_REPLACE('this is a string', '\w', 'x') FROM DUAL;
This replaces all non-whitespace characters with an x. To replace letters only, try this:
SELECT REGEXP_REPLACE('this is a string', '[A-Za-z]', 'x') FROM DUAL;
You can create user defined function as below and call that function in your query to mask the data.
create or replace function scrubbing(word in varchar2)
return varchar2
as
each_var char(2);
final_val varchar2(100);
complete_data varchar2(4000);
each_word varchar2(1000);
cursor val is select substr(replace(word,' ','#'),-level,1) from dual connect by level<=length(word);
begin
open val;
--final_val:= '';
loop
fetch val into each_var;
exit when val%NOTFOUND;
--dbms_output.put_line(each_var);
final_val := trim(final_val)||trim(each_var);
--dbms_output.put_line(final_val);
select regexp_substr(final_val,'[A-Za-z]+') into each_word from dual;
select replace(translate(final_val,each_word,dbms_random.string('L',length(word))),'#',' ') into complete_data from dual;
end loop;
return complete_data;
end;

Oracle: Return multiple values in a function

I'm trying to return a multiple values in a %rowtype from a function using two table(employees and departments), but it not working for me.
create or replace function get_employee
(loc in number)
return mv_emp%rowtype
as
emp_record mv_emp%rowtype;
begin
select a.first_name, a.last_name, b.department_name into emp_record
from employees a, departments b
where a.department_id=b.department_id and location_id=loc;
return(emp_record);
end;
The above function compiled without any error? What is the type of MV_EMP? Ideally, it should be something like below.
create or replace type emp_type
(
first_name varchar2(20)
, last_name varchar2(20)
, depart_name varchar2(20)
)
/
create or replace function get_employee
(loc in number)
return emp_type
as
emp_record emp_type;
begin
select a.first_name, a.last_name, b.department_name into emp_record
from employees a, departments b
where a.department_id=b.department_id and location_id=loc;
return(emp_record);
end;
create type t_row as object (a varchar2(10));
create type t_row_tab as table of t_row;
We will now create a function which will split the input string.
create or replace function get_number(pv_no_list in varchar2) return t_row_tab is
lv_no_list t_row_tab := t_row_tab();
begin
for i in (SELECT distinct REGEXP_SUBSTR(pv_no_list, '[^,]+', 1, LEVEL) no_list FROM dual
CONNECT BY REGEXP_SUBSTR(pv_no_list, '[^,]+', 1, LEVEL) IS NOT NULL)
loop
lv_no_list.extend;
lv_no_list(lv_no_list.last) := t_row(i.no_list);
end loop;
return lv_no_list;
end get_number;
Once the function is in place we can use the table clause of sql statement to get the desired result. As desired we got multiple values returned from the function.
SQL> select * from table(get_number('1,2,3,4'));
A
----------
1
3
2
4
So now our function is simply behaving like a table. There can be a situation where you want these comma separated values to be a part of "IN" clause.
For example :
select * from dummy_table where dummy_column in ('1,2,3,4');
But the above query will not work as '1,2,3,4' is a string and not individual numbers. To solve this problem you can simply use following query.
select * from dummy_table where dummy_column in ( select * from table(get_number('1,2,3,4')) );
References : http://www.oraclebin.com/2012/12/returning-multiple-values-from-function.html
CREATE OR replace FUNCTION Funmultiple(deptno_in IN NUMBER)
RETURN NUMBER AS v_refcursur SYS_REFCURSOR;
BEGIN
OPEN v_refcursor FOR
SELECT *
FROM emp
WHERE deptno = deptno_in;
retun v_refcursor;
END;
To call it, use:
variable x number
exec :x := FunMultiple(10);
print x