How to query many cases in SQL CASE statement - sql

I am trying to solve an SQL online-challenge
I have three tables:
sales: customer_id, order_date, product_id.
members: join_date, member_id.
menu: product_id, product_name, price.
and one of the questions is: What is the total items and amount spent for each member before they became a member?
I think I got the write answer right the following query:
with cte as
(
SELECT
CASE WHEN s.customer_id = 'A' THEN count(s.product_id)*m.price END AS purchases_A,
CASE WHEN s.customer_id = 'B' THEN count(s.product_id)*m.price END AS purchases_B,
CASE WHEN s.customer_id = 'C' THEN count(s.product_id)*m.price END AS purchases_C,
case when s.customer_id = 'A' THEN count(s.product_id) END AS total_A,
case when s.customer_id = 'B' THEN count(s.product_id) END AS total_B,
case when s.customer_id = 'C' THEN count(s.product_id) END AS total_C
from sales s
join menu m on s.product_id = m.product_id
join members mb on mb.customer_id = s.customer_id and mb.join_date > s.order_date
group by s.customer_id, m.price
)
select
sum(purchases_A) as total_spendings_a,
sum (total_A) as total_items_A,
sum(purchases_B) as total_spendings_b,
sum (total_B) as total_items_B,
sum(purchases_C) as total_spendings_c,
sum (total_C) as total_items_C
from cte;
And my question is. is there a better way or more efficient way in writing this query? it feels too long and repetitive.
In this case I had only three customers: A,B, and C
what if I have 100 or 1000 customers?

Simply join the tables and aggregate by member:
select
m.member_id,
coalesce(count(*), 0) as total_amount,
coalesce(sum(price), 0) as total_price
from members m
left join sales s on s.customer_id = m.customer_id and s.order_date < m.join_date
left join menu p on p.product_id = s.product_id
group by m.member_id
order by by m.member_id;
You mention both members.member_id and members.customer_id, so I've used them both. If this was a typo in your request, then just change the wrong column name to the correct one in my query.

Related

How to calculate rows count in where statement in sql?

I have two tables in SQL Server:
order (columns: order_id, payment_id)
payment (columns: payment_id, is_pay)
I want to get all orders with two more properties:
How many rows where is_pay is 1:
where payment_id = <...> payment.is_pay = 1
And the count of the rows (without the first filter)
select count(*)
from payment
where payment_id = <...>
So I wrote this query:
select
*,
(select count(1) from payment p
where p.payment_id = o.payment_id and p.is_pay = 1) as total
from
order o
The problem is how to calculate the rows without the is_pay = 1?
I mean the "some of many"
First aggregate in payment and then join to order:
SELECT o.*, p.total_pay, p.total
FROM [order] o
LEFT JOIN (
SELECT payment_id, SUM(is_pay) total_pay, COUNT(*) total
FROM payment
GROUP BY payment_id
) p ON p.payment_id = o.payment_id;
Change LEFT to INNER join if all orders have at least 1 payment.
Also, if is_pay's data type is BIT, change SUM(is_pay) to:
SUM(CASE WHEN is_pay = 1 THEN 1 ELSE 0 END)
Use a join with conditional aggregation:
SELECT
o.payment_id,
COUNT(CASE WHEN p.is_pay = 1 THEN 1 END) AS pay_cnt,
COUNT(p.payment_id) AS all_cnt
FROM "order" o
LEFT JOIN payment p
ON o.payment_id = p.payment_id
GROUP BY
o.payment_id;
You can use a lateral join (outer apply) for this:
select o.*, p.*
from orders o outer apply
(select count(*) as num_payments,
sum(case when is_pay = 1 then 1 else 0 end) as num_payments_1
from payments p
where p.payment_id = o.payment_id
) p;
Note: Assuming that is_pay only takes on the values of 0 and 1 (which seems reasonable given the name), you can simplify this to:
select o.*, p.*
from orders o outer apply
(select count(*) as num_payments,
sum(is_pay) as num_payments_1
from payments p
where p.payment_id = o.payment_id
) p;
If you are looking for counts per payment id then use this:
select
payment.payment_id,
count(*) as total,
count(case when payment.is_pay = 1 then 1 else 0) end as total_is_pay_orders
from orders
left join payment
on orders.payment_id = payment.payment_id
group by 1

SQL. Find the customers who bought same brands and at-least 2 products in each brand

I have two tables :
Sales
columns: (Sales_id, Date, Customer_id, Product_id, Purchase_amount):
Product
columns: (Product_id, Product_Name, Brand_id,Brand_name)
I have to write a query to find the customers who bought the brands 'X' and 'Y' (both) and at least 2 products of each brand. Is the following query correct? Any recommended changes?
SELECT S.Customer_id "Customer ID"
FROM Sales S LEFT JOIN Product P
ON S.Product_id = P.Product_id
AND P.Brand_Name IN ('X','Y')
GROUP BY S.Customer_id
HAVING COUNT(DISTINCT S.Product_id)>=2 -----at least 2 products in each brand
AND COUNT(S.Customer_id) =2 ---------------customers who bought both brands
Any help will be appreciated. Thanks in advance
Use COUNT() window function to count the number of distinct brands and the number of distinct products of each brand that each customer has bought.
Then filter out the customers who haven't bought both brands and GROUP BY customer with a HAVING clause that filters out the customers who haven't bought at least 2 products of each brand.
Also your join should be an INNER join and not a LEFT join.
select t.customer_id "Customer ID"
from (
select s.customer_id,
count(distinct p.brand_id) over (partition by s.customer_id) brands_counter,
count(distinct p.product_id) over (partition by s.customer_id, p.brand_id) products_counter
from sales s inner join product p
on p.product_id = s.product_id
where p.brand_name in ('X', 'Y')
) t
where t.brands_counter = 2
group by t.customer_id
having min(t.products_counter) >= 2
Starting from your existing query, you can use the following HAVING clause:
HAVING
AND COUNT(DISTINCT CASE WHEN p.brand_name = 'X' then S.product_id end) >= 2
AND COUNT(DISTINCT CASE WHEN p.brand_name = 'Y' then S.product_id end) >= 2
This ensures that the customer bought at least two products in both brands. This implicitly guarantees that it placed ordered in both brands, so there is no need for additional logic for this.
You could also express this with MIN() and MAX():
HAVING
AND MIN(CASE WHEN p.brand_name = 'X' THEN S.product_id END)
<> MAX(CASE WHEN p.brand_name = 'X' then S.product_id end)
AND MIN(CASE WHEN p.brand_name = 'Y' THEN S.product_id END)
<> MAX(CASE WHEN p.brand_name = 'Y' then S.product_id end)
You can use two levels of aggregation:
SELECT Customer_id
FROM (SELECT S.Customer_id, S.Brand_Name, COUNT(DISTINCT S.Product_Id) as num_products
FROM Sales S LEFT JOIN
Product P
ON S.Product_id = P.Product_id
WHERE P.Brand_Name IN ('X', 'Y')
GROUP BY S.Customer_id, S.Product_Id
) s
GROUP BY Customer_Id
HAVING COUNT(*) = 2 AND MIN(num_products) >= 2;

Output of two different queries as one result in oracle

I have two different tables and I want to get these two different output in single result. Here I want to display the result of both queries in single report.
Query 1
Select name, sum(purchasqty)-sum (soldqty) as pending
from (select p.name, p.qty as purchasqty, s.qty as soldqty
from purchase p
left join sold s on p.id = s.id )
group by name;
Query 2
Select name, sum(qty) as damage
from purchase p
where con = 'c'
group by name
This will do
Select name, sum(purchasqty)-sum (soldqty) as pending, '' as damage
from (select p.name, p.qty as purchasqty, s.qty as soldqty
from purchase p
left join sold s on p.id = s.id )
group by name
union all
Select name, '' as pending, sum(qty) as damage
from purchase p
where con = 'c'
group by name
This is a little tricky because you seem to be aggregating at a level different from what you are joining on. I would recommend:
select p.name, (p.purchasqty - coalesce(ps.soldqty, 0) as pending ,
p.damage
from (select p.name, sum(qty) as purchase_qty,
sum(case when con = 'c' then qty else 0 end) as damage
from purchase p
group by p.name
) p left join
(select p.name, sum(s.qty) as soldqty
from purchase p join
sold s
on p.id = s.id
group by p.name
) ps
on ps.name = p.name;

Find total revenue generated from "Male" customers in "Electronics" category? But output should display total revenue by prod_subcat

I have joined 3 tables for getting total revenue of each sub categories under electronics category.
But in output instead of getting specific revenue for each sub category I am getting one common value(total revenue generated by electronics category) for each sub category.
select prod_subcat as subcat, sum(total_amt) as total_revenue
from transection
left join customer_details
on transection.cust_id = customer_details.customer_ID
left join [dbo].[Product_cat_info]
on [dbo].[transection].[prod_cat_code] = [dbo].[Product_cat_info].[Prod_Cat_cod]
where Gender like 'M' and prod_Cat in (
select prod_Cat
from [dbo].[Product_cat_info]
where prod_Cat like 'Electronics'
)
group by [prod_subcat]
The output which I am getting is
subcat total_revenue
Personal Appliances 5702069
Mobiles 5702069
Computers 5702069
Audio and video 5702069
Cameras 5702069
Remove this subquery in IN causing more duplicates as you have already joined this table and replace this below query first also, add prod_cat to group by
prod_Cat in (select prod_Cat from [dbo].
[Product_cat_info]
where prod_Cat like 'Electronics')
with and prod_Cat like 'Electronics'
SELECT prod_subcat as subcat,
gender,
sum(total_amt) as total_revenue
from tbl_Tran T1
left join tbl_Cust C1
on T1.cust_id = C1.customer_ID
left join Prod_cat_info P1
on T1.prod_cat_code = P1.prod_cat_code
and T1.prod_subcat_code=P1.prod_sub_cat_code
where Gender like 'M' and prod_Cat in (
select prod_Cat
from Prod_cat_info
where prod_Cat like 'Electronics'
)
group by prod_subcat,Gender
Replace tbl_tran with transaction
and tbl_cust with customer
SELECT SUM(TOTAL_AMT) AS TOTAL_AMT , prod_subcat FROM DBO.prod_INFO AS A
LEFT JOIN DBO.Transactions AS B ON A.prod_cat_code =B.prod_cat_coDE AND A.prod_sub_cat_code
=B.prod_subcat_codE
LEFT JOIN DBO.Customer AS C ON B.cust_id = C.customer_Id
WHERE PROD_CAT IN ('ELECTRONICS') AND GENDER = 'M'
GROUP BY
prod_subcat
There is no need for inline subquery in WHERE statement.

Oracle SQL - Getting Max Date with several joins

I think I may be overthinking this, but I'm having an issue when trying to find a max date with several joins in an Oracle database as well as several where clauses. I've found many examples of simple max queries, but nothing specifically like this. The query works fine if I add a line in to find all records above a specific date (there are only a handful of results returned). However, I want to automatically get the most recent date for all records from the bill table. This database has an additional table where the actual bill amount is stored, so that adds another layer.
SELECT p.purchase_id, p.account_id, b.bill_date, bp.current_amount
FROM Purchases p
JOIN Bill_Purchases bp ON p.purchase_id = bp.purchase_id
JOIN Bills b ON bp.bill_id = b.bill_id
--NEED TO GET MOST RECENT DATE FROM BILL TABLE FOR EACH BILL
WHERE p.type != 'CASH'
AND bp.amount > '100.00'
AND p.status = 'Approved'
AND p.purchase_id IN ( ... list of purchases ...);
I have tried doing subqueries with Max functions in them, but am not having any luck. Each query returns the same amount of records as the original query. How would I rearrange this query to still retrieve all of the necessary columns and where clauses while still limiting this to only the most recent purchase that was billed?
Try like below this
SELECT p.purchase_id, p.account_id, b.bill_date, bp.current_amount
FROM Purchases p
JOIN Bill_Purchases bp ON p.purchase_id = bp.purchase_id
JOIN ( SELECT bill_id, MAX(bill_date) bill_date
FROM Bills
GROUP BY bill_id
)b ON bp.bill_id = b.bill_id
WHERE p.type != 'CASH'
AND bp.amount > '100.00'
AND p.status = 'Approved'
AND p.purchase_id IN ( ... list of purchases ...);
You may want to solve this problem using Rank() function,
SELECT p.purchase_id, p.account_id, , bp.current_amount, RANK() OVER ( partition by b.bill_id order by b.bill_date) as max_bill_date
FROM Purchases p
JOIN Bill_Purchases bp ON p.purchase_id = bp.purchase_id
JOIN Bills b ON bp.bill_id = b.bill_id
--NEED TO GET MOST RECENT DATE FROM BILL TABLE FOR EACH BILL
WHERE max_bill_date = 1
AND p.type != 'CASH'
AND bp.amount > '100.00'
AND p.status = 'Approved'`enter code here`
AND p.purchase_id IN ( ... list of purchases ...);
Do either of these work for you?
WITH data as (
SELECT
p.purchase_id, p.account_id, b.bill_date, bp.current_amount,
FROM
Purchases p
INNER JOIN Bill_Purchases bp ON p.purchase_id = bp.purchase_id
INNER JOIN Bills b ON b.bill_id = bp.bill_id
WHERE p.type <> 'CASH'
AND bp.amount > '100.00'
AND p.status = 'Approved'
AND p.purchase_id IN ( ... list of purchases ...)
), most_recent as (
SELECT max(bill_date) as bill_date FROM data
)
SELECT *
FROM data
WHERE bill_date = (select bill_date from most_recent);
SELECT purchase_id, account_id, bill_date, current_amount
FROM (
SELECT
p.purchase_id, p.account_id, b.bill_date, bp.current_amount,
dense_rank() over (order by b.bill_date desc) as dr
FROM
Purchases p
INNER JOIN Bill_Purchases bp ON p.purchase_id = bp.purchase_id
INNER JOIN Bills b ON b.bill_id = bp.bill_id
WHERE p.type <> 'CASH'
AND bp.amount > '100.00'
AND p.status = 'Approved'
AND p.purchase_id IN ( ... list of purchases ...)
) data
WHERE dr = 1;
I got this working pretty much right after posting, was a stupid mistake on my part. Data for the max function needed to be joined on the account_id, not the bill_id.