convert a function into SQL statement - sql

I have a below function for rounding the value of an amount based on some logic -
Currently I use this round function in my SQL to get the rounded value of amount as
f_round_value(nom_ccy_cd,tran_amt) -
However, my current requirement is to not use this function. Instead I am trying to achieve the same in SQL directly. Should I use case statements, or any other way to achieve the below logic..
I am using oracle 10i
Function f_round_value ( in_t_ccy_cd IN CCY.ccy_cd%TYPE, in_n_amt IN NUMBER)
RETURN VARCHAR2 AS
ln_dec_place_cnt CCY.decimal_place_cnt%TYPE;
ln_out_amt NUMBER;
lv_out_amt_str VARCHAR2(30);
lb_decimal_reqd BOOLEAN :=TRUE;
lb_neg_val BOOLEAN :=FALSE;
BEGIN
IF in_n_amt IS NULL THEN
lv_out_amt_str:=NULL;
ELSE IF in_n_amt < 0 THEN
lb_neg_val:=TRUE;
END IF;
IF in_t_ccy_cd IN (C_CCY_CD_JP, C_CCY_CD_IT, C_CCY_CD_IR, C_CCY_CD_KR) THEN
ln_dec_place_cnt :=0;
lb_decimal_reqd:=FALSE;
ELSE
ln_dec_place_cnt :=2; lb_decimal_reqd:=TRUE;
END IF;
ln_out_amt:=ROUND(in_n_amt,ln_dec_place_cnt);
IF lb_decimal_reqd THEN
lv_out_amt_str:=TRIM(TO_CHAR(ln_out_amt,'S999,999,999,999,990.99'));
ELSE
lv_out_amt_str:=TRIM(TO_CHAR(ln_out_amt,'S999,999,999,999,999'));
END IF;
IF lb_neg_val THEN
lv_out_amt_str:='('||SUBSTR(lv_out_amt_str,2)||')';
ELSE
lv_out_amt_str:= SUBSTR(lv_out_amt_str,2);
END IF;
END
Any help will be appreciated.

You do realize there are currencies with 3 decimal places don't you? Anyway, you don't show the complete contents of the CCY table, but if it should contain the decimal places of each currency, you're in luck. You have everything you need. Here is a sample CCY table with 4 currencies and a list with a value for each currency.
WITH
CCY AS(
SELECT 'BGN' ccy_cd, '975' ccy_id, 2 DecPlaces, 'Bulgarian lev' CCY_Name, 'Bulgaria' Cntry FROM dual UNION ALL
SELECT 'BHD', '048', 3, 'Bahraini dinar', 'Bahrain' FROM dual UNION ALL
SELECT 'BIF', '108', 0, 'Burundian franc', 'Burundi' FROM dual UNION ALL
SELECT 'BMD', '060', 2, 'Bermudian dollar', 'Bermuda' FROM dual
),
CcyValues as(
SELECT 'BGN' ccy_cd, 15.852 amt FROM dual UNION ALL
SELECT 'BHD', -15.852 FROM dual UNION ALL
SELECT 'BIF', 15.852 FROM dual UNION ALL
SELECT 'BMD', -15.852 FROM dual
)
SELECT v.ccy_cd, v.amt, y.DecPlaces,
translate( to_char( round( v.amt, y.DecPlaces ),
CASE y.DecPlaces
WHEN 2 THEN 'FM999,999,999,999,990.99PR'
WHEN 3 THEN 'FM999,999,999,999,990.999PR'
ELSE 'FM999,999,999,999,990PR'
END ), '<>', '()' ) Amt_Str
FROM CcyValues v
JOIN CCY y
on y.ccy_cd = v.ccy_cd;

Related

Oracle SQL : Check if specified words are present in comma separated string

I have an SQL function that returns me a string of comma separated country codes.
I have configured some specific codes in another table and I may remove or add more later.
I want to check if the comma separated string is only the combination of those specific country codes or not. That said, if that string is having even a single country code other than the specified ones, it should return true.
Suppose I configured two rows in the static data table GB and CH. Then I need below results:
String from function
result
GB
false
CH
false
GB,CH
false
CH,GB
false
GB,FR
true
FR,ES
true
ES,CH
true
CH,GB,ES
true
I am on Oracle 19c and can use only the functions available for this version. Plus I want it to be optimised. Like I can check the number of values in string and then count for each specific code. If not matching then obviously some other codes are present. But I don't want to use loops.
Can someone please suggest me a better option.
Assuming that all country codes in the static table, as well as all tokens in the comma-separated strings, are always exactly two-letter strings, you could do something like this:
with
static_data(country_code) as (
select 'GB' from dual union all
select 'CH' from dual
)
, sample_inputs(string_from_function) as (
select 'GB' from dual union all
select 'CH' from dual union all
select 'GB,CH' from dual union all
select 'CH,GB' from dual union all
select 'GB,FR' from dual union all
select 'FR,ES' from dual union all
select 'ES,CH' from dual union all
select 'CH,GB,ES' from dual
)
select string_from_function,
case when regexp_replace(string_from_function,
',| |' || (select listagg(country_code, '|')
within group (order by null)
from static_data))
is null then 'false' else 'true' end as result
from sample_inputs
;
Output:
STRING_FROM_FUNCTION RESULT
---------------------- --------
GB false
CH false
GB,CH false
CH,GB false
GB,FR true
FR,ES true
ES,CH true
CH,GB,ES true
The regular expression replaces comma, space, and every two-letter country code from the static data table with null. If the result of the whole thing is null, then all coded in the csv are in the static table; that's what you need to test for.
The assumptions guarantee that a token like GBCH (for a country like "Great Barrier Country Heat") would not be mistakenly considered OK because GB and CH are OK separately.
You can convert a csv column to a table and use EXISTS. For example
with tbl(id,str) as
(
SELECT 1,'GB,CH' FROM DUAL UNION ALL
SELECT 2,'GB,CH,FR' FROM DUAL UNION ALL
SELECT 3,'GB' FROM DUAL
),
countries (code) as
(SELECT 'GB' FROM DUAL UNION ALL
SELECT 'CH' FROM DUAL
)
select t.* ,
case when exists (
select 1
from xmltable(('"' || REPLACE(str, ',', '","') || '"')) s
where trim(s.column_value) not in (select code from countries)
)
then 'true' else 'false' end flag
from tbl t
One option is to match the country codes one by one, and then determine whether there exists an extra non-matched country from the provided literal as parameter.
The following one with FULL JOIN would help by considering the logic above
WITH
FUNCTION with_function(i_countries VARCHAR2) RETURN VARCHAR2 IS
o_val VARCHAR2(10);
BEGIN
SELECT CASE WHEN SUM(NVL2(t.country_code,0,1))=0 THEN 'false'
ELSE 'true'
END
INTO o_val
FROM (SELECT DISTINCT REGEXP_SUBSTR(i_countries,'[^ ,]+',1,level) AS country
FROM dual
CONNECT BY level <= REGEXP_COUNT(i_countries,',')+1) tt
FULL JOIN t
ON tt.country = t.country_code;
RETURN o_val;
END;
SELECT with_function(<comma-seperated-parameter-list>) AS result
FROM dual
Demo
Here is one solution
with cte as
(select distinct
s,regexp_substr(s, '[^,]+',1, level) code from strings
connect by regexp_substr(s, '[^,]+', 1, level) is not null
)
select
s string,min(case when exists
(select * from countries
where cod = code) then 'yes'
else 'no'end) all_found
from cte
group by s
order by s;
STRING | ALL_FOUND
:----- | :--------
CH | yes
CH,GB | yes
ES | no
ES,CH | no
FR | no
GB | yes
GB,CH | yes
GB,ES | no
db<>fiddle here
If you have a small number of values in the static table then the simplest method may not be to split the values from the function but to generate all combinations of values from the static table using:
SELECT SUBSTR(SYS_CONNECT_BY_PATH(value, ','), 2) AS combination
FROM static_table
CONNECT BY NOCYCLE PRIOR value != value;
Which, for the sample data:
CREATE TABLE static_table(value) AS
SELECT 'GB' FROM DUAL UNION ALL
SELECT 'CH' FROM DUAL;
Outputs:
COMBINATION
GB
GB,CH
CH
CH,GB
Then you can use a simple CASE expression to your string output to the combinations:
SELECT function_value,
CASE
WHEN function_value IN (SELECT SUBSTR(SYS_CONNECT_BY_PATH(value, ','), 2)
FROM static_table
CONNECT BY NOCYCLE PRIOR value != value)
THEN 'false'
ELSE 'true'
END AS not_matched
FROM string_from_function;
Which, for the sample data:
CREATE TABLE string_from_function(function_value) AS
SELECT 'GB' FROM DUAL UNION ALL
SELECT 'CH' FROM DUAL UNION ALL
SELECT 'GB,CH' FROM DUAL UNION ALL
SELECT 'CH,GB' FROM DUAL UNION ALL
SELECT 'GB,FR' FROM DUAL UNION ALL
SELECT 'FR,ES' FROM DUAL UNION ALL
SELECT 'ES,CH' FROM DUAL UNION ALL
SELECT 'CH,GB,ES' FROM DUAL;
Outputs:
FUNCTION_VALUE
NOT_MATCHED
GB
false
CH
false
GB,CH
false
CH,GB
false
GB,FR
true
FR,ES
true
ES,CH
true
CH,GB,ES
true
db<>fiddle here

Converting base32 to a decimal number

Is there anybody who can help me converting base32 values to decimal numbers?
I have a sql statement which returns me a list of base32 values. For example the return of the select looks like this:
5
8
H
13r
Now I need to figure out which of these values is the highest (in this case 13r). Therefore I would need a function to convert these base32 values to decimal numbers in order to sort them.
Does anybody have such a function or does anybody have a different approach?
It will be little bit tricky.
You can create a function which will separate each character/digit from the original base32 number and multiply it with 32^(position in base32 number) to get decimal equivalent number and use that function in your query.
create function convert32todecimal(p_num in varchar2)
return number
as
lv_outnum number;
begin
select sum(val) into lv_outnum from
(
select power(32,pos) * case when d between '0' and '9'
then to_number(d)
else 10 + ascii(d) - ascii('A')
end as val
from (
select upper(substr(p_num,length(p_num)+1-level,1)) d,
level - 1 as pos
from dual
connect by level <= length(p_num)
)
);
return lv_outnum;
end;
/
Now, use this function in your query as following:
with your_data(num) as (
select '5' from dual union all
select '8' from dual union all
select 'H' from dual union all
select '13r' from dual
)
select num, convert32todecimal(num)
from your_data
order by 2 desc;
db<>fiddle demo
Cheers!!

Comparing 2 lists in Oracle

I have 2 lists which I need to compare. I need to find if at least one element from List A is found in List B. I know IN doesn't work with 2 lists. What are my other options?
Basically something like this :
SELECT
CASE WHEN ('A','B','C') IN ('A','Z','H') THEN 1 ELSE 0 END "FOUND"
FROM DUAL
Would appreciate any help!
You are probably looking for something like this. The WITH clause is there just to simulate your "lists" (whatever you mean by that); they are not really part of the solution. The query you need is just the last three lines (plus the semicolon at the end).
with
first_list (str) as (
select 'A' from dual union all
select 'B' from dual union all
select 'C' from dual
),
second_list(str) as (
select 'A' from dual union all
select 'Z' from dual union all
select 'H' from dual
)
select case when exists (select * from first_list f join second_list s
on f.str = s.str) then 1 else 0 end as found
from dual
;
FOUND
----------
1
In Oracle you can do:
select
count(*) as total_matches
from table(sys.ODCIVarchar2List('A', 'B', 'C')) x,
table(sys.ODCIVarchar2List('A', 'Z', 'H')) y
where x.column_value = y.column_value;
You need to repeat the conditions:
SELECT (CASE WHEN 'A' IN ('A', 'Z', 'H') OR
'B' IN ('A', 'Z', 'H') OR
'C' IN ('A', 'Z', 'H')
THEN 1 ELSE 0
END) as "FOUND"
FROM DUAL
If you are working with collection of String you can try Multiset Operators.
create type coll_of_varchar2 is table of varchar2(4000);
and:
-- check if exits
select * from dual where cardinality (coll_of_varchar2('A','B','C') multiset intersect coll_of_varchar2('A','Z','H')) > 0;
-- list of maching elments
select * from table(coll_of_varchar2('A','B','C') multiset intersect coll_of_varchar2('A','Z','H'));
Additionally:
-- union of elemtns
select * from table(coll_of_varchar2('A','B','C') multiset union distinct coll_of_varchar2('A','Z','H'));
select * from table(coll_of_varchar2('A','B','C') multiset union all coll_of_varchar2('A','Z','H'));
-- eelemnt from col1 not in col2
select * from table(coll_of_varchar2('A','A','B','C') multiset except all coll_of_varchar2('A','Z','H'));
select * from table(coll_of_varchar2('A','A','B','C') multiset except distinct coll_of_varchar2('A','Z','H'));
-- check if col1 is subset col2
select * from dual where coll_of_varchar2('B','A') submultiset coll_of_varchar2('A','Z','H','B');
I am trying to do something very similar but the first list is another field on the same query created with listagg and containing integer numbers like:
LISTAGG(my_first_list,', ') WITHIN GROUP(
ORDER BY
my_id
) my_first_list
and return this with all the other fields that I am already returning
SELECT
CASE WHEN my_first_list IN ('1,2,3') THEN 1 ELSE 0 END "FOUND"
FROM DUAL

Convert SQL to PL/SQL Block (ORACLE DB)

I have the following SQL code:
INSERT INTO TIMES (saleDay, dayType)
SELECT saleDate, CASE WHEN h.hd IS NOT NULL THEN 'Holiday'
WHEN to_char(saleDate, 'd') IN (1,7) THEN 'Weekend'
ELSE 'Weekday' END dayType
FROM SALES s LEFT JOIN
(SELECT '01.01' hd FROM DUAL UNION ALL
SELECT '15.01' FROM DUAL UNION ALL
SELECT '19.01' FROM DUAL UNION ALL
SELECT '28.05' FROM DUAL UNION ALL
SELECT '04.07' FROM DUAL UNION ALL
SELECT '08.10' FROM DUAL UNION ALL
SELECT '11.11' FROM DUAL UNION ALL
SELECT '22.11' FROM DUAL UNION ALL
SELECT '25.12' FROM DUAL) h
ON h.hd = TO_CHAR(s.saleDate, 'dd.mm');
And I need to convert it to a PL/SQL block. I initially just turned the code above into a create or replace procedure and then called it, but I've been asked to complete the code above in PL/SQL.
Unfortunately, I've really struggled in this area and am still trying to grasp the concept of PL/SQL, especially in situations like this where it doesn't make much sense to conduct the INSERT code via PL/SQL. Any pointers/instruction on what the best way to convert this to PL/SQL is?
Thank you!
I agree, this is better as plain SQL. If this is just for a course, I'd do it with a simple loop and a collection. If you're dealing with a high-volume production environment, a BULK COLLECT .. FORALL approach will have much better performance.
declare
vDayType varchar2(10);
TYPE Holidays is table of varchar2(5);
hd Holidays := Holidays('01.01','15.01','19.01','28.05','04.07','08.10','11.11','22.11','25.12');
begin
for s in (select distinct saleDate from Sales) loop
vDayType := case when TO_CHAR(s.saleDate, 'dd.mm') member of hd then
'Holiday'
when to_char(s.saleDate, 'd') IN (1,7) then
'Weekend'
else
'Weekday'
end;
insert into times (saleDay, dayType) values (s.saleDate, vDayType);
end loop;
end;
/
I feel like PL/SQL procedures like this are a good choice when so much processing needs to be done for each record that it would be awkward, less readable, or impossible to do in SQL. Maybe you're building HTML or modifying a DOCX file in a clob column. I don't know. Honestly, I don't come across use cases for this kind of thing very often in my work.
Thinking I may just go with this...Although I'm not sure if it'll be taken as PL/SQL.
Better solutions are still welcomed!
BEGIN
INSERT INTO TIMES (saleDay, dayType)
SELECT saleDate, CASE WHEN h.hd IS NOT NULL THEN 'Holiday'
WHEN to_char(saleDate, 'd') IN (1,7) THEN 'Weekend'
ELSE 'Weekday' END dayType
FROM SALES s LEFT JOIN
(SELECT '01.01' hd FROM DUAL UNION ALL
SELECT '15.01' FROM DUAL UNION ALL
SELECT '19.01' FROM DUAL UNION ALL
SELECT '28.05' FROM DUAL UNION ALL
SELECT '04.07' FROM DUAL UNION ALL
SELECT '08.10' FROM DUAL UNION ALL
SELECT '11.11' FROM DUAL UNION ALL
SELECT '22.11' FROM DUAL UNION ALL
SELECT '25.12' FROM DUAL) h
ON h.hd = TO_CHAR(s.saleDate, 'dd.mm');
END;
/
if TIMES table has only two columns (salesDay and DayType), you can also do it like this,
BEGIN
FOR rec IN
(SELECT saleDate, CASE WHEN h.hd IS NOT NULL THEN 'Holiday'
WHEN to_char(saleDate, 'd') IN (1,7) THEN 'Weekend'
ELSE 'Weekday' END dayType
FROM SALES s LEFT JOIN
(SELECT '01.01' hd FROM DUAL UNION ALL
SELECT '15.01' FROM DUAL UNION ALL
SELECT '19.01' FROM DUAL UNION ALL
SELECT '28.05' FROM DUAL UNION ALL
SELECT '04.07' FROM DUAL UNION ALL
SELECT '08.10' FROM DUAL UNION ALL
SELECT '11.11' FROM DUAL UNION ALL
SELECT '22.11' FROM DUAL UNION ALL
SELECT '25.12' FROM DUAL) h
ON h.hd = TO_CHAR(s.saleDate, 'dd.mm')))
LOOP
INSERT INTO TIMES VALUES rec;
END LOOP;
END;
/
I don't have your tables, but I would do something like I am showing below - creating a table to accept the inserts (similar to yours) and using HIREDATE from the EMP table in the SCOTT schema.
I see that kfinity just posted a similar answer; I am still posting mine because I feel it is cleaner, but it's really the same answer.
Also: keeping the dates in a nested table, as kfinity has done, is the correct approach (declare all the "magic things" at the top of your code, so they are easy to find and modify if/when needed); however, it is possible that in your course you haven't covered collections yet. In my solution I hard-coded the values directly where they were needed.
drop table my_table purge;
create table my_table ( hireday date, daytype varchar2(20) );
begin
for r in ( select hiredate from scott.emp )
loop
insert into my_table ( hireday, daytype ) values
( r.hiredate,
case when to_char(r.hiredate, 'dd.mm') in (
'01.01', '15.01', '19.01', '28.05', '04.07',
'08.10', '11.11', '22.11', '25.12') then 'Holiday'
when to_char(r.hiredate, 'd') in ('1', '7') then 'Weekend'
else 'Weekday' end
);
end loop;
end;
/

How to return an expected value with decimal places using dual table on Oracle SQL?

I'm currently trying to force a number to have a two decimal character on the output no need to round,
if there is a decimal value exist I just need to have 2 decimal places
Example:
26 will return 26.00
26.1 will return 26.10
26.20 will return 26.20
26.09090909 will return 26.09
26.23090909 will return 26.23
0.14000 will return 0.14
0.04000 will return 0.04
0 will return 0.00
but the in-house language that I'm currently using has a very limited function to do it (possible but it will take a lot of lines).
but I am able to do a query on Oracle SQL. Now I would like to ask if it is possible to query using the dual table on SQL.
My plan is to pass the value to variable and this variable will be use on my SQL command and SQL will return my expected value.
Thanks in advance.
You simply need format mask:
select to_char(yourVariable, 'fm9999999990.00') from dual
According to Boneist's suggestion, 'fm' in format mask prevents the creation of blanks, while '0' digits are useful to always write the digit:
with test(num) as
(
select 26 from dual union all
select 26.1 from dual union all
select 26.20 from dual union all
select 26.09090909 from dual union all
select 26.23090909 from dual union all
select 0.14000 from dual union all
select 0.04000 from dual union all
select 0 from dual
)
select num, to_char(num, 'fm9999999990.00')
from test
You can try like this:
SELECT TO_CHAR(26, '99.99') AS X FROM DUAL
For numbers less than 1 in which you need to have 0 explicitly you have to provide it like
SELECT TO_CHAR(0.14000,'0.99') FROM DUAL;
So you can use it like
CASE WHEN myNum > 0 and myNum < 1
then TO_CHAR(myNum,'0.99') else
TO_CHAR(myNum, '99.99')
or more simplified(as suggested in comments):
select to_char(num, 'fm9999999990.00') from dual