Pl/SQL function returns string with changed characters - oracle

How to write a simple function that returns in parameter changed so it doesn't contain certain symbols anymore?
(č=>c, ć=>c, š=>s, đ=>d, ž=>z..)
e.g. *đurđević* => '*djurdjevic*'
e.g. *kuća* => *kuca*
e.g. *čaćkati* => *cackati*
I have no code so far. I am very new at this and am trying to learn something.

For Croatian characters you can use a combination of TRANSLATE and REPLACE, since TRANSLATE doesn't support one to many translation (e.g. đ => dj).
SELECT REPLACE(TRANSLATE('đak žvakaća čičak šuma','žćčš', 'zccs'), 'đ', 'dj') out FROM dual;
And the output:
out
--------------------------
djak zvakaca cicak suma
Edit
So here are two wrapper functions which implements this feature. The first one uses the built-ins while the other one has its own custom implementation.
-- first
CREATE OR REPLACE FUNCTION f_translate(p_string IN VARCHAR)
RETURN VARCHAR
AS
BEGIN
RETURN REPLACE(TRANSLATE(p_string,'žćčš', 'zccs'), 'đ', 'dj');
END;
-- second
CREATE OR REPLACE FUNCTION f_translate_custom(p_string IN VARCHAR)
RETURN VARCHAR
AS
v_current varchar(1);
v_retval VARCHAR(255);
BEGIN
FOR i IN 1..LENGTH(p_string) LOOP
v_current := SUBSTR(p_string, i, 1);
v_retval := v_retval || CASE v_current
WHEN 'č' THEN 'c'
WHEN 'ć' THEN 'c'
WHEN 'ž' THEN 'z'
WHEN 'š' THEN 's'
WHEN 'đ' THEN 'dj'
ELSE v_current
END;
END LOOP;
RETURN v_retval;
END;
And some test code.
SET SERVEROUTPUT ON;
BEGIN
DBMS_OUTPUT.PUT_LINE('Built-in: ' || f_translate('đak žvakaća čičak šuma'));
DBMS_OUTPUT.PUT_LINE('Custom: ' || f_translate_custom('đak žvakaća čičak šuma'));
END;

Check out translate
https://www.techonthenet.com/oracle/functions/translate.php
Example:
TRANSLATE('1tech23', '123', '456')
Result: '4tech56'

You can try the convert function.
SELECT CONVERT('Ä Ê Í Õ Ø A B C D E ', 'US7ASCII', 'WE8ISO8859P1')
FROM DUAL;
CONVERT('ÄÊÍÕØABCDE'
---------------------
A E I ? ? A B C D E ?

This is the closest you can get without creating any structure:
SELECT utl_raw.cast_to_varchar2((nlssort('čaćkati', 'nls_sort=binary_ai')))
FROM dual;
If you have a lot of combinations, I suggest on creating a table with possible combinations and using the TRANSLATE function.

Related

how can we return records from pl/sql stored procedure without taking out parameter

My Question is "How can we return multiple records from pl/sql stored procedure without taking OUT parameter".I got this doubt because if we are using cursors or refcursor in out parameter it may degrade performance.So what is the solution??
As OldProgrammer wrote, i think the performance of a cursor wouldn't be you problem. But here a Solution anyway:
You can return custom types like Table of number. If it's only a list of numbers you could return a table of numbers. If you Want to return rows from a table you could return table of 'tablename'%ROWTYPE. But i guess you want to create some custom types.
CREATE OR REPLACE TYPE PUWB_INT.MyOrderType AS OBJECT
(
OrderId NUMBER,
OrderName VARCHAR2 (255)
)
/
CREATE OR REPLACE TYPE PUWB_INT.MyOrderListType AS TABLE OF MYORDERtype
/
Now we can use them similar to a return myNumberVariable;
Let's build a function (procedures don't have return values):
CREATE OR REPLACE FUNCTION PUWB_INT.MyFunction (SomeInput VARCHAR2)
RETURN MyOrderListType
IS
myOrderList MyOrderListType := MyOrderListType ();
BEGIN
FOR o IN (SELECT 1 AS Id, 'One' AS Name FROM DUAL
UNION ALL
SELECT 2 AS Id, 'Two' AS Name FROM DUAL)
LOOP
myOrderList.EXTEND ();
myOrderList (myOrderList.COUNT) := MyOrderType (o.Id, o.Name || '(' || SomeInput || ')');
END LOOP;
RETURN myOrderList;
END MyFunction;
/
Now we can call the function and get a table of our custom-type:
DECLARE
myOrderList MyOrderListType;
myOrder MyOrderType;
BEGIN
myOrderList := MyFunction ('test');
FOR o IN myOrderList.FIRST .. myOrderList.LAST
LOOP
myOrder := myOrderList (o);
DBMS_OUTPUT.put_line ('Id: ' || myOrder.OrderId || ', Name: ' || myOrder.OrderName);
END LOOP;
END;
Be aware, that the calling schema, has to know the type.

PL/SQL Why my function return NULL value instead of a value in 'HH24:MI:SS' format?

I'm trying to code a PL/SQL function to receive by parameter a number in VARCHAR2 type and return a hour in format 'HH24:MI:SS'. eg: If I send by parameter '6', the function will return 06:00:00. The problem is the function returned NULL value but If I write the same process in a simple PL/SQL block it works fine. Do you have any idea?. Thanks :)
CREATE OR REPLACE FUNCTION format_hour_test (
p_hour VARCHAR2)
RETURN VARCHAR2
IS
v_hour_time TIMESTAMP;
v_hour_char VARCHAR2(50);
BEGIN
IF
REGEXP_LIKE(p_hour, '(0-9)') THEN
v_hour_time := TO_TIMESTAMP(p_hour, 'HH24');
END IF;
v_hour_char := TO_CHAR(v_hour_time, 'HH24:MI:SS');
RETURN v_hour_char;
END format_hour_test;
/
SHOW ERRORS;
When I write a anonymus block with the same process this works fine.
SET SERVEROUTPUT ON
DECLARE
v_hour VARCHAR2(20) := '6';
v_hour_time TIMESTAMP := TO_TIMESTAMP(v_hour, 'HH24');
v_result VARCHAR2(20) := TO_CHAR(v_hour_time, 'HH24:MI:SS');
BEGIN
DBMS_OUTPUT.PUT_LINE(v_result);
END;
You have the wrong type of brackets in your regular expression; it should be:
IF REGEXP_LIKE(p_hour, '[0-9]') THEN
You used grouping parentheses, insteaf of matching/class brackets. You would get a match if you passed in the string '0-9', which wouldn't then be converted to a date/time.
Read more.
Your anonymous block is not the same process. In the anonymous block you do not have if regexp_like... In regular expressions, parens group, so you are looking for the string 0-9. So v_hour_time is not being set in the procedure.
I think you are looking for [0-9], which is match one of the characters in the range. Alternatively, [[:digit:]] https://docs.oracle.com/cd/B12037_01/server.101/b10759/ap_posix001.htm#i690819
Your v_hour_time is never initialized, because of your REGEXP_LIKE conditional:
select (case when REGEXP_LIKE('6', '(0-9)') then 'true' else 'false' end) as res from dual;
returns:
RES
-----
false

Oracle - change columns in WHERE clause based on input

I want use separate columns in WHERE clause based on the INPUT received in the stored procedure.
If TYPE_DEFINITION = 'SUP' then use SUPPLIER column
If TYPE_DEFINITION = 'CAT' then use CATEGORY column
I know I can write two separate SELECT's using a CASE statement, but that will be very dumb and redundant. Any cleaner way of doing it?
CREATE OR REPLACE PROCEDURE SG.STORED_PROCEDURE (
TYPE_DEFINITION IN VARCHAR2,
VALUE IN VARCHAR2,
STORELIST IN VARCHAR2)
AS
BEGIN
SELECT O.ORGNUMBER,
S.SKU,
FROM SKU S JOIN ORG O ON S.ORGID = O.ORGID
WHERE
AND O.ORGNUMBER IN (STORELIST)
AND (CASE TYPE_DEFINITION
WHEN 'SUP' THEN S.SUPPLIER = VALUE
ELSE S.CATEGORY = VALUE
END);
END;
/
Your code is very close. The CASE THEN must return an expression, not a condition. But the CASE can be used as part of a condition, just move the = VALUE
to the outside.
Change this:
AND (CASE TYPE_DEFINITION
WHEN 'SUP' THEN S.SUPPLIER = VALUE
ELSE S.CATEGORY = VALUE
END);
To This:
AND VALUE = (CASE TYPE_DEFINITION
WHEN 'SUP' THEN S.SUPPLIER
ELSE S.CATEGORY
END);
Your code makes sense. This limitation is probably a result of Oracle not fully supporting Booleans.
UPDATE
If you run into performance problems you may want to use dynamic SQL or ensure that the static SQL is correctly using FILTER operations. When Oracle builds an execution plan it is able to use bind variables like constants, and choose a different plan based on the input. As Ben pointed out, these FILTER operations don't always work perfectly, sometimes it may help if you use simplified conditions like this:
(TYPE_DEFINITION = 'SUP' AND S.SUPPLIER = VALUE)
OR
((TYPE_DEFINITION <> 'SUP' OR TYPE_DEFINITION IS NULL) AND S.CATEGORY = VALUE)
You need to use dynamic sql in your procedure.
Something like this:
CREATE OR REPLACE PROCEDURE SG.STORE_PROC (
TYPE_DEFINITION IN VARCHAR2,
VALUE IN VARCHAR2,
STORELIST IN VARCHAR2)
AS
TYPE EmpCurTyp IS REF CURSOR;
v_emp_cursor EmpCurTyp;
v_stmt_str VARCHAR2(200);
v_orgnumber VARCHAR2(200);
v_sku VARCHAR2(200);
BEGIN
v_stmt_str := 'SELECT O.ORGNUMBER, S.SKU,FROM SKU S JOIN ORG O ON S.ORGID = O.ORGID ';
if type_definition = 'SUP' then
v_stmt_str := v_stmt_str || 'WHERE s.supplier = :v';
else
v_stmt_str := v_stmt_str || 'WHERE s.category = :v';
end if;
-- Open cursor & specify bind variable in USING clause:
OPEN v_emp_cursor FOR v_stmt_str USING value;
-- Fetch rows from result set one at a time:
LOOP
FETCH v_emp_cursor INTO v_orgnumber, v_sku;
-- you can do something here with your values
EXIT WHEN v_emp_cursor%NOTFOUND;
END LOOP;
-- Close cursor:
CLOSE v_emp_cursor;
END;
/

Oracle datapump exported tables

My situation is quite strange, and i don't have any ideea on how to handle it. Scenario:
In variable v_tables_param I have the following string (the names of the tables that iwant to export) 'IN(''REPORT_PERIOD'',''OBJECT_AVAILABILITY'')'.
when i try to specify the following metadata filter that i need in order export the tables :
DBMS_DATAPUMP.METADATA_FILTER(handle => n_h1, name =>'NAME_EXPR',value =>v_tables_param);
i get a ORA-39001: invalid argument value.
However, if i hrad code the exact value of v_tables_param into the metadata filter, it works like a charm :
DBMS_DATAPUMP.METADATA_FILTER(handle => n_h1, name =>'NAME_EXPR',value =>'IN(''REPORT_PERIOD'',''OBJECT_AVAILABILITY'')');
Any idea what is happening here?
Are there some weird scenarios in oracle when a hard coded string is different from a variable that has the same value ?
EDIT: I added the function that computes the value of v_tables_param
FUNCTION SPLIT_TABLES(
v_tables_list VARCHAR2 --this is a string that looks like "table1,table2,table3"
) RETURN VARCHAR2
IS
n_idx PLS_INTEGER;
n_i PLS_INTEGER := 0;
v_tables VARCHAR2(2000) := v_tables_list;
v_filter_value VARCHAR(2000);
v_current_table VARCHAR2(200);
BEGIN
v_filter_value := '''IN(';
LOOP
n_idx := instr(v_tables,',');
IF n_idx > 0 THEN
v_current_table := (substr(v_tables,1,n_idx-1));
v_filter_value := v_filter_value || '''''' || v_current_table || ''''',';
v_tables := substr(v_tables,n_idx+1);
n_i := n_i + 1;
ELSE
v_current_table := v_tables;
v_filter_value := v_filter_value || '''''' || v_current_table || ''''')''';
EXIT;
END IF;
END LOOP;
RETURN v_filter_value;
END SPLIT_TABLES;
Ther're actually more parameters to metadata_filter (nice example of a doc bug):
SQL> desc dbms_datapump.metadata_filter
Parameter Type Mode Default?
----------- -------- ---- --------
HANDLE NUMBER IN
NAME VARCHAR2 IN
VALUE VARCHAR2 IN
OBJECT_PATH VARCHAR2 IN Y
OBJECT_TYPE VARCHAR2 IN Y
HANDLE NUMBER IN
NAME VARCHAR2 IN
VALUE CLOB IN
OBJECT_PATH VARCHAR2 IN Y
OBJECT_TYPE VARCHAR2 IN Y
...and I believe you'll have to qualify the object type you're filtering for:
object_type => 'TABLE'
update after you provided the function source:
Remove two apostrophes from each side of the filter values.
Double apostrophes are required by the compiler only. The value of the filter parameter must contain single apostrophes, but your function creates them in pairs.
your variable should contain only single quote
variable = 'IN('REPORT_PERIOD','OBJECT_AVAILABILITY')'
then, pls try , it works

check if "it's a number" function in Oracle

I'm trying to check if a value from a column in an oracle (10g) query is a number in order to compare it. Something like:
select case when ( is_number(myTable.id) and (myTable.id >0) )
then 'Is a number greater than 0'
else 'it is not a number'
end as valuetype
from table myTable
Any ideas on how to check that?
One additional idea, mentioned here is to use a regular expression to check:
SELECT foo
FROM bar
WHERE REGEXP_LIKE (foo,'^[[:digit:]]+$');
The nice part is you do not need a separate PL/SQL function. The potentially problematic part is that a regular expression may not be the most efficient method for a large number of rows.
Assuming that the ID column in myTable is not declared as a NUMBER (which seems like an odd choice and likely to be problematic), you can write a function that tries to convert the (presumably VARCHAR2) ID to a number, catches the exception, and returns a 'Y' or an 'N'. Something like
CREATE OR REPLACE FUNCTION is_number( p_str IN VARCHAR2 )
RETURN VARCHAR2 DETERMINISTIC PARALLEL_ENABLE
IS
l_num NUMBER;
BEGIN
l_num := to_number( p_str );
RETURN 'Y';
EXCEPTION
WHEN value_error THEN
RETURN 'N';
END is_number;
You can then embed that call in a query, i.e.
SELECT (CASE WHEN is_number( myTable.id ) = 'Y' AND myTable.id > 0
THEN 'Number > 0'
ELSE 'Something else'
END) some_alias
FROM myTable
Note that although PL/SQL has a boolean data type, SQL does not. So while you can declare a function that returns a boolean, you cannot use such a function in a SQL query.
Saish's answer using REGEXP_LIKE is the right idea but does not support floating numbers. This one will ...
Return values that are numeric
SELECT foo
FROM bar
WHERE REGEXP_LIKE (foo,'^-?\d+(\.\d+)?$');
Return values not numeric
SELECT foo
FROM bar
WHERE NOT REGEXP_LIKE (foo,'^-?\d+(\.\d+)?$');
You can test your regular expressions themselves till your heart is content at http://regexpal.com/ (but make sure you select the checkbox match at line breaks for this one).
This is a potential duplicate of Finding rows that don't contain numeric data in Oracle. Also see: How can I determine if a string is numeric in SQL?.
Here's a solution based on Michael Durrant's that works for integers.
SELECT foo
FROM bar
WHERE DECODE(TRIM(TRANSLATE(your_number,'0123456789',' ')), NULL, 'number','contains char') = 'number'
Adrian Carneiro posted a solution that works for decimals and others. However, as Justin Cave pointed out, this will incorrectly classify strings like '123.45.23.234' or '131+234'.
SELECT foo
FROM bar
WHERE DECODE(TRIM(TRANSLATE(your_number,'+-.0123456789',' ')), NULL, 'number','contains char') = 'number'
If you need a solution without PL/SQL or REGEXP_LIKE, this may help.
You can use the regular expression function 'regexp_like' in ORACLE (10g)as below:
select case
when regexp_like(myTable.id, '[[:digit:]]') then
case
when myTable.id > 0 then
'Is a number greater than 0'
else
'Is a number less than or equal to 0'
end else 'it is not a number' end as valuetype
from table myTable
I'm against using when others so I would use (returning an "boolean integer" due to SQL not suppporting booleans)
create or replace function is_number(param in varchar2) return integer
is
ret number;
begin
ret := to_number(param);
return 1; --true
exception
when invalid_number then return 0;
end;
In the SQL call you would use something like
select case when ( is_number(myTable.id)=1 and (myTable.id >'0') )
then 'Is a number greater than 0'
else 'it is not a number or is not greater than 0'
end as valuetype
from table myTable
This is my query to find all those that are NOT number :
Select myVarcharField
From myTable
where not REGEXP_LIKE(myVarcharField, '^(-)?\d+(\.\d+)?$', '')
and not REGEXP_LIKE(myVarcharField, '^(-)?\d+(\,\d+)?$', '');
In my field I've . and , decimal numbers sadly so had to take that into account, else you only need one of the restriction.
How is the column defined? If its a varchar field, then its not a number (or stored as one). Oracle may be able to do the conversion for you (eg, select * from someTable where charField = 0), but it will only return rows where the conversion holds true and is possible. This is also far from ideal situation performance wise.
So, if you want to do number comparisons and treat this column as a number, perhaps it should be defined as a number?
That said, here's what you might do:
create or replace function myToNumber(i_val in varchar2) return number is
v_num number;
begin
begin
select to_number(i_val) into v_num from dual;
exception
when invalid_number then
return null;
end;
return v_num;
end;
You might also include the other parameters that the regular to_number has. Use as so:
select * from someTable where myToNumber(someCharField) > 0;
It won't return any rows that Oracle sees as an invalid number.
Cheers.
CREATE OR REPLACE FUNCTION is_number(N IN VARCHAR2) RETURN NUMBER IS
BEGIN
RETURN CASE regexp_like(N,'^[\+\-]?[0-9]*\.?[0-9]+$') WHEN TRUE THEN 1 ELSE 0 END;
END is_number;
Please note that it won't consider 45e4 as a number, But you can always change regex to accomplish the opposite.
#JustinCave - The "when value_error" replacement for "when others" is a nice refinement to your approach above. This slight additional tweak, while conceptually the same, removes the requirement for the definition of and consequent memory allocation to your l_num variable:
function validNumber(vSomeValue IN varchar2)
return varchar2 DETERMINISTIC PARALLEL_ENABLE
is
begin
return case when abs(vSomeValue) >= 0 then 'T' end;
exception
when value_error then
return 'F';
end;
Just a note also to anyone preferring to emulate Oracle number format logic using the "riskier" REGEXP approach, please don't forget to consider NLS_NUMERIC_CHARACTERS and NLS_TERRITORY.
well, you could create the is_number function to call so your code works.
create or replace function is_number(param varchar2) return boolean
as
ret number;
begin
ret := to_number(param);
return true;
exception
when others then return false;
end;
EDIT: Please defer to Justin's answer. Forgot that little detail for a pure SQL call....
You can use this example
SELECT NVL((SELECT 1 FROM DUAL WHERE REGEXP_LIKE (:VALOR,'^[[:digit:]]+$')),0) FROM DUAL;
Function for mobile number of length 10 digits and starting from 9,8,7 using regexp
create or replace FUNCTION VALIDATE_MOBILE_NUMBER
(
"MOBILE_NUMBER" IN varchar2
)
RETURN varchar2
IS
v_result varchar2(10);
BEGIN
CASE
WHEN length(MOBILE_NUMBER) = 10
AND MOBILE_NUMBER IS NOT NULL
AND REGEXP_LIKE(MOBILE_NUMBER, '^[0-9]+$')
AND MOBILE_NUMBER Like '9%' OR MOBILE_NUMBER Like '8%' OR MOBILE_NUMBER Like '7%'
then
v_result := 'valid';
RETURN v_result;
else
v_result := 'invalid';
RETURN v_result;
end case;
END;
Note that regexp or function approaches are several times slower than plain sql condition.
So some heuristic workarounds with limited applicability make sence for huge scans.
There is a solution for cases when you know for sure that non-numeric values would contain some alphabetic letters:
select case when upper(dummy)=lower(dummy) then '~numeric' else '~alpabetic' end from dual
And if you know some letter would be always present in non-numeric cases:
select case when instr(dummy, 'X')>0 then '~alpabetic' else '~numeric' end from dual
When numeric cases would always contain zero:
select case when instr(dummy, '0')=0 then '~alpabetic' else '~numeric' end from dual
if condition is null then it is number
IF(rtrim(P_COD_LEGACY, '0123456789') IS NULL) THEN
return 1;
ELSE
return 0;
END IF;
Here's a simple method which :
does not rely on TRIM
does not rely on REGEXP
allows to specify decimal and/or thousands separators ("." and "," in my example)
works very nicely on Oracle versions as ancient as 8i (personally tested on 8.1.7.4.0; yes, you read that right)
SELECT
TEST_TABLE.*,
CASE WHEN
TRANSLATE(TEST_TABLE.TEST_COLUMN, 'a.,0123456789', 'a') IS NULL
THEN 'Y'
ELSE 'N'
END
AS IS_NUMERIC
FROM
(
-- DUMMY TEST TABLE
(SELECT '1' AS TEST_COLUMN FROM DUAL) UNION
(SELECT '1,000.00' AS TEST_COLUMN FROM DUAL) UNION
(SELECT 'xyz1' AS TEST_COLUMN FROM DUAL) UNION
(SELECT 'xyz 123' AS TEST_COLUMN FROM DUAL) UNION
(SELECT '.,' AS TEST_COLUMN FROM DUAL)
) TEST_TABLE
Result:
TEST_COLUMN IS_NUMERIC
----------- ----------
., Y
1 Y
1,000.00 Y
xyz 123 N
xyz1 N
5 rows selected.
Granted this might not be the most powerful method of all; for example ".," is falsely identified as a numeric. However it is quite simple and fast and it might very well do the job, depending on the actual data values that need to be processed.
For integers, we can simplify the Translate operation as follows :
TRANSLATE(TEST_TABLE.TEST_COLUMN, 'a0123456789', 'a') IS NULL
How it works
From the above, note the Translate function's syntax is TRANSLATE(string, from_string, to_string). Now the Translate function cannot accept NULL as the to_string argument.
So by specifying 'a0123456789' as the from_string and 'a' as the to_string, two things happen:
character a is left alone;
numbers 0 to 9 are replaced with nothing since no replacement is specified for them in the to_string.
In effect the numbers are discarded. If the result of that operation is NULL it means it was purely numbers to begin with.

Resources