我有三张 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/