0

Im trying to get a list of products from our database, grouped on pattern ,and brand and using MIN() to get the lowest price of each pattern, Im using the query builder in codeIgniter 3 to build the following query which almost works as needed, except that the product ID does not match the lowest priced product, so when you click on a product rather than it going to the lowest priced product, it will go to another priced product albeit from the same brand and pattern.

I know I need to use some kind of subquery, but everything i've tried so far seems to not give the correct number of products back? im just not sure how to apply it to MYSQL or codeIgniter 3, Any help would be very much appreciated. Im currently testing it in phpmyadmin, so an MYSQL example would be fine at this stage.

Thank you

 SELECT  
   `id` ,  `title` ,  `image` ,  `category` , 
    MIN( price ) AS  `price` ,  `rrp` , `product_to_category`.`product_id` ,    
    LEFT( short_description, 140 ) AS description,  
   `measurements` ,  `colour_names` ,  `colour_values` , `brand` ,  `pattern` 
FROM  `products` 
JOIN  `product_to_category` ON  `id` =  product_to_category`.`product_id` 
LEFT JOIN  `product_short_descriptions` ON  `product_short_descriptions`.`product_id` =  `id` 
WHERE  `category_id` =2 AND  `status` =  '1' AND  `price` >=  '102.30' AND  `price` <=  '804.10' 
GROUP BY  `pattern` ,  `brand` 
ORDER BY  `sort_order` ,  `price` ASC  
LIMIT 40

More Information

All data comes from the products table with the exception of the description which comes from the product_short_description table, and category_id which comes from the product_to_category table.

those two tables are joined by product_id to products.id

Here I can get the data I want, without the Joins:-

SELECT id, pattern, price
FROM products p1
WHERE price = ( 
SELECT MIN( price ) 
FROM products AS p2
WHERE p2.pattern = p1.pattern ) 
AND  `status` =1
AND material =713

Note: material is added to substitute the category_id just for this example, as category 2 contains all products with material of 713.

The question is how do I add my 2 Joins to this and keep the results the same, as when I try and add my joins, and other Where clauses but keep the same results, as when I try it, I end up with just 2 results instead of the expected 12, so im guessing im not adding my joins correctly.

SELECT `id`, `title`, `image`, `category`, price, `rrp`, `product_to_category`.`product_id`, LEFT(short_description, 140) as description, `measurements`, `colour_names`, `colour_values`, `brand`, `pattern`
FROM `products` p1
JOIN `product_to_category` ON `id` = `product_to_category`.`product_id`
JOIN `product_short_descriptions` ON `product_short_descriptions`.`product_id` = `id`
WHERE `category_id` = 2
AND price = ( 
SELECT MIN( price ) 
FROM products AS p2
WHERE p2.pattern = p1.pattern ) 
AND `status` = '1'
AND `price` >= '102.30'
AND `price` <= '804.10'
GROUP BY `pattern`, `brand`
ORDER BY `sort_order`, `price` ASC
 LIMIT 40 

Above only shows 2 results instead of 12..

Edit: I can see now why there is only 2 results, as the inner select is not looking in category_id of 2 , So when I thought i was looking at 12 results of correct data, I was infact only looking at 2, the other ten rows were coming from outside of category 2 so when I was saying only show category 2, this is why I was only seeing two results, adding a join within this select does solve the issue, but takes a massive performance hit so is unusable.

How can the following query be rewritten for best performance? when categories can have upto 8000+ products

  SELECT  `id` ,  `title` ,  `image` ,  `category` , price,  `rrp` ,  `product_to_category`.`product_id` , LEFT( short_description, 140 ) AS description,  `measurements` ,  `colour_names` ,  `colour_values` ,  `brand` ,  `pattern` 
FROM  `products` p1
JOIN  `product_to_category` ON  `id` =  `product_to_category`.`product_id` 
JOIN  `product_short_descriptions` ON  `product_short_descriptions`.`product_id` =  `id` 
WHERE  `category_id` =2
AND price = ( 
SELECT MIN( price ) 
FROM products AS p2
JOIN product_to_category ptc ON id = ptc.product_id
WHERE p2.pattern = p1.pattern
AND category_id =2 ) 
AND  `status` =  '1'
GROUP BY  `pattern` ,  `brand` 
ORDER BY  `sort_order` ,  `price` ASC 
LIMIT 40
Paul 501
  • 707
  • 7
  • 14
  • 2
    show us sample data and expected output. Please read [**How-to-Ask**](http://stackoverflow.com/help/how-to-ask) And here is a great place to [**START**](http://spaghettidba.com/2015/04/24/how-to-post-a-t-sql-question-on-a-public-forum/) to learn how improve your question quality and get better answers. – Juan Carlos Oropeza Nov 02 '16 at 12:51
  • Possible duplicate of [Get records with max value for each group of grouped SQL results](http://stackoverflow.com/questions/12102200/get-records-with-max-value-for-each-group-of-grouped-sql-results) – Juan Carlos Oropeza Nov 02 '16 at 12:52
  • You misunderstand how MIN works. It will get you the min value of that field for the group column, but the other columns which are not aggregate fields nor in the group by clause come from undertermined rows. They could come from the row with the MIN value, the MAX value, the first inserted value or any other value from those rows. – Kickstart Nov 02 '16 at 13:03
  • The reason you will get 2 rows is likely to be down to the GROUP BY clause. You probably only have 2 combinations of pattern and brand. **Please put up the table declares and a bit of sample data, and what you want output for that sample data** then I can test it (currently all I can do is guess, which is pretty useless and will take me ages). – Kickstart Nov 03 '16 at 09:27
  • There is 12 combinations , as in the original question the only issue I have is that the ID does not match the product with the MIN price() and the new query posted above returns 12 rows with the MIN price and Correct ID but after adding the joins I only get two results. The product table is huge, I inherited the project and it has something like 80 odd columns, so could be a problem post here. – Paul 501 Nov 03 '16 at 09:44
  • @Paul501 - 80 columns is fine, or just do it in sqlfiddle (or just cut it down to the columns that matter), and only enough rows to demonstrate the problem. Otherwise I am left fumbling around for half an hour or more guessing at the table declares and data in the vague hope that I get something close enough for any testing to be worthwhile, and I can't waste that much time when I am trying to do my own real work – Kickstart Nov 03 '16 at 12:13
  • 1
    Thanks for your help @kickstart, I`ll see if I can upload something to SQLFiddle when I get chance. – Paul 501 Nov 03 '16 at 15:07

1 Answers1

0

Making guesses at your table structure (and as such untested), something like this:-

SELECT   id ,  
        title ,  
        image ,  
        category , 
        price ,  
        rrp , 
        product_to_category.product_id ,    
        LEFT( short_description, 140 ) AS description,  
        measurements ,  
        colour_names ,  
        colour_values , 
        brand ,  
        pattern 
FROM
(
    SELECT  brand ,  
            pattern ,
            SUBSTRING_INDEX(GROUP_CONCAT(id ORDER BY price), ',', 1) AS first_product_id
    FROM  products 
    WHERE  status =  '1' 
    AND  price >=  '102.30' 
    AND  price <=  '804.10' 
    GROUP BY  pattern,  
            brand
) sub0
INNER JOIN  products ON sub0.brand = products.brand AND sub0.pattern = products.pattern AND sub0.first_product_id = products.product_id
INNER JOIN  product_to_category ON  products.id =  product_to_category.product_id 
LEFT OUTER JOIN  product_short_descriptions ON  product_short_descriptions.product_id =  products.id 
WHERE  product_to_category.category_id = 2
ORDER BY  sort_order ,  
            price ASC  
LIMIT 40

This is using a sub query to find the id of the cheapest product in each brand / pattern. It does that using GROUP_CONCAT to get all the ids in price order, and then uses SUBSTRING_INDEX to get the first one (ie cheapest). This is then joined back against the other tables to get all the other fields.

This previous answer might help you understand how aggregate functions and GROUP BY work, and why your original query does not bring back the results you require:-

MYSQL - Order By Id In DESC Order, Group By X

Community
  • 1
  • 1
Kickstart
  • 21,403
  • 2
  • 21
  • 33
  • Thanks for your example, I know you said it was untested, but just to let you know it gives the error of : **Unknown column 'category_id' in 'where clause'** when run in PHPMyAdmin – Paul 501 Nov 02 '16 at 16:49
  • @Paul501 - without the table declares (and preferably a bit of sample data) there is no way I can test it. In this case I made an assumption that category_id is on the products table, hence checked it in the sub query. If you can modify your question with the table declares I can try and debug it. – Kickstart Nov 02 '16 at 17:08
  • Yeah Sorry, category_id comes from the products_to_category table that is joined. – Paul 501 Nov 02 '16 at 17:36
  • I have moded the SQL, but it is still a guess as I do not know where the other columns come from. – Kickstart Nov 02 '16 at 17:58
  • Thanks @Kickstart, Ive added more details above – Paul 501 Nov 03 '16 at 09:31