ORACLE SQL - Wildcard on rowname - sql

I have 2 tables - Table1 and Table2.
Table1.ROrd = 00123 and Table2.Ord = 123.
I need to find all values where Table2.Ord contains part of the value in able1.ROrd.
Since I was getting invalid number error, I tried this. But this is not good enough because the values are not exact match.
select * from Table1 where to_char(ROrd) in (select to_char(Ord) from Table2)

If you perform cross join on those tables and compute some similarities or a good, old instr function's result, then you get something like this (with my sample data, of course):
SQL> with
2 tab1 (ord) as
3 (select '00123' from dual union all
4 select 'ab445' from dual union all
5 select 'xyz' from dual
6 ),
7 tab2 (ord) as
8 (select '123' from dual union all
9 select 'ab556' from dual union all
10 select 'zyx' from dual
11 )
12 select a.ord, b.ord,
13 utl_match.jaro_winkler_similarity(a.ord, b.ord) jwsim,
14 utl_match.edit_distance_similarity(a.ord, b.ord) edsim,
15 --
16 instr(a.ord, b.ord) ins
17 from tab1 a cross join tab2 b;
ORD ORD JWSIM EDSIM INS
----- ----- ---------- ---------- ----------
00123 123 0 60 3
00123 ab556 0 0 0
00123 zyx 0 0 0
ab445 123 0 0 0
ab445 ab556 78 40 0
ab445 zyx 0 0 0
xyz 123 0 0 0
xyz ab556 0 0 0
xyz zyx 55 34 0
9 rows selected.
SQL>
Now, decide which option suits you most.
INSTR is simple enough:
<snip>
17 from tab1 a cross join tab2 b
18 where instr(a.ord, b.ord) > 0;
ORD ORD JWSIM EDSIM INS
----- ----- ---------- ---------- ----------
00123 123 0 60 3
SQL>
Choose similarity level by yourself, e.g. 60, but that's probably not what you want in this case:
<snip>
17 from tab1 a cross join tab2 b
18 where utl_match.jaro_winkler_similarity(a.ord, b.ord) > 60;
ORD ORD JWSIM EDSIM INS
----- ----- ---------- ---------- ----------
ab445 ab556 78 40 0
SQL>
I guess instr produces better result (according to what you described).

Use EXISTS and LIKE to compare the two tables:
select *
from Table1 t1
where EXISTS (
SELECT 1
FROM table2 t2
WHERE t1.ROrd LIKE '%' || t2.Ord || '%'
)
or, if the values are just zero-padded then:
select *
from Table1 t1
where EXISTS (
SELECT 1
FROM table2 t2
WHERE t1.ROrd = LPAD( t2.Ord, 5, '0' )
)

Related

How can I select a data from another column from rows that have been selected?

I tried my best to figure and google this out, but couldn't really find a solid answer to it.
The problem I'm facing is that
Table 1:
ID Value 1
1 a
2 b
3 c
Table 2:
ID Value 2
1 4a
3 5b
4 6c
and I'd basically have to select the value from Table 1 that doesn't exist on Table 2 (Thus, 'b')
I can select and identify the ID that I want by using minus function between the tables, but can't seem to figure out a way to call a query to instead call the data.
Use the MINUS as a subquery (i.e. an inline view) (lines #14 - 16):
Sample data:
SQL> with
2 table1(id, value1) as
3 (select 1, 'a' from dual union all
4 select 2, 'b' from dual union all
5 select 3, 'c' from dual
6 ),
7 table2 (id, value2) as
8 (select 1, '4a' from dual union all
9 select 3, '5b' from dual union all
10 select 4, '6c' from dual
11 )
Query begins here:
12 select a.*
13 from table1 a
14 where a.id in (select t1.id from table1 t1
15 minus
16 select t2.id from table2 t2
17 );
ID VALUE1
---------- ----------
2 b
SQL>
Alternatively, use not exists:
<snip>
12 select a.*
13 from table1 a
14 where not exists (select null
15 from table2 b
16 where b.id = a.id
17 );
ID VALUE1
---------- ----------
2 b
SQL>

Aggregate vertices-as-rows to string

I have multi-part polyline vertices stored as individual rows in an Oracle 18c table.
ASSET_ID PART_NUM VERTEX_NUM X Y M
---------- ---------- ---------- ---------- ---------- ----------
001 1 1 0 5 0
001 1 2 10 10 11.18
001 1 3 30 0 33.54
001 2 1 50 10 33.54
001 2 2 60 10 43.54
Sample data:
db<>fiddle
I want to aggregate the rows into a single text value (without the single quotes):
'MULTILINESTRING ((0 5 0, 10 10 11.18, 30 0 33.54),(50 10 33.54, 60 10 43.54))'
How can I do that using Oracle SQL?
Use Listagg function
with cte as (
select 001 c1,1 c2,1 c3,0 c4,5 c5,0 c6 from dual union all
select 001,1,2,10,10,11.18 from dual union all
select 001,1,3,30,0,33.54 from dual union all
select 001,2,1,50,10,33.54 from dual union all
select 001,2,2,60,10,43.54 from dual
), cte1 as
(
select c2,listagg(c4||' '||c5||' '||c6,',')
s1 from cte group by c2 order by c2 asc)
select 'MULTILINESTRING ('||listagg('('||s1||')',',')
within group (order by c2) ||')' as result from cte1;
fiddle

Procedure for adding one column from one table to another table and its data

I have two tables table1 and table2.
Table 1 has three columns
id name age
----------------
1 ram 27
2 rafi 30
Table 2-
no place
--------------
101 agra
102 delhi
103 chennai
104 hyd
In this situation I want to create a procedure to get the no column of table2 will be added to id column of table1 and the remaining data should be copied same and and if the count of table2 is more then the data should be repeated as shown below
id name age
-----------------
1 ram 27
2 rafi 30
101 ram 27
102 rafi 30
103 ram 27
104 rafi 30
Please help
Assuming table1.id1 is a monotonically increasing series starting at 1 this simple trick will work:
insert into table1
select sq2.no#
, t1.name
, t1.age
from table1 t1
inner join (
select t2.no#
, mod(rownum, sq.cnt)+1 as mod#
from table2 t2
cross join (select count(*) as cnt from table1) sq
) sq2 on sq2.mod# = t1.id
/
There is a demo on db<>fiddle.
If table1.id1 contains gaps, or does not start from 1, you will need to replace table1 in the FROM clause above with another subquery ...
(select t1.*
, row_number() over (order by t1.id) as mod#
from table1 t1 ) sq1
... and inner join that to the existing subquery on mod#.
You can achieve it using row_number analytical function with join using MOD function as following:
SQL> WITH TABLE_1(ID, NAME, AGE)
2 AS (SELECT 1, 'ram', 27 FROM DUAL UNION ALL
3 SELECT 2, 'rafi', 30 FROM DUAL),
4 TABLE_2(NO, PLACE)
5 AS (SELECT 101, 'agra' FROM DUAL UNION ALL
6 SELECT 102, 'delhi' FROM DUAL UNION ALL
7 SELECT 103, 'chennai' FROM DUAL UNION ALL
8 SELECT 104, 'hyd' FROM DUAL)
9 -- YOUR QUERY STARTS FROM HERE
10 SELECT ID, NAME, AGE FROM TABLE_1
11 UNION ALL
12 SELECT T2.NO, T1.NAME, T1.AGE
13 FROM
14 (SELECT T.*,
15 ROW_NUMBER() OVER( ORDER BY ID DESC) AS R,
16 COUNT(1) OVER() C
17 FROM TABLE_1 T ) T1
18 --
19 JOIN (SELECT T.*,
20 ROW_NUMBER() OVER( ORDER BY NO ) AS R,
21 COUNT(1) OVER() C
22 FROM TABLE_2 T ) T2
23 ON ( MOD(T2.R, T1.C) = T1.R - 1 )
24 ORDER BY ID;
ID NAME AGE
---------- ---- ----------
1 ram 27
2 rafi 30
101 ram 27
102 rafi 30
103 ram 27
104 rafi 30
6 rows selected.
SQL>
Cheers!!

Oracle Finding a string match from multiple database tables

This is somewhat a complex problem to describe, but I'll try to explain it with an example. I thought I would have been able to use the Oracle Instr function to accomplish this, but it does not accept queries as parameters.
Here is a simplification of my data:
Table1
Person Qualities
Joe 5,6,7,8,9
Mary 7,8,10,15,20
Bob 7,8,9,10,11,12
Table2
Id Desc
5 Nice
6 Tall
7 Short
Table3
Id Desc
8 Angry
9 Sad
10 Fun
Table4
Id Desc
11 Boring
12 Happy
15 Cool
20 Mad
Here is somewhat of a query to give an idea of what I'm trying to accomplish:
select * from table1
where instr (Qualities, select Id from table2, 1,1) <> 0
and instr (Qualities, select Id from table3, 1,1) <> 0
and instr (Qualities, select Id from table3, 1,1) <> 0
I'm trying to figure out which people have at least 1 quality from each of the 3 groups of qualities (tables 2,3, and 4)
So Joe would not be returned in the results because he does not have the quality from each of the 3 groups, but Mary and Joe would since they have at least 1 quality from each group.
We are running Oracle 12, thanks!
Here's one option:
SQL> with
2 table1 (person, qualities) as
3 (select 'Joe', '5,6,7,8,9' from dual union all
4 select 'Mary', '7,8,10,15,20' from dual union all
5 select 'Bob', '7,8,9,10,11,12' from dual
6 ),
7 table2 (id, descr) as
8 (select 5, 'Nice' from dual union all
9 select 6, 'Tall' from dual union all
10 select 7, 'Short' from dual
11 ),
12 table3 (id, descr) as
13 (select 8, 'Angry' from dual union all
14 select 9, 'Sad' from dual union all
15 select 10, 'Fun' from dual
16 ),
17 table4 (id, descr) as
18 (select 11, 'Boring' from dual union all
19 select 12, 'Happy' from dual union all
20 select 15, 'Cool' from dual union all
21 select 20, 'Mad' from dual
22 ),
23 t1new (person, id) as
24 (select person, regexp_substr(qualities, '[^,]+', 1, column_value) id
25 from table1 cross join table(cast(multiset(select level from dual
26 connect by level <= regexp_count(qualities, ',') + 1
27 ) as sys.odcinumberlist))
28 )
29 select a.person,
30 count(b.id) bid,
31 count(c.id) cid,
32 count(d.id) did
33 from t1new a left join table2 b on a.id = b.id
34 left join table3 c on a.id = c.id
35 left join table4 d on a.id = d.id
36 group by a.person
37 having ( count(b.id) > 0
38 and count(c.id) > 0
39 and count(d.id) > 0
40 );
PERS BID CID DID
---- ---------- ---------- ----------
Bob 1 3 2
Mary 1 2 2
SQL>
What does it do?
lines #1 - 22 represent your sample data
T1NEW CTE (lines #23 - 28) splits comma-separated qualities into rows, per every person
final select (lines #29 - 40) are outer joining t1new with each of "description" tables (table2/3/4) and counting how many qualities are contained in there for each of person's qualities (represented by rows from t1new)
having clause is here to return only desired persons; each of those counts have to be a positive number
Maybe this will help:
{1} Create a view that categorises all qualities and allows you to SELECT quality IDs and categories . {2} JOIN the view to TABLE1 and use a join condition that "splits" the CSV value stored in TABLE1.
{1} View
create or replace view allqualities
as
select 1 as category, id as qid, descr from table2
union
select 2, id, descr from table3
union
select 3, id, descr from table4
;
select * from allqualities order by category, qid ;
CATEGORY QID DESCR
---------- ---------- ------
1 5 Nice
1 6 Tall
1 7 Short
2 8 Angry
2 9 Sad
2 10 Fun
3 11 Boring
3 12 Happy
3 15 Cool
3 20 Mad
{2} Query
-- JOIN CONDITION:
-- {1} add a comma at the start and at the end of T1.qualities
-- {2} remove all blanks (spaces) from T1.qualities
-- {3} use LIKE and the qid (of allqualities), wrapped in commas
--
-- inline view: use UNIQUE, otherwise we may get counts > 3
--
select person
from (
select unique person, category
from table1 T1
join allqualities A
on ',' || replace( T1.qualities, ' ', '' ) || ',' like '%,' || A.qid || ',%'
)
group by person
having count(*) = ( select count( distinct category ) from allqualities )
;
-- result
PERSON
Bob
Mary
Tested w/ Oracle 18c and 11g. DBfiddle here.

Projection/Replication in SQL Query?

My SQL is a bit rusty -- is there a SQL way to project an input table that looks something like this:
Name SlotValue Slots
---- --------- -----
ABC 3 1
ABC 4 2
ABC 6 5
Into a 'projected' result table that looks like this:
Name SlotSum Slot
---- ------- ----
ABC 13 1
ABC 10 2
ABC 6 3
ABC 6 4
ABC 6 5
In other words, the result set should contain a number of rows equal to MAX(Slots), enumerated (Slot) from 1 to MAX(Slots), and Sum for each of these 'slots' should reflect the sum of the SlotValues projected out to the 'Slots' position. for the pathological case:
Name SlotValue Slots
---- --------- -----
ABC 4 3
we should get:
Name SlotSum Slot
---- ------- ----
ABC 4 1
ABC 4 2
ABC 4 3
The summation logic is pretty straightforward -- project each SlotValue out to the number of Slots:
SlotValue SlotValue SlotValue Slot Sum
--------- --------- --------- ---- ---
3 4 6 1 13 (3+4+6)
0 4 6 2 10 (0+4+6)
0 0 6 3 6 (0+0+6)
0 0 6 4 6 (0+0+6)
0 0 6 5 6 (0+0+6)
UPDATE: In the end I used a variant of LOCALGHOST's approach in a stored proc. I was hoping there might be a way to do this without a loop.
I'm not sure you'll be able to do this in a view. You'd have to use a procedure.
You could make ProjectedTable a temporary/variable table in the procedure as well. I'd be really interested to see how you'd get this into a view because you need to dynamically generate a range of numbers.
declare #maxSlot int
set #maxSlot = select max(slots) from SlotTable
truncate ProjectedTable
while #i > 0
begin
insert into ProjectedTable (
SlotSum
,Slot
) values (
(select sum(slotValue) from SlotTable where slots >= #maxSlot)
,#maxSlot
)
set #maxSlot = #maxSlot - 1
end
select SlotSum, Slot from ProjectedTable
Here you go. This will do up to 100 slots in its current form. You can use your imagination to accommodate more.
DECLARE #SLOT TABLE
(
SlotName varchar(25) NOT NULL,
SlotValue int NOT NULL,
Slot int NOT NULL
)
INSERT INTO #SLOT (SlotName, SlotValue, Slot)
SELECT 'ABC', 3, 1
UNION
SELECT 'ABC', 4, 2
UNION
SELECT 'ABC', 6, 5
SELECT
CASE
WHEN SLOT.SlotName IS NOT NULL THEN SLOT.SlotName
ELSE
COALESCE(
(SELECT TOP 1 SL.SlotName FROM #SLOT AS SL WHERE SL.Slot < SLOT_PROJECT.Slot ORDER BY SL.Slot DESC),
(SELECT TOP 1 SL.SlotName FROM #SLOT AS SL WHERE SL.Slot > SLOT_PROJECT.Slot ORDER BY SL.Slot ASC)
)
END AS SlotName,
(
SELECT
SUM(SLOT10.SlotValue)
FROM
#SLOT AS SLOT10
WHERE
SLOT10.Slot >= SLOT_PROJECT.Slot
) AS SlotSum,
SLOT_PROJECT.Slot
FROM
(
SELECT
(TENS.Seq + ONES.Seq) AS Slot
FROM
(
SELECT 1 AS Seq
UNION ALL
SELECT 2
UNION ALL
SELECT 3
UNION ALL
SELECT 4
UNION ALL
SELECT 5
UNION ALL
SELECT 6
UNION ALL
SELECT 7
UNION ALL
SELECT 8
UNION ALL
SELECT 9
) AS ONES
CROSS JOIN
(
SELECT 0 AS Seq
UNION ALL
SELECT 10
UNION ALL
SELECT 20
UNION ALL
SELECT 30
UNION ALL
SELECT 40
UNION ALL
SELECT 50
UNION ALL
SELECT 60
UNION ALL
SELECT 70
UNION ALL
SELECT 80
UNION ALL
SELECT 90
) AS TENS
WHERE
(TENS.Seq + ONES.Seq) <= (SELECT MAX(Slot) FROM #SLOT)
) AS SLOT_PROJECT
LEFT JOIN #SLOT AS SLOT ON
SLOT.Slot = SLOT_PROJECT.Slot