Select Customers who purchased one specific Product - sql

We have two tables:
Customers:
Products:
The goal is to select [Id] and [CustomerName] of Customers who purchased Milk AND did not purchase Bread. In the case the correct query should return a customer with Id 2 (Ann).
The query which I thought of (and which is obviously incorrect) is:
select CustomerName from dbo.Customers
where Id in
(
select CustomerId from dbo.Products
where ProductName = 'Milk' and ProductName != 'Bread'
)
It returns two customers: 1 (John) and 2 (Ann).
How to rewrite the query so it would return only customer with Id 2?

You can try the query below
SELECT CustomerName
FROM dbo.Customers c
WHERE EXISTS (
SELECT 1
FROM dbo.Products
WHERE CustomerId = c.Id
AND ProductName = 'Milk'
) AND NOT EXISTS (
SELECT 1
FROM dbo.Products
WHERE CustomerId = c.Id
AND ProductName = 'Bread'
)

You don't need to use two exists, just use where clause with not exists :
select c.*
from customer c
where ProductName = 'Milk' and
not exists (select 1 from Products p where p.CustomerId = c.id and p.ProductName = 'Bread');

I am inclined to use aggregation for this. Here is one method:
select c.customerId
from dbo.Products p
where p.productName in ('Milk', 'Bread')
group by c.customerId
having sum(case when p.productName = 'Milk' then 1 else 0 end) > 0 and
sum(case when p.productName = 'Bread' then 1 else 0 end) = 0 ;
You can add the join in to get the customer name, if you really need that.
Basically, this counts the number of rows for each customer that have 'Milk'. The > 0 says there is at least one. It then counts the number of rows that have 'Bread'. The = 0 says that there are none.

SELECT P.Id ,C.Customers
FROM Customers AS C , Product AS P
WHERE (C.Id = P.CustomerId)
AND (P.ProductName = 'Milk')
AND NOT EXISTS (
SELECT 1
FROM Products
WHERE CustomerId = C.Id
AND ProductName = 'Bread'
)

If normalization is not the case, efficiency isn't either. Here you go:
select CustomerName from dbo.Customers
where
Id in (select CustomerId from dbo.Products where ProductName = 'Milk')
and Id not in (select CustomerId from dbo.Products where ProductName = 'Bread')
Ah. Just saw it could be considered a duplicate of Eric's answer. Using exists may be a little faster indeed.

Related

Multiple SQL filter on same column

How do I display the customer_id of customers who bought products A and B, but didn’t buy product C, ordered by ascending customer ID.
I tried the below code, but does not give me any result.
select customer_id, product_name from orders where customer_id = 'A' and 'product_name '= 'B'
select customer_id from orders where product_name = 'A'
intersect
select customer_id from orders where product_name = 'B'
except
select customer_id from orders where product_name = 'C'
You can use analytical function as follows:
Select * from
(select customer_id, product_name ,
Count(distinct case when product_name in ('A','B') then product_name end)
Over (partition by customer_id) as cntab ,
Count(case when product_name = 'C' then product_name end)
Over (partition by customer_id) as cntc
from orders t) t
Where cntab = 2 and cntc = 0;
One method use exists and not exists:
select o.*
from orders o
where exists (select 1
from orders o2
where o2.customer_id = o.customer_id and o2.product_name = 'A'
) and
exists (select 1
from orders o2
where o2.customer_id = o.customer_id and o2.product_name = 'B'
) and
not exists (select 1
from orders o2
where o2.customer_id = o.customer_id and o2.product_name = 'C'
)
order by customer_id;
To me, this query reads well ...
SELECT customer_id
FROM (
SELECT TableA.customer_id
FROM (
SELECT customer_id
FROM orders
WHERE product_name = 'A'
) AS TableA
INNER JOIN (
SELECT customer_id
FROM orders
WHERE product_name = 'B'
) AS TableB
ON TableA.customer_id = TableB.customer_id
) AS TableX
WHERE customer_id NOT IN (SELECT customer_id FROM orders WHERE product_name = 'C')
Explanation. Get a list of customer ids that bought products A and B with self inner join. Then, pare that list down by removing any rows where those customers bought product C.

Find the number and name of each customer that did not place an order on 10/23/2010

I'm having a slight issue with a question from my textbook "A Guide to SQL 8e"
the question is to "find number and name of each customer that did not place an order on October 23, 2010"
the query I wrote looks like this:
SELECT CustomerNum, CustomerName
FROM tblCustomer
WHERE EXISTS
(SELECT Orderdate
FROM tblOrders
Where NOT OrderDate = '10/23/2010');
the output is incorrect according to our answer key.
can someone tell me what I'm doing wrong?
Move NOT to right place:
SELECT CustomerNum, CustomerName
FROM tblCustomer as c
WHERE NOT EXISTS
(SELECT TOP 1 1
FROM tblOrders as o
Where c.CustomerNum = o.CustomerNum AND OrderDate = '10/23/2010');
Hoping i understood your question correctly.
Please check below query.
select CustomerNum, CustomerName from
(
SELECT CustomerNum, CustomerName ,
sum(case when Orderdate='10/23/2010' then 1 end) ord_on_23
FROM tblOrders
group by CustomerNum, CustomerName
)
where ord_on_23=0;
We could also phrase this query using a join:
SELECT t1.CustomerNum, t1.CustomerName
FROM tblCustomer t1
LEFT JOIN
(
SELECT t1.CustomerNum
FROM tblCustomer t1
LEFT JOIN tblOrders t2
ON t1.CustomerNum = t2.CustomerNum
GROUP BY t1.CustomerNum
HAVING SUM(CASE WHEN t2.OrderDate = '10/23/2010' THEN 1 ELSE 0 END) > 0
) t2
ON t1.CustomerNum = t2.CustomerNum
WHERE t2.CustomerNum IS NULL
The subquery identifies all customer numbers for which one or more orders on 10/23/2010 took place. The WHERE clause removes these customers. Note that customers not having placed any orders would also be retained using this query.
I would query it this way:
select distinct CustomerNum,CustomerName from tblCustomer
except
select o.CustomerNum,c.CustoemrName from tblCustomer c, tblOrders o where c.CustomerNumber=o.CustomerNumber and OrderDate='10/23/2010'

(Advanced) Sql queries on database association table

Assuming one has the following tables on an association in a database:
Customer(Id, Name, City),
Orders(Cust_Id, Prod_Id),
Product(Id, Name, Price, Country)
Taken for granted that a Customer has ordered a particular Product only once, I would like to execute the following queries:
1) Get the Customers ordered all the Products coming from Country "XY".
Is that the solution?:
Select *
from Customers c
join Orders o on c.id = o.orders.cust_id
join Product p on o.prod_id = p.id
where p.id = (Select id from Product where Product.country = "XY")
2) Get the Customers ordered Products only from the Country "XY"
What are the right queries in both cases?
1) Customers who ordered all the products of a specific country:
DECLARE #Country AS VARCHAR(100)
SET #Country = 'XY'
;WITH OrdersCTE AS
(
SELECT *
FROM Orders
WHERE Prod_Id IN (SELECT Id FROM Product WHERE Country = #Country)
)
SELECT *
FROM Customers
WHERE Id IN
(
SELECT Cust_Id
FROM OrdersCTE
GROUP BY Cust_Id
HAVING COUNT(DISTINCT Prod_Id) = (SELECT COUNT(*) FROM Product WHERE Country = #Country)
)
2) Customers who bought only products of a specific country:
DECLARE #Country AS VARCHAR(100)
SET #Country = 'XY'
SELECT C.Id,
C.Name,
C.City
FROM Customers C
Left JOIN Orders O
ON C.Id = O.Cust_Id
LEFT JOIN Product P
ON P.Id = O.Prod_Id
GROUP BY C.Id,
C.Name,
C.City
HAVING COUNT(DISTINCT Country) = 1

How filter tables in sql in group

I have two tables.
Customer | OrderItems
CustomerID CustomerName | OrderItemID OrderID CustomerID Status
1 ABC | 1 1 1 Started
2 1 1 Started
| 3 1 1 NotStarted
Now I want to get the record of all the customer where the status of orderItems is Completed. Means in this case the order is incomplete.
so If I want to get the status of incomplete orders it should give me for customer 1 is order1.
even though the items are started of 1st two but still I want to get that Incomplete.
Not sure if I understood you correctly, but this should do the job:
select distinct c.CustomerId, oi.OrderId
from Customer c
inner join OrderItems oi on c.CustomerID = oi.CustomerID
where c.OrderId not in (select o.OrderId from OrderItems o where o.Status <> 'Started')
select OrderID, CustomerID,
SUM(case Status when 'Started' Then 0 Else 1) NS
from OrderItems
having NS > 0;
If you only want the customers with NO INCOMPLETE ORDERS, then this should do the trick:
SELECT C.CustomerID, C.CustomerName
FROM Customer AS C
WHERE (((C.CustomerID) Not In
(SELECT DISTINCT [O].CustomerID
FROM OrderItems AS O
WHERE ((([O].Status)="NotStarted")))));
I miss something though: I think there should be an ORDER table with information regarding the order. Data in your OrderItems table would be inconsistent if you changed the customerID for one record let's say for OrderItemID = 3 the clientID is 2. Are there two different clients for the same order? I suppose that you didn't handle all the information and that there is an Order table.
Regards,
select OrderID, CustomerID,
SUM(case Status when 'NotStarted' Then 1 Else 0) NS
from OrderItems
group by OrderID, CustomerID
having NS > 0;
select c.customerId,c.Customername,O.OrderID, from Customer c left join OrderItem O on C.customerID = o.OrderID where o.Status = 'started'

Inner join on the same columns

Customer Table
----------------------
CustomerName
Peter
Sam
Sales Table
-----------------------
ProductName Customer
Cloth Peter
Mobile Peter
Cloth Sam
Laptop Sam
Expected result
Customer
Sam
I want result as customer who buyed 'Cloths' but not 'Mobile', i tried
select c.CustomerName from Customer c inner join Sales s1 on (s1.customer = c.customername and s1.productname = 'Cloth') inner join Sales s2 on (s2.customer = c.customername and s2.productname != 'Mobile');
but it always return both entries
Customer
Peter
Sam
Sam
A correlated subquery would be better as you're not interested in getting multiple rows for customers who bought cloths multiple times.
select
c.CustomerName
from
Customer c
where
exists (
select null
from sales
where sales.customer = c.customername and
s1.productname = 'Cloth') and
not exists (
select null
from sales
where sales.customer = c.customername and
s1.productname = 'Mobile');
You can use the Oracle MINUS operator to make it simple;
SELECT "Customer" FROM SalesTable WHERE "ProductName"='Cloth'
MINUS
SELECT "Customer" FROM SalesTable WHERE "ProductName"='Mobile'
Another slightly more complex option is a LEFT JOIN;
SELECT DISTINCT s1."Customer"
FROM SalesTable s1
LEFT JOIN SalesTable s2
ON s1."Customer" = s2."Customer"
AND s2."ProductName" = 'Mobile'
WHERE s1."ProductName" = 'Cloth'
AND s2."Customer" IS NULL;
An SQLfiddle to test both with.
This is an example of a "set-within-sets" query. I think a good approach is to use aggregation:
select s.Customer
from Sales s
group by s.Customer
having sum(case when s.ProductName = 'Cloth' then 1 else 0 end) > 0 and -- has cloth
sum(case when s.ProductName = 'Mobile' then 1 else 0 end) = 0 -- does not have mobile
I prefer putting the logic in the having clause, because it is quite flexible. You can add additional conditions quite easily for other products.
try this:
select c.CustomerName
from Customer c
where exists(select 1 from sales s1 where s1.customer = c.customername and s1.productname = 'Cloth')
and not exists (select 1 from sales s2 where s2.customer = c.customername and s2.productname = 'Mobile')
first of all you should review your database schema.
You never do inner join without an id.
Try to create your tables using relationships. Like this:
create table customer
(
id_customer int not null,
ds_customername varchar(80) null,
primary key (id_customer)
)
create table products
(
id_product int not null,
ds_product varchar(100) null,
primary key (id_product)
)
create table sales
(
id_sales int not null,
id_product int not null,
id_customer int not null,
foreign key (id_product) references products (id_product),
foreign key (id_customer) references customer (id_customer)
)
select customer.ds_customername
from customer
inner join sales (customer.id_customer = sales.id_customer)
inner join products (products.id_product = sales.id_product)
where products.ds_product = 'Cloth'
Ok,
if you can't do this, you can do your query (in an old way) this:
select Customer.customername
from Customer
inner join on (customer.customername = sales.customer)
where sales.productname = 'Cloth'
I hope help you.
Hugs,
Vin.