Extract specific values from the string and join with other table - sql

I have a requirement in which I have two tables one stores condition and the second stores lookup values.
For eg.
Table 1 (Condition Table):
Condition
"100000010073024" = "BILLED"
"100000010073027" = "Not Billed"
"100000010073026" = "Not Billed" Or "100000010073055" = "Billed"
Table2(Lookup Values):
Lookup Id
Meaning
100000010073024
Test
100000010073027
Test1
100000010073026
Test2
100000010073055
Test3
So I want results like:
Result
Test = "BILLED"
Test1 = "Not Billed"
Test2 = "Not Billed" Or Test3 = "Billed"
So how can I achieve this through oracle sql?

Here's one option.
Based on sample data you posted:
SQL> with
2 t1 (condition) as
3 (select '"100000010073024" = "BILLED"' from dual union all
4 select '"100000010073027" = "Not Billed"' from dual union all
5 select '"100000010073026" = "Not Billed" Or "100000010073055" = "Billed"' from dual
6 ),
7 t2 (lookup_id, meaning) as
8 (select 100000010073024, 'Test' from dual union all
9 select 100000010073027, 'Test1' from dual union all
10 select 100000010073026, 'Test2' from dual union all
11 select 100000010073055, 'Test3' from dual
12 ),
13 --
Split conditions to rows (separated by "or") so that you could (in the final select) work on each of them, separately:
14 -- split conditions to rows
15 splcon as
16 (select trim(regexp_substr(replace(upper(condition), 'OR', '#'), '[^#)]+', 1, column_value)) condition
17 from t1 cross join
18 table(cast(multiset(select level from dual
19 connect by level <= regexp_count(upper(condition), 'OR') + 1
20 ) as sys.odcinumberlist))
21 )
Join condition is made by instr function (whether condition contains lookup_id or not):
22 select
23 b.meaning ||
24 replace(substr(s.condition, instr(s.condition, '=') - 1), '"', '') as result
25 from splcon s join t2 b on instr(s.condition, b.lookup_id) > 0;
RESULT
------------------------------
Test = BILLED
Test1 = NOT BILLED
Test2 = NOT BILLED
Test3 = BILLED
SQL>

The simplest method may be to refactor your conditions table to have each lookup id/term pair in a separate row:
CREATE TABLE conditions (id, lookup_id, term) AS
SELECT 1, 100000010073024, 'BILLED' FROM DUAL UNION ALL
SELECT 2, 100000010073027, 'Not Billed' FROM DUAL UNION ALL
SELECT 3, 100000010073026, 'Not Billed' FROM DUAL UNION ALL
SELECT 3, 100000010073055, 'Billed' FROM DUAL;
Then you could also add referential constraints to the lookups table.
ALTER TABLE conditions ADD CONSTRAINT conditions__lookup_id__fk
FOREIGN KEY (lookup_id) REFERENCES lookups(lookup_id);
The query is simply:
SELECT LISTAGG('"' || l.meaning || '" = "' || c.term || '"', ' OR ')
WITHIN GROUP (ORDER BY ROWNUM) AS result
FROM conditions c
INNER JOIN lookups l
ON (c.lookup_id = l.lookup_id)
GROUP BY c.id;
Which outputs:
RESULT
"Test" = "BILLED"
"Test1" = "Not Billed"
"Test2" = "Not Billed" OR "Test3" = "Billed"
db<>fiddle here

Related

Oracle: Comparing string columns from two different table without a primary key to find match/unmatched string

I have two tables, and we want to compare the two string columns from these tables. there is no primary key in these table to match in the where clause. Tables are as below:
The compare should ignore :
IGNORE SPACES AT ANY PLACE IN THE STRING 2) IGNORE CASE SENSITIVE
OUTPUT WANT:
You can use full outer join with upper and replace function in join condition as follows:
Select t1.str, t2.str,
Case when t1.str is not null and t2.str is not null then 'exist in both table'
when t1.str is not null then 'missing in table2'
Else 'missing in table1'
End as differences
From table1 t1 full join table2 t2
On upper(replace(t1.str,' ','') = upper(replace(t2.str,' ','')
Similarly; sample data from line #1 - 16. Query you might need begins at line #17.
SQL> with
2 tab1 (col) as
3 (select 'mg/kl' from dual union all
4 select 'k/mg' from dual union all
5 select 'l/g/kg' from dual union all
6 select 'umol_Ulp' from dual union all
7 select '10*12_/KG' from dual union all
8 select 'a/L/G' from dual
9 ),
10 tab2 (col) as
11 (select '10*12_/kg' from dual union all
12 select '1/L/G' from dual union all
13 select 'PG/7#R' from dual union all
14 select 'KG/CM/L' from dual union all
15 select 'l/g/kg' from dual
16 )
17 select a.col, b.col,
18 case when lower(a.col) = lower(b.col) then
19 'exists in both tables ' ||
20 case when a.col = b.col then 'and exact match'
21 else 'but different'
22 end
23 when a.col is null then 'missing in table 1'
24 when b.col is null then 'missing in table 2'
25 end differences
26 from tab1 a full outer join tab2 b on lower(a.col) = lower(b.col);
COL COL DIFFERENCES
--------- --------- -------------------------------------
mg/kl missing in table 2
k/mg missing in table 2
l/g/kg l/g/kg exists in both tables and exact match
umol_Ulp missing in table 2
10*12_/KG 10*12_/kg exists in both tables but different
a/L/G missing in table 2
KG/CM/L missing in table 1
PG/7#R missing in table 1
1/L/G missing in table 1
9 rows selected.
SQL>
try the below SQL once and see if works. Otherwise can you please share those values which it says 'missing in table 2' even though it exists in both the tables?
Select t1.str, t2.str,
Case when nvl(t1.str,'X') != 'X' and nvl(t2.str,'X') != 'X' then 'exist in both table'
when nvl(t1.str,'X') != 'X' then 'missing in table2'
Else 'missing in table1'
End as differences
From table1 t1 full join table2 t2
On upper(trim(t1.str)) = upper(trim(t2.str))

How to union a hardcoded row after each grouped result

After every group / row i want to insert a hardcoded dummy row with a bunch of 'xxxx' to act a separator.
I would like to use oracle sql to do this query. i can execute it using a loop but i don't want to use plsql.
As the others suggest, it is best to do it on the front end.
However, if you have a burning need to be done as a query, here is how.
Here I did not use the rownum function as you have already done. I assume, your data is returned by a query, and you can replace my table with your query.
I made few more assumptions, as you have data with row numbers in it.
[I am not sure what do you mean by not PL/SQL]
Select Case When MOD(rownm, 2) = 0 then ' '
Else to_char((rownm + 1) / 2) End as rownm,
name, total, column1
From
(
select (rownm * 2 - 1) rownm,name, to_char(total) total ,column1 from t
union
SELECT (rownm * 2) rownm,'XXX' name, 'XXX' total, 'The row act .... ' column1 FROM t
) Q
Order by Q.rownm;
and here is the fiddle
Since you're already grouping the data, it might be easier to use GROUPING SETS instead of a UNION.
Grouping sets let you group by multiple sets of columns, including the same set twice to duplicate rows. Then the GROUP_ID function can be used to determine when the fake values should be used. This code will be a bit smaller than a UNION approach, and should be faster since it doesn't need to reference the table multiple times.
select
case when group_id() = 0 then name else '' end name,
case when group_id() = 0 then sum(some_value) else null end total,
case when group_id() = 1 then 'this rows...' else '' end column1
from
(
select 'jack' name, 22 some_value from dual union all
select 'jack' name, 1 some_value from dual union all
select 'john' name, 44 some_value from dual union all
select 'john' name, 1 some_value from dual union all
select 'harry' name, 1 some_value from dual union all
select 'harry' name, 1 some_value from dual
) raw_data
group by grouping sets (name, name)
order by raw_data.name, group_id();
You can use row generator technique (using CONNECT BY) and then use CASE..WHEN as follows:
SQL> SELECT CASE WHEN L.LVL = 1 THEN T.ROWNM END AS ROWNM,
2 CASE WHEN L.LVL = 1 THEN T.NAME
3 ELSE 'XXX' END AS NAME,
4 CASE WHEN L.LVL = 1 THEN TO_CHAR(T.TOTAL)
5 ELSE 'XXX' END AS TOTAL,
6 CASE WHEN L.LVL = 1 THEN T.COLUMN1
7 ELSE 'This row act as separator..' END AS COLUMN1
8 FROM T CROSS JOIN (
9 SELECT LEVEL AS LVL FROM DUAL CONNECT BY LEVEL <= 2
10 ) L ORDER BY T.ROWNM, L.LVL;
ROWNM NAME TOTAL COLUMN1
---------- ---------- ----- ---------------------------
1 Jack 23
XXX XXX This row act as separator..
2 John 45
XXX XXX This row act as separator..
3 harry 2
XXX XXX This row act as separator..
4 roy 45
XXX XXX This row act as separator..
5 Jacob 26
XXX XXX This row act as separator..
10 rows selected.
SQL>

Excluding records from table based on rules from another table

I'm using Oracle SQL and I have a product table with diffrent attributes and sales volume for each product and another table with certain exclusion rules for different level of aggregation. Let's look at the example:
Here is our main table with sales data on which we want to perform some calculations:
And the other table contains diffrent rules which are supposed to exclude certain rows from table above:
When there is an "x", this column shouldn't be considered so our rules are:
1. exclude all rows with ATTR_3 = 'no'
2. exlcude all rows with ATTR_1 = 'Europe' and ATTR_2 = 'snacks' and ATTR_3 = 'no'
3. exlcude all rows with ATTR_1 = 'Africa'
And based on that our final output should be like that:
How this could be achived in SQL? I was thinking about join but I have no idea how to handle different levels of aggregation for exclusions.
I think your expected output is wrong. None of the rules excludes the 2nd row (Europe - snacks - yes).
SQL> with
2 -- sample data
3 test (product_id, attr_1, attr_2, attr_3) as
4 (select 81928 , 'Europe', 'beverages', 'yes' from dual union all
5 select 16534 , 'Europe', 'snacks' , 'yes' from dual union all
6 select 56468 , 'USA' , 'snacks' , 'no' from dual union all
7 select 129921, 'Africa', 'drinks' , 'yes' from dual union all
8 select 123021, 'Africa', 'snacks' , 'yes' from dual union all
9 select 165132, 'USA' , 'drinks' , 'yes' from dual
10 ),
11 rules (attr_1, attr_2, attr_3) as
12 (select 'x' , 'x' , 'no' from dual union all
13 select 'Europe', 'snacks', 'no' from dual union all
14 select 'Africa', 'x' , 'x' from dual
15 )
16 -- query you need
17 select t.*
18 from test t
19 where (t.attr_1, t.attr_2, t.attr_3) not in
20 (select
21 decode(r.attr_1, 'x', t.attr_1, r.attr_1),
22 decode(r.attr_2, 'x', t.attr_2, r.attr_2),
23 decode(r.attr_3, 'x', t.attr_3, r.attr_3)
24 from rules r
25 );
PRODUCT_ID ATTR_1 ATTR_2 ATT
---------- ------ --------- ---
81928 Europe beverages yes
16534 Europe snacks yes
165132 USA drinks yes
SQL>
You can use the join using CASE .. WHEN statement as follows:
SELECT P.*
FROM PRODUCT P
JOIN RULESS R ON
(R.ATTR_1 ='X' OR P.ATTR_1 <> R.ATTR_1)
AND (R.ATTR_2 ='X' OR P.ATTR_2 <> R.ATTR_2)
AND (R.ATTR_3 ='X' OR P.ATTR_3 <> R.ATTR_3)
You can use NOT EXISTS
SELECT *
FROM sales s
WHERE NOT EXISTS (
SELECT 0
FROM attributes a
WHERE ( ( a.attr_1 = s.attr_1 AND a.attr_1 IS NOT NULL )
OR a.attr_1 IS NULL )
AND ( ( a.attr_2 = s.attr_2 AND a.attr_2 IS NOT NULL )
OR a.attr_2 IS NULL )
AND ( ( a.attr_3 = s.attr_3 AND a.attr_3 IS NOT NULL )
OR a.attr_3 IS NULL )
)
where I considered the x values within the attributes table as NULL. If you really have x characters, then you can use :
SELECT *
FROM sales s
WHERE NOT EXISTS (
SELECT 0
FROM attributes a
WHERE ( ( NVL(a.attr_1,'x') = s.attr_1 AND NVL(a.attr_1,'x')!='x' )
OR NVL(a.attr_1,'x')='x' )
AND ( ( NVL(a.attr_2,'x') = s.attr_2 AND NVL(a.attr_2,'x')!='x' )
OR NVL(a.attr_2,'x')='x' )
AND ( ( NVL(a.attr_3,'x') = s.attr_3 AND NVL(a.attr_3,'x')!='x' )
OR NVL(a.attr_3,'x')='x' )
)
instead.
Demo
I would do this with three different not exists:
select p.*
from product p
where not exists (select 1
from rules r
where r.attr_1 = p.attr_1 and r.attr_1 <> 'x'
) and
not exists (select 1
from rules r
where r.attr_2 = p.attr_2 and r.attr_2 <> 'x'
) and
not exists (select 1
from rules r
where r.attr_3 = p.attr_3 and r.attr_3 <> 'x'
) ;
In particular, this can take advantage of indexes on (attr_1), (attri_2) and (attr_3) -- something that is quite handy if you have a moderate number of rules.

Oracle SQL - Comparing duplicate values

I have a table with the field RANGE that has the values in the following way:
102453
104953-256454
The values can be single 6 digit number or two 6 digit numbers separated by a dash.
User enters the a value (either a 6 digit number or two 6 digit number separated by a dash), and the value has to be compared with the existing values in the database.
if the value entered is 6 digit number, then it should be compared with all the single 6 digit values in the database, and also with the separate 6 digit values stored as 2 digit values separated by a (-)
if the value entered is two 6 digit numbers, then it should be parsed, and each value has to be compared by each value in the database.
How do I go about it ?
I assume there is some kind of ID field (I hope).
with Dumbassery as
(
select id, 'Single' as NumType, substr(MyField,1,6) as Num1, '' as Num2
from MyTable
where length(MyField) = 6
union
select id, 'Range' as NumType, substr(MyField,1,6) as Num1, substr(MyField,8,6) as Num2
from MyTable
where length(MyField) = 13
)
, Filter1 as
(
select id,
case
when exists (select 1 from Dumbassery D2 where D1.Num1 between D2.Num1 and D2.Num2 and D2.NumType = 'Range') then 'Exists'
when exists (select 1 from Dumbassery D2 where D2.NumType = 'Single' and D1.Num1 = D2.Num1) then 'Exists'
else 'New'
end as NumExist
from Dumbassery D1
where D1.NumType = 'Single'
union
select id,
case
when exists (select 1 from Dumbassery D2 where D1.Num1 between D2.Num1 and D2.Num2 and D2.NumType = 'Range') then 'Exists'
when exists (select 1 from Dumbassery D2 where D1.Num2 between D2.Num1 and D2.Num2 and D2.NumType = 'Range') then 'Exists'
when exists (select 1 from Dumbassery D2 where D2.Num2 between D1.Num1 and D1.Num2 and D2.NumType = 'Single') then 'Exists'
else 'New'
end as NumExist
from Dumbassery D1
where D1.NumType = 'Range'
)
select distinct *
from Filter1
where NumExist = 'New'
As you field named range I think you need to find intersections with new range. I add table test_val to check some variations of new_range.
WITH
table_ranges AS /*is table with ranges*/
(select '123212' as range from dual union all
select '123214-223214' as range from dual union all
select '123900-987121' as range from dual )
,test_val as /*is table with test values*/
(select '123212' as test_range from dual union all
select '123213' as test_range from dual union all
select '123215' as test_range from dual union all
select '123213-123290' as test_range from dual union all
select '124000-125000' as test_range from dual union all
select '987000-987124' as test_range from dual union all
select '987122-987124' as test_range from dual )
select CAse WHEN EXISTS (select null
from table_ranges
where substr(t.test_range, 1,6) between substr(range, 1,6) and substr(range, -6,6)
or substr(t.test_range, -6,6) between substr(range, 1,6) and substr(range, -6,6) )
THEN 'already exists'
ELSE'intersection not found'
END AS test_status
FROM test_val t

Select a table based on input condition

I am using oracle 10g and i need to write a query where in the table that is to be considered for producing the output is based on the user input.
i have written in the following manner, but getting an error.
UNDEFINE CDR
SELECT F.EMPLOYEE_ID FROM
( SELECT DECODE(&&CDR,25,'TABLE 1' ,22,'TABLE 2' ,19,'TABLE 3' ,16,'TABLE 4') FROM DUAL ) F
WHERE F.FLAG='G';
The closest that you can come without dynamic SQL is:
select EMPLOYEE_ID
from table1
where flag = 'G' and &&CDR = 25
union all
select EMPLOYEE_ID
from table2
where flag = 'G' and &&CDR = 19
union all
select EMPLOYEE_ID
from table4
where flag = 'G' and &&CDR = 16
union all
select EMPLOYEE_ID
from table1
where flag = 'G' and &&CDR not in (25, 19, 16)