【问题标题】:Select at least one from each category?从每个类别中至少选择一个?
【发布时间】:2012-09-13 01:08:01
【问题描述】:

SQLFiddle Link

我有一个 SQLite 数据库,其中包含一堆测试/考试问题。每个问题都属于一个问题类别

我的桌子是这样的:

目标
我正在尝试做的是选择 5 个随机问题,但结果必须包含至少每个类别中的一个。目标是从每个类别中随机选择一组问题。

例如,输出可能是问题 ID 1, 2, 5, 7, 82, 3, 6, 7, 88, 6, 3, 1, 7

ORDER BY category_id, RANDOM()
我可以通过执行下面的 SQL 从 SQLite 获取随机的问题列表,但我如何确保结果包含来自我的每个类别的问题?

基本上,我正在寻找类似this 的东西,SQLite 版本。

我想只获得 5 个结果,但每个类别都有一个(或多个)结果集中表示所有类别。

赏金
添加了赏金,因为我很好奇是否可以仅在 SQLite 中完成此操作。我可以在 SQLite+Java 中做到这一点,但有没有办法只在 SQLite 中做到这一点? :)

SQLFiddle Link

【问题讨论】:

  • SELECT DISTINCT(category_id) FROM questions LIMIT 0,5 你试过这个吗?
  • 这样可以获得唯一的类别列表,但我想要的并不那么容易:(.
  • 离题:是的,感谢我们的 stackoverflow 小部件,找到了一个同事的问题!

标签: android database sqlite random


【解决方案1】:

基本上您要寻找的是选择前 N 个最大值。我早上花了 3-4 个小时来搜索它。 (我仍然没有成功,你可能需要再等几个小时)。

对于临时解决方案,您可以使用 group by 选项,如下所示,

String strQuery = "SELECT * FROM so_questions group by category_id;";

输出如下,

会根据您的要求回来。

【讨论】:

  • 但是当只选择 5 个问题时,我仍然会遇到从每个类别中获取一个问题的问题。它只是从结果集中选择前 5 行。 (在这种情况下,id 为 1,2,3,4,5 的行)
  • @TerrySeidler,不,输出只有 4 行,因为只有 1,2,3,4 个不同的类别
  • 但是 OP 想要随机 5 行,其中每个类别必须至少包含一行...
  • @Sam,这意味着任何一个类别都会重复,因为总共只有 4 个类别,对吧?
  • 是的。但重复哪个类别并不重要。
【解决方案2】:

因为它是 sqlite(因此是本地的):在有 5 个答案和四个不同类别之前进行查询会有多慢,每次迭代都会删除重复的类别行。

我认为,如果每个类别的代表相同,那么您需要超过 3 次迭代的可能性很小,而这仍应低于 1 秒。

它在算法上不是很好,但对我来说,在 SQL 语句中使用 random() 在算法上也不是很好。

【讨论】:

  • 很好的观察!这似乎是获得我想要的东西的最佳方法。当我昨天提出这个问题时,我仍然希望有一个仅限 SQL 的解决方案,但这是一个有效的替代方案。今晚我会看看实现这个:)。
【解决方案3】:

答案的关键在于结果中有两种问题:对于每个类别,一个问题必须约束来自该类别;以及一些剩余的问题。

首先,限制性问题:我们只从每个类别中选择一条记录:

SELECT id, category_id, question_text, 1 AS constrained, max(random()) AS r
FROM so_questions
GROUP BY category_id

(此查询依赖于 SQLite 3.7.11(在 Jelly Bean 或更高版本中)中引入的功能:在查询 SELECT a, max(b) 中,a 的值保证来自具有最大 @987654324 的记录@值。)

我们还必须得到非约束问题(过滤掉已经在约束集中的重复项将在下一步发生):

SELECT id, category_id, question_text, 0 AS constrained, random() AS r
FROM so_questions

当我们将这两个查询与UNION 组合在一起,然后按id 分组时,我们将所有重复项放在一起。选择 max(constrained) 可确保对于具有重复项的组,仅保留受限问题(而所有其他问题无论如何每组只有一条记录)。

最后,ORDER BY 子句确保首先出现受约束的问题,然后是一些随机的其他问题:

SELECT *, max(constrained)
FROM (SELECT id, category_id, question_text, 1 AS constrained, max(random()) AS r
      FROM so_questions
      GROUP BY category_id
      UNION ALL
      SELECT id, category_id, question_text, 0 AS constrained, random() AS r
      FROM so_questions)
GROUP BY id
ORDER BY constrained DESC, r
LIMIT 5

对于早期的 SQLite/Android 版本,我没有找到不使用临时表的解决方案(因为受限问题的子查询必须多次使用,但由于random() 而不会保持不变):

BEGIN TRANSACTION;

CREATE TEMPORARY TABLE constrained AS
SELECT (SELECT id
        FROM so_questions
        WHERE category_id = cats.category_id
        ORDER BY random()
        LIMIT 1) AS id
FROM (SELECT DISTINCT category_id
      FROM so_questions) AS cats;

SELECT ids.id, category_id, question_text
FROM (SELECT id
      FROM (SELECT id, 1 AS c
            FROM constrained
            UNION ALL
            SELECT id, 0 AS c
            FROM so_questions
            WHERE id NOT IN (SELECT id FROM constrained))
      ORDER BY c DESC, random()
      LIMIT 5) AS ids
JOIN so_questions ON ids.id = so_questions.id;

DROP TABLE constrained;
COMMIT TRANSACTION;

【讨论】:

  • 很好,这行得通!感谢您的解释。我在 SQLite+Java 中也做了同样的事情,但这确实是一个“纯 SQLite”的解决方案:)。 (可能会在 1 小时内奖励赏金)
  • (我确实意识到 Jelly Bean 仅存在于 1.8% 的 Android 设备上。我很好奇的是纯 SQLite 解决方案,而不是适用于所有 Android 的纯 SQLite 解决方案设备。所以对我来说,这个答案非常好。)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-02-07
  • 2011-05-01
  • 1970-01-01
  • 1970-01-01
  • 2012-11-22
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多