Search code examples
mysqlnested-sets

mysql: Select menu - deselected all category's not present in the results


Using mysql (and php) I try to hide out of a select menu category's that are not available for the item selection. Basically I'm stuck and can't thing of a way to do it.

Asos uses this technique to browse trough there products ( http://www.asos.com/Men/Jeans/Cat/pgecategory.aspx?cid=4208 ).

To be clear the problem is not how to do the ajax stuff but how efficiently use mysql and php to make a system like this.

I've tried to figure out to do this with nested sets:

    (1)items(22)----------------------------------
          |                           |
          |                           |
    (2)producs(7)                  (8)origin(21)
          |                           |
(3)single(4)-(5)bundle(6)             |
                             ---------------------
                             |                   |
                            (9)EU(14 )        (15)ASIA(20)   
                             |                   |   
                          ----------------      -----------------
                          |             |         |              |
                        (10)DU(11)  (12)PL(13)  (16)CN(17)   (18)JP(19)

Menu:
type:
-single
-bundle

origin:
-EU
-ASIA

country:
-DU
-PL
-CN
-JP

Lets say there's one item in the db: item '1' is in:
-single
-EU
-DU

Item category table:

itemID - cat_lft - cat_rgt
  1        10        11
  1        7         12
  1        2         3

In the menu selecting DU should select:
DU-EU-SINGLE

And deselected the rest (return a array?) because theres is only one item for now.

You could get the items and compare if the result are present in the remaining category's, but that sounds like using a lot of query's...

Any advice where to go from here?


Solution

  • Here's how I would do it. Make sure your products are categorized with leaves only (i.e. left = right + 1). So, a product can be categorized as "DU," but not "origin" or "ASIA."

    Category table:

    • category_id
    • name
    • left
    • right

    Products table:

    • product_id
    • name
    • ...

    Categories_Products table:

    • category_id
    • product_id

    SQL query to determine checked vs. unchecked categories:

    SELECT
        `categories`.`category_id`,
        `categories`.`name`,
        IFNULL(COUNT(`categories_products`.`id`), 0) AS `count` FROM `categories`
    
    LEFT JOIN `categories` as `children` ON
        `children`.`left`  > `categories`.`left`  AND
        `children`.`right` < `categories`.`right`
    
    JOIN `categories_products` ON
        `categories_products`.`category_id` = `children`.`category_id`
    
    GROUP BY `categories`.`category_id`
    

    If "count" is 0, leave it unchecked; otherwise, it's checked, and you know how many items are in that category. This is untested, so it might need some tweaking. You DEFINITELY want indexes on category_id, product_id, left, and right columns.

    Addition: SQL query to select products in one or more categories (see below for compound conditions):

    SELECT `products`.* FROM `categories`
    
    JOIN `categories` as `children` ON
        `children`.`left`  >= `categories`.`left`  AND
        `children`.`right` <= `categories`.`right`
    
    JOIN `categories_products` ON
        `categories_products`.`category_id` = `children`.`category_id`
    
    JOIN `products` ON
        `products`.`product_id` = `categories_products`.`product_id`
    
    WHERE `categories`.`category_id` IN (1,2,3,n,...)
    
    GROUP BY `products`.`product_id`
    

    Addition: Logic to select products in multiple categories, compound filtered (product must be in all categories to be a match):

    You'll have to do separate joins for every category requirement you want. Please note that this type of query could get pretty slow, so do some benchmarking as you go. You might be better off with multiple queries (one for each category), combining the results in the application code.

    Along those same lines, I'd recommend that something like "single vs. bundle" be an ENUM in your products table, and categorization is left to more abstract structures (region works, and so would a traditional consumer-facing, "logical" categorization structure) that aren't a simple either/or flag.