Concanate data from one table to another - sql

I want to concanate the data from TABLE1 to TABLE 2
TABLE 1
id grp_name
-----------------------------
1 A#erf,R#erf.in
2 B#go.in,D#st.org/S#rec.uy
3 C#st.org,X#we.in,S#erl.in
4 D#gh.ou#F#rt.ot
5 E#rth.or
TABLE 2
code name
-----------------------------------
1 A#we.ot,D#ref.as
2 B#de.in
3 C#gr.cpm
4 D#yahoo.com
5 E#erf.com
6 F#google.com
I want to join grp_name data with name data like concanate using comma (',')
grp_name data having unwanted symboles like '#', '/', I want to elimate those too.
I created below procedure, but i dont know i effective or not.
If it is possible with simple update statement alone or merge statement alone let me know.
Excepted result
code name
1 A#we.ot,D#ref.as,A#erf,R#erf.in
2 B#de.in,B#go.in,D#st.org,S#rec.uy
3 C#gr.cpm,C#st.org,X#we.in,S#erl.in
4 D#yahoo.com,D#gh.ou,F#rt.ot
5 E#erf.com,E#rth.or
6 F#google.com
CREATE OR REPLACE PROCEDURE procedure1
AS
CURSOR cur
IS
SELECT id, grp_name
FROM TABLE 1;
CURSOR cur2
IS
SELECT code, name
FROM TABLE 2;
v_a VARCHAR2(300);
v_b VARCHAR2(25);
v_c VARCHAR2(4000);
v_d VARCHAR2(250);
BEGIN
FOR i IN cur
LOOP
v_a := ','||i.grp_name;
v_b := i.id;
FOR e IN cur2
LOOP
v_c := e.name || v_a ;
v_d := i.code;
UPDATE schema_name.TABLE 2
SET name = v_c
WHERE v_d = v_b;
END LOOP;
END LOOP;
-- COMMIT;
END;

In the simplest case, like #jarlh said, you can just do an update:
update table2
set name = name
|| (select ',' || regexp_replace(grp_name, '[/#]', ',') -- replace / and # with ,
from table1
where table1.id = table2.code)
where code in (select id from table1); -- only update matching rows
The last line is not strictly necessary, but it's good to avoid unnecessary updates.
If you also have rows in TABLE1 which don't have a match in TABLE2 that you want to add, use an insert:
insert into table2 (code, name)
select id, regexp_replace(grp_name, '[/#]', ',')
from table1
where id not in (select code from table2);
Edit: and like Gordon mentioned, you would have an easier time with the SQL if you stored your data like this:
code name
1 A#we.ot
1 D#ref.as
1 A#erf
1 R#erf.in
2 B#de.in
2 B#go.in
2 D#st.org
2 S#rec.uy
3 C#gr.cpm
3 C#st.org
3 X#we.in
3 S#erl.in

Please try something like this:
With tab1 as
(
select 1 as id,'A#er#f,R#erf.in' as grp_name from dual union all
select 2 as id,'B#go.in,D#st.or#g/S#rec.uy' as grp_name from dual
),
tab2 as (
select 1 as id,'A#we.ot,D#ref.as' as name from dual union all
select 2 as id,'B#de.in' as name from dual
)
select REGEXP_REPLACE (grp_name||','||name,'[^a-zA-Z0-9|\#|\,]','') as name from tab1 inner join tab2 using(id);
Result:
A#erf,R#erfin,A#weot,D#refas
B#goin,D#storgS#recuy,B#dein

Related

Query select with next id order

I have a table with ID and NextID like this:
MainID || NextID
1 || 2
2 || 3
3 || 5
4 || 6
5 || 4
6 || ...
... || ...
what I want to achieve is select data into like this
MainID || NextID
1 || 2
2 || 3
3 || 5
5 || 4
4 || 6
6 || ...
... || ...
what i've tried is simple query like :
SELECT * FROM 'table' ORDER BY NextID
but of course it didn't meet my needs,
I have an idea to create a temp table and insert with loop but takes too much time to complete :
WHILE #NextID IS NOT NULL
BEGIN
INSERT INTO 'table'(MainID, NextID)
SELECT MainID, NextId
FROM 'table' WHERE MainID=#NextID
END
Can anyone help me?
Thanks
Recursive cte will return rows in the order of nodes visited
with t as (
select f.*, right('00000000'+cast(f.mainId as varchar(max)),9) path
from yourtable f
where MainID=1
union all
select f.*, path + '->' + right('00000000'+cast(f.mainId as varchar(max)),9)
from t
join yourtable f on t.NextID = f.MainID
)
select *
from t
order by path
db<>fiddle
where MainId=1 is an arbitrary start. You may wish also start with
where not exists (select 1 from yourtable f2 where f2.Nextid = f.MainId)
Edit
Added explicit order by
For this particular case you may use right join with some ordering
select t2.*
from some_table t1
right join some_table t2
on t1. main_id = t2.next_id
order by case when t2.next_id is null then 9999999 else t2.main_id + t2.next_id end;
the 999999 in the "order by" part is to place last line (6, null) to the end of the output.
Good luck with adopting the query to your real data

nested for loop in oracle to find similarity optimize

I have two tables both has same value but bot are from different source.
Table 1
------------
ID Title
1 Introduction to Science
2 Introduction to C
3 Let is C
4 C
5 Java
Table 2
------------------------
ID Title
a Intro to Science
b Intro to C
c Let is C
d C
e Java
I want to compare all the title in table 1 with title in table 2 and find the similarity match.
I Have used the built-in function in orcale "UTL_MATCH.edit_distance_similarity (LS_Title, LSO_Title);"
Script:
DECLARE
LS_count NUMBER;
LSO_count NUMBER;
percentage NUMBER;
LS_Title VARCHAR2 (4000);
LSO_Title VARCHAR2 (4000);
LS_CPNT_ID VARCHAR2 (64);
LSO_CPNT_ID VARCHAR2 (64);
BEGIN
SELECT COUNT (*) INTO LS_count FROM tbl_zim_item;
SELECT COUNT (*) INTO LSO_count FROM tbl_zim_lso_item;
DBMS_OUTPUT.put_line ('value of a: ' || LS_count);
DBMS_OUTPUT.put_line ('value of a: ' || LSO_count);
FOR i IN 1 .. LS_count
LOOP
SELECT cpnt_title
INTO LS_Title
FROM tbl_zim_item
WHERE iden = i;
SELECT cpnt_id
INTO LS_CPNT_ID
FROM tbl_zim_item
WHERE iden = i;
FOR j IN 1 .. lso_count
LOOP
SELECT cpnt_title
INTO LSO_Title
FROM tbl_zim_lso_item
WHERE iden = j;
SELECT cpnt_id
INTO LSO_CPNT_ID
FROM tbl_zim_lso_item
WHERE iden = j;
percentage :=
UTL_MATCH.edit_distance_similarity (LS_Title, LSO_Title);
IF percentage > 50
THEN
INSERT INTO title_sim
VALUES (ls_cpnt_id,
ls_title,
lso_cpnt_id,
lso_title,
percentage);
END IF;
END LOOP;
END LOOP;
END;
This is running for more than 15 hours. Kindly provide a better solution.
Note : My table 1 has 20000 records and table 2 has 10000 records.
Unless I'm missing something, you don't need all of the looping and row-by-row lookups since SQL can do cross joins. Therefore my first try would be just:
insert into title_sim
( columns... )
select ls_cpnt_id
, ls_title
, lso_cpnt_id
, lso_title
, percentage
from ( select i.cpnt_id as ls_cpnt_id
, i.cpnt_title as ls_title
, li.cpnt_id as lso_cpnt_id
, li.cpnt_title as lso_title
, case -- Using Boneist's suggestion:
when i.cpnt_title = li.cpnt_title then 100
else utl_match.edit_distance_similarity(i.cpnt_title, li.cpnt_title)
end as percentage
from tbl_zim_item i
cross join tbl_zim_lso_item li )
where percentage > 50;
If there is much repetition in the titles, you might benefit from some scalar subquery caching by wrapping the utl_match.edit_distance_similarity function in a ( select ... from dual ).
If the titles are often exactly the same and assuming in those cases percentage should be 100%, you might avoid calling the function when the titles are an exact match:
begin
select count(*) into ls_count from tbl_zim_item;
select count(*) into lso_count from tbl_zim_lso_item;
dbms_output.put_line('tbl_zim_item contains ' || ls_count || ' rows.');
dbms_output.put_line('tbl_zim_lso_item contains ' || lso_count || ' rows.');
for r in (
select i.cpnt_id as ls_cpnt_id
, i.cpnt_title as ls_title
, li.cpnt_id as lso_cpnt_id
, li.cpnt_title as lso_title
, case
when i.cpnt_title = li.cpnt_title then 100 else 0
end as percentage
from tbl_zim_item i
cross join tbl_zim_lso_item li
)
loop
if r.percentage < 100 then
r.percentage := utl_match.edit_distance_similarity(r.ls_title, r.lso_title);
end if;
if r.percentage > 50 then
insert into title_sim (columns...)
values
( ls_cpnt_id
, ls_title
, lso_cpnt_id
, lso_title
, percentage );
end if;
end loop;
end;
Rather than looping through all the data, I'd merely join the two tables together, eg:
WITH t1 AS (SELECT 1 ID, 'Introduction to Science' title FROM dual UNION ALL
SELECT 2 ID, 'Introduction to C' title FROM dual UNION ALL
SELECT 3 ID, 'Let is C' title FROM dual UNION ALL
SELECT 4 ID, 'C' title FROM dual UNION ALL
SELECT 5 ID, 'Java' title FROM dual UNION ALL
SELECT 6 ID, 'Oracle for Newbies' title FROM dual),
t2 AS (SELECT 'a' ID, 'Intro to Science' title FROM dual UNION ALL
SELECT 'b' ID, 'Intro to C' title FROM dual UNION ALL
SELECT 'c' ID, 'Let is C' title FROM dual UNION ALL
SELECT 'd' ID, 'C' title FROM dual UNION ALL
SELECT 'e' ID, 'Java' title FROM dual UNION ALL
SELECT 'f' ID, 'PL/SQL rocks!' title FROM dual)
SELECT t1.title t1_title,
t2.title t2_title,
UTL_MATCH.edit_distance_similarity(t1.title, t2.title)
FROM t1
INNER JOIN t2 ON UTL_MATCH.edit_distance_similarity(t1.title, t2.title) > 50;
T1_TITLE T2_TITLE UTL_MATCH.EDIT_DISTANCE_SIMILA
----------------------- ---------------- ------------------------------
Introduction to Science Intro to Science 70
Introduction to C Intro to C 59
Let is C Let is C 100
C C 100
Java Java 100
By doing that, you can then reduce the whole thing to a single DML statement, something like:
INSERT INTO title_sim (t1_id,
t1_title,
t2_id,
t2_title,
percentage)
SELECT t1.id t1_id,
t1.title t1_title,
t2.id t2_id,
t2.title t2_title,
UTL_MATCH.edit_distance_similarity(t1.title, t2.title) percentage
FROM t1
INNER JOIN t2 ON UTL_MATCH.edit_distance_similarity(t1.title, t2.title) > 50;
which ought to be a good deal faster than your row-by-row attempt, particularly as you are unnecessarily selecting from each table twice.
As an aside, you know that you can select multiple columns into multiple variables in the same query, right?
So instead of having:
SELECT cpnt_title
INTO LS_Title
FROM tbl_zim_item
WHERE iden = i;
SELECT cpnt_id
INTO LS_CPNT_ID
FROM tbl_zim_item
WHERE iden = i;
you could instead do:
SELECT cpnt_title, cpnt_id
INTO LS_Title, LS_CPNT_ID
FROM tbl_zim_item
WHERE iden = i;
https://www.techonthenet.com/oracle/intersect.php
this will give you data which is similar in both queries
select title from table_1
intersect
select title from table_2

Oracle XE, count and display different combinations of rows based on one column

need help with a complicated query. This is an extract from my table:
USERID SERVICE
1 A
1 B
2 A
3 A
3 B
4 A
4 C
5 A
6 A
7 A
7 B
Ok, I would like the query to return and display all possible combinations that exist in my table with their respective counts based on the SERVICE column. For example first user has A and B service, this is one combination which occurred once. Next user has only service A, this is one more combination which occurred once. Third user has service A and B, this has happened once already and the count for this combination is 2 now, etc. So my output based on this particular input would be a table like this:
A AB AC ABC B BC
3 3 1 0 0 0
So to clarify a bit more, if there are 3 services, then there is 3! possible combinations; 3x2x1=6 and they are A, B, C, AB, AC, BC and ABC. And my table should contain count of users which have these combination of services assigned to them.
I have tried building a matrix using this query and then getting all counts using the CUBE function:
select service_A, service_B, service_C from
(select USERID,
max(case when SERVICE =A then 1 else null end) service_A,
max(case when SERVICE =B then 1 else null end) service_B,
max(case when SERVICE =C then 1 else null end) service_C
from SOME_TABLE)
group by CUBE(service_A, service_B,service_C);
But I don't get the count of all combinations. I need only combinations which happened, so counts 0 are not necessary but it is ok to display them. Thanks.
Don't output it as dynamic columns (it is difficult to do without using PL/SQL and dynamic SQL) but output it as rows instead (if you have a front-end then it can usually translate rows to columns much easier than oracle can):
Oracle Setup:
CREATE TABLE some_table ( USERID, SERVICE ) AS
SELECT 1, 'A' FROM DUAL UNION ALL
SELECT 1, 'B' FROM DUAL UNION ALL
SELECT 2, 'A' FROM DUAL UNION ALL
SELECT 3, 'A' FROM DUAL UNION ALL
SELECT 3, 'B' FROM DUAL UNION ALL
SELECT 4, 'A' FROM DUAL UNION ALL
SELECT 4, 'C' FROM DUAL UNION ALL
SELECT 5, 'A' FROM DUAL UNION ALL
SELECT 6, 'A' FROM DUAL UNION ALL
SELECT 7, 'A' FROM DUAL UNION ALL
SELECT 7, 'B' FROM DUAL;
Query:
SELECT service,
COUNT( userid ) AS num_users
FROM (
SELECT userid,
LISTAGG( service ) WITHIN GROUP ( ORDER BY service ) AS service
FROM some_table
GROUP BY userid
)
GROUP BY service;
Output:
SERVICE NUM_USERS
------- ----------
AC 1
A 3
AB 3
PL/SQL for dynamic columns:
VARIABLE cur REFCURSOR;
DECLARE
TYPE string_table IS TABLE OF VARCHAR2(4000);
TYPE int_table IS TABLE OF INT;
t_services string_table;
t_counts int_table;
p_sql CLOB;
BEGIN
SELECT service,
COUNT( userid ) AS num_users
BULK COLLECT INTO t_services, t_counts
FROM (
SELECT userid,
CAST( LISTAGG( service ) WITHIN GROUP ( ORDER BY service ) AS VARCHAR2(2) ) AS service
FROM some_table
GROUP BY userid
)
GROUP BY service;
p_sql := EMPTY_CLOB() || 'SELECT ';
p_sql := p_sql || t_counts(1) || ' AS "' || t_services(1) || '"';
FOR i IN 2 .. t_services.COUNT LOOP
p_sql := p_sql || ', ' || t_counts(i) || ' AS "' || t_services(i) || '"';
END LOOP;
p_sql := p_sql || ' FROM DUAL';
OPEN :cur FOR p_sql;
END;
/
PRINT cur;
Output:
AC A AB
--- --- ---
1 3 3

Oracle SQL : Retrieving non-existing values from IN clause

Having following query:
select table_name
from user_tables
where table_name in ('A','B','C','D','E','F');
Assuming only user_tables records B,C, and F exist, I want to retrieve the non-existing values A,D and E. This is a simple example, on real world the list can be huge.
A good way to generate fake rows is with a standard collection such as sys.odcivarchar2list:
select
tables_to_check.table_name,
case when user_tables.table_name is null then 'No' else 'Yes'end table_exists
from
(
select column_value table_name
from table(sys.odcivarchar2list('does not exist', 'TEST1'))
) tables_to_check
left join user_tables
on tables_to_check.table_name = user_tables.table_name
order by tables_to_check.table_name;
TABLE_NAME TABLE_EXISTS
---------- ------------
TEST1 Yes
does not exist No
if you have list of all those tables to be checked in Table1 then you can use NOT EXISTS clause
select name
from Table1 T1
where not exists ( select 1 from
user_tables U
where T1.name = U.table_name)
Only way is to use NOT EXISTS by converting the IN clause String into a Table of values.(CTE)
This is not a clean solution though. As The maximum length of IN clause expression is going to be 4000 only, including the commas..
WITH MY_STRING(str) AS
(
SELECT q'#'A','B','C','D','E','F'#' FROM DUAL
),
VALUES_TABLE AS
(
SELECT TRIM(BOTH '''' FROM REGEXP_SUBSTR(str,'[^,]+',1,level)) as table_name FROM MY_STRING
CONNECT BY LEVEL <= REGEXP_COUNT(str,',')
)
SELECT ME.* FROM VALUES_TABLE ME
WHERE NOT EXISTS
(SELECT 'X' FROM user_tables u
WHERE u.table_name = ME.table_name);
You can't. These values have to be entered into a temporary table at the least to do the desired operation. Also Oracle's IN clause list cannot be huge (i.e, not more than 1000 values).
Are you restricted to receiving those values as a comma delimited list?
instead of creating a comma delimited list with the source values, populate an array (or a table).
pass the array into a pl/sql procedure (or pull a cursor from the table).
loop through the array(cursor) and use a dynamic cusror to select count(table_name) from user_tables where table_name = value_pulled.
insert into table B when count(table_name) = 0.
then you can select all from table B
select * from tab1;
------------------
A
B
C
D
E
F
Create or replace procedure proc1 as
cursor c is select col1 from tab1;
r tab1.col1%type;
i number;
begin
open c;
loop
fetch c into r;
exit when c%notfound;
select count(tname) into i from tab where tname = r;
if i = 0 then
v_sql := 'insert into tab2 values ('''||r||''');
execute immediate v_sql;
commit;
end if;
end loop;
close c;
end proc1;
select * from tab2;
------------------
A
D
E
if this is not a one-off, then having this proc on hand will be handy.

How to use Varray or cursor or any other array in decode function to compare the values

I want to compare the last_name values in emp_x table, name column for first 3 character if match any of the records then i want to return that value from emp_x table.
Below is my tables and records:
select substr(x.last_name,1,3) from employee x;
Mathews
Smith
Rice
Black
Green
Larry
Cat
select * from emp_x;
Mataaa
Mmitta
Smitta
Riceeeee
Expected Result:
select decode(substr(x.last_name,1,3), substr(x.last_name,1,3), (select name from emp_x y where y.name like substr(x.last_name,1,3)||'%'),x.last_name) from employee x;
Mataaa
Smitta
Riceeeee
NULL
NULL
NULL
NULL
I am getting the exact result but is there any other best way to use it in pl/sql procedure.
say for example, I am taking the 'Mathews' last_name value from employee table and read the first 3 digit and comparing in emp_x table and getting the Mataa value as result in the decode function above.
Instead of selecting values from table can we use any array or cursor to compare and get the values from varaible in pl/SQL Procedure...
Please help to resolve this..
I know this code is not the best solution, it's sort of work(ish) in nature. Anyways I wrote it to just pass some time at work, and I hope even if its not the complete solution, you get an idea regarding what you wanna achieve
DECLARE
CURSOR c1 IS select substr(x.last_name,1,3) from employee x;
c1_rec c1%ROWTYPE;
CURSOR c2 IS select * from emp_x;
c2_rec c2%ROWTYPE;
l_name employee.last_name%TYPE;
BEGIN
FOR c1_rec IN c1
LOOP
FOR c2_rec IN c2
LOOP
IF(c1_rec.last_name == c2_rec.name) THEN
SELECT last_name into l_name from EMPLOYEE where substr(last_name,1,3) = c1_rec.last_name;
dbms_output.put_line(l_name);
ELSE
dbms_output.put_line("NULL");
END IF;
END LOOP;
END LOOP;
EXCEPTION
WHEN OTHERS THEN
dbms_output.put_line("exception occured");
END;
I would rather stick with this approach:
SQL> with employee as
2 (select 'Mathews' name from dual
3 union all
4 select 'Smith' from dual
5 union all
6 select 'Rice' from dual
7 union all
8 select 'Black' from dual
9 union all
10 select 'Green' from dual
11 union all
12 select 'Larry' from dual
13 union all
14 select 'Cat' from dual),
15 emp_x as
16 (select 'Mataaa' pattern from dual
17 union all
18 select 'Mmitta' from dual
19 union all
20 select 'Smitta' from dual
21 union all
22 select 'Riceeeee' from dual)
23 select nvl(ex.pattern, 'NULL') result
24 from employee e
25 left outer join emp_x ex
26 on substr(ex.pattern, 1, 3) = substr(e.name, 1, 3);
RESULT
--------
Mataaa
Smitta
Riceeeee
NULL
NULL
NULL
NULL
7 rows selected
You didn't provide any information about how big are your tables, but anyway hash join in this query would be much faster then any procedural code. Of course if you need to use it in some procedure you could wrap it in cursor:
for c1 in (select ...)
loop
dbms_output.put_line(c1.result);
end loop;
This can be used for comparing collections using cursors:
declare
cursor c1 is
select last_name ls from empc;
type x is table of employees.last_name%type;
x1 x := x();
cnt integer :=0;
begin
for z in c1 loop
cnt := cnt +1;
x1.extend;
x1(cnt) := z.ls;
if x1(cnt) is NULL then
DBMS_OUTPUT.PUT_LINE('ASHISH');
end if;
dbms_output.put_line(cnt || ' '|| x1(cnt));
end loop;
end;