3

我有一个网站,人们可以在上面对汽车进行投票。向用户展示了 4 辆汽车,他/她可以对他们最喜欢的汽车进行投票。

该表cars具有重要的列:

car_id   int(10) (not auto_increment, so has gaps)
views    int(7)
points   int(7)
car_type int(1) (value = 1, 2 or 3)

目前我为所有 car_types 使用一个映射表,它有一个没有间隙的 PK。我选择映射表的最大 ID 并创建 4 个随机数 (PHP),从映射中选择这些行并获取相应的 car_id。cars我用这些数字从表中选择汽车。

问题是后来添加到数据库中的汽车获得与之前添加的汽车相同的分数的机会更少。

我的问题是如何显示 4 辆具有相同数量的点(随机)的汽车,按最少的视图(视图 asc)排序。还有重要的注意事项:

  • 选择应该只查询至少有1 分的汽车。
  • 该数据库将包含超过 3000 万辆汽车,这不是关于汽车的,而是关于我认为更容易的问题 :)。
  • 当 70% 的汽车得 1 分、20% 的汽车得 2 分、10% 的汽车得 3 分时,随机点应该选择汽车 70% 得 1 分、20% 得 2 分、10% 得 3 分。
  • 查询将用于向访问者显示 4 辆车,我们都知道用户不耐烦所以查询越快越好:)
  • 我可以(如果需要)使用一个映射表,它在 PK 中没有间隙(就像我现在一样)。
  • 只会显示特定 car_type 内的汽车。例如,4 辆随机的 2 型车(即家用车),因为我不想同时显示跑车和家用车。

如果您知道解决上述问题的另一种解决方案,我愿意接受所有类型的解决方案(PHP/SQL)。

赏金,因为它是一个比一般的 Stackoverflow 问题更大的问题(/答案)。赏金将奖励给描述解决方案或(首选)解决方案代码的人。无论如何,这是我感谢帮助我的人并确保我非常感谢您的帮助的方式。

更新:

感谢您到目前为止的所有答案!你的答案是对的。过去几个小时我确实想了很多,我开始意识到数据库实际上从来没有为这样的事情而构建(显示随机数据),它的创建是为了显示快速访问的精确准确的数据。这就是为什么在 PK 上选择 30M 或更多行仍然非常快的原因。这就是为什么我正在考虑在 PHP 中做所有随机的事情。所以我在 PHP 中生成 40 个随机数,然后从正确车型的映射表中选择这 40 行。这个选择IN非常快(比如 0.0006 秒)。在这个选择之后,我得到了 40 个 car_id,我也选择了IN从汽车表。我循环汽车并将它们放在一个数组中并进行一些自定义排序(基于点和视图)。在此之后,我从 40 辆汽车中的所有点中选择一个随机数,并从最接近该点数且观看次数最少的数组中抓取汽车。这样,PHP 会处理随机性以及视图部分和查询,因为您要求精确的数据非常快(每个 0.0006 秒)。

4

7 回答 7

4

我很想给出一个具体的答案,但我需要帮助来理解你的思维过程......

你先写:

我有一个网站,人们可以在上面投票(...)他们最喜欢的汽车。

问题是后来添加到数据库中的汽车获得与之前添加的汽车相同的分数的机会更少。

但是你接着写:

当 70% 的汽车得 1 分、20% 的汽车得 2 分、10% 的汽车得 3 分时,随机点应该选择 70% 的汽车得 1 分、20% 的汽车得 2 分、10% 的汽车得 3 分。

对我来说,鉴于第一条评论,后一种规范几乎没有意义。

恕我直言,您真正想要的是让用户有相同数量的机会为每辆车投票。或者更准确地说,将每辆车的投票与其他车进行比较。

如果您假设(汽车)变量是独立的,那么您需要计算一个选择出现了多少次,而不是它被投票了多少次,并相应地调整您的决策过程。这是一个数学问题,它并没有那么难看,然后它可以被翻译成 SQL,无论好坏——我敢说它可能会更糟。

如果您像我一样假设它们不是独立的,那么您还需要考虑相关性 - 并存储它们彼此出现的次数。因为,嗯,你不会更喜欢这辆梅赛德斯而不是那辆塔塔、那辆新凯或那辆 AvtoVAZ 的可能性非常小。但如果要在同一辆梅赛德斯、宝马、保时捷和法拉利之间进行选择,这个决定可能并不那么明确。

换句话说,您的规范根本没有回答您提出的问题。

我目前正在乞求同意两个小时前发布的答案:选择它们真的很随机​​,没有额外的代码你会很满意......


作为旁注,如果您的 id 确实没有间隙,请在 php 或其他任何内容中生成四个 id 并使用in()语句获取它们。你不会比这更有效率。

于 2013-06-12T20:13:33.113 回答
2

我不确定你是否可以这样做,但如果你忘记了“随机”并创建一个不同的公式来模拟它呢?我的建议是在 中创建一列lastViewed类型datecars table因此在更新该views列期间,它也会lastViewed使用当前日期更新

然后可以通过这种方式完成选择查询:

select * from cars where points=?, car_type=? order by views desc, lastViewed limit 4

此 sql 将始终根据低视图和查看它的最新日期为访问者返回“随机”结果。这个解决方案最酷的部分是它会优先考虑一段时间未查看的内容。因此,当插入一个新car的时,默认值lastViewed可以是从 1900 年开始的日期。

于 2013-06-12T20:46:26.467 回答
1

你可以编写一个执行以下操作的存储过程:

(不要把语法当作大多数伪代码那样正确)

首先选择点:

SELECT @varpoints = points FROM cars ORDER BY RAND() LIMIT 1

这样我们就可以获得点列的随机值。

将该值存储在 var 中并执行以下操作(伪代码):

WHILE (SELECT COUNT(car_id) FROM cars WHERE points = @varpoints ORDER BY views ASC) > 4
{
     SET @varpoints = @varpoints - 1;
}

现在只检索具有所需结果的 SQL:

SELECT car_id FROM cars WHERE points = @varpoints ORDER BY views ASC

那应该做的工作。

这将采用随机点值并使用该值查询汽车。如果它没有得到至少 4,它将减去 1 并重试。如果存在每个点少于 4 辆汽车的机会,那么某种尝试捕获会很好。

于 2013-06-10T15:17:04.510 回答
1

确保您已组合索引car_typepoints列。首先,获取您感兴趣的总行数:

$condition = "car_type=? AND points > 0";
$q_count = "SELECT count(*) FROM cars WHERE {$condition}";
$r_count = mysql_query($q_count);
$car_count = mysql_result($r_count, 0, 0);

$car_count保存我们可以生成以检索汽车的最大整数。我们将在随机数生成器中使用它。现在SELECT您感兴趣的所有行:

$q_cars = "SELECT car_id FROM cars WHERE {$condition}";
$r_cars = mysql_query($q_cars);
$car_ids = array();
for($i = 0; $i < 4; ++$i)
{
    $random_row = rand(0, $car_count);
    $car_ids[] = mysql_result($r_cars, $random_row, 0);
}

我假设来自随机数生成器的正态分布。不检查空表或重复记录,因为在 30M 记录中出现这种情况的概率非常低。您可能需要考虑调整表架构。30M 记录的表查询速度很慢,ORDER BY RAND()真的很糟糕以及使用LIMITand OFFSET

于 2013-06-12T21:40:07.573 回答
1

所以,看起来你的主要问题是速度。在这种情况下,您可以进行一些预处理,比方说 - 有一个可以像队列一样使用的表格,其中包含 4 辆汽车的组,准备显示。当然,这将区分最后一刻查看/投票的汽车,但您可以定期刷新此队列。


  • 当 70% 的汽车得 1 分、20% 的汽车得 2 分、10% 的汽车得 3 分时,随机点应该选择汽车 70% 得 1 分、20% 得 2 分、10% 得 3 分。

如果您真的随机选择它们,那么无需额外代码就可以满足。

于 2013-06-12T17:57:54.983 回答
0

Oracle 使用 Rownum 来表示一个没有间隙的假 ID。

Select rownum, c.* from Cars where points > 1

将为您提供具有有序 ID 的结果集。

有了给定的提示,您可以使用此处引用的以下 mysqlCode 模拟相同的事情

SELECT @rownum:=@rownum+1 rownum, c.* 
  FROM (SELECT @rownum:=0) r, Cars c where points > 1;

现在有了与 oracle 类似的预期结果集,您可以使用子选择选择任意 4 行。假设您知道超过 1 点的汽车的行数。

select * from 
 (the above select)
where rownum in (Random[0], Random[1], Random[2], Random[3]) 

或您想要构建查询的任何方式。

这不会在 mysql 和 php 之间移动整个数据,但会给 mysql 带来一些压力。

于 2013-06-14T06:21:13.220 回答
0

我认为您可以在 vanilla sql 中引用来自http://www.kahunaburger.com/2008/10/13/selecting-random-weighted-records-from-mysql/的帖子来执行此操作,我通过此链接跟踪:MySQL:选择随机条目,但偏重某些条目

您的问题的难点在于概率,并且该帖子提出了同样的问题。解决方案在评论中给出ORDER BY -LOG(1.0 - RAND()) / weighting

SELECT * FROM cars
WHERE points >= 1
    AND car_type = ROUND((RAND() * 2) + 1)
ORDER BY -LOG(1.0 - RAND()) / 70
LIMIT 4;

希望这可以帮助。

于 2013-06-13T00:30:10.453 回答