Xaver
Xaver

Reputation: 11702

Joining same table with NULL values in MySQL

I have a simple table like

user_id | meta_key | meta_value
===============================
   1    | color    | green
   2    | color    | red
   1    | size     | L
   2    | size     | L
   3    | color    | blue

...

Now I like a single query where I get all colors and sizes of a certain user

SELECT a.user_id AS ID, a.meta_value AS color, b.meta_value AS size
   FROM table AS a
   LEFT JOIN table AS b ON a.user_id = b.user_id 
WHERE a.meta_key = 'color' AND b.meta_key = 'size' GROUP BY a.meta_value, b.meta_value

This works as long I have a size for each user. If size is missing (ID 3 in example) the user isn't in the result

Upvotes: 0

Views: 1111

Answers (4)

spencer7593
spencer7593

Reputation: 108530

The predicate on b.meta_key in the WHERE is negating the "outerness" of the LEFT JOIN operation.

One option is to relocate that predicate to the ON clause, for example:

SELECT a.user_id AS ID, a.meta_value AS color, b.meta_value AS size
   FROM table AS a
   LEFT JOIN table AS b ON a.user_id = b.user_id 
      AND b.meta_key = 'size'
 WHERE a.meta_key = 'color' GROUP BY a.meta_value, b.meta_value

FOLLOWUP

Q: What if color is missing?

A: You'd need a table or inline view as a rowsource that returns every value of user_id that you want included in the result set. For example, if you had a table named "users" with one row per user_id, and you want to return every id in that table:

 SELECT u.id         AS user_id
      , a.meta_value AS color
      , b.meta_value AS size
   FROM user u
   LEFT
   JOIN `table` a
     ON a.user_id = u.id
    AND a.meta_key = 'color'
   LEFT
   JOIN `table` b
     ON b.user_id = u.id
    AND b.meta_key = 'size'
  GROUP
     BY u.id
      , a.meta_value
      , b.meta_value

We also note that the GROUP BY in your original query does not include the user_id column, so any rows that have the same meta_value for color and size will be collapsed, and you'll get only one of the user_id values that have matching color and size.

Note that if the combination of user_id and meta_key is UNIQUE (in table table), then the GROUP BY isn't necessary.


In the normative relational model, attributes of an entity are implemented as columns. As a good example, the resultset being returned by the query looks a lot like the table we'd expect.

When an Entity Attribute Value (EAV) model is implemented in a relational database, the SQL gets an order of magnitude more complicated.


If you want to return ONLY values of user_id that have either color or size, you could get a list of user_id values with an inline view (query against table). This query is the same as above, but replacing the reference to the user table with an inline view:

 SELECT u.id         AS user_id
      , a.meta_value AS color
      , b.meta_value AS size
   FROM ( SELECT t.user_id AS id
            FROM table t
           WHERE t.meta_key IN ('color','size')
          GROUP BY t.user_id
        ) u
   LEFT
   JOIN `table` a
     ON a.user_id = u.id
    AND a.meta_key = 'color'
   LEFT
   JOIN `table` b
     ON b.user_id = u.id
    AND b.meta_key = 'size'
  GROUP
     BY u.id
      , a.meta_value
      , b.meta_value

Upvotes: 2

Hituptony
Hituptony

Reputation: 2860

SELECT a.user_id AS ID,
a.meta_value AS color, b.meta_value AS size
FROM table AS a
LEFT JOIN table AS b ON a.user_id = b.user_id 
WHERE a.meta_key in ('Color', 'Size')

Upvotes: 0

Jey
Jey

Reputation: 60

try following code,

SELECT a.user_id AS ID, a.meta_value AS color, b.meta_value AS size FROM table AS a where a.meta_key = 'color' or a.meta_key = 'size'

Upvotes: 0

Andrew
Andrew

Reputation: 8758

The where clause is killing you. Let's take b.meta_key = 'size'. If there are no rows in your B table, meta_key will come back null. The filter on that column removes them from the result. Try adding the b.meta_key = 'size' to the on clause.

Upvotes: 1

Related Questions