Oracle: how to get only rows with a specific condition in columns - sql

I have three tables, customers, orders and customers-orders like this:
Customers:
ID NAME
1 Peter
2 Jack
3 Lisa
Orders:
ID INIT_DATE END_DATE
1 10-11-2014 10-11-2015
2 23-11-2014 01-01-2015
3 23-11-2014 03-05-2015
4 04-04-2016 08-11-2016
5 13-07-2016 01-11-2016
6 04-06-2016 30-10-2016
7 12-11-2014 01-05-2015
8 26-11-2014 10-10-2015
9 05-09-2016 11-11-2016
Customers_Orders:
CUSTOMER_ID ORDER_ID
1 1
1 2
1 3
2 4
2 5
2 6
3 7
3 8
3 9
I need to get customers who have all orders outdated. I mean, those orders which sysdate is not between init_date and end_date. In this case, the expected result is 1-Peter.
What i tried is this:
SELECT *
FROM Customers
WHERE
ID NOT IN (
SELECT DISTINCT Customers_Orders.CUSTOMER_ID
FROM Customers_Orders, Orders
WHERE Customers_Orders.ORDER_ID = Orders.ID
AND Orders.INIT_DATE <= SYSDATE AND Orders.END_DATE > SYSDATE
)
But i don't like the "NOT IN" syntax because the low performance. Is there another way?
Note: date format is dd-mm-yyyy
Edit: I corrected the question (in bold).

This will exclude all customers where the INIT_DATE is less than today and the END_DATE > today.
with Curr_ord as
(
select co1.customer_id, co1.order_id
from customers_orders co1
inner join orders o1
on o1.id = co1.order_id
where o1.INIT_DATE <= sysdate
and o1.END_DATE > sysdate
)
select C1.*
from Customers C1
left join Curr_Ord CO2
on C1.Customer_ID = CO2.Customer_ID
where CO2.Customer_ID is null

Join the three tables and use conditional aggregation to check if all the orders for a customer are outdated.
select c.id,c.name
from customer_orders co
join orders o on o.id=co.order_id
join customers c on c.id=co.customer_id
group by c.id,c.name
having count(case when sysdate between co.init_date and co.end_date then 1 end) = 0

Why not just do the join?
SELECT DISTINCT C.CUSTOMER_ID
FROM customers c
JOIN Customers_Orders co on co.customer_id = c.customer_id
JOIN Orders od on od.order_id = co.order_id
WHERE Od.INIT_DATE <= SYSDATE
AND Od.END_DATE > SYSDATE
With the right indexing on orders, you might get the performance gain by putting the filter into the join:
SELECT DISTINCT C.CUSTOMER_ID
FROM customers c
JOIN Customers_Orders co on co.customer_id = c.customer_id
JOIN (SELECT od.order_id
from Orders
WHERE Od.INIT_DATE <= SYSDATE
AND Od.END_DATE > SYSDATE ) od on od.order_id = co.order_id
but now we're talking performance tuning strategies.... and in this case we would need to talk about your data profiles etc to get the optimal solution.

Related

I need to categorize my customer for eligible for bonus and Ineligible based on orders count

I have 2 tables, the first one is Orders as the following:
Customer_ID
ORDER_ID
STATUS
A
11
completed
A
12
completed
B
13
completed
B
14
completed
B
15
completed
C
16
completed
B
17
cancelled
A
18
cancelled
And the second one is Customers as the following:
Customer_ID
Customer_status
join_date
A
15
2022-02-09
b
15
2022-02-10
c
10
2022-02-10
I tried a query to use as a sub-query but it didn't work, I'm new to this and still struggling.
SELECT T1.customer_id, count (T1.ORDER_ID) as Orders_count
FROM orders T1 LEFT join customers T2
on T1.Customer_ID = T2.Customer_ID
where T1.STATUS= 6 AND T2.Customer_status= 15
AND T2.join_date between timestamp'2022-02-10 'and timestamp '2022-02-11'
GROUP BY T1.Customer_ID ORDER BY T1.Customer_ID
I want to categorize the users as eligible or ineligible for a bonus. The eligibles are the ones whose user_status = 15, who made more than 1 order, and whose joining date is 2022-02-10, others are ineligible. I want the table to show both, I'm using redash for that matter.
group by and having can be used to create a list of customers who have placed many orders.
Then a left join to join this list and see who is eligible or not.
select c.*, if(s.Customer_ID is not null, 'YES','NO') as Eligible
from customers c
left join (
select Customer_ID
from orders
group by Customer_ID
having count(1) > 1
) as s on s.Customer_ID = c.Customer_ID and c.Customer_status = 15 and c.join_date = '2022-02-10'
Demo here

SQL get table1 names with a count of table2 and table3

I have three tables, table1 is connected to table2 and table3, but table2 and table3 are not connected. I need an output count of table2 and table3 for each table1 row. I have to use joins and a group by table1.name
SELECT Tb_Product.Name, count(TB_Offers.Prod_ID) 'Number of Offers', count(Tb_Requests.Prod_ID) 'Number of Requests'
FROM Tb_Product LEFT OUTER JOIN
Tb_Requests ON Tb_Product.Prod_ID = Tb_Requests.Prod_ID LEFT OUTER JOIN
TB_Offers ON Tb_Product.Prod_ID = TB_Offers.Prod_ID
GROUP BY Tb_Product.Name
I need to combine these queries:
SELECT Tb_Product.[Name], count(TB_Offers.Prod_ID) 'Number of Offers'
FROM Tb_Product LEFT OUTER JOIN
TB_Offers ON Tb_Product.Prod_ID = TB_Offers.Prod_ID
GROUP BY Tb_Product.[Name]
SELECT Tb_Product.[Name], count(Tb_Requests.Prod_ID) 'Number of Requests'
FROM Tb_Product LEFT OUTER JOIN
Tb_Requests ON Tb_Product.Prod_ID = Tb_Requests.Prod_ID
GROUP BY Tb_Product.[Name]
Results:
Name Number of Offers
Airplane 6
Auto 5
Bike 3
Camera 0
Computer 12
Milk 4
Oil 4
Orange 6
Telephone 0
Truck 6
TV 4
Name Number of Requests
Airplane 1
Auto 5
Bike 0
Camera 2
Computer 6
Milk 4
Oil 5
Orange 6
Telephone 0
Truck 1
TV 5
My results for offers and requests are the same value. I am not sure what I am doing wrong with the joins. Do I need to somehow join product to request and separately join product to offers? This needs to be done in one query.
This is for a class. Explanation would also be appreciated.
The simplest way to do this is to count the distinct values of each column:
SELECT
Tb_Product.Name,
count(distinct TB_Offers.Prod_ID) 'Number of Offers',
count(distinct Tb_Requests.Prod_ID) 'Number of Requests'
FROM
Tb_Product
LEFT OUTER JOIN
Tb_Requests ON Tb_Product.Prod_ID = Tb_Requests.Prod_ID
LEFT OUTER JOIN
TB_Offers ON Tb_Product.Prod_ID = TB_Offers.Prod_ID
GROUP BY
Tb_Product.Name
This is necessary because of the way joins work consecutively to produce a rowset that is a combination of all the input relations. COUNT() normally performs a count of non-null values in a column.
You can also do something like this, which aggregates the counts from the child tables independently and then joins them to the base table:
SELECT
p.Name,
o.cnt as Offer_Count,
r.cnt as Request_Count
FROM
TB_Product p
LEFT OUTER JOIN
(SELECT Prod_ID, COUNT(1) cnt FROM TB_Offers GROUP BY Prod_ID) o
LEFT OUTER JOIN
(SELECT Prod_ID, COUNT(1) cnt FROM TB_Requests GROUP BY Prod_ID) r
More explanation...
Let's say you have two products:
Prod_ID
Name
1
Widget
2
Gizmo
And two offers, one for each product:
Offer_ID
Prod_ID
100
1
200
2
And two requests for each product:
Request_ID
Prod_ID
1001
1
1002
1
2001
2
2002
2
Now you join Product relation to Offer relation on Prod_ID, you get a result like this:
Prod_ID
Name
Offer_ID
Prod_ID
1
Widget
100
1
2
Gizmo
200
2
Now when you join that relation to Requests on Prod_ID, you get something like this:
Prod_ID
Name
Offer_ID
Prod_ID
Request_ID
Prod_ID
1
Widget
100
1
1001
1
1
Widget
100
1
1002
1
2
Gizmo
200
2
2001
2
2
Gizmo
200
2
2002
2
Now when you count any of these columns you get 4 because each column has 4 values.

SQL query to the required output

I need a desired output as below out of two input tables
order tableusers table
id order_date id username
1 2019-01-01 1 A
2 2019-01-01 2 B
3 2019-01-02 3 B
4 2019-01-03 4 A
5 2019-01-03 5 B
Desired Output
order_date username orders
2019-01-01 A 1
2019-01-02 A 0
2019-01-03 A 1
I tried with this query,
SELECT o. order_date as order_date, u.username as username,
ISNULL (COUNT (username),0) AS orders
FROM Order O LEFT JOIN users U ON o.id=u.id
WHERE u.username = ‘A’
GROUP BY o. order_date, u.username
ORDER BY o. order_date, u.username
Which give me this result
order_date username orders
2019-01-01 A 1
2019-01-03 A 1
I don't know how to bring this part in the result "2019-01-02 A 0"
could anyone please help me with the query, Thanks in advance
You can do:
select d.order_date, 'A' as username, coalesce(cnt, 0) as orders
from (select distinct order_date as order_date from orders) d
left join (
select o.order_date, count(*) as cnt
from orders o
join users u on u.id = o.id
where u.username = 'A'
group by o.order_date
) t on t.order_date = d.order_date
order by d.order_date
Result:
order_date username orders
----------- --------- ------
2019-01-01 A 1
2019-01-02 A 0
2019-01-03 A 1
See running example at db<>fiddle.
You can use the query below in which includeAllUsers (using CROSS JOIN) allows you to include 'A' --without put it in the SELECT clause--, and StrictMatching gives you the real dataset using matching ID between the two tables Order & Users (by the way, I really recommend you to change the name of your order table in Orders or other words, because ORDER is a reserved word).
select includeAllUsers.Order_Date,
coalesce(StrictMatching.username,includeAllUsers.username) User_Name,
count(distinct StrictMatching.username) Total_Orders
from (select o.order_date, u.username, u.id from orders o cross join users u) includeAllUsers
left join (select o.order_date, u.username,o.id from orders o join users u on o.id=u.id) StrictMatching
on includeAllUsers.order_date = StrictMatching.order_date and StrictMatching.username='A'
where includeAllUsers.username='A'
group by
includeAllUsers.order_date, StrictMatching.username, includeAllUsers.username;
By combining includeAllUsers and StrictMatching and filtering by StrictMatching.username='A' (in the criterium of the JOIN clause) et again by includeAllUsers.username='A' (in WHERE clause), you get the correct result.
Here is a link to verify

SQL query to find top 2 customers done max amount of transaction( oracle db) [duplicate]

This question already has answers here:
How do I do top 1 in Oracle? [duplicate]
(9 answers)
Closed 5 years ago.
I want to get TOP 2 customers done maximum amount transaction from below the table.I am working on oracle db.
my table transactions:
tranID PROD_ID QTY PRICE CID
1 100 5 10000 1000
2 103 16 5000 1001
3 102 8 5000 1003
4 200 10 9000 1002
5 204 8 9000 1002
6 207 4 8000 1002
CUSTOMERS
CID CNAME
1001 X
1002 Y
1003 Z
If Oracle 12c , you can use this.
SELECT a.CID, b.CNAME, SUM (a.QTY * a.PRICE) amount
FROM transactions a JOIN CUSTOMERS b ON a.CID = b.CID
GROUP BY a.CID, b.CNAME
ORDER BY amount DESC
FETCH FIRST 2 ROWS ONLY
You could use a summ group by and a join and last filter for rownum
select a.CID, b.CNAME, sum(a.QTY*a.PRICE) amount
from transactions a
INNER JOIN CUSTOMERS b on a.CID = b.CID
WHERE ROWNUM < 3
GROUP BY a.CID, b.CNAME
ORDER BY amount
This can be done using the following query. First, you compute sums, then you order it and then you select the first two rows.
SELECT t.*
FROM
(
SELECT t.CID, c.name, sum(t.PRICE * t.qty) tsum
FROM transaction t
JOIN customer c ON t.CID = c.CID
GROUP BY t.CID , c.name
ORDER BY tsum DESC
) t
WHERE rownum < 3

Group by with two columns

I am trying to write a query using group by in sub query ,I referred lot of blogs but could not get all the values.
I have three tables and below is the structure of those tables.
Pet_Seller_Master
ps_id ps_name city_id
2 abc 1
3 xyz 2
4 fer 4
5 bbb 1
City_Master
city_id city_name
1 Bangalore
2 COIMBATORE
4 MYSORE
Api_Entry
api_id ps_id otp
1 2 yes
2 3
3 2 yes
4 3 yes
5 4
6 5 yes
7 5 yes
8 5 yes
Query is to get number of sellers, no of pet sellers with zero otp, no of pet sellers with 1 otp, no of pet sellers with 2 otp,no of pet sellers with otp>2 for the particular city and within date range.
Through Below query I am able to get city , psp , and zero otp
select cm.city_name,
count(ps.ps_id) as PSP,
((select count(ps1.ps_id)
FROM ps_master ps1
WHERE ps1.city = cm.city_id)-
(SELECT count(distinct ps1.ps_id)
from ps_master ps1
INNER JOIN api_entry ae ON ps1.ps_id = ae.ps_id and otp!=''
WHERE ps1.city = cm.city_id and date(timestamp) >= curdate() - INTERVAL DAYOFWEEK(curdate())+6 DAY AND date(timestamp) < curdate())) as zero_psp
from ps_master ps INNER JOIN city_master cm ON ps.city = cm.city_id and cm.city_type = 'IN HOUSE PNS'
group by city_id
Please tell me the solution to solve this query.
Thanks in advance
It's not hard to do and you were on a right track. Here is what I would use:
select c.city_name, a.otp, p.ps_name, COUNT(*) nbr
from Api_Entry a
inner join Pet_Seller_Master p on p.ps_id=a.ps_id
inner join City_Master c on p.city_id=c.city_id
group by c.city_name, a.otp, p.ps_name
Now, if you want to get the number of sellers with zero otp, you just apply where clause:
where otp <> 'yes'
If you want to get the number of pet sellers with otp>2, then you just use subquery:
select *
from (
select c.city_name, a.otp, p.ps_name, COUNT(*) nbr
from #tempA a
inner join #tempP p on p.ps_id=a.ps_id
inner join #tempC c on p.city_id=c.city_id
group by c.city_name, a.otp, p.ps_name
) g
where nbr > 2