MySQL JOIN, GROUP BY, ORDER BY - mysql

I have a table of products:
CREATE TABLE products (`id` INT);
And a table of images for those products:
CREATE TABLE images (`id` INT, `product_id` INT, `default` TINYINT(1));
I need to select all the products, and join the images table so that images with (default = 1) will be preferred, and if a product has no images with (default = 1), an image with (default = 0) will be shown in its place.
Here's an image showing what I'm looking for:
Right now I have this query:
SELECT p.id, i.id
FROM products AS p
LEFT JOIN (
SELECT product_id, url
FROM images
ORDER BY default
) AS i
ON p.id = i.product_id
GROUP BY p.id
ORDER BY p.name
Which doesn't prioritize "default" images. The subquery doesn't seem to do anything.

SQLFiddle demo
select products.id,
coalesce(t1.mid,t2.mid) as image_id
from products
left join (select min(id) mid,product_id
from images where `default`=1
group by product_id ) t1
on products.id=t1.product_id
left join (select min(id) mid,product_id
from images where `default`=0
group by product_id ) t2
on products.id=t2.product_id

Looks like I was just missing a 'DESC' in the subquery's ORDER BY
:\

If you want to show only one image for product, then try this query -
SELECT p.*, i.* FROM products p
JOIN (SELECT * FROM images ORDER BY `default` DESC) i
ON p.id = i.product_id
GROUP BY p.id

Try this:
SELECT
p.id productid,
IFNULL(i1.id, i2.id)
FROM products AS p
LEFT JOIN images AS i1 ON p.id = i1.product_id
AND i1.`Default` = 1
LEFT JOIN images AS i2 ON p.id = i2.product_id
AND i2.`Default` = 0
GROUP BY p.id;

Related

MySQL Left join cannot remove duplicates in a single query

I have 2 tables:
products:
- id
- name
product_images:
- id
- image
- product_id
I'm doing the following query:
SELECT p.*, i.image
FROM products p
LEFT JOIN product_images i
ON p.id = i.product_id`
ORDER BY created_at DESC
However if a product has a few images, then this product row is duplicated. How can i remove these duplicates, by showing only the 1st match in a pair of p.id = i.product_id
For this dataset, simple aggregation should do it:
SELECT p.*, min(i.image)
FROM products p
LEFT JOIN product_images i ON p.id = i.product_id
GROUP BY <enumerate all columns from products here>
ORDER BY created_at DESC
LIMIT ${limit}
If you want more colums from product_images, then you can also filter with a correlated subquery; assuming that product_images has primary key id, that would look like:
SELECT p.*, i.image
FROM products p
LEFT JOIN product_images i
ON i.id = (SELECT MIN(i1.id) FROM product_images i1 WHERE i1.product_id = p.id)
ORDER BY created_at DESC
LIMIT ${limit}
If image's data type is varchar or int then instead of joining the table product_images, join to the MIN of the images of each product:
SELECT p.*, i.image
FROM products p
LEFT JOIN (
SELECT product_id, MIN(image) image
FROM product_images
GROUP BY product_id
) i
ON p.id = i.product_id
ORDER BY created_at DESC
LIMIT ${limit}

Simple Query, Huge tables

I have 2 tables:
Products ( 53k rows )
Product Categories ( 170k rows )
I'm trying to find uncategorized products with this query:
SELECT * FROM `jp_harley_products`
WHERE product_id NOT IN
(SELECT p_cat_product_id
FROM jp_harley_product_cats )
also tried this:
SELECT p.product_id,pc.p_cat_product_id
FROM `jp_harley_products` p
LEFT JOIN `jp_harley_product_cats` pc on pc.p_cat_product_id = p.product_id
My PHPmyAdmin can't handle this, as it doesn't show any results.
In harley_product_cats I have columns like: Product ID, Cat ID, so basically I want to get these products which doesn't have any relation in harley_product_cats
Can anyone help me with this?
You need to include a IS NULL for the LEFT JOIN one to work.
SELECT p.product_id, pc.p_cat_product_id
FROM `jp_harley_products` p
LEFT JOIN `jp_harley_product_cats` pc on pc.p_cat_product_id = p.product_id
WHERE p.product_id IS NULL
Or try using WHERE NOT EXISTS
SELECT p.product_id, pc.p_cat_product_id
FROM `jp_harley_products` p
WHERE NOT EXISTS (SELECT 1 FROM jp_harley_product_cats pc WHERE p.product_id = pc.p_cat_product_id );

Select from 3 tables with two order by before two group by

I try to get a list of products with each newest and lowest offer price
Table product:
id | name
Table offer:
id | product_id | price | created | dealer_id
Table invalids:
id | offer_id | status
I have tried:
SELECT * FROM product INNER JOIN
(
SELECT offer.product_id , offer.price
FROM offer
LEFT JOIN invalids
ON offer.id = invalids.offer_id
WHERE invalids.id IS NULL
GROUP BY offer.dealer_id
ORDER BY offer.created DESC
) o
ON o.product_id = product.id
ORDER BY product.name
I have tried an sqlfiddle http://sqlfiddle.com/#!9/32658/3 with this offer values:
(`id`, `price`, `dealer_id`, `product_id`, `created`)
(1,12.60,1,1,'2015-05-17 08:44:45'),
(2,13.00,1,1,'2015-08-17 08:44:45'),
(3,20.00,1,1,'2015-08-17 08:45:30'),
(4,10.00,1,1,'2015-08-17 08:45:46'),
(5,4.00,2,1,'2015-05-17 08:44:11'),
(6,11.00,2,1,'2015-08-17 08:44:46'),
(7,5.00,2,1,'2015-08-17 08:45:31'),
(9,110.00,2,2,'2015-08-17 08:46:58'),
(10,11.00,2,2,'2015-08-17 08:47:12');
Expected value for product ID 1 is offer ID 7 with price 5.
These steps I think I must realize:
Order offers by created and group by dealer_id to get newest entries
Take result from step 1 and order it by price to get smallest price.
Make this for all products
Maybe I must use a second SELECT FROM offer with GROUP BY and ORDER BY but how do I get I the product_id from the first (outer) select?
Well I would start by getting the latest date for each product offer like this:
SELECT product_id, MAX(created) AS latestOffer
FROM offer
GROUP BY product_id;
Once you have that, you can join it to the original table to get that offer:
SELECT o.*
FROM offer o
JOIN(
SELECT product_id, MAX(created) AS latestOffer
FROM offer
GROUP BY product_id) tmp ON tmp.product_id = o.product_id AND tmp.latestOffer = o.created;
Here is an SQL Fiddle example.
This query should help you:
SELECT *
FROM product
JOIN (
SELECT product_id, min(price) as minPrice, max(created) as newestOffer
FROM offer
WHERE id NOT IN (SELECT offer_id FROM invalids)
GROUP BY 1
) as b
ON product.id = b.product_id
A shot in the dark based on what I understand you to be after...
lots of nested subqueries.. keep thinking there's got to be a better way...
SELECT OO.ID, OO.Price, OO.Dealer_Id, OO.Product_ID, OO.created, P.name
FROM Offer OO
INNER JOIN (
SELECT Min(Price) as MinP
FROM offer O
INNER JOIN (
SELECT max(OI.created) as LatestOffer, OI.Dealer_ID, OI.Product_ID
FROM Offer OI
LEFT JOIN invalids I
on OI.Id = I.offer_Id
WHERE I.ID is null
GROUP BY OI.Dealer_Id, OI.Product_Id
) B
on O.Dealer_Id = B.Dealer_Id
and O.Product_Id = B.Product_Id
and O.Created = B.LatestOffer
) Z
on OO.Price = Z.MinP
INNER JOIN product P
on P.ID = OO.Product_ID
SQL FIDDLE

mysql inner join statement error

I am getting the following error:
1052 - Column 'product_id' in field list is ambiguous
When I run the following:
SELECT `product_id`, `product_name`
FROM `products`
INNER JOIN `products_has_product_category`
ON `products.product_id` = `products_has_product_category.product_id`
AND `products_has_product_category.category_id` = 1
ORDER BY `products.product_name`
My PRODUCTS table has
product_id, product_name, etc
My products_has_product_category table has
product_id, category_id
This is my first try at a join, so I appreciate the help!
You need to specify which table the product_id comes from. Since the product_id is in both tables, when you SELECT it you need to specify which table you want the value from. With a table alias:
SELECT p.product_id, p.product_name
FROM `products` p
INNER JOIN `products_has_product_category` pc
ON p.product_id = pc.product_id
AND pc.category_id = 1
ORDER BY p.product_name
Without table aliases:
SELECT `products`.`product_id`, `products`.`product_name`
FROM `products`
INNER JOIN `products_has_product_category`
ON `products.product_id` = `products_has_product_category.product_id`
AND `products_has_product_category.category_id` = 1
ORDER BY `products.product_name`
If I understand your intention correctly, you probably meant WHERE rather than AND:
SELECT `products`.`product_id`, `products`.`product_name`
FROM `products`
INNER JOIN `products_has_product_category`
ON `products.product_id` = `products_has_product_category.product_id`
WHERE `products_has_product_category.category_id` = 1
ORDER BY `products.product_name`

MySQL LEFT JOIN, GROUP BY and ORDER BY not working as required

I have a table
'products' => ('product_id', 'name', 'description')
and a table
'product_price' => ('product_price_id', 'product_id', 'price', 'date_updated')
I want to perform a query something like
SELECT `p`.*, `pp`.`price`
FROM `products` `p`
LEFT JOIN `product_price` `pp` ON `pp`.`product_id` = `p`.`product_id`
GROUP BY `p`.`product_id`
ORDER BY `pp`.`date_updated` DESC
As you can probably guess the price changes often and I need to pull out the latest one. The trouble is I cannot work out how to order the LEFT JOINed table. I tried using some of the GROUP BY functions like MAX() but that would only pull out the column not the row.
Thanks.
It appears that it is impossible to use an ORDER BY on a GROUP BY summarisation. My fundamental logic is flawed. I will need to run the following subquery.
SELECT `p`.*, `pp`.`price` FROM `products` `p`
LEFT JOIN (
SELECT `price` FROM `product_price` ORDER BY `date_updated` DESC
) `pp`
ON `p`.`product_id` = `pp`.`product_id`
GROUP BY `p`.`product_id`;
This will take a performance hit but as it is the same subquery for each row it shouldn't be too bad.
You need to set aliases properly I think and also set what you are joining on:
SELECT p.*, pp.price
FROM products AS p
LEFT JOIN product_price AS pp
ON pp.product_id = p.product_id
GROUP BY p.product_id
ORDER BY pp.date_updated DESC
This will give you the last updated price:
select
p.*, pp.price
from
products p,
-- left join this if products may not have an entry in prodcuts_price
-- and you would like to see a null price with the product
join
(
select
product_price_id,
max(date_updated)
from products_price
group by product_price_id
) as pp_max
on p.product_id = pp.product_id
join products_price pp on
pp_max.prodcuts_price_id = pp.products_price_id
Mysqlism:
SELECT p.*, MAX(pp.date_updated), pp.price
FROM products p
LEFT JOIN product_price pp ON pp.product_id = p.product_id
GROUP BY p.product_id
Will work on some RDBMS:
SELECT p.*, pp.date_updated, pp.price
FROM products p
LEFT JOIN product_price pp ON pp.product_id = p.product_id
WHERE (p.product_id, pp.date_updated)
in (select product_id, max(date_updated)
from product_price
group by product_id)
Will work on most RDBMS:
SELECT p.*, pp.date_updated, pp.price
FROM products p
LEFT JOIN product_price pp ON pp.product_id = p.product_id
WHERE EXISTS
(
select null -- inspired by Linq-to-SQL style :-)
from product_price
WHERE product_id = p.product_id
group by product_id
HAVING max(date_updated) = pp.date_updated
)
Will work on all RDBMS:
SELECT p.*, pp.date_updated, pp.price
FROM products p
LEFT JOIN product_price pp ON pp.product_id = p.product_id
LEFT JOIN
(
select product_id, max(date_updated) as recent
from product_price
group by product_id
) AS latest
ON latest.product_id = p.product_id AND latest.recent = pp.date_updated
And if nate c's code intent is to just get one row from product_price, no need to table-derive (i.e. join (select product_price_id, max(date_updated) from products_price) as pp_max), he might as well just simplify(i.e. no need to use the product_price_id surrogate primary key) it like the following:
SELECT p.*, pp.date_updated, pp.price
FROM products p
LEFT JOIN product_price pp ON pp.product_id = p.product_id
WHERE pp.date_updated = (select max(date_updated) from product_price)