Oracle SQL - Not in subquery - sql

I'm newish to this and using Oracle SQL. I have the following tables:
Table 1 CaseDetail
CaseNumber | CaseType
1 | 'RelevantToThisQuestion'
2 | 'RelevantToThisQuestion'
3 | 'RelevantToThisQuestion'
4 | 'NotRelevantToThisQuestion'
Table 2 LinkedPeople
CaseNumber | RelationshipType | LinkedPerson
1 | 'Owner' | 123
1 | 'Agent' | 124
1 | 'Contact' | 125
2 | 'Owner' | 126
2 | 'Agent' | 127
2 | 'Contact' | 128
3 | 'Owner' | 129
3 | 'Agent' | 130
3 | 'Contact' | 131
Table 3 Location
LinkedPerson| Country
123 | 'AU'
124 | 'UK'
125 | 'UK'
126 | 'US'
127 | 'US'
128 | 'UK'
129 | 'UK'
130 | 'AU'
131 | 'UK'
I want to count CaseNumbers that are relevant to this question with no LinkedPeople in 'AU'. So the results from the above data would be 1
I've been trying to combine aggregate functions and subqueries but I think I might be over-complicating things.
Just need a push in the right direction, thanks!

To get all the records:
SELECT COUNT(DISTINCT CaseNumber)
FROM LinkedPeople
WHERE CaseNumber NOT IN
(
SELECT DISTINCT C.CaseNumber
FROM CaseDetail C
INNER JOIN LinkedPeople P ON C.CaseNumber = P.CaseNumber
INNER JOIN Location L
ON P.LinkedPerson = L.LinkedPerson
WHERE Country = 'AU' AND C.CaseType = 'RelevantToThisQuestion'
)

SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE CASEDETAIL ( CaseNumber, CaseType ) AS
SELECT 1, 'RelevantToThisQuestion' FROM DUAL
UNION ALL SELECT 2, 'RelevantToThisQuestion' FROM DUAL
UNION ALL SELECT 3, 'RelevantToThisQuestion' FROM DUAL
UNION ALL SELECT 4, 'NotRelevantToThisQuestion' FROM DUAL;
CREATE TABLE LINKEDPEOPLE ( CaseNumber, RelationshipType, LinkedPerson ) AS
SELECT 1, 'Owner', 123 FROM DUAL
UNION ALL SELECT 1, 'Agent', 124 FROM DUAL
UNION ALL SELECT 1, 'Contact', 125 FROM DUAL
UNION ALL SELECT 2, 'Owner', 126 FROM DUAL
UNION ALL SELECT 2, 'Agent', 127 FROM DUAL
UNION ALL SELECT 2, 'Contact', 128 FROM DUAL
UNION ALL SELECT 3, 'Owner', 129 FROM DUAL
UNION ALL SELECT 3, 'Agent', 130 FROM DUAL
UNION ALL SELECT 3, 'Contact', 131 FROM DUAL;
CREATE TABLE LOCATION ( LinkedPerson, Country ) AS
SELECT 123, 'AU' FROM DUAL
UNION ALL SELECT 124, 'UK' FROM DUAL
UNION ALL SELECT 125, 'UK' FROM DUAL
UNION ALL SELECT 126, 'US' FROM DUAL
UNION ALL SELECT 127, 'US' FROM DUAL
UNION ALL SELECT 128, 'UK' FROM DUAL
UNION ALL SELECT 129, 'UK' FROM DUAL
UNION ALL SELECT 130, 'AU' FROM DUAL
UNION ALL SELECT 131, 'UK' FROM DUAL;
Query 1:
SELECT COUNT( DISTINCT CASENUMBER ) AS Num_Relevant_Cases
FROM CASEDETAIL c
WHERE CaseType = 'RelevantToThisQuestion'
AND NOT EXISTS ( SELECT 1
FROM LINKEDPEOPLE p
INNER JOIN LOCATION l
ON ( p.LinkedPerson = l.LinkedPerson )
WHERE c.CaseNumber = p.CaseNumber
AND l.Country = 'AU' )
Results:
| NUM_RELEVANT_CASES |
|--------------------|
| 1 |

I'm not sure about the exact syntax, but I believe you want something like:
select count distinct CaseNumber from LinkedPeople where Country != 'AU'

Related

Oracle filter groups that contains more than one row condition

I need a SQL query for Oracle that select groups that contains the elements "ABC" and "ANQ"
group X Column Q
-------- ---------
123 ABC
123 AAA
123 ANQ
456 ANQ
456 PKR
579 AAA
579 XYZ
886 ABC
The desired result should be
group X Column Q
-------- ---------
123 ABC
123 AAA
123 ANQ
You can query the table only once by using the analytic COUNT function with conditional aggregation:
SELECT x,
q
FROM (
SELECT x,
q,
COUNT(CASE q WHEN 'ABC' THEN 1 END) OVER (PARTITION BY x) AS num_abc,
COUNT(CASE q WHEN 'ANQ' THEN 1 END) OVER (PARTITION BY x) AS num_anq
FROM table_name
)
WHERE num_abc > 0
AND num_anq > 0;
Which, for the sample data:
CREATE TABLE table_name (X, Q) AS
SELECT 123, 'ABC' FROM DUAL UNION ALL
SELECT 123, 'AAA' FROM DUAL UNION ALL
SELECT 123, 'ANQ' FROM DUAL UNION ALL
SELECT 456, 'ANQ' FROM DUAL UNION ALL
SELECT 456, 'PKR' FROM DUAL UNION ALL
SELECT 579, 'AAA' FROM DUAL UNION ALL
SELECT 579, 'XYZ' FROM DUAL UNION ALL
SELECT 886, 'ABC' FROM DUAL;
Outputs:
X
Q
123
ABC
123
AAA
123
ANQ
fiddle
For example:
Sample data:
SQL> with test (x, q) as
2 (select 123, 'abc' from dual union all
3 select 123, 'aaa' from dual union all
4 select 123, 'anq' from dual union all
5 select 456, 'anq' from dual union all
6 select 456, 'pkr' from dual union all
7 select 579, 'aaa' from dual union all
8 select 579, 'xyz' from dual union all
9 select 886, 'abc' from dual
10 )
Query:
11 select x, q
12 from test a
13 where exists (select null
14 from test b
15 where b.q in ('abc', 'anq')
16 and b.x = a.x
17 group by b.x
18 having count(distinct b.q) = 2
19 );
X Q
---------- ---
123 abc
123 aaa
123 anq
SQL>

Only keep rows within certain field based on another field

Having trouble with this one, can this be done in one query? Or should I delete all the N records and then use another query to get what I need? Help is appreciated!!
Only keep rows belonging to a group_id where the group_id contains YY for the same brand. If the flag is a Y/N or N/N for the same brand within a group_id, then remove those records.
Next time, please supply your data so that we can copy-paste it into an example and work with it. You would have saved me at least 15 min ....
I did not bother with unimportant data - they will be different from your input, but don't matter.
One way ist to use an OLAP function partitioned by grp_id,brand,flag, and a counter, and filtering by flag and counter value 2, or you could also use a grouping query, and join that grouping query back to the base table.
Here is the OLAP solution:
WITH
-- your (simplified) input, don't use in query )
indata(grp_id,secondary_id,account_id,site_id,op_id,brand,flag) AS (
SELECT 1, 1055884386, 123, 'usa', 2,'toyota', 'N' FROM dual
UNION ALL SELECT 1, 1055884386, 123, 'usa', 2,'toyota', 'N' FROM dual
UNION ALL SELECT 1, 1055884386, 123, 'usa', 2,'tesla' , 'Y' FROM dual
UNION ALL SELECT 1, 1055884386, 123, 'usa', 2,'tesla' , 'Y' FROM dual
UNION ALL SELECT 2, 1055884386, 123, 'usa', 2,'toyota', 'Y' FROM dual
UNION ALL SELECT 2, 1055884386, 123, 'usa', 2,'toyota', 'N' FROM dual
UNION ALL SELECT 2, 1055884386, 123, 'usa', 2,'tesla' , 'Y' FROM dual
UNION ALL SELECT 2, 1055884386, 123, 'usa', 2,'tesla' , 'N' FROM dual
UNION ALL SELECT 3, 1055884386, 123, 'usa', 2,'toyota', 'Y' FROM dual
UNION ALL SELECT 3, 1055884386, 123, 'usa', 2,'toyota', 'N' FROM dual
UNION ALL SELECT 3, 1055884386, 123, 'usa', 2,'tesla' , 'Y' FROM dual
UNION ALL SELECT 3, 1055884386, 123, 'usa', 2,'tesla' , 'N' FROM dual
UNION ALL SELECT 4, 1055884386, 123, 'usa', 2,'toyota', 'Y' FROM dual
UNION ALL SELECT 4, 1055884386, 123, 'usa', 2,'toyota', 'Y' FROM dual
UNION ALL SELECT 4, 1055884386, 123, 'usa', 2,'tesla' , 'N' FROM dual
UNION ALL SELECT 4, 1055884386, 123, 'usa', 2,'tesla' , 'N' FROM dual
)
-- end of your input; replace following comma with "WITH" ...
,
w_counter AS (
SELECT
*
, COUNT(*) OVER(PARTITION BY grp_id,brand,flag) AS occ_count
FROM indata
)
SELECT
*
FROM w_counter
WHERE flag='Y'
AND occ_count = 2
;
-- out grp_id | secondary_id | account_id | site_id | op_id | brand | flag | occ_count
-- out --------+--------------+------------+---------+-------+--------+------+-----------
-- out 1 | 1055884386 | 123 | usa | 2 | tesla | Y | 2
-- out 1 | 1055884386 | 123 | usa | 2 | tesla | Y | 2
-- out 4 | 1055884386 | 123 | usa | 2 | toyota | Y | 2
-- out 4 | 1055884386 | 123 | usa | 2 | toyota | Y | 2
And, for good measure, also the grouping solution - not repeating the input WITH clause ...
grp AS (
SELECT
grp_id
, brand
FROM indata
WHERE flag='Y'
GROUP BY
grp_id
, brand
HAVING COUNT(brand) = 2
)
SELECT
i.*
FROM indata i
JOIN grp USING(grp_id,brand)
ORDER BY grp_id
;
-- out grp_id | secondary_id | account_id | site_id | op_id | brand | flag
-- out --------+--------------+------------+---------+-------+--------+------
-- out 1 | 1055884386 | 123 | usa | 2 | tesla | Y
-- out 1 | 1055884386 | 123 | usa | 2 | tesla | Y
-- out 4 | 1055884386 | 123 | usa | 2 | toyota | Y
-- out 4 | 1055884386 | 123 | usa | 2 | toyota | Y

Compare date time to another table date_time

select uid , max(price) keep(dense_rank last order by usage_date ASC) amount
from HISTORY group by uid" ;
compare HISTORY to ADJUSTMENT
If the latest usage_date (each id ) is before (sen_date)
I would like to add the price of ADJUSTMENT
How can I do this ?
*I want to add the price of ADJUSTMENT to max(price) if usage_date is older than ADJUSTMENT
in this case uid= 5 is should be added 200
ADJUSTMENT
uid,price,sen_date
1,100,2020-10-23
2,200,2020-10-23
2,200,2020-09-22
2,200,2020-08-23
2,200,2020-10-22
3,300,2020-10-20
5,200,2020-10-20
HISTORY
uid,price,usage_date
1,1000,2020-10-23
1,1000,2020-10-19
1,1000,2020-10-03
2,1000,2020-10-23
3,1000,2020-10-23
3,1000,2020-10-23
3,1000,2020-10-23
4,1000,2020-10-20
4,1000,2020-10-23
4,1000,2020-10-19
4,1000,2020-10-23
4,1000,2020-10-23
5,1000,2020-10-02
5,1000,2020-10-03
5,1000,2020-10-04
6,1000,2020-10-23
7,1000,2020-10-23
Take your query and LEFT OUTER JOIN it to a similar query for the ADJUSTMENT table correlating on uid with a later date and then use COALESCE to combine the dates:
SELECT h."UID",
h.usage_date,
a.sen_date,
h.price AS base_price,
COALESCE( a.price, 0 ) AS price_adjustment,
h.price + COALESCE( a.price, 0 ) AS price
FROM (
SELECT "UID",
MAX( usage_date ) AS usage_date,
MAX(price) KEEP ( DENSE_RANK LAST ORDER BY usage_date ASC) AS price
FROM HISTORY
GROUP BY "UID"
) h
LEFT OUTER JOIN
(
SELECT "UID",
MAX( sen_date ) AS sen_date,
MAX(price) KEEP ( DENSE_RANK LAST ORDER BY sen_date ASC) AS price
FROM ADJUSTMENT
GROUP BY "UID"
) a
ON ( h."UID" = a."UID" AND h.usage_date < a.sen_date )
ORDER BY "UID"
Which, for the sample data:
CREATE TABLE adjustment ( "UID",price,sen_date ) AS
SELECT 1,100,DATE '2020-10-23' FROM DUAL UNION ALL
SELECT 2,200,DATE '2020-10-23' FROM DUAL UNION ALL
SELECT 2,200,DATE '2020-09-22' FROM DUAL UNION ALL
SELECT 2,200,DATE '2020-08-23' FROM DUAL UNION ALL
SELECT 2,200,DATE '2020-10-22' FROM DUAL UNION ALL
SELECT 3,300,DATE '2020-10-20' FROM DUAL UNION ALL
SELECT 5,200,DATE '2020-10-20' FROM DUAL;
CREATE TABLE HISTORY ( "UID",price,usage_date) AS
SELECT 1,1000,DATE '2020-10-23' FROM DUAL UNION ALL
SELECT 1,1000,DATE '2020-10-19' FROM DUAL UNION ALL
SELECT 1,1000,DATE '2020-10-03' FROM DUAL UNION ALL
SELECT 2,1000,DATE '2020-10-23' FROM DUAL UNION ALL
SELECT 3,1000,DATE '2020-10-23' FROM DUAL UNION ALL
SELECT 3,1000,DATE '2020-10-23' FROM DUAL UNION ALL
SELECT 3,1000,DATE '2020-10-23' FROM DUAL UNION ALL
SELECT 4,1000,DATE '2020-10-20' FROM DUAL UNION ALL
SELECT 4,1000,DATE '2020-10-23' FROM DUAL UNION ALL
SELECT 4,1000,DATE '2020-10-19' FROM DUAL UNION ALL
SELECT 4,1000,DATE '2020-10-23' FROM DUAL UNION ALL
SELECT 4,1000,DATE '2020-10-23' FROM DUAL UNION ALL
SELECT 5,1000,DATE '2020-10-02' FROM DUAL UNION ALL
SELECT 5,1000,DATE '2020-10-03' FROM DUAL UNION ALL
SELECT 5,1000,DATE '2020-10-04' FROM DUAL UNION ALL
SELECT 6,1000,DATE '2020-10-23' FROM DUAL UNION ALL
SELECT 7,1000,DATE '2020-10-23' FROM DUAL;
Outputs:
UID | USAGE_DATE | SEN_DATE | BASE_PRICE | PRICE_ADJUSTMENT | PRICE
--: | :--------- | :-------- | ---------: | ---------------: | ----:
1 | 23-OCT-20 | null | 1000 | 0 | 1000
2 | 23-OCT-20 | null | 1000 | 0 | 1000
3 | 23-OCT-20 | null | 1000 | 0 | 1000
4 | 23-OCT-20 | null | 1000 | 0 | 1000
5 | 04-OCT-20 | 20-OCT-20 | 1000 | 200 | 1200
6 | 23-OCT-20 | null | 1000 | 0 | 1000
7 | 23-OCT-20 | null | 1000 | 0 | 1000
db<>fiddle here
Do you just mean this (shown here)?
SELECT a.* FROM adjustment a
WHERE a.sen_date > (SELECT max(usage_date) FROM history WHERE uid = a.uid)

Combine the result of one sql query into another

I have the below table.
CREATE TABLE Employee_id_credits ( Employee_id, credits ) AS
SELECT 10, 1 FROM DUAL UNION ALL
SELECT 12, 1 FROM DUAL UNION ALL
SELECT 10, 1 FROM DUAL UNION ALL
SELECT 12, 1 FROM DUAL UNION ALL
SELECT 12, 1 FROM DUAL UNION ALL
SELECT 14, 1 FROM DUAL;
The below query groups and counts the total number of credits for employees.
select Employee_id, count(*) as "Total_credits"
from Employee_id_credits
group by Employee_id;
Gives the below output.
Employee_id Total_credits
----------- -------------
10 2
12 3
14 1
I have a Employee Manager table with the hierarchy.
CREATE TABLE Employee_Manager ( Employee_id, Manager_id ) AS
SELECT 10, 101 FROM DUAL UNION ALL
SELECT 12, 120 FROM DUAL UNION ALL
SELECT 13, 120 FROM DUAL UNION ALL
SELECT 14, 150 FROM DUAL UNION ALL
SELECT 101, NULL FROM DUAL UNION ALL
SELECT 120, 130 FROM DUAL UNION ALL
SELECT 130, NULL FROM DUAL;
I have a query to find the top level manager of the employee.
SELECT
Employee_id
FROM
Employee_Manager
WHERE
Manager_id is null
CONNECT BY PRIOR
Manager_id = Employee_id
START WITH
Employee_id = '12';
I want to combine the above two queries so that the output would look like below. How do I combine both queries?
Manager Total_credits
------- -------------
101 2
130 3
150 1
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE Employee_id_credits ( Employee_id, credits ) AS
SELECT 10, 1 FROM DUAL UNION ALL
SELECT 12, 1 FROM DUAL UNION ALL
SELECT 10, 1 FROM DUAL UNION ALL
SELECT 12, 1 FROM DUAL UNION ALL
SELECT 12, 1 FROM DUAL UNION ALL
SELECT 14, 1 FROM DUAL;
CREATE TABLE Employee_Manager ( Employee_id, Manager_id ) AS
SELECT 10, 101 FROM DUAL UNION ALL
SELECT 12, 120 FROM DUAL UNION ALL
SELECT 13, 120 FROM DUAL UNION ALL
SELECT 14, 150 FROM DUAL UNION ALL
SELECT 101, NULL FROM DUAL UNION ALL
SELECT 120, 130 FROM DUAL UNION ALL
SELECT 130, NULL FROM DUAL;
Query 1 - Find the managers of each employee:
SELECT CONNECT_BY_ROOT( Employee_id ) AS Employee_id,
COALESCE( manager_id, employee_id ) AS manager_id
FROM Employee_manager
WHERE CONNECT_BY_ISLEAF = 1
CONNECT BY PRIOR Manager_id = Employee_id
Results:
| EMPLOYEE_ID | MANAGER_ID |
|-------------|------------|
| 10 | 101 |
| 12 | 130 |
| 13 | 130 |
| 14 | 150 |
| 101 | 101 |
| 120 | 130 |
| 130 | 130 |
Query 2 - Join that with the credits table and aggregate:
SELECT m.manager_id,
SUM( c.credits ) As total_credits
FROM Employee_id_credits c
INNER JOIN
(
SELECT CONNECT_BY_ROOT( Employee_id ) AS Employee_id,
COALESCE( manager_id, employee_id ) AS manager_id
FROM Employee_manager
WHERE CONNECT_BY_ISLEAF = 1
CONNECT BY PRIOR Manager_id = Employee_id
) m
ON ( c.employee_id = m.employee_id )
GROUP BY m.manager_id
Results:
| MANAGER_ID | TOTAL_CREDITS |
|------------|---------------|
| 101 | 2 |
| 130 | 3 |
| 150 | 1 |

SQL query to select e.g. buyer which made 3 specific buys

I have a table like this:
ToyStore
+----+------------+-------------------+
| ID | NAME | PURCHASE |
+----+------------+-------------------+
| 1 | Ramesh | Teddy bear |
| 2 | Khilan | Drum |
| 3 | Chaitali | Chess |
| 4 | Hardik | Wooden sword |
|... | ... | ... |
+----+------------+-------------------+
I need to select all the buyers which have made all 3 purchases - Teddy bear, Chess and Wooden sword and their purchases
As a result there should be something like this:
+--------+-------------------+
| NAME | PURCHASE |
+--------+-------------------+
| Ramesh | Teddy bear |
| Ramesh | Chess |
| Ramesh | Wooden sword |
| Khilan | Teddy bear |
| Khilan | Chess |
| Khilan | Wooden sword |
+--------+-------------------+
Thanks in advance
select * from ToyStore ts1 where
exists (select 1 from ToyStore ts2 where
ts1.name=ts2.name and purchase ='Teddy bear')
and
exists (select 1 from ToyStore ts2 where
ts1.name=ts2.name and purchase ='Chess')
and
exists (select 1 from ToyStore ts2 where
ts1.name=ts2.name and purchase ='Wooden sword')
If you want to find all names with exact one row of each matching toy and only These three rows you could use the following query:
(of course there are smarter ways, but this is very easy to understand and to Change)
With
-- Data
TOYSTORE AS (
SELECT 1 ID, 'Ramish' NAME, 'Teddy Bear' PURCHASE FROM DUAL UNION ALL
SELECT 2, 'Khilan', 'Drum' FROM DUAL UNION ALL
SELECT 3, 'Chaitali', 'Chess' FROM DUAL UNION ALL
SELECT 4, 'Hardik', 'Wooden sword' FROM DUAL UNION ALL
SELECT 5, 'Hardik', 'Chess' FROM DUAL UNION ALL
SELECT 6, 'Hardik', 'Teddy Bear' FROM DUAL UNION ALL
SELECT 7, 'Chaitali', 'Chess' FROM DUAL UNION ALL
SELECT 8, 'Chaitali', 'Wooden sword' FROM DUAL UNION ALL
SELECT 9, 'Chaitali', 'Teddy Bear' FROM DUAL UNION ALL
SELECT 10, 'Khilan', 'Chess' FROM DUAL UNION ALL
SELECT 11, 'Khilan', 'Wooden sword' FROM DUAL UNION ALL
SELECT 12, 'Khilan', 'Teddy Bear' FROM DUAL
),
-- Matching items
MyMatches AS (
SELECT 'Teddy Bear' PURCHASE FROM DUAL UNION ALL
SELECT 'Chess' FROM DUAL UNION ALL
SELECT 'Wooden sword' FROM DUAL
),
-- all single hits of matching items
MyStrikes AS (
SELECT NAME, PURCHASE, COUNT(*) CNT
FROM TOYSTORE
NATURAL JOIN MyMatches
GROUP BY NAME, PURCHASE
HAVING COUNT(*) = 1
),
-- all names with exactly one item of each kind
My3Strikes AS (
SELECT NAME, SUM(CNT) CNT
FROM MyStrikes
GROUP BY NAME
HAVING SUM(CNT) = 3
),
-- number of all not matching items
MyBlanks AS (
SELECT NAME, COUNT(PURCHASE) CNT --COUNT(PURCHASE) CNT
FROM TOYSTORE
WHERE NOT PURCHASE IN ('Teddy Bear', 'Chess', 'Wooden sword')
GROUP BY NAME
),
-- all names
MyNames AS (
SELECT D.NAME
FROM TOYSTORE D
GROUP BY D.NAME
)
-- the result
SELECT D.NAME
FROM MyNames D
JOIN My3Strikes S ON S.Name = D.Name
LEFT JOIN MyBlanks B ON B.Name = D.Name
WHERE B.CNT IS NULL
;
There are some things not completely clear for me. Do you want to know, whether any 3 items are in list? If so, the next query would help.
SELECT NAME, COUNT(PURCHASE)
FROM TOYSTORE
GROUP BY NAME
HAVING COUNT(PURCHASE) = 3
gives all names, that have exactly 3 items in his list.
This query you need for an inner join:
SELECT T.NAME, T.PURCHASE
FROM TOYSTORE T
JOIN (
SELECT NAME, COUNT(PURCHASE)
FROM TOYSTORE
GROUP BY NAME
HAVING COUNT(PURCHASE) = 3
) I
ON I.NAME = T.NAME
Does this solve your Problem?
If not, tell us more.
Questions may be:
What, if someone buys 2 teddybears and 1 sword?
What, if someone buys all three items and another more item?