Generate Result based on max count in secondary column after a join - sql

I have two tables which have a common key between them, and quite a lot of other important infos ; for the sake of simplicity i will be using Combination A and Combination B. When a combination is met, whichever table has the maximum number of records should be the source where i collect the information ; in this case say IDs. The priority when counts are same is Table1.
COMMONKEY column is the combination/join condition in my tables.
(Table 1)
SELECT '123' table1_id,'Comb A' commonkey from dual UNION
SELECT '124' table1_id,'Comb A' commonkey from dual UNION
SELECT '125' table1_id,'Comb A' commonkey from dual UNION
SELECT '126' table1_id,'Comb A' commonkey from dual UNION
SELECT '215' table1_id,'Comb B' commonkey from dual UNION
SELECT '216' table1_id,'Comb B' commonkey from dual UNION
SELECT '559' table1_id,'Random Combination 1' commonkey from dual UNION
SELECT '560' table1_id,'Random Combination 2' commonkey from dual ;
( Table 2 )
SELECT 'abc1' table2_id,'Comb A' commonkey from dual UNION
SELECT 'abc2' table2_id,'Comb A' commonkey from dual UNION
SELECT 'abc3' table2_id,'Comb A' commonkey from dual UNION
SELECT 'abc4' table2_id,'Comb A' commonkey from dual UNION
SELECT 'xyz1' table2_id,'Comb B' commonkey from dual UNION
SELECT 'xyz2' table2_id,'Comb B' commonkey from dual UNION
SELECT 'xyz3' table2_id,'Comb B' commonkey from dual UNION
SELECT 'xyz2' table2_id,'Comb B' commonkey from dual UNION
SELECT '416abc1' table2_id,'Random Combination 91' commonkey from dual UNION
SELECT '416abc2' table2_id,'Random Combination 92' commonkey from dual;
Result Set Expected :
ID COMMONKEY
123 Comb A
124 Comb A
125 Comb A
126 Comb A
xyz1 Comb B
xyz2 Comb B
xyz3 Comb B
559 Random Combination 1
560 Random Combination 1
416abc1 Random Combination 91
416abc2 Random Combination 92
Updated Image :
( the image shows a screenshot of the trail data in an excel; The Requirement and Strategy are color mapped to make it quickly understandable )
I need to generate the result set using SQL as follows :
When table1.commonkey = table2.commonkey hits, I need to-
If table1 has 10 IDs, table2 has 5 IDs -> Pick 10 IDs from table1.
If table1 has 15 IDs, table2 has 30 IDs -> Pick 30 IDs from table2.
If table1 has 4 IDs, table2 has 4 IDs -> Pick 4 IDs from table1.
( when equal, choose table1 IDs )
When no matches occur with the common key, prevent a cross join and add in the rowsets linearly to the result table.
Edit : I've initially gone on routes with
a left join b where b.key IS null ;
a full outer join b where b.key IS NULL or a.key is NULL ;
to achieve workarounds with A-B or B-A result sets but both these approaches were quite wrong. Gathering Delta sets or Exclusion sets didnt go well.

Here's one option; see comments within code
SQL> with
2 -- sample data
3 a (id, ckey) as
4 (select '123', 'ca' from dual union all
5 select '124', 'ca' from dual union all
6 select '125', 'ca' from dual union all
7 select '126', 'ca' from dual union all
8 select '215', 'cb' from dual union all
9 select '216', 'cb' from dual union all
10 select '551', 'r1' from dual union all
11 select '552', 'r2' from dual
12 ),
13 b (id, ckey) as
14 (select 'abc1', 'ca' from dual union all
15 select 'abc2', 'ca' from dual union all
16 select 'abc3', 'ca' from dual union all
17 select 'abc4', 'ca' from dual union all
18 select 'xyz1', 'cb' from dual union all
19 select 'xyz2', 'cb' from dual union all
20 select 'xyz3', 'cb' from dual union all
21 select '9991', 'r3' from dual union all
22 select '9992', 'r4' from dual
23 ),
24 -- count rows per each CKEY (common key)
25 tempa as
26 (select id, ckey, count(*) over (partition by ckey) cnt
27 from a
28 ),
29 tempb as
30 (select id, ckey, count(*) over (partition by ckey) cnt
31 from b
32 )
33 -- final query
34 select distinct
35 case when a.cnt >= b.cnt then a.id
36 else b.id
37 end id,
38 a.ckey
39 from tempa a join tempb b on b.ckey = a.ckey
40 union all
41 select ckey, id from a
42 where not exists (select null from b where a.ckey = b.ckey)
43 union all
44 select ckey, id from b
45 where not exists (select null from a where a.ckey = b.ckey)
46 order by 1, 2;
which results in
ID CKEY
---- -----
r1 551
r2 552
r3 9991
r4 9992
xyz1 cb
xyz2 cb
xyz3 cb
123 ca
124 ca
125 ca
126 ca
11 rows selected.
SQL>

Related

How to group columns based on specific text and sum them using ORACLE database

I am a newbie in oracle. Trying to group columns based on a specific text and sum other columns.
let's say I have below tables
I want to group by all people with the last name. Like below
I have no clue how should I proceed.
Note: There could be multiple names but I just want to filter for Sharma, Decosta and Liver.
Updating the question.
Sorry for updating the question. but in a real scenario search string is not always at last.
Let me give you another example.
Output.
The queries offered so far does seem to work to get what you need. But the below does:
With inputs as
(
select 'Rahul Kumar Sharma' as Name, 10 as No_of_beer_bottles from dual union all
select 'Rohith Kumar Sharma' as Name, 5 as No_of_beer_bottles from dual union all
select 'Sharma' as Name, 20 as No_of_beer_bottles from dual union all
select 'Rahul Kumar Varma' as Name, 10 as No_of_beer_bottles from dual
)
select SUBSTR(name,Instr(name,' ',-1)+1) as Name, sum(No_of_beer_bottles) as No_of_beer_bottles
from inputs
group by SUBSTR(name,Instr(name,' ',-1)+1);
Output:
You can use regexp_substr():
select regexp_substr(name, '[^ ]+$') as last_name, sum(num_beers)
from t
group by regexp_substr(name, '[^ ]+$');
If you use have a table with all the words you are searching for (or define them in a Common Table Expression like I did below), you can join on that search term then sum the numeric column.
Query 1
WITH
search_words (word)
AS
(SELECT 'Sharma' FROM DUAL
UNION ALL
SELECT 'Decosta' FROM DUAL
UNION ALL
SELECT 'Liver' FROM DUAL),
d (name, num_beers)
AS
(SELECT 'Ravi Sharma', 1 FROM DUAL
UNION ALL
SELECT 'Sam Decosta', 4 FROM DUAL
UNION ALL
SELECT 'Jhony Liver', 5 FROM DUAL
UNION ALL
SELECT 'Ravi Sharma', 2 FROM DUAL
UNION ALL
SELECT 'Rohul Sharma', 3 FROM DUAL
UNION ALL
SELECT 'Jhon Decosta', 3 FROM DUAL
UNION ALL
SELECT 'V Decosta', 3 FROM DUAL)
SELECT sw.word, SUM (d.num_beers) AS total_beer
FROM search_words sw, d
WHERE INSTR (UPPER (d.name), UPPER (sw.word)) > 0
GROUP BY sw.word;
Result 1
WORD TOTAL_BEER
__________ _____________
Decosta 10
Sharma 6
Liver 5
Query 2
WITH
search_words (word)
AS
(SELECT 'Pen' FROM DUAL
UNION ALL
SELECT 'Dog' FROM DUAL),
d (name, quantity)
AS
(SELECT 'Ravi have red pens', 2 FROM DUAL
UNION ALL
SELECT 'Sam''s grifriend have black dogs', 4 FROM DUAL
UNION ALL
SELECT 'Jhony has blue pen', 1 FROM DUAL
UNION ALL
SELECT 'Ravi has white dog', 1 FROM DUAL
UNION ALL
SELECT 'Rahul has small dogs', 3 FROM DUAL
UNION ALL
SELECT 'There are pens inside Jhon''s car', 3 FROM DUAL
UNION ALL
SELECT 'My dog and me are flying.', 1 FROM DUAL)
SELECT sw.word, SUM (d.quantity) AS total_quantity
FROM search_words sw, d
WHERE INSTR (UPPER (d.name), UPPER (sw.word)) > 0
GROUP BY sw.word;
Result 2
WORD TOTAL_QUANTITY
_______ _________________
Dog 9
Pen 6

ORACLE SQL JOINS

I have the two tables:
TABLE1:
id name values
1 john AB
2 marry CD
3 sreya YG
TABLE2:
pid country values
45 india JKABHJ
46 usa YURRRCD
47 uk YGHJJKLJL
output
name values country
john AB india
marry CD usa
sreya YG uk
I want to join these two tables on the common columns values, but the other table columns contain extra data. How to overcome this?
table2 column "values" contains data matching to table1 "values"
values
AB
CD
YG
values
JKABHJ
YURRRCD
YGHJJKLJL
You can use like operator in query for matching values in table1 and table2.
For this query:
WITH table1 as (
select 1 as id, 'john' as name, 'AB' as value from dual union all
select 2 as id, 'marry' as name, 'CD' as value from dual union all
select 3 as id, 'sreya' as name, 'YG' as value from dual
),
table2 as (
select 45 as id, 'india' as country, 'JKABHJ' as value from dual union all
select 46 as id, 'usa' as country, 'YURRRCD' as value from dual union all
select 47 as id, 'uk' as country, 'YGHJJKLJL' as value from dual
)
select a.name, a.value, b.country
from table1 a
join table2 b on b.value like '%'||a.value||'%';
Output:
NAME VALUE COUNTRY
john AB india
marry CD usa
sreya YG uk
But I would recommend you to change a structure to make it more efficient. For example, by adding new table table2_values with column id referenced to table2.id and split values:
WITH table1 as (
select 1 as id, 'john' as name, 'AB' as value from dual union all
select 2 as id, 'marry' as name, 'CD' as value from dual union all
select 3 as id, 'sreya' as name, 'YG' as value from dual
),
table2 as (
select 45 as id, 'india' as country from dual union all
select 46 as id, 'usa' as country from dual union all
select 47 as id, 'uk' as country from dual
),
table2_values as (
select 45 as id, 'JK' as value from dual union all
select 45 as id, 'AB' as value from dual union all
select 45 as id, 'HJ' as value from dual union all
select 46 as id, 'YU' as value from dual union all
select 46 as id, 'RRR' as value from dual union all
select 46 as id, 'CD' as value from dual union all
select 47 as id, 'YG' as value from dual union all
select 47 as id, 'HJ' as value from dual
)
select a.name, a.value, c.country
from table1 a
join table2_values b on b.value = a.value
join table2 c on c.id = b.id;
You should use like operator while joining the two tables.
As below
SELECT *
FROM TABLE1
JOIN TABLE2
ON TABLE1.values like CONCAT('%',TABLE2.values,'%')

SQL How to align ranges of data points in rows?

Suppose having the data set:
with
data_table(title, x) as (
select 'a', 1 from dual union all
select 'a', 3 from dual union all
select 'a', 4 from dual union all
select 'a', 5 from dual union all
select 'b', 1 from dual union all
select 'b', 2 from dual union all
select 'b', 3 from dual union all
select 'b', 6 from dual
)
select * from data_table;
TITLE | X
-----------
a 1
a 3
a 4
a 5
b 1
b 2
b 3
b 6
Wee see that points related to a and b are different.
How to align values in X column so both groups have the same points, filling the gaps with NULL?
Expected result is:
TITLE | X
-----------
a 1
a NULL
a 3
a 4
a 5
a NULL
b 1
b 2
b 3
b NULL
b NULL
b 6
Straightforward solution I got is
with
data_table(title, x) as (
select 'a', 1 from dual union all
select 'a', 3 from dual union all
select 'a', 4 from dual union all
select 'a', 5 from dual union all
select 'b', 1 from dual union all
select 'b', 2 from dual union all
select 'b', 3 from dual union all
select 'b', 6 from dual
),
all_points(x) AS (
select distinct x from data_table
),
all_titles(title) AS (
select distinct title from data_table
),
aligned_data(title, x) as (
select t.title, p.x from all_points p cross join all_titles t
)
select ad.title, dt.x
from aligned_data ad
left join data_table dt on dt.title = ad.title and dt.x = ad.x
order by ad.title, ad.x;
As wee see cross join in aligned_data definition is bottleneck and can kill performance on valuable data sets.
I wonder if this task could be solved more elegantly. Maybe a trick with window functions can be proposed.

Find count value for a different group with reference with single column (Oracle)

I have the below scenario data. I need a count for column 'c1' with different set of data. total count should be based on unique no of data from column c1 and e1.
with t as
(
select 'cab1' as c1, 'ae1' as e1 from dual
union all
select 'cab1' , 'ae2' from dual
union all
select 'cab1' , 'ae3' from dual
union all
select 'cab1' , 'ae4' from dual
union all
select 'cab3' , 'ae1' from dual
union all
select 'cab3' , 'ae1' from dual
union all
select 'cab2' , 'ae' from dual
)
select
c1,e1, COUNT(*) OVER (partition by c1 order by c1,e1 ) as p1
from t;
my result should be
c1 e1 count
-----------------------
cab1 ae3 4
cab1 ae2 4
cab1 ae1 4
cab1 ae4 4
cab2 ae 1
cab3 ae1 1
Can anyone help on this.
SqlFiddleDemo
with t as
(
select 'cab1' as c1, 'ae1' as e1 from dual
union all
select 'cab1' , 'ae2' from dual
union all
select 'cab1' , 'ae3' from dual
union all
select 'cab1' , 'ae4' from dual
union all
select 'cab3' , 'ae1' from dual
union all
select 'cab3' , 'ae1' from dual
union all
select 'cab2' , 'ae' from dual
)
SELECT
c1,
e1,
COUNT(*) OVER (partition by c1) as p1
FROM t
GROUP BY c1, e1

Group by using Analytical function Oracle

I have the below demo code.
How can I get the desired output.Its just not coming in my mind :-(.It has to be a simple change.
WITH Abc AS (SELECT 'AB' AS code,'Z12' AS des, 123 AS tk FROM dual UNION
SELECT 'AB','Z14',234 FROM dual UNION
SELECT 'ACB','D12',34 FROM dual UNION
SELECT 'ACB','D12',36 FROM dual UNION
SELECT 'ACB','F12',35 FROM dual UNION
SELECT 'ABX','G44',243 FROM dual UNION
SELECT 'ABX','H44',654 FROM dual UNION
SELECT 'ABX','J12',534 FROM dual UNION
SELECT 'ABQ',NULL,356 FROM dual)
SELECT DISTINCT tk,code,des ,code||'_'||row_number () OVER (PARTITION BY code ORDER BY code,des ) AS str FROM abc
ORDER BY 2,3;
OUTPUT :
tk code des str
--- --- --- ---
123 AB Z12 AB_1
234 AB Z14 AB_2
356 ABQ ABQ_1
243 ABX G44 ABX_1
654 ABX H44 ABX_2
534 ABX J12 ABX_3
34 ACB D12 ACB_1 --- ACB_1
36 ACB D12 ACB_2 ----I want ACB_1 here too since desc is same for that code
35 ACB F12 ACB_3
**USE RANK() instead of rownumber()**
WITH Abc AS (SELECT 'AB' AS code,'Z12' AS des, 123 AS tk FROM dual UNION
SELECT 'AB','Z14',234 FROM dual UNION
SELECT 'ACB','D12',34 FROM dual UNION
SELECT 'ACB','D12',36 FROM dual UNION
SELECT 'ACB','F12',35 FROM dual UNION
SELECT 'ABX','G44',243 FROM dual UNION
SELECT 'ABX','H44',654 FROM dual UNION
SELECT 'ABX','J12',534 FROM dual UNION
SELECT 'ABQ',NULL,356 FROM DUAL)
SELECT DISTINCT TK,CODE,DES ,CODE||'_'||rank() OVER (PARTITION BY CODE ORDER BY CODE,DES ) AS STR FROM ABC
ORDER BY 2,3;