Struggling with MySQL NOT EXISTS - mysql

I am trying to query our database to pull out customer data. The crucial parts are:
Pull out all customers/orders that haven't bought product_a.
This will list out customers/ orders who have purchased, product_b, product_c and product_d.
But it needs to make sure that the customer hasn't EVER purchased product_a. I need them excluded.
Is this the right way to go about the NOT EXISTS? I still feel like it's including some records that bought product_a.
SELECT
*
FROM
orders
JOIN customers AS cus ON orders.CustomerNumber = cus.CustomerNumber
WHERE
product != 'product_a'
OR (
HomeTelephone = ''
AND MobileTelephone != ''
)
AND NOT EXISTS (
SELECT
OrderNumber
FROM
orders AS o
JOIN customers AS c ON o.CustomerNumber = c.CustomerNumber
WHERE
c.EmailAddress = cus.EmailAddress
AND Product = 'product_a'
AND completed = 1
)
ORDER BY
orderdate
Without the NOT EXISTS statement, a customer record could be included even if they have bought product_a separately right?

Your Not Exists is a little off and where product != 'product_a' is redundant.
SELECT
*
FROM
orders AS o1
JOIN customers AS cus ON o1.CustomerNumber = cus.CustomerNumber
WHERE
cus.HomeTelephone = ''
AND cus.MobileTelephone != ''
AND NOT EXISTS (
SELECT
1
FROM
orders o2
WHERE
o2.CustomerNumber = cus.CustomerNumber
AND Product = 'product_a'
AND completed = 1
)
ORDER BY
o1.orderdate
This will give you the customers with their orders. Based on your description though, if you were wanting just the customer information, you could exclude the join to orders in the first part of your query, and use the Not Exist to determine if that customer purchased product_a or not.
SELECT
*
FROM
customers cus
WHERE
HomeTelephone = ''
AND MobileTelephone != ''
AND NOT EXISTS (
SELECT
1
FROM
orders o
WHERE
o.CustomerNumber = cus.CustomerNumber
AND Product = 'product_a'
AND completed = 1
)

SELECT c.*
FROM customers c
LEFT
JOIN orders o
ON o.CustomerNumber = c.CustomerNumber
AND o.product = 'product_a'
WHERE o.CustomerNumber IS NULL

Related

Filtering query by passing idProduct, quantity

I want to create an Order_List with different ITEMS from a table called: Products
Inside table Products there are duplicates because a product is sell in different supermarkets, with differents prices.
I want the user to enter the desired product inside List table and get the lowest price. DO NOTE that every user has a different zipcode and every product belongs to a different supermarket with a different zipcode. The idea is to get the lowest price ONLY if the item has the same customer zipcode.Also, the quantity that was inserted in List table must be validated against Stock table.
This is what i have:
http://sqlfiddle.com/#!9/f9f73a/1
This is my example:
image
This is what i tried so far:
select p.idProduct, name, price, min(price)
from product p
inner join market m
on p.idMarket = m.idMarket
inner join stock s
on p.idProduct = s.idProduct
inner join list l
on p.idProduct = l.idProduct
where p.idProduct = 14
and exists (select 1
from stock s
where p.idProduct = s.idProduct
and l.quantity <= s.quantity)
group by p.idProduct, name, price
Could you please help me to solve this mess?
select p.idProduct, p.name, p.price, min(p.price)
from product p
inner join market m
on p.idMarket = m.idMarket
inner join stock s
on p.idProduct = s.idProduct
inner join list l
on p.idProduct = l.idProduct
where p.idProduct = 14
and exists (select 1
from stock s1
where p.idProduct = s1.idProduct
and l.quantity <= s1.quantity)
and p.price = (select min(price) from product p2 where p2.idProduct = p.idProduct)
group by p.idProduct, p.name, p.price

How to select records where all joined records aren't a match for criteria

I've a setup with the following tables (using MySQL):
orders, which have many:
a join table order_items, which have one from the:
products table
I've written a query to select orders where all their products are of a certain type:
SELECT orders.* FROM orders
INNER JOIN order_items ON order_items.order_id = orders.id
INNER JOIN products ON products.id = order_items.product_id
WHERE products.type = 'FooProduct'
AND (
NOT EXISTS (
SELECT null
FROM products
INNER JOIN order_items ON order_items.product_id = products.id
WHERE order_items.order_id = orders.id
AND products.type != 'FooProduct'
)
)
I run similar a couple of times: firstly to get orders comprised of all FooProducts, and again to get orders with all BarProducts.
My sticking point has been generating a third query to get all other orders, i.e. where all their products' types are not exclusively FooProducts, or exclusively BarProducts (aka a mix of the two, or other product types).
So, my question is how can I get all records where all product types aren't exclusively FooProducts or exclusively BarProduct.
Here's a little example data, from which I'd like to return the orders with the IDs 3 and 4:
- orders
id
1
2
3
4
-- order_items
id order_id product_id
1 1 1
2 1 1
3 2 2
4 2 2
5 3 3
6 3 4
7 4 1
8 4 2
-- products
id type
1 'FooProduct'
2 'BarProduct'
3 'OtherProduct'
4 'YetAnotherProduct'
I've attempted this, awfully so placing as a subtext, with the following in place of the existing AND (even the syntax is way off):
NOT HAVING COUNT(order_items.*) = (
SELECT null
FROM products
INNER JOIN order_items ON order_items.product_id = products.id
WHERE order_items.order_id = orders.id
AND products.type IN ('FooProduct', 'BarProduct')
)
Instead of using Correlated subqueries, you can use Having and conditional aggregation function based filtering.
products.type IN ('FooProduct', 'BarProduct') will return 0 if a product type is none of them. We can use Sum() function on it, for further filtering.
Try the following instead:
SELECT orders.order_id
FROM orders
INNER JOIN order_items ON order_items.order_id = orders.id
INNER JOIN products ON products.id = order_items.product_id
GROUP BY orders.order_id
HAVING SUM(products.type IN ('FooProduct', 'BarProduct')) < COUNT(*)
For the case, where you are looking for orders which has only FooProduct type, you can use the following instead:
SELECT orders.order_id
FROM orders
INNER JOIN order_items ON order_items.order_id = orders.id
INNER JOIN products ON products.id = order_items.product_id
GROUP BY orders.order_id
HAVING SUM(products.type <> 'FooProduct') = 0
Another possible approach is:
SELECT orders.order_id
FROM orders
INNER JOIN order_items ON order_items.order_id = orders.id
INNER JOIN products ON products.id = order_items.product_id
GROUP BY orders.order_id
HAVING SUM(products.type = 'FooProduct') = COUNT(*)
You can use aggregation and a having clause for this:
SELECT o.*
FROM orders o INNER JOIN
order_items oi
ON oi.order_id = o.id INNER JOIN
products p
ON p.id = oi.product_id
GROUP BY o.id -- OK assuming `id` is the primary key
HAVING SUM(p.type NOT IN ('FooProduct', 'BarProduct')) > 0; -- at least one other product
Actually, that is not quite right. This gets orders that have some other product, but it doesn't pick up orders that are mixes only of foo and bar. I think this gets the others:
HAVING SUM(p.type = 'FooProduct') < COUNT(*) AND
SUM(p.type = 'BarProduct') < COUNT(*)
This is a basic solution, not so efficient but easy:
SELECT * FROM orders WHERE id NOT IN (
SELECT orders.id FROM orders
INNER JOIN order_items ON order_items.order_id = orders.id
INNER JOIN products ON products.id = order_items.product_id
WHERE products.type = 'FooProduct'
AND (
NOT EXISTS (
SELECT null
FROM products
INNER JOIN order_items ON order_items.product_id = products.id
WHERE order_items.order_id = orders.id
AND products.type != 'FooProduct'
)
)
) AND id NOT IN (
SELECT orders.id FROM orders
INNER JOIN order_items ON order_items.order_id = orders.id
INNER JOIN products ON products.id = order_items.product_id
WHERE products.type = 'BarProduct'
AND (
NOT EXISTS (
SELECT null
FROM products
INNER JOIN order_items ON order_items.product_id = products.id
WHERE order_items.order_id = orders.id
AND products.type != 'BarProduct'
)
)
)
I would suggest using count(distinct) in joined subselect like this:
SELECT orders.*
FROM orders
inner join (
SELECT orderid, max(products.type) as products_type
FROM order_items
INNER JOIN products ON products.id = order_items.product_id
GROUP BY orderid
-- distinct count of different products = 1
-- -> all order items are for the same product type
HAVING COUNT(distinct products.type ) = 1
-- alternative is:
-- min(products.type )=max(products.type )
) as tmp on tmp.orderid=orders.orderid
WHERE 1=1
-- if you want only single type product orders for some specific product
and tmp.products_type = 'FooProduct'
This is a relational division problem.
One solution to find orders where all products are of a given type is this:
SELECT *
FROM orders
INNER JOIN order_items ON order_items.order_id = orders.id
INNER JOIN products ON products.id = order_items.product_id
WHERE orders.id IN (
SELECT order_items.order_id
FROM order_items
INNER JOIN products ON products.id = order_items.product_id
GROUP BY order_items.order_id
HAVING COUNT(CASE WHEN products.type = 'FooProduct' THEN 1 END) = COUNT(*)
)
Tweak the above just a little to find orders where all products are from a list of given types is this:
HAVING COUNT(CASE WHEN products.type IN ('FooProduct', 'BarProduct') THEN 1 END) = COUNT(*)
And to find all orders where all products match all types from a given list is this:
HAVING COUNT(CASE WHEN products.type IN ('FooProduct', 'BarProduct') THEN 1 END) = COUNT(*)
AND COUNT(DISTINCT products.type) = 2
DB Fiddle with tests

UNION MySQL, Check if its bold

Hello guys i have that SQL:
SELECT p.* FROM products p WHERE required_product_id IS NULL
UNION ALL
SELECT p.* FROM products p, orders o WHERE p.required_product_id = o.product_id
AND o.user_id = 1
UNION DISTINCT
SELECT p.`*` FROM products p, orders o WHERE p.id NOT IN (SELECT product_id FROM orders WHERE product_id = p.id AND o.user_id = 1)
AND p.max_buys = 1;
This query first checking if item is purchased and show next item! i want to check if user is purchased that product to return only that product that user is not bought it
table structure = Products: http://prntscr.com/k6ogp4 ,Orders: http://prntscr.com/k6ogrz
max_buys colum on products (if it 1 it can buy it once , if its 0 it can be buyed many times)
In your case I prefer to have a flexible query to manage requirements and also I think that UNION is not required in this case [if your description is complete].
SELECT w.* from (
SELECT
(SELECT count(o.product_id) FROM orders o WHERE o.product_id = p.id AND o.user_id = 1) bought_count,
(SELECT count(q.product_id) FROM orders q WHERE q.product_id = p.required_product_id AND q.user_id = 1) order_depend,
p.*
FROM products p ) w
where
(order_depend>0 or required_product_id is null) and -- unlock order depended products
(max_buys=0 or -- can buy more than once
bought_count=0) -- or not bought yet
order by
order_depend desc, -- dependent products to ordered products in first level
bought_count asc, -- not bought products in second level
recommended desc -- recommended products in third level
You can also manage any other order according to your requirement.

Rewriting a query that has two sub queries using no sub queries

Given the database schema:
Part( PID, PName, Producer, Year, Price)
Customer( CID, CName, Province)
Supply(SID, PID, CID, Quantity, Amount, Date)
And the query:
Select cname, Province
From Customer c
Where exists (
Select *
from Supply s
join Part p on p.pId = s.pId
Where CId = c.CId
and p.Producer = 'Apple'
)
and Not exists (
Select *
from Supply n
join Part nap on nap.pId = n.pId
Where CId = c.CId
and nap.Producer != 'Apple'
)
How would I go about rewriting this query without the two sub queries?
You can use the LEFT JOIN/NULL pattern to find customers who haven't bought any non-Apple products. Then you can do this all with just joins. You'll have to join with Supply and Parts twice, once for finding Apple products, then again for excluding non-Apple products.
SELECT distinct c.name, c.province
FROM Customer AS c
JOIN Supply AS s1 ON s1.cid = c.cid
JOIN Parts AS p1 ON p1.pid = s1.pid
LEFT JOIN Supply AS s2 ON s2.cid = c.cid
LEFT JOIN Parts AS p2 ON p2.pid = s2.pid AND p2.producer != 'Apple'
WHERE p1.producer = 'Apple' AND p2.pid IS NULL
Notice that in the LEFT JOIN you put restrictions of the second table in the ON clause, not the WHERE clause. See Return row only if value doesn't exist for more about this part of the query.
You want customer who only bought Apple products?
One possible solution is based on conditional aggregation:
Select c.cname, c.Province
From Customer c
join
( -- this is not a Subquery, it's a Derived Table
Select s.CId -- assuming there's a CId in Supply
from Supply s
join Part p
on p.pId = s.pId
group by s.CId
-- when there's any other supplier this will return 1
having max(case when p.Producer = 'Apple' then 0 else 1 end) = 0
) as p
on p.CId = c.CId

Update Query with Correlated Subquery

I'm trying to convert a Foxpro application into .NET. As part of that conversion I'm converting the data from DBF tables to Sql server.
I need to come up with a couple new fields in the Customer table based on the Orders table, FirstOrder and LastOrder.
I just can't seem to muddle through how to do this in TSql. I know how I'd do it in Foxpro, and I could actually still do it there if I had to, but I know I need to learn how to do this in Sql.
Here is the basic structure.
Customer Table has an Id, then the FirstOrder and LastOrder fields I need updated.
Order Table has OrderDate, but here is the real curve. The Customer Id can exist in 5 different fields inside the Order: ShipperId, PickupId, ConsigneeId, DeliveryId, or BillingId.
So something like:
UPDATE customers
SET FirstOrderDate =
(Select MIN(OrderDate)
FROM Orders o
WHERE o.ShipperId = Customers.Id or
o.PickupId = Customers.Id or
o.ConsigneeId = Customers.Id or
o.DeliveryId = Customers.Id or
o.BillingId = Customers.Id)
Just can't seem to find out how to tie the subquery with the main update query.
Thanks,
-Sid
EDIT:
Here's the SELECT that's working based on MarkD's suggestion:
Select C.Id,Min(o.OrderDate) as firstorder, MAX(o.OrderDate) as lastorder
from Customers C
JOIN Orders o
on o.ShipperId = C.Id or
o.PickupId = C.Id or
o.ConsigneeId = C.Id or
o.DeliveryId = C.Id or
o.BillingId = C.Id
GROUP BY C.Id
So now do I use this as a subquery or cursor to post back to the Customers table?
Although I think the JOIN criteria is highly unlikely, it looks like you're trying to do this?
EDIT: I've modified the JOIN criteria but this is what you're after.
Grouping By columns that are OR'd is odd.
;WITH MinOrderDates AS
(
SELECT CustID
,OrderDate = MIN(OrderDate)
FROM Orders
GROUP BY CustID
)
UPDATE C
SET FirstOrderDate = MIN(O.OrderDate)
FROM Customers C
JOIN MinOrderDates O ON C.Id = O.CustID
This is what your query would look like with the ORs
;WITH MinOrderDates AS
(
SELECT ShipperId
,PickupId
,ConsigneeId
,DeliveryId
.BillingId
,OrderDate = MIN(OrderDate)
FROM Orders
GROUP BY ShipperId
,PickupId
,ConsigneeId
,DeliveryId
.BillingId
)
UPDATE C
SET FirstOrderDate = MIN(O.OrderDate)
FROM Customers C
JOIN MinOrderDates O ON o.ShipperId = C.Id or
o.PickupId = C.Id or
o.ConsigneeId = C.Id or
o.DeliveryId = C.Id or
o.BillingId = C.Id
EDIT: Though I am having a hard time finding fault with your posted syntax.
Try this
UPDATE customers
SET FirstOrderDate =
(Select MIN(OrderDate)
FROM Orders
WHERE ShipperId = Customers.Id or
PickupId = Customers.Id or
ConsigneeId = Customers.Id or
DeliveryId = Customers.Id or
BillingId = Customers.Id)