需要了解这个查询逻辑

时间:2014-03-20 18:04:49

标签: mysql join left-join

我有三张桌子。一个是具有user_id, displayname,....列的用户表 第二个表是user_values,其结构类似于

  -------------------------------------
  | id  | item_id |  field_id | value |
  -------------------------------------
  | 1   |   1     |  15       | 2     |
  -------------------------------------
  | 2   |   1     |  15       | 6     |
  -------------------------------------
  | 3   |   1     |  16       | start |
  -------------------------------------
  | 2   |   2     |  15       | 2    |
  -------------------------------------

在此表中,item_id实际上是加入用户表的user_id。在此字段中,每个字段可以针对一个item_id(user_id)具有多个值。现在我需要针对用户的某些字段找到某些值。我写了以下查询,它正是找到我需要的结果。

SELECT 
 `eu`.`user_id`, `eu`.`displayname`, 
 GROUP_CONCAT( CASE WHEN eufv.field_id = 19 THEN eufv.value END ) AS city , 
 GROUP_CONCAT( CASE WHEN eufv.field_id = 15 THEN eufv.value END )AS interests , 
 GROUP_CONCAT( CASE WHEN eufv.field_id = 6 THEN eufv.value END )AS age 
FROM 
 `engine4_users` AS `eu` 
INNER JOIN 
 `engine4_user_fields_values` AS `eufv` 
ON 
 eu.user_id = eufv.item_id 
GROUP BY `eu`.`user_id`

现在有另一个表记录用户登录历史记录。该表再次存储user_id和last_login时间戳。现在我还需要user_last登录。现在如果我写像

这样的查询
 SELECT 
`eu`.`user_id`, `eu`.`displayname`, 
GROUP_CONCAT( CASE WHEN eufv.field_id = 19 THEN eufv.value END ) AS city , 
GROUP_CONCAT( CASE WHEN eufv.field_id = 15 THEN eufv.value END )AS interests , 
GROUP_CONCAT( CASE WHEN eufv.field_id = 6 THEN eufv.value END )AS age,
    MAX(eul.timestamp) as user_login 
 FROM 
`engine4_users` AS `eu` 
 INNER JOIN 
`engine4_user_fields_values` AS `eufv` 
 ON 
eu.user_id = eufv.item_id 
 Left Join
    engine4_user_logins as eul
  ON
     eu.user_id - eul.user_id
  GROUP BY `eu`.`user_id`

此查询返回错误的结果。如果用户在登录表中有7个条目,则此查询返回city,age和interets值乘以7.例如,对于item_id 1,field_id 15,它返回2,2,2,2,2,2,2,6,6,6,6,6,6,6。我不知道为什么会这样返回结果。

但是如果我写一个子查询来获取上次登录时间,比如

 SELECT 
`eu`.`user_id`, `eu`.`displayname`, 
GROUP_CONCAT( CASE WHEN eufv.field_id = 19 THEN eufv.value END ) AS city , 
GROUP_CONCAT( CASE WHEN eufv.field_id = 15 THEN eufv.value END ) AS interests , 
GROUP_CONCAT( CASE WHEN eufv.field_id = 6 THEN eufv.value END ) AS age,
(SELECT MAX(eul.timestamp) FROM engine4_user_logins AS eul WHERE eul.user_id = eu.user_id) AS last_login 
   FROM 
   `engine4_users` AS `eu` 
   INNER JOIN 
    `engine4_user_fields_values` AS `eufv` 
   ON 
    eu.user_id = eufv.item_id 
   GROUP BY `eu`.`user_id`

现在此查询返回完全结果。我首先想问第二个查询中哪些错误返回字段值。我完全不明白这一点。我不想要子查询。

请先告诉我该查询有什么问题,如何在没有子查询的情况下获得准确的结果。

1 个答案:

答案 0 :(得分:1)

要回答您的第一个问题,当您从每个表格返回多个匹配的行时,您的查询正在创建一个"交叉产品"。

来自engine4_user_fields_values的每个匹配行与engine4_user_logins返回的每一行匹配。结果集是这两组的交叉乘积。

这不是SQL中的错误,它是预期的行为。

与我们从此演示查询中得到的结果类似:

SELECT a.i, b.j
  FROM (SELECT 2 AS i UNION ALL SELECT 3 UNION ALL SELECT 5 UNION ALL SELECT 7) a
  JOIN (SELECT 11 AS j UNION ALL SELECT 13 UNION ALL SELECT 17) b

产生12行(4行×3行)


回答你的第二个问题:有几种方法可以解决这个问题。一种是避免创建交叉产品,另一种方法是继续生产交叉产品,然后消除重复产品。


避免跨产品将涉及单独的查询,或使用内联视图的单个查询(但内联视图实际上是&#34;子查询&#34;,并且您说您想避免这种情况。)< / p>

但仅仅是为了展示如何使用JOIN操作对内联视图(而不是相关的子查询)进行操作,这里有一个例子:

SELECT eu.user_id
     , eu.displayname
     , GROUP_CONCAT( CASE WHEN eufv.field_id = 19 THEN eufv.value END ) AS city 
     , GROUP_CONCAT( CASE WHEN eufv.field_id = 15 THEN eufv.value END ) AS interests 
     , GROUP_CONCAT( CASE WHEN eufv.field_id = 6 THEN eufv.value END ) AS age
     , ll.last_login 
  FROM `engine4_users` eu 
  JOIN `engine4_user_fields_values` eufv
    ON eufv.item_id = eu.user_id
  LEFT
  JOIN ( SELECT eul.user_id
              , MAX(eul.timestamp) AS last_login
           FROM engine4_user_logins eul
          GROUP BY eul.user_id
       ) ll
    ON ll.user_id = eu.user_id
 GROUP BY eu.user_id

别名为ll的内联视图每个user_id最多返回一行,因此对该集合的JOIN不会产生任何&#34;重复&#34;。内联视图查询的性能将使用(user_id,timestamp)上的适当索引进行优化。


另一种方法是处理&#34;重复&#34;通过消除生成的重复项从交叉产品返回的值。一种方法是在GROUP_CONCAT函数中包含DISTINCT关键字。但请注意,这将删除所有重复项,而不仅仅是交叉产品引入的重复项。

GROUP_CONCAT(DISTINCT expr)

请注意,MySQL可能仍会经历产生交叉产品的旋转,如果用户有大量登录,则最终会变得相当大,并且从另一个表返回的行数很多。然后MySQL必须通过整个集合来选择MAX()并选出不同的值。