MySQL:select随机input,但重量对某些条目

我有一个MySQL表,里面有一堆条目,还有一个名为“Multiplier”的列。 该列的默认值(最常用)为0,但可以是任何数字。

我需要做的是随机从表中select一个条目。 但是,根据“乘数”列中的数字对行进行加权。 值为0意味着它根本没有加权。 值为1意味着它的权重是两倍,就好像表中有两次一样。 值为2意味着它的权重是三倍,就好像表中有三次一样。

我试图修改我的开发人员已经给我的东西,所以很抱歉如果安装程序没有很大的意义。 我可能可以改变它,但要尽可能保持现有的表设置。

我一直想弄清楚如何用SELECT和RAND()做到这一点,但不知道如何做权重。 可能吗?

这家伙问同样的问题。 他说的和Frank一样,但是权重并不正确,而且在有人build议使用ORDER BY -LOG(RAND()) / Multiplier ,在我的testing中,这个结果非常完美。

(如果有math家想要解释为什么这是正确的,请赐教!但它是有效的。)

缺点是你不能将权重设置为0来临时禁用一个选项,因为你最终将除以零。 但是你总是可以使用WHERE Multiplier > 0过滤。

不要使用0,1和2,而是使用1,2和3.然后,可以将此值用作乘数:

 SELECT * FROM tablename ORDER BY (RAND() * Multiplier); 

为了获得更好的性能 (特别是在大表上),首先索引权重列并使用这个查询:

 SELECT * FROM tbl WHERE id IN (SELECT id FROM (SELECT id FROM tbl ORDER BY -LOG(1-RAND())/weight LIMIT x) t) 

使用了两个子查询,因为MySQL在第一个子查询中不支持LIMIT。

在40MB的桌子上, 通常的查询在我的i7机器上花费1s这个花费0.04s

那么,我会把权重的逻辑放在PHP中:

 <?php $weight_array = array(0, 1, 1, 2, 2, 2); $multiplier = $weight_array[array_rand($weight_array)]; ?> 

和查询:

 SELECT * FROM `table` WHERE Multiplier = $multiplier ORDER BY RAND() LIMIT 1 

我认为这将工作:)

 <?php /** * Demonstration of weighted random selection of MySQL database. */ $conn = mysql_connect('localhost', 'root', ''); // prepare table and data. mysql_select_db('test', $conn); mysql_query("drop table if exists temp_wrs", $conn); mysql_query("create table temp_wrs ( id int not null auto_increment, val varchar(16), weight tinyint, upto smallint, primary key (id) )", $conn); $base_data = array( // value-weight pair array. 'A' => 5, 'B' => 3, 'C' => 2, 'D' => 7, 'E' => 6, 'F' => 3, 'G' => 5, 'H' => 4 ); foreach($base_data as $val => $weight) { mysql_query("insert into temp_wrs (val, weight) values ('".$val."', ".$weight.")", $conn); } // calculate the sum of weight. $rs = mysql_query('select sum(weight) as s from temp_wrs', $conn); $row = mysql_fetch_assoc($rs); $sum = $row['s']; mysql_free_result($rs); // update range based on their weight. // each "upto" columns will set by sub-sum of weight. mysql_query("update temp_wrs a, ( select id, (select sum(weight) from temp_wrs where id <= i.id) as subsum from temp_wrs i ) b set a.upto = b.subsum where a.id = b.id", $conn); $result = array(); foreach($base_data as $val => $weight) { $result[$val] = 0; } // do weighted random select ($sum * $times) times. $times = 100; $loop_count = $sum * $times; for($i = 0; $i < $loop_count; $i++) { $rand = rand(0, $sum-1); // select the row which $rand pointing. $rs = mysql_query('select * from temp_wrs where upto > '.$rand.' order by id limit 1', $conn); $row = mysql_fetch_assoc($rs); $result[$row['val']] += 1; mysql_free_result($rs); } // clean up. mysql_query("drop table if exists temp_wrs"); mysql_close($conn); ?> <table> <thead> <th>DATA</th> <th>WEIGHT</th> <th>ACTUALLY SELECTED<br />BY <?php echo $loop_count; ?> TIMES</th> </thead> <tbody> <?php foreach($base_data as $val => $weight) : ?> <tr> <th><?php echo $val; ?></th> <td><?php echo $weight; ?></td> <td><?php echo $result[$val]; ?></td> </tr> <?php endforeach; ?> <tbody> </table> 

如果你想selectN行…

  1. 重新计算总和。
  2. 重置范围(“上至”列)。
  3. select$rand指向哪一行。

以前select的行应该排除在每个select循环上。 where ... id not in (3, 5);

无论你做什么,这是可怕的,因为它会涉及:*获得所有列的总“权重”作为一个数字(包括应用乘数)。 *获取一个介于0和这个总数之间的随机数。 *获取所有条目并运行,从随机数中扣除重量,并在物品用完时select一个条目。

平均来说,你会跑到一半的桌子上。 性能 – 除非表格很小,那么在mySQL之外的内存中执行 – 将会很慢。

伪码(rand(1, num) % rand(1, num))将朝向num更靠近0而更less。 从num中减去结果得到相反的结果。

所以如果我的应用程序语言是PHP,它应该看起来像这样:

 $arr = mysql_fetch_array(mysql_query( 'SELECT MAX(`Multiplier`) AS `max_mul` FROM tbl' )); $MaxMul = $arr['max_mul']; // Holds the maximum value of the Multiplier column $mul = $MaxMul - ( rand(1, $MaxMul) % rand(1, $MaxMul) ); mysql_query("SELECT * FROM tbl WHERE Multiplier=$mul ORDER BY RAND() LIMIT 1"); 

上面代码的解释:

  1. 在“乘数”列中获取最高值
  2. 计算一个随机乘数值(加权乘数列中的最大值)
  3. 取一个具有该乘数值的随机行

这也可以通过使用MySQL来实现。

certificate伪代码(rand(1, num) % rand(1, num))将向0加权:执行以下PHP代码来查看原因(在本例中,16是最高数字):

 $v = array(); for($i=1; $i<=16; ++$i) for($k=1; $k<=16; ++$k) isset($v[$i % $k]) ? ++$v[$i % $k] : ($v[$i % $k] = 1); foreach($v as $num => $times) echo '<div style="margin-left:', $times ,'px"> times: ',$times,' @ num = ', $num ,'</div>'; 

对于其他Googlesearch这个主题,我相信你也可以做这样的事情:

 SELECT strategy_id FROM weighted_strategies AS t1 WHERE ( SELECT SUM(weight) FROM weighted_strategies AS t2 WHERE t2.strategy_id<=t1.strategy_id )>@RAND AND weight>0 LIMIT 1 

所有logging的权重总和必须是n-1,@RAND应该是0到n-1之间的随机值。

@RAND可以在SQL中设置,也可以从调用代码中插入为整数值。

子查询将总结所有前面logging的权重,检查它是否超过随机值。

 SELECT * FROM tablename ORDER BY -LOG(RAND()) / Multiplier; 

是一个给你正确的分配。

 SELECT * FROM tablename ORDER BY (RAND() * Multiplier); 

给你错误的分配。

例如,表中有两个条目A和B. A的重量为100,而B的重量为200.对于第一个(指数随机variables),它给你Pr(A赢)= 1/3,而第二个给你1/4,这是不正确的。 我希望我能告诉你math。 但是,我没有足够的代表发布相关链接。

虽然我意识到这是一个关于MySQL的问题,但对于使用SQLite3的人来说,下面的内容可能会很有用,它具有不同的RANDOM和LOG实现。

 SELECT * FROM table ORDER BY (-LOG(abs(RANDOM() % 10000))/weight) LIMIT 1; 

重量是包含整数的表中的一列(我用表1-100作为范围)。

SQLite中的RANDOM()生成-9.2E18和+ 9.2E18之间的数字(更多信息参见SQLite文档 )。 我使用模运算符来将数字的范围缩小一点。

abs()将删除负数,以避免LOG只处理非零正数的问题。

LOG()实际上并不存在于SQLite3的默认安装中。 我使用了SQL SQLite3 CreateFunction调用来使用SQL中的php函数。 有关这方面的信息,请参阅PHP文档 。