Only output specific Values using SQL - sql

I am trying to work out how the SQL if I want to bring back ALL "Names" from table1 and only list the output in Column 2 to contain the value if it is "Apple". If its not "Apple" or is "Null" then column 2 should also be "Null"
Table 1
ID(pk)
Name
24
Boris
25
Dominic
26
Rishi
27
Elizabeth
28
Ben
Table 2
ID(fk)
Description
27
Apple
27
Orange
27
Pear
26
Apple
26
Pear
25
Pear
24
Orange
Required Output
Name
Description
Boris
Dominic
Rishi
Apple
Elizabeth
Apple
Sajid

If there are no duplicates in the second table (in a comment below the question you confirmed that this is the case), this is a simple application of outer join.
Here I include the test data in the with clause. You don't need it - you have the actual tables. Remove the with clause, and inspect the main query and change the table and column names as needed.
with
table_1 (id, name) as (
select 24, 'Boris' from dual union all
select 25, 'Dominic' from dual union all
select 26, 'Rishi' from dual union all
select 27, 'Elizabeth' from dual union all
select 28, 'Ben' from dual
)
, table_2 (id, description) as (
select 27, 'Apple' from dual union all
select 27, 'Orange' from dual union all
select 27, 'Pear' from dual union all
select 26, 'Apple' from dual union all
select 26, 'Pear' from dual union all
select 25, 'Pear' from dual union all
select 24, 'Orange' from dual
)
select t1.name, t2.description
from table_1 t1 left outer join table_2 t2
on t2.id = t1.id and t2.description = 'Apple'
order by t1.id
;
NAME DESCRIPTION
--------- -----------
Boris
Dominic
Rishi Apple
Elizabeth Apple
Ben

You can solve it in a variety of ways, one example:
SELECT t1.name
, MAX(CASE t2.descripton WHEN 'Apple' THEN t2.descripton END)
FROM t1
JOIN t2
USING(id)
GROUP BY t1.name;
The CASE expression will map all but Apple to null. MAX will then reduce the set to either Apple or null for each name.

I think below query will work for you:
Select Name, CASE WHEN ID IN (SELECT DISTINCT ID FROM table2 WHERE LOWER(description) = 'apple') THEN 'Apple' ELSE Null END as Description FROM table1;

Related

How to use multiple max Function in SQL select with left join

Suppose I have the following three tables:
Table1:
ID
Value_1
11
abc
22
def
33
xyz
Table2:
ID
Date_1
11
12-Mar-22
11
01-Jan-23
22
19-Dec-22
22
07-Feb-23
33
07-Mar-22
Table3:
ID
Length_1
11
574
11
1029
22
9220
33
1093
33
876
Now, I need an SQL query that would select each ID with Max Lenth_1 and Max Date_1.
Desired output:
ID
Value_1
Date_1
Length_1
11
abc
01-Jan-23
1029
22
def
07-Feb-23
9220
33
xyz
07-Mar-22
1093
I have used max() fuction to achieve this with left join between 2 tables together, however struggling when I have to use Max () twice with 3 tables. I am relatively new to SQL.
SQL Select Max(Date) out of rows with Duplicate Id
I tried this for two tables
Aggregate before you join the tables:
SELECT t1.id,
t1.value_1,
t2.date_1,
t3.length_1
FROM table1 t1
INNER JOIN (
SELECT id,
MAX(date_1) AS date_1
FROM table2
GROUP BY id
) t2
ON (t1.id = t2.id)
INNER JOIN (
SELECT id,
MAX(length_1) AS length_1
FROM table3
GROUP BY id
) t3
ON (t1.id = t3.id)
Which, for the sample data:
CREATE TABLE Table1 (ID, Value_1) AS
SELECT 11, 'abc' FROM DUAL UNION ALL
SELECT 22, 'def' FROM DUAL UNION ALL
SELECT 33, 'xyz' FROM DUAL;
CREATE TABLE Table2 (ID, Date_1) AS
SELECT 11, DATE '2022-03-12' FROM DUAL UNION ALL
SELECT 11, DATE '2023-01-01' FROM DUAL UNION ALL
SELECT 22, DATE '2022-12-19' FROM DUAL UNION ALL
SELECT 22, DATE '2023-02-07' FROM DUAL UNION ALL
SELECT 33, DATE '2022-03-07' FROM DUAL;
CREATE TABLE Table3 (ID, Length_1) AS
SELECT 11, 574 FROM DUAL UNION ALL
SELECT 11, 1029 FROM DUAL UNION ALL
SELECT 22, 9220 FROM DUAL UNION ALL
SELECT 33, 1093 FROM DUAL UNION ALL
SELECT 33, 876 FROM DUAL;
Outputs:
ID
VALUE_1
DATE_1
LENGTH_1
11
abc
2023-01-01 00:00:00
1029
22
def
2023-02-07 00:00:00
9220
33
xyz
2022-03-07 00:00:00
1093
fiddle
One option is to use subqueries selecting max values from tables 2 and 3:
Select t1.ID, t1.VALUE_1,
(Select Max(DATE_1) From Table2 Where ID = t1.ID) "DATE_1",
(Select Max(LENGTH_1) From Table3 Where ID = t1.ID) "LENGTH_1"
From Table1 t1
Order By t1.ID
... another one is to use analytic function with distinct keyword but it could be performance costly with big datasets:
Select DISTINCT
t1.ID, t1.VALUE_1, Max(t2.DATE_1) OVER(Partition By t1.ID) "DATE_1", Max(t3.LENGTH_1) OVER(Partition By t1.ID) "LENGTH_1"
From Table1 t1
Inner Join Table2 t2 ON(t2.ID = t1.ID)
Inner Join Table3 t3 ON(t3.ID = t1.ID)
Order By t1.ID
... both with your sample data:
WITH
Table1 (ID, VALUE_1) AS
(
Select 11, 'abc' From Dual Union All
Select 22, 'def' From Dual Union All
Select 33, 'xyz' From Dual
),
Table2 (ID, DATE_1) AS
(
Select 11, To_Date('2022-03-12', 'yyyy-mm-dd') From Dual Union All
Select 11, To_Date('2023-01-01', 'yyyy-mm-dd') From Dual Union All
Select 22, To_Date('2022-12-19', 'yyyy-mm-dd') From Dual Union All
Select 22, To_Date('2023-02-07', 'yyyy-mm-dd') From Dual Union All
Select 33, To_Date('2022-03-07', 'yyyy-mm-dd') From Dual
),
Table3 (ID, LENGTH_1) AS
(
Select 11, 574 From Dual Union All
Select 11, 1029 From Dual Union All
Select 22, 9220 From Dual Union All
Select 33, 1093 From Dual Union All
Select 33, 876 From Dual
)
results as:
ID VALUE_1 DATE_1 LENGTH_1
---------- ------- --------- ----------
11 abc 01-JAN-23 1029
22 def 07-FEB-23 9220
33 xyz 07-MAR-22 1093
Select t1.id, t1.value1, max(t2.date_1) date_1, max(t3.length_1)t3
from table_1 t1
left join table_2 t2 on t1.id=t2.id
left join table_3 t3 on t1.id=t3.id
group by t1.id, t1.value1
order by 1

SQL query based on another table

I have tables like this:
table 1:
FNAME
ID1
FID
BREAD
XYZ
18BREAD
FISH
ABC
45FISH
BREAD_OLD
BNQ
18BREAD_OLD
BACON
TBG
77BACON
EGGS
CGS
99EGGS
BANANA
BHG
BANANA18
table 2:
FNAME
FID
BREAD
18
FISH
45
BACON
77
EGGS
99
currently a simple search is done on table 1 to find id's of food as follows:
SELECT ID1
FROM TABLE1
WHERE NAME IN NAME_LIST
so for example when the name list is: ('BREAD','FISH') then it returns XYZ, ABC. The problem is this misses 'BREAD_OLD' which is an older version with the same ID. (18)
I need to change this so now the search is done based on the FID of food rather than the NAME to find affected foods but I cannot change the input.
eg: given a list of foods: ('BREAD', 'FISH')
the result should be XYZ, ABC, BNQ (because BREAD matches 18 which matches BNQ in table 1)
how can this can be done? I think I need to use a join or do a 'select within a select' but I'm not sure how this would work with multiple inputs.
edit: ORACLE is the database
edit 2: adding BANANA18 to table 1, needs to be leading match
The correct solution would be to link on the FID fields. However, the FID field on table1 seems to concatenate the ID with the name. The solution would therfore be to extract the numeric value from that field and then use that to link it to the FID field on table 2. For example:
SELECT t1.FNAME, t1.ID1
FROM table1 t1
INNER JOIN table2 t2 ON t2.FID = regexp_replace(t1.FID, '^[^0-9]', '')
WHERE
t2.FNAME IN ('BREAD','FISH');
Assuming that table1.FID equals table2.FID concatenated with table1.FNAME then you do not need (slow) regular expressions and can use a simple equality combined with string concatenation:
SELECT t1.FNAME,
t1.ID1
FROM table1 t1
INNER JOIN table2 t2
ON t1.FID = t2.fid || t1.fname
WHERE t2.FNAME IN ('BREAD','FISH');
Which, for the sample data:
CREATE TABLE table1 (FNAME, ID1, FID) AS
SELECT 'BREAD', 'XYZ', '18BREAD' FROM DUAL UNION ALL
SELECT 'FISH', 'ABC', '45FISH' FROM DUAL UNION ALL
SELECT 'BREAD_OLD', 'BNQ', '18BREAD_OLD' FROM DUAL UNION ALL
SELECT 'BACON', 'TBG', '77BACON' FROM DUAL UNION ALL
SELECT 'EGGS', 'CGS', '99EGGS' FROM DUAL UNION ALL
SELECT 'BANANA', 'BHG', 'BANANA18' FROM DUAL UNION ALL
SELECT 'TOAST', 'TST', 'TOAST181' FROM DUAL;
CREATE TABLE table2 (FNAME, FID) AS
SELECT 'TOAST', 181 FROM DUAL UNION ALL
SELECT 'BREAD', 18 FROM DUAL UNION ALL
SELECT 'FISH', 45 FROM DUAL UNION ALL
SELECT 'BACON', 77 FROM DUAL UNION ALL
SELECT 'EGGS', 99 FROM DUAL;
Outputs:
FNAME
ID1
BREAD
XYZ
BREAD_OLD
BNQ
FISH
ABC
db<>fiddle here
Sample data:
SELECT * FROM TABLE1;
FNAME ID1 FID
-------------------------
BREAD XYZ 18BREAD
FISH ABC 45FISH
BACON TBG 77BACON
EGGS CGS 99EGGS
BREAD_OLD BNQ 18BREAD_OLD
SELECT * FROM TABLE2;
FNAME FID
----------
BREAD 18
FISH 45
BACON 77
EGGS 99
Query:
In query we join TABLE1 and TABLE2 on FID (using REGEX_SUBSTR to extract first n numbers of TABLE1 FID) so row 'BREAD_OLD' will be joined with row 'BREAD' from TABLE2 and when we add condition FNAME IN ('BREAD') both ID1 of 'BREAD' AND 'BREAD_OLD' will be selected.
SELECT ID1
FROM
(SELECT
TO_NUMBER(REGEXP_SUBSTR(FID,'^[0-9]{1,}'),'9999') AS FID,FNAME,ID1
FROM TABLE1)V
JOIN TABLE2 T
ON (V.FID=T.FID)
WHERE T.FNAME IN ('BREAD','FISH')
Result:
ID1
---
XYZ
ABC
BNQ
One option is joining the tables by matching concatenated columns of table2 with the extracted substrings upto _ character for fid column of table1 such as
SELECT id1
FROM table1 t1
JOIN table2 t2
ON REGEXP_SUBSTR(t1.fid,'[^_]+') = t2.fid||t2.fname
WHERE t2.fname IN ('BREAD','FISH')
Demo

Oracle listagg - Can I pull data from other table based on the values selected by listagg

I have two tables and want to get data from one table based on the values got from Listtagg in the second table:
T1
ID Name
==============
1 Name1
2 Person2
3 Someone3
4 Mr.4
T2
ID Acct
===============
1 1234
1 5678
2 1234
3 5678
3 8769
4 1234
My listagg query on T2 has returned the following:
Acct Id
====== ========
1234 1,2,4
5678 1,3
I need the result with Names from other table something like:
Acct Id Name
====== ======== ==========
1234 1,2,4 Name1, Person2, Mr.4
5678 1,3 Name1, Someone3
Why would you first aggregate IDs, and then put effort in splitting them to collect NAMEs? Do it immediately. Not that it can't be done (it can, in a relatively simple manner, but - why?!?).
Sample data is from line #1 - 15; query you might need begins at line #16.
SQL> with
2 t1 (id, name) as
3 (select 1, 'Name1' from dual union all
4 select 2, 'Person2' from dual union all
5 select 3, 'Someone3' from dual union all
6 select 4, 'Mr4' from dual
7 ),
8 t2 (id, acct) as
9 (select 1, 1234 from dual union all
10 select 1, 5678 from dual union all
11 select 2, 1234 from dual union all
12 select 3, 5678 from dual union all
13 select 3, 8769 from dual union all
14 select 4, 1234 from dual
15 )
16 select b.acct,
17 listagg(b.id, ', ') within group (order by b.id) id,
18 listagg(a.name, ', ') within group (order by b.id) name
19 from t1 a join t2 b on a.id = b.id
20 group by b.acct;
ACCT ID NAME
---------- ---------- --------------------
1234 1, 2, 4 Name1, Person2, Mr4
5678 1, 3 Name1, Someone3
8769 3 Someone3
SQL>
#Littlefoot's answer is absolutely correct. But just as an addition: don't use listagg if you are going to split those aggregated values. Just use collect() aggregate function to get needed data as a collection.
For example:
select
cast(collect(level) as sys.odcinumberlist) as varray_of_numbers,
cast(collect(level) as ORA_MINING_NUMBER_NT) as nested_table_of_numbers,
cast(collect(sys.ku$_objnum(level)) as sys.KU$_OBJNUMSET) as nested_table_of_objnum
from dual connect by level<=3;
--Result:
VARRAY_OF_NUMBERS NESTED_TABLE_OF_NUMBERS NESTED_TABLE_OF_OBJNUM(OBJ_NUM)
------------------------- ------------------------------ ------------------------------------------------------------
ODCINUMBERLIST(1, 2, 3) ORA_MINING_NUMBER_NT(1, 2, 3) KU$_OBJNUMSET(KU$_OBJNUM(1), KU$_OBJNUM(2), KU$_OBJNUM(3))
Update: This is a query for your tables, as you asked in the comment:
select b.acct,
cast(collect(b.id) as ORA_MINING_NUMBER_NT) as nested_table_of_numbers,
cast(collect(a.name) as ORA_MINING_VARCHAR2_NT) as nested_table_of_varchar2
-- listagg(b.id, ', ') within group (order by b.id) id,
-- listagg(a.name, ', ') within group (order by b.id) name
from t1 a join t2 b on a.id = b.id
group by b.acct;
Full example:
with
t1 (id, name) as
(select 1, 'Name1' from dual union all
select 2, 'Person2' from dual union all
select 3, 'Someone3' from dual union all
select 4, 'Mr4' from dual
),
t2 (id, acct) as
(select 1, 1234 from dual union all
select 1, 5678 from dual union all
select 2, 1234 from dual union all
select 3, 5678 from dual union all
select 3, 8769 from dual union all
select 4, 1234 from dual
)
select b.acct,
cast(collect(b.id) as ORA_MINING_NUMBER_NT) as nested_table_of_numbers,
cast(collect(a.name) as ORA_MINING_VARCHAR2_NT) as nested_table_of_varchar2
-- listagg(b.id, ', ') within group (order by b.id) id,
-- listagg(a.name, ', ') within group (order by b.id) name
from t1 a join t2 b on a.id = b.id
group by b.acct;

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.

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,'%')