How make this eav query to make horizontal result

后端 未结 3 1599
离开以前
离开以前 2021-01-03 05:33

The case:

tables:

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


        
相关标签:
3条回答
  • 2021-01-03 06:12

    There are several ways to implement this. Something like this should work joining back on 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 is the Fiddle.

    Here is 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.

    0 讨论(0)
  • 2021-01-03 06:13

    Let me first say this is a really poor design. Under your current approach, you will need to run multiple subqueries or joins with table aliases to achieve the result you want.

    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 definitive list of attributes, and a single attribute value per product.

    SELECT attribute_sid, value FROM product_attribute WHERE product_id = 1
    

    ... will retrieve all the attributes and values, which can conveniently be placed in a dict, array, or map.

    0 讨论(0)
  • One approach is to use correlated subqueries in the SELECT list, although this can be less than optimum for performance on large sets. For retrieving just a few rows rows from the product table, it won't be bad. (You'll definitely want 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 the row from product, along with the values of the attributes, if they exist. If the attribute values are not found, the query will return a NULL in place of the attribute value. (This differs from the behavior of a query that uses INNER JOIN in place of correlated subquery... where a "missing" row from the attribute or product_attribute table would filter out the row from product being returned.)

    The purpose of the LIMIT clauses is to guarantee that the subqueries will return not return more than one row. (If a subquery in the SELECT list were return more than one row, MySQL would return an error.) The purpose of the ORDER BY is to make the query deterministic, again, in the event there is more than one row that satisfies the subquery. (Absent the ORDER BY clause, when there is more than one row, MySQL is free to arbitrarily return whichever row it chooses to.)

    For "multi-valued" attributes, the same approach works. We just add more subqueries, but specify LIMIT 1,1 to return the second attribute value, LIMIT 2,1 to return the third value, etc.


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


    Followup:

    Q: "... more general case as it happen in eav db that we dont know before which attributes 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 a query. A SELECT statement includes a specific list of expressions to be returned; this cannot vary and the datatypes of the values returned by each expression does not vary from row to row.

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

    For a more "more general case", we would really expect that each attribute value would be returned on its own separate row.

    The more general query, if you don't know "ahead of time" what 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
    

    That will return a resultset which can easily be processed:

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

    Q: "looks like should be done in 2 stages: 1. get all attributes names for product 2.then your code with server side code of attributes names in for loop"

    A: No, it looks like a single query will return all the attribute name and value pairs for a product. Each attribute name/value will be on a separate row.

    There's no need to use a "for loop" to generate additional queries against the database. Yes, it's possible to do that, but totally unnecessary.

    If you have some bizarre requirement to compose another query to run against the database to return the resultset in the format you specified, whatever processing you would do to process the resultset from the "more general case" statement, it would probably be more efficient to just process the resultset, without running any more queries against the database.

    If you need to return a resultset that looks like this:

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

    (as bizarre a requirement as that is, to compose another query) it's entirely possible to use that resultset from the "more general query" to generate a query that looks like this:

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

    Though such an exercise is rather pointless, given that you aren't returning any new information you didn't return previously, and now you have another resultset that you need to process, which just seems to me to be a boatload of unnecessary overhead.

    0 讨论(0)
提交回复
热议问题