Data masking in Oracle SQL select statement - sql

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;

Related

Oracle function to replace all names to their nick names?

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

How include array (or similar) into an in-clause SQL statement?

I am new to oracle and PL SQL. Currently I struggle with the handling of arrays and "similar things" like i.e. collection. I am trying to build a procedure like:
procedure insert_by_array( my_array some_array_type)
begin
insert into table1 (some_column)
select some_column
from table2
where column2 in my_array
;
end;
However I could not make I did try some array types but I did not find the right one. The entries of the type must be varchar2 - a part of this criterium I am open to any array type. I.e when my array_type is
type array_of_strings is varray(100) of varchar2(40);
My error would be: "local collection types are not alowed in sql statements"
I am using Oracle Database 12c Enterprise Edition Release 12.1.0.2.0.
So at the end, this worked:
create type table_of_strings IS TABLE OF VARCHAR2(64); --define it global;
declare
my_table table_of_strings;
begin
my_table := table_of_strings('aaa', 'bb','c');
insert into table1 (some_column)
select some_column
from table2
where column2 in (select * from table(my_table))
;
end;
First of all, you have to define sql level collection. (varray is not an option)
create type array_of_strings as table of varchar2(40);
Now you can use table or "member of" approach
declare
c array_of_strings := new array_of_strings();
begin
c.extend();
c(c.count) := 'A';
c.extend();
c(c.count) := 'B';
for rec in (select * from dual where 'A' member of c ) loop
dbms_output.put_line('Option with memeber of ');
end loop;
for rec in (select * from dual where 'A' in (select * from table(c))) loop
dbms_output.put_line('Option with table');
end loop;
end;
As error says, local collections, defined in procedure, function, code block, cannot be used in queries, at least in Oracle 11 which I use.
This does not work (PLS-00642):
declare
type strings is table of varchar2(5);
v_i strings := strings('A', 'X', 'Q');
v_o strings;
begin
select * bulk collect into v_o from dual where dummy in (select * from table(v_i));
end;
So... either use type defined at schema level (your own or predefined sys.odcivarchar2list):
declare
type strings is table of varchar2(5);
v_i strings := strings('A', 'X', 'Q');
v_o strings;
v_so sys.odcivarchar2list := sys.odcivarchar2list();
begin
v_so.extend(v_i.count);
for i in 1..v_i.count loop
v_so(i) := v_i(i);
end loop;
select * bulk collect into v_o from dual where dummy in (select * from table(v_so));
end;
... either use dynamic sql:
declare
type strings is table of varchar2(5);
v_i strings := strings('A', 'X', 'Q');
v_o strings;
v_str varchar2(4000);
begin
for i in 1..v_i.count loop
v_str := v_str || case when i > 1 then ', ' end || ''''||v_i(i)||'''';
end loop;
execute immediate 'select * from dual where dummy in ('||v_str||')' bulk collect into v_o ;
end;
... either use loop (probably slowest):
declare
type strings is table of varchar2(5);
v_i strings := strings('A', 'X', 'Q');
v_o strings := strings();
begin
for rec in (select * from dual) loop
if rec.dummy member of v_i then
v_o.extend();
v_o(v_o.count) := rec.dummy;
end if;
end loop;
end;

Converting String concatenated with ',' to be used in subquery at oracle SQL

I want to use my result of function e.g. 'S500,S600,S700,S800' in a subquery in another script like:
where dept_no in (my result of function)
So I want to convert my string result to be like this ('S500','S600','S700','S800').
I tried to do this with dynamic SQL but I can't get it to work.
Hope below snipet suffice your requirement.
Approach 1 -> More effective
--Create a table type of VARCHAR
CREATE OR REPLACE type string_table
IS
TABLE OF VARCHAR2(100);
--Function to return tabl type
CREATE OR REPLACE
FUNCTION string_manipulate
RETURN string_table
AS
str_tab string_table;
BEGIN
SELECT 's00'||level bulk collect INTO str_tab FROM dual CONNECT BY level < 10;
RETURN str_tab;
end;
--Use function in the query
SELECT distinct 1
FROM
(SELECT 's001' dn FROM dual
UNION ALL
SELECT 's002' dn FROM dual
UNION ALL
SELECT 's003' dn FROM dual
UNION ALL
SELECT 's004' dn FROM dual
UNION ALL
SELECT 's005' dn FROM dual
UNION ALL
SELECT 's006' dn FROM dual
UNION ALL
SELECT 's007' dn FROM dual
UNION ALL
SELECT 's008' dn FROM dual
UNION ALL
SELECT 's009' dn FROM dual
)a
WHERE a.dn IN
(SELECT * FROM TABLE(string_manipulate)
);
--Approach 2
--Function to get output as mentioned.
CREATE OR REPLACE
FUNCTION string_manipulate
RETURN VARCHAR2
AS
BEGIN
RETURN 'S2009,S2020,S2021';
END;
-- Use function value in a query
SELECT 1
FROM dual
WHERE '''S2009'',''S2020'',''S2021''' = (''''
||REPLACE(string_manipulate,',',''',''')
||'''');
You need an iterator and text splitting by comma sign:
select empno,ename,sal,deptno
from emp
where empno in (
select to_number(
rtrim(
substr(emps,
instr(emps,',',1,iter.pos)+1,
instr(emps,',',1,iter.pos+1) -
instr(emps,',',1,iter.pos)),',')) emps
from (select ','||'7654,7698,7782,7788'||',' emps from t1) csv,
(select rownum pos from emp) iter
where iter.pos <= ((length(csv.emps) -
length(replace(csv.emps,',')))/length(','))-1
)
But better rewrite your function to return cursor.
you can use collection:
SELECT *
FROM YOUR_TABLE
WHERE DEPT_NO IN (SELECT *
FROM TABLE (SPLIT ('S500,S600,S700,S800')))--splits text with comma, for other chars use split(text, split_char)
With usage of MEMBER OF
SELECT *
FROM YOUR_TABLE
WHERE DEPT_NO MEMBER OF SPLIT ('S500,S600,S700,S800')--splits text with comma, for other chars use split(text, split_char)
the split fuction is:
CREATE OR REPLACE TYPE SPLIT_TBL AS TABLE OF VARCHAR2 (32767);
CREATE OR REPLACE FUNCTION SPLIT (P_LIST VARCHAR2, P_DEL VARCHAR2 := ',')
RETURN SPLIT_TBL
PIPELINED
IS
L_IDX PLS_INTEGER;
L_LIST VARCHAR2 (32767) := P_LIST;
BEGIN
LOOP
L_IDX := INSTR (L_LIST, P_DEL);
IF L_IDX > 0
THEN
PIPE ROW (SUBSTR (L_LIST, 1, L_IDX - 1));
L_LIST := SUBSTR (L_LIST, L_IDX + LENGTH (P_DEL));
ELSE
PIPE ROW (L_LIST);
EXIT;
END IF;
END LOOP;
RETURN;
END SPLIT;
FUNCTION GET_TS_EACH_DAY_DEPARTMENT (P_SER_NO VARCHAR2,
P_TS_DATE DATE
)
RETURN STRING_TABLE
IS
V_DEPT_NO VARCHAR2 (4000);
V_DEPT VARCHAR2(4000);
V_TABLE STRING_TABLE:=STRING_TABLE();
J NUMBER:=1;
BEGIN
for i in (select distinct ts_day dayy from WEB_TS_USER_LOCATIONS_V ) loop
V_TABLE.EXTEND;
V_TABLE(J):= WEB_TS_PKG.GET_TS_DAY_DEPARTMENT (P_SER_NO ,P_TS_DATE , i.dayy );
J:=J+1;
end loop;
RETURN V_TABLE;
END GET_TS_EACH_DAY_DEPARTMENT;

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!

SQL: if cannot convert to_number set as null

I have a table with this values:
ID VALUE
-----------------------
23559 200
23562 -1 & {14376}#-1
and I want to do to a select that if I cannot convert to number set NULL.
I generally use translate for this because it is such an odd corner case:
SELECT
CASE
WHEN NOT TRIM(TRANSLATE(COLUMN_NAME, '1234567890', ' ')) IS NULL THEN NULL
ELSE COLUMN_NAME
END AS "NUMERIC_COLUMN"
FROM
TABLE_NAME;
If necessary, that can be turned into a procedure, but I'm not sure that there would be terribly much benefit performance-wise.
You can create a function that tries to convert the string to a number and catches the exception. Something like
CREATE OR REPLACE FUNCTION my_to_number( p_str IN VARCHAR2 )
RETURN NUMBER
IS
l_num NUMBER;
BEGIN
BEGIN
l_num := to_number( p_str );
EXCEPTION
WHEN others THEN
l_num := null;
END;
RETURN l_num;
END;
Then you can
SELECT id, my_to_number( value )
FROM your_table
You could also use REGEXP_LIKE:
SELECT id
, CASE WHEN regexp_like(value,'^[0-9]+$') THEN TO_NUMBER(value)
ELSE NULL
END value
FROM your_table;
For example:
SQL> WITH q AS (
2 SELECT 1 ID, '200' col FROM dual
3 UNION
4 SELECT 2, '-1 & {14376}#-1' FROM dual
5 )
6 SELECT id, CASE WHEN regexp_like(col,'^[0-9]+$') THEN TO_NUMBER(col) ELSE NULL END TEST FROM q;
ID TEST
---------- ----------
1 200
2
With Oracle 12.2 this can be done a bit easier using the on conversion error option:
select id, cast(value as number default null on conversion error) as value
from the_table;
Optionally you can also specify a format mask, similar to the to_number() function.
I assume this would be faster than using a PL/SQL function, not sure about the performance compared to a case with a regex. But it is definitely a lot shorter.
CREATE OR REPLACE FUNCTION asnumber(p_val IN VARCHAR2) RETURN NUMBER IS
l_val NUMBER;
BEGIN
l_val := TO_NUMBER(p_val);
RETURN l_val;
EXCEPTION WHEN VALUE_ERROR THEN
RETURN null;
END;