Search code examples
phpmysqldatabase-designone-to-manyentity-attribute-value

Getting specific products from database with one-to-many relationship


I am making a products catalog using mySQL and PHP framework Codeigniter 4. Here I will show a simplified example. I have two tables in my database. One has common data like product name and price. The other has various attributes like color, material etc. So one product has many attributes. Something like that:


Products

| id -- | name ----- | price -- |

| 0 --- | Chair 1 -- | 50 ----- |

| 1 --- | Chair 2 -- | 75 ----- |


Attributes

| id -- | product_id -- | attribute -- | value -- |

| 0 --- | 0 ------------ | color ------ | black -- |

| 1 --- | 0 ------------ | size -------- | small -- |

| 2 --- | 1 ------------ | color ------ | white -- |

| 3 --- | 1 ------------ | size -------- | large -- |


My problem involves getting specific products, as when there is a filter of products and the user wants to see all products with certain attributes. If I need to get one product with one attribute, I have no problem with that. I do this:

$product = $productsModel->join('attributes', 'attributes.product_id = products.id')
                         ->where('attribute', 'color')
                         ->where('value', 'black')
                         ->find();

However I cannot get products according to multiple attributes. Like if I need a chair that is black and small, I cannot add ->where('attribute', 'size')->where('value', 'small') because color and size are different rows. It is easy to see if I use the same join and try to get the product with id 0. The result is:

Array
    (
        [0] => Array
            (
                [id] => 0
                [name] => Chair 1
                [price] => 50
                [product_id] => 1
                [attribute] => color
                [value] => black
            )    
        [1] => Array
            (
                [id] => 1
                [name] => Chair 1
                [price] => 50
                [product_id] => 0
                [attribute] => size
                [value] => small
            )    
    )

Any idea how to solve this problem? I need to create a filter with checkboxes that will display all available attributes of products and allow users to search for any combination.


Solution

  • In raw MySQL your current query reads:

    SELECT products.*
    FROM products 
    INNER JOIN attributes ON attributes.product_id = products.id
    WHERE attribute = 'color' AND value = 'black';
    

    Adding a second join could look something like this:

    SELECT P.*
    FROM products AS P
    INNER JOIN attributes AS A1 ON A1.product_id = P.id
    INNER JOIN attributes AS A2 ON A2.product_id = P.id
    WHERE A1.attribute = 'color' AND A1.value = 'black' AND
          A2.attribute = 'size'  AND A2.value = 'small';
    

    See: https://www.mysqltutorial.org/mysql-inner-join.aspx