SQL大表中的随机行(带where子句)

时间:2013-06-10 15:00:06

标签: php mysql sql database

我有一个人们可以在汽车上投票的网站。向用户展示了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点的汽车。
  • 数据库将包含超过30M的汽车,它不是关于汽车,但对于这个问题我觉得更容易:)。
  • 当70%的汽车有1分,20%有2分,10%有3分,比随机分数应该选择汽车70%的时间1分20%2分和10%3分分。
  • 查询将用于向访客显示4辆车,我们都知道用户不耐烦所以查询越快越好:)
  • 我可以(如果需要)使用映射表,它在PK中没有间隙(正如我现在所做的那样)。
  • 将显示某个car_type中的的汽车。例如,4辆车的2型(家用车),因为我不想同时展示跑车和家用车。

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

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

更新

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

7 个答案:

答案 0 :(得分:4)

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

首先写下:

  

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

     

问题在于,后来添加到数据库中的汽车获得与先前添加的汽车相同的点数的机会较少。

但是你继续写下:

  

当70%的汽车有1分,20%有2分,10%有3分,比随机分数应该选择汽车70%的时间1分20%2分和10%3分分。

对我来说,根据第一句话,后一个规范没有任何意义。

Imho,您真正想要的是让用户拥有相同数量的投票机会。或者更准确地说,与每辆车相比,每辆车都要投票。

如果你认为(汽车)变量是独立的,那么你需要计算一个选择出现的次数,而不是投票的次数,并相应地调整你的决策过程。这是一个数学问题,它不是那么难看,然后它可以被转化为SQL,无论好坏 - 我冒昧地说它可能会更糟。

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

换句话说,你的规范并没有像你提出的那样回答问题。

我目前乞求同意两小时前发布的答案:选择它们是非常随意的,如果没有额外的代码你就会感到满意......


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

答案 1 :(得分:2)

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

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

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

此sql将始终返回基于低视图和最新查看日期的访问者的“随机”结果。这个解决方案的一个很酷的部分是它将优先考虑一段时间没有被观看过的那个。 因此,当插入新的car时,lastViewed的默认值可以是1900年之后的日期。

答案 2 :(得分:1)

Yo可以编写一个执行以下操作的商店程序:

(不要采用与大多数伪代码一样正确的语法)

首先选择要点:

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

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

将该值存储在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辆车的机会,那么某种尝试捕获会很好。

答案 3 :(得分:1)

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


  • 当70%的汽车有1分,20%有2分,10%有3分,比随机分数应该选择汽车70%的时间1分20%2分和10%3分分。

如果您选择它们​​是非常随机的,那么无需额外代码即可满足。

答案 4 :(得分: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()非常糟糕,使用LIMITOFFSET

答案 5 :(得分:0)

我认为您可以在引用http://www.kahunaburger.com/2008/10/13/selecting-random-weighted-records-from-mysql/帖子的vanilla sql中执行此操作,我通过此链接跟踪该帖子:MySQL: Select Random Entry, but Weight Towards Certain Entries

问题的难点在于概率和帖子提出了同样的问题。注释中的解决方案为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;

希望这有帮助。

答案 6 :(得分:0)

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

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

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

使用给定的提示,您可以使用以下mysqlCode referred here

模拟相同的事情
SELECT @rownum:=@rownum+1 rownum, c.* 
  FROM (SELECT @rownum:=0) r, Cars c where points > 1;

现在具有与oracle中类似的预期结果集,您可以使用子选择选择任意4行。假设你知道有多个点的汽车行数。

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

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

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