mysql - 需要理解这个查询逻辑

标签 mysql join left-join

我有三张 table 。一种是用户表,其列如 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 个条目,则此查询将返回城市、年龄和兴趣值乘以 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`

现在这个查询返回准确的结果。我首先想问第二个查询出了什么问题,它返回了错误的字段值。我完全不明白这一点。我不想子查询。

请首先让我知道该查询出了什么问题,以及如何在没有子查询的情况下获得准确的结果。

最佳答案

要回答您的第一个问题,当您从每个表返回多个匹配行时,您的查询正在创建“叉积”。

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 行 x 3 行)

<小时/>

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

<小时/>

避免叉积将涉及单独的查询,或使用内联 View 的单个查询(但内联 View 实际上是一个“子查询”,并且您说您想避免这种情况。)

但只是为了展示如何使用对内联 View (而不是相关子查询)的 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 的内联 View 将为每个 user_id 最多返回一行,因此该集合的 JOIN 不会产生任何“重复项”。内联 View 查询的性能将通过(user_id,时间戳)上的适当索引进行优化。

<小时/>

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

GROUP_CONCAT(DISTINCT expr)

请注意,MySQL 可能仍会经历生成叉积的循环,如果用户有大量登录,并且从另一个表返回大量行,叉积最终可能会相当大。然后 MySQL 必须遍历整个集合来挑选出 MAX() 并挑选出不同的值。

关于mysql - 需要理解这个查询逻辑,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22541516/

相关文章:

php - Dreamweaver CS5.5 动态相关文件

java - Hibernate 中 select Double 的问题

mysql - NodeJS 中从 MYSQL 返回 bool 值

sql - 在 Left Join 期间对整行进行空检查

sql - 选择A中存在但B中不存在的数据

mysql - 如何在select中设置执行时间限制,但最后返回结果

python - 使用python的multiprocessing和process defunc进行并行编程

php - 无法在带有 PDO 的 UPDATE 查询中使用 JOIN

sql - SQL-内部联接2个表,但如果1个表为空则返回全部

mysql - 优化MySQL 3个表之间的Left join查询以减少执行时间