2012-12-14 61 views
1

有2个引用(user_iditem_id),我需要查询找到与某些项目的所有用户一个表排序。 棘手的部分是,我需要根据结果排序结果,而不仅仅是结果的数量,而是基于它们的WHICH项目。从单一的表中返回结果由我多少结果

这里的表:

+--------------+-----------------------+------+-----+---------+-------+ 
| Field  | Type     | Null | Key | Default | Extra | 
+--------------+-----------------------+------+-----+---------+-------+ 
| user_id  | int(11)    | NO |  | 0  |  | 
| item_id  | int(11) unsigned  | YES |  | NULL |  | 
+--------------+-----------------------+------+-----+---------+-------+ 

所以我的查询如下所示:

SELECT user_id, item_id 
FROM  user_items 
WHERE item_id IN (2, 122, 132) 
GROUP BY user_id, item_id 
HAVING SUM(item_id = 2); 

看起来很容易吗?这里是艰难的部分进来:

item_id = 2是必需的 item_id = 122和132是可选的。 132之后的任何内容也是可选的。

我需要基于订购的结果: 1)如果所有的项目被发现。 2)如果仅找到项目2和122。 3)如果只找到第2项。

下面是摆弄的SQL小提琴文件:http://sqlfiddle.com/#!2/6b1c1/6/0

我在想,如果有一些方法我可以设置的,这样的事情:SELECT查询说

IF (item_id = 2 AND item_id = 122 AND item_id = 132) AS matches_all, 
IF (item_id = 2, item_id = 122) AS matches_some, 
IF (item_id = 2) AS matches_first 

编辑与更新查询 这是我迄今为止。它是我所需要的约95%: http://sqlfiddle.com/#!2/6b1c1/47

SELECT user_id, item_id, 
    @tmp_1 := IF(SUM(item_id = 2), 1, 0) AS tmp_1, 
    @tmp_2 := IF(SUM(item_id = 122), 1, 0) AS tmp_2, 
    @tmp_3 := IF(SUM(item_id = 132), 1, 0) AS tmp_3, 
    @tmp_4 := IF(SUM(item_id = 126), 1, 0) AS tmp_4, 
    CAST(@tmp_3 + @tmp_4 AS UNSIGNED) AS total_other 
FROM  user_items 
WHERE item_id IN (2, 122, 132, 126) 
GROUP BY user_id 
HAVING SUM(item_id = 2) 
ORDER BY tmp_1 DESC, tmp_2 DESC, total_other DESC 

一对夫妇更多的细节:

1)我只会有一个最大的12个项目进入,所以我可以指定每一个它自己的温度场如果需要的话。

2)上面的查询完全适用于tmp_1和TMP_2。如果我们有一个拥有第2项和第122项的用户,它将这些放在列表的顶部。 对于剩下的,3-4(3至最多12个),我需要匹配的数目,这就是为什么在我CAST(@tmp_3 + @tmp_4做出了尝试的计算。我不知道如何让这些计算。

3)一旦我有项目3的总计算 - 12,则这将是ORDER BY子句中的第三个和最后一个项目。

结果示例 基于在SQL小提琴文件中提供的模式,这里要说的是,应根据搜索与ITEM_ID所有用户返回的结果'S:2, 122, 132, 126

+---------+--------------+----------------+-------------+ 
| USER_ID | PRIMARY_ITEM | SECONDARY_ITEM | OTHER_ITEMS | 
+---------+--------------+----------------+-------------+ 
| 39  | 1   | 1    | 2   | 
| 54  | 1   | 1    | 0   | 
| 55  | 1   | 0    | 0   | 
+---------+--------------+----------------+-------------+ 
+1

您的查询总废话:'HAVING SUM(ITEM_ID = 2)'? – Bohemian

+2

老兄!如果你有更好的东西,请告诉我!我没有发布我的问题,因为我的查询是完美的。我正在尝试做一些我无法弄清楚如何去做的事情。查看SQLFiddle链接以了解它是如何工作的。 'HAVING SUM(item_id = 2)'只是确保item_id 2存在于结果中。 –

+1

'HAVING SUM(item_id = 2)'不一定是“无稽之谈”。这个特殊的表达式可能不会达到理想的结果集,但像这样的表达式通常是有效的,有时是有用的,偶尔也是不可或缺的。 – spencer7593

回答

1

UPDATE:

基础上更新您的问题(包括所期望的结果集),这里是一个返回结果集的查询。(这是非常类似于在联视图查询说明在原来的答案)

SELECT i.user_id       AS user_id 
     , MAX(IF(i.item_id= 2 ,1,0))  AS primary_item 
     , MAX(IF(i.item_id= 122 ,1,0))  AS secondary_item 
     , MAX(IF(i.item_id= 132 ,1,0)) + 
     MAX(IF(i.item_id= 126 ,1,0))  AS other_items 
    FROM user_items i 
    WHERE i.item_id IN (2, 122, 132, 126) 
    GROUP BY i.user_id 
    HAVING primary_item 
    ORDER 
     BY primary_item DESC 
     , secondary_item DESC 
     , other_items DESC 
     , i.user_id 

注意,表达式来计算other_items柱可以扩展到处理任何数量的其他items_id值。 (你只是想确定在那里没有指定相同的item_id两次,或者它将被“计数”两次),例如,

 , MAX(IF(i.item_id= 132 ,1,0)) + 
     MAX(IF(i.item_id= 133 ,1,0)) + 
     MAX(IF(i.item_id= 135 ,1,0)) + 
     MAX(IF(i.item_id= 137 ,1,0)) + 
     MAX(IF(i.item_id= 143 ,1,0))  AS other_items 

这基本上做检查对于每个项目,然后得出一个1或0,然后加入了1和0拿出总。

还要注意的是,IF()函数的调用是没有必要的,这些表达实际上可以简化为:

 , MAX(i.item_id= 2)     AS primary_item 
     , MAX(i.item_id= 122)    AS secondary_item 

注意,WHERE条款实际上并不需要返回正确的结果集。 (但是如果它存在,谓词必须与SELECT列表中正在检查的item_id值相匹配

还要注意,ORDER BY不需要包含primary_item DESC,因为我们的查询保证值为primary_item将是一个1。这是足以与secondary_item DESC启动顺序,因为这可以是1或0。

覆盖索引on (user_id,item_id)可以加速性能,或可能的item_id领先列的索引可能会更好。(缺少WHERE子句,查询将需要检查表中的每一行,基本上是全表扫描或全索引扫描。)

从结果集中看,如果用户拥有一个或多个项目(而不是计算他拥有的特定项目的数量),则您想返回'1'。如果要返回的内容是每个项目的数量的计数,那么你会用SUM()聚合来代替MAX()聚合,但是这对于解读OTHER_ITEMS列的内容来说更成问题。

注意HAVING primary_item子句是让我们只有行的那些用户至少有一个item_id = 2


UPDATE:

弗朗西斯说......该查询[在你原来的答案]将返回每个用户多个结果,这是不是我后。

A:这是一个很好的例子,显示你想返回的结果集的例子会有好处。您的查询在SELECT列表中同时包含user_id和item_id`,并且没有任何迹象表明您希望每个用户只返回一行,或者每个user_id和item_id组合只返回一行。

为了得到这个结果,只需在ORDER BY子句前添加一个GROUP BY d.user_idGROUP BY d.user_id, d.item_id子句。


这是不优雅,但我认为这将返回指定的结果集。

SELECT d.user_id 
    , d.item_id 
    FROM user_items d 
    JOIN ( 
     SELECT i.user_id 
       , MAX(IF(i.item_id=2 ,1,0)) AS item_2 
       , MAX(IF(i.item_id=122,1,0)) AS item_122 
       , MAX(IF(i.item_id=132,1,0)) AS item_132 
      FROM user_items i 
      WHERE i.item_id IN (2, 122, 132) 
      GROUP BY i.user_id 
     HAVING item_2 
      ORDER BY 3 DESC, 4 DESC, 1 
     ) f 
    ON d.user_id = f.user_id 
WHERE d.item_id IN (2, 122, 132) 
ORDER BY (f.item_122 AND f.item_132) DESC 
     , f.item_122 DESC 
     , d.user_id 
     , d.item_id 

内联视图(别名为f查询)不“检验”该项目中的被发现的用户。


要看到这是如何工作的,我们第一,我们检查只是内嵌视图的结果...

  SELECT i.user_id 
       , MAX(IF(i.item_id=2 ,1,0)) AS item_2 
       , MAX(IF(i.item_id=122,1,0)) AS item_122 
       , MAX(IF(i.item_id=132,1,0)) AS item_132 
      FROM user_items i 
      WHERE i.item_id IN (2, 122, 132) 
      GROUP BY i.user_id 
     HAVING item_2 
      ORDER BY 3 DESC, 4 DESC, 1 

WHERE条款可以被省略。对于我们这里的目的,我们基本上只是获取user_id的列表,以及它们具有哪些指定项目的指示符。

MAX聚合内部的表达式检查item_id是否分别匹配2,122或132,并返回1或0.我们使用MAX聚合拉出我们找到的任何值1。

我们确实需要GROUP BY,所以我们得到一个明确的user_id列表。

我们使用HAVING子句,以便省略没有item_id = 2的用户。它可以被写成这样

  HAVING item_2 > 0 

(增加大于零越大,但不是必需的,因为我们保证ITEM_2将有值为0或1)

ORDER BY是不是真的(因为我们打算将它加回到user_items表)(ORDER BY只在最外面的查询中需要)。但它确实证明可以获得此结果集的有序性。

(如果这是我的要求,我可能只是停在这里,并利用此结果集的,但是这不是你所指定的结果集。)

我们加入一个查询(使用它作为内嵌视图,或以MySQL的说法派生的表)到user_items表,所以我们只返回那些匹配那个查询中的user_id的用户。

我们需要添加WHERE子句,所以我们只在指定的列表中提取item_id值。

而且我们需要ORDER BY以指定的顺序将结果集传给我们。

+0

这并不完全是我之后的事情。我刚刚使用新的SQLFiddle文件更新了我的问题,这个文件在这一点上非常接近,所以您可以更好地了解我所追求的内容。看起来我只需要了解如何计算tmp字段。 –

+0

@Francis Lewis:我建议你非常小心的用户变量,并确保你了解操作的顺序。他们有时不像你期望的那样工作。 – spencer7593

+0

@Francis Lewis:我不太清楚我提供的查询的结果集不符合规范。 – spencer7593

0

它看起来像你所需要的是哪些字段是必需的,哪些字段是可选的规则或映射。如果你有某种数学规则说,我不知道,也许需要编号为10,所有其他选项都是可选的,你可以在那里使用某些条款。

假设item_id是完全随机的,我建议您创建一个映射表,对您的项目进行排名/优先级排序。也许是这样的item_rank表:

 
------------------------- 
| item_id | is_optional | 
------------------------- 
|  2 |   1 | 
------------------------- 
|  122 |   0 | 
------------------------- 
|  133 |   0 | 
------------------------- 

那么你的查询是:

SELECT user_map.user_id, user_map.item_id, 
FROM user_map 
INNER JOIN item_rank 
ON user_map.item_id = item_rank.item_id 
    AND user_map.item_id IN (2,122,133) 
GROUP BY user_map.user_id 
HAVING item_rank.is_optional > 0 
ORDER BY COUNT(user_map.item_id); 

我不正是这样的解决方案,但是不知道多一点的你想在完成什么一天结束时,我无法提供更具体的解决方案。

在附注中,当问题很难解决时,通常意味着你试图以错误的方式来对待它们。当我发现自己处于架构绑定中时,当我从头开始重新追踪并思考时,我倾向于始终找到更清晰的解决方案。显然取决于你有多远,但可能是值得的。

祝你好运!

+0

映射表比我之前的要复杂一点,它不会考虑每个可能的用户输入,因为它需要首先由包含所有结果的匹配来排序,然后通过包含前2个和最高的匹配其他结果的数量,然后通过包含第一个匹配加上其他匹配的最高数量的匹配。 –

+0

@FrancisLewis啊,我明白了。你能提供一些关于你想要做什么的背景吗?我的意思是更高的水平。像“我有一个项目列表,并希望看到用户是否购买了某些项目来确定价格点”? – tazer84

0

好吧,这就是我想出来的。我只需要在第一个2之后计算任何项目,所以我想出了一个比使用临时字段更清洁的解决方案,并且最终能够实现一个更清晰的解决方案。

SELECT user_id, 
     IF(SUM(item_id = 2), 1, 0) AS primary_item, 
     IF(SUM(item_id = 122), 1, 0) AS secondary_item, 
     (IF(SUM(item_id = 132), 1, 0) + IF(SUM(item_id = 126), 1, 0)) AS other_items 
FROM  user_items 
WHERE item_id IN (2, 122, 132, 126) 
GROUP BY user_id 
HAVING SUM(item_id = 2) 
ORDER BY primary_item DESC, secondary_item DESC, other_items DESC 

所以这给了我一个领域的第一项,第二项,所以我可以看到,如果这些匹配,则计数所有的休息,这可以有多达10个其他项目。

然后它根据我们是否有第一个项目,第二个项目,然后是所有其他项目的总计数进行订购。

你可以在这里看到的最终结果是:http://sqlfiddle.com/#!2/6b1c1/131