1

我有一个包含 2 个引用 (user_iditem_id) 的表,我需要查询它以查找具有某些项目的所有用户。棘手的部分是,我需要对结果进行排序,而不仅仅是他们拥有的结果数量,而是基于他们拥有的哪些项目。

这是表格:

+--------------+-----------------------+------+-----+---------+-------+
| 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 query to say

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 fiddle 文件中提供的架构,以下是搜索所有具有 item_id 的用户时应返回的结果:2, 122, 132, 126

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

3 回答 3

1

更新:

根据对您的问题的更新(包括所需的结果集),这是一个返回该结果集的查询。(这与我原始答案中解释的内联视图中的查询非常相似)

  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_item1。以 开始排序就足够了secondary_item DESC,因为它可以是 1 或 0。

覆盖索引on (user_id,item_id)可能会提高性能,或者可能具有前导列的索引item_id可能会更好。(没有 WHERE 子句,查询将需要检查表中的每一行,基本上是全表扫描或全索引扫描。)

从结果集中,如果用户拥有一个或多个项目(而不是计算他拥有多少特定项目),您似乎想要返回“1”。如果您要返回的是计数每个项目的数量,然后您将用MAX()聚合替换SUM()聚合,但这对于破译 OTHER_ITEMS 列的内容更成问题。

请注意,该HAVING primary_item子句只为那些至少具有item_id = 2.


更新:

弗朗西斯说......那个查询[在你原来的答案中]为每个用户返回多个结果,这不是我所追求的。

答:这是一个很好的例子,说明显示您想要返回的结果集的示例会很有用。您的查询user_id在 SELECT 列表中同时具有和 item_id`,并且没有指示您希望每个用户只返回一行,或者每个 user_id 和 item_id 组合只返回一行。

要做到这一点,只需在子句之前添加一个GROUP BY d.user_id或一个子句。GROUP BY d.user_id, d.item_idORDER BY


这并不优雅,但我认为它会返回您指定的结果集。

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省略没有 an 的用户。可以这样写

         HAVING item_2 > 0 

(添加大于零的值,但这不是必需的,因为我们保证 item_2 的值为 0 或 1)

此处实际上并不需要 (因为我们将把它ORDER BY加入到 user_items 表中。)(ORDER BY仅在最外层的查询中才需要 。)但它确实表明可以让这个结果集排序。

(如果这是我的要求,我可能会停在这里,并利用这个结果集;但这不是您指定的结果集。)

我们将该查询(将其用作内联视图或MySQL 术语中的派生表user_items)加入到表中,因此我们只为那些与该查询中的 user_id 匹配的用户返回行。

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

我们需要ORDER BY以指定的顺序获取结果集。

于 2012-12-15T00:37:07.120 回答
0

看起来您需要的是一个规则或映射,哪些字段是必需的,哪些字段是可选的。如果你有某种数学规则说,我不知道,也许 id < 10 是必需的,其他一切都是可选的,你可以用它做一些花哨的 where 子句。

假设 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 );

我不完全喜欢这个解决方案,但是在不知道你最终想要完成的事情的情况下,我无法提供更具体的解决方案。

附带说明一下,当问题很困难时,通常意味着您试图以错误的方式解决它们。当我发现自己处于架构束缚中时,当我重新跟踪并从头开始考虑时,我倾向于总是找到一个更干净的解决方案。显然取决于你走了多远,但可能是值得的。

祝你好运!

于 2012-12-15T00:42:24.200 回答
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

于 2012-12-15T07:21:03.560 回答