我有三张桌子。一个是具有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`
现在此查询返回完全结果。我首先想问第二个查询中哪些错误返回字段值。我完全不明白这一点。我不想要子查询。
请先告诉我该查询有什么问题,如何在没有子查询的情况下获得准确的结果。
答案 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()并选出不同的值。