How to make this eav request to get horizontal result

Happening:

tables:

product:
product_id|name        |
------------------------
1         |iphone 4    |
2         |gallaxy 2   |
3         |blackbery 6 |

product_attribute:

id|product_id|attribute_id
--------------------------------------------------
 1 |1        |2
 2 |1        |6
 .    .        .

attribute:
------------------------------
attribute_id|name  |value|
        1   |width |300
        2   |width |320
        3   |width |310
        4   |height|390
        5   |height|370
        6   |height|380

      

should get the result:

product_id|height|width
 1        |380   |320
 ......................

      

Edit: height and width only attribute to it a part of the product attributes - the product must have a dynamic ability to be added by the user to the backend, as is done in magento, because I choose eav db design. Please email inquiries if possible if we do not know which products are named.

thank

+3


source to share


3 answers


There are several ways to accomplish this. Something like this should work, joining back to the table multiple times for each attribute value:

SELECT p.product_id,
    a.value height,
    a2.value width
FROM Product p
    JOIN Product_Attribute pa ON p.product_id = pa.product_id 
    JOIN Attribute a ON pa.attribute_id = a.attribute_id AND a.name = 'height'
    JOIN Product_Attribute pa2 ON p.product_id = pa2.product_id 
    JOIN Attribute a2 ON pa2.attribute_id = a2.attribute_id AND a2.name = 'width'

      

And here's the Fiddle .



Here's an alternative approach using MAX and GROUP BY that I personally prefer:

SELECT p.product_id,
    MAX(Case WHEN a.name = 'height' THEN a.value END) height,
    MAX(Case WHEN a.name = 'width' THEN a.value END) width
FROM Product p
    JOIN Product_Attribute pa ON p.product_id = pa.product_id 
    JOIN Attribute a ON pa.attribute_id = a.attribute_id 
GROUP BY p.product_id

      

Good luck.

+4


source


One approach is to use correlated subqueries in the SELECT list, although this may be less optimal for performance on large sets. For getting just a few rows of rows from the product table, this would not be the case. (You will definitely need the appropriate indexes.)

SELECT p.product_id
     , ( SELECT a1.value
           FROM attribute a1
           JOIN product_attribute q1
             ON q1.attribute_id = a1.attribute_id
          WHERE q1.product_id = p.product_id
            AND a1.attribute_name = 'height'
          ORDER BY a1.id
          LIMIT 0,1
       ) AS height_1
     , ( SELECT a2.value
           FROM attribute a2
           JOIN product_attribute q2
             ON q2.attribute_id = a2.attribute_id
          WHERE q2.product_id = p.product_id
            AND a2.attribute_name = 'width'
          ORDER BY a2.id
          LIMIT 0,1
       ) AS width_1
  FROM product p
 WHERE p.product_id = 1

      


This query will return a string from the product along with attribute values, if any. If no attribute values ​​are found, the query returns NULL instead of the attribute value. (This differs from the behavior of a query that uses an INNER JOIN instead of a correlated subquery ... where a "missing" row from the attribute table or product_attribute filters the row from the returned product.)

The purpose of LIMIT clauses is to ensure that subqueries return rather than returning more than one row. (If more than one row was returned in a subquery in the SELECT list, MySQL will return an error.) The purpose of ORDER BY is to make the query deterministic, again, in case there is more than one row that satisfies the subquery, (In the absence of an ORDER BY clause, when there is more than one row, MySQL can randomly return whichever row it chooses.)

The same approach works for "multi-valued" attributes. We just add more subqueries, but we specify LIMIT 1.1 to return the second attribute value, LIMIT 2.1 to return the third value, and so on.


(Oh, the joy of the EAV model implemented in a relational database.)


Followup:

Q: "... a more general case, as it happens in eav db, that we don't know before what attribute names we have."

A: The relational model is based on the principle that a tuple contains a specified number of columns of a specified type.

What you are (apparently) trying to do is return a variable number of columns when you run your query. The SELECT statement contains a specific list of returned expressions; it cannot change, and the data types returned by each expression do not change from row to row.

The query above returns one instance of the "height" attribute value and one instance of the value of the "width" attribute for each product.



For the more "more general case", we really expect each attribute value to be returned on its own separate line.

A more general query, if you don't know "ahead of time" which attributes are associated with a product, would be:

SELECT p.product_id
     , a.attribute_id
     , a.name         AS attribute_name
     , a.value        AS attribute_value
  FROM product p
  LEFT
  JOIN product_attribute q
    ON q.product_id = p.product_id
  LEFT
  JOIN attribute a
    ON a.attribute_id = q.attribute_id
 WHERE p.product_id = 1
 ORDER
    BY p.product_id
     , a.name
     , a.attribute_id

      

This will return a result set that can be easily processed:

product_id attribute_id attribute_name attribute_value
---------- ------------ -------------- ---------------
         1            6 height         380
         1            2 width          320

      

Q: "looks like it should be done in 2 steps: 1. get all the attribute names for the product 2. then your code with the server side code of the attribute names in a loop"

A: No, it looks like one query is returning all attribute name and value pairs for a product. Each attribute name / value will be on a separate line.

There is no need to use a for loop to generate additional database queries. Yes, it can be done, but completely unnecessary.

If you have some weird requirement to compose another database query to return a result set in the format you specify, regardless of the processing you do to process the result set from the "more general case" operator, it probably would be process the result set more efficiently without running any more database queries.

If you need to return a result set that looks like this:

 product_id height width
 ---------- ------ -----
          1 380    320

      

(as a fancy requirement, since it is necessary to compose another query), it is entirely possible to use this result set from a "more general query" to generate a query that looks like this:

SELECT 1 AS product_id, '380' AS height, '320' AS width

      

While such an exercise is pretty pointless, given that you are not returning any new information that you have not previously returned, and now you have another set of results that you need to process, which I think is a boatload of unnecessary overhead.

+3


source


Let me first say that this is really bad design. ... As per your current approach, you will need to run multiple subqueries or joins with table aliases to achieve the desired result.

SELECT 
    product_id,
    (
        SELECT product_attribute.value 
        FROM product_attribute, attribute 
        WHERE product_attribute.product_id=product.product_id 
        AND product_attribute.attribute_id=attribute.attribute_id
        AND product_attribute.name = 'width'
    ) AS 'width',
    (
        SELECT product_attribute.value 
        FROM product_attribute, attribute 
        WHERE product_attribute.product_id=product.product_id 
        AND product_attribute.attribute_id=attribute.attribute_id
        AND product_attribute.name = 'height'
    ) AS 'height'
FROM
    product
ORDER BY 
    ...      

      

Let me suggest:

attribute
   attribute_sid  (eg, string id)

product
   product_id
   name
   ...

product_attribute
   product_id   (foreign key to product table)
   attribute_sid  (foreign key to attribute table)
   value   

      

This way, you have a final list of attributes and one attribute value for each product.

SELECT attribute_sid, value FROM product_attribute WHERE product_id = 1

      

... will retrieve all attributes and values ​​that are conveniently placed in dict

, array

or map

.

+1


source







All Articles