我有一个MySQL表,里面有一堆条目,还有一个名为“Multiplier”的列。此列的默认(和最常见)值为0,但可以是任意数字。
我需要做的是随机从该表中选择一个条目。但是,行根据“乘数”列中的数字进行加权。值为0意味着它根本没有加权。值为1意味着它的加权值是两倍,就像条目在表中两次一样。值为2意味着它的加权值是其三倍,就像条目在表中三次一样。
我正在尝试修改我的开发人员已经给我的内容,很抱歉,如果设置没有多大意义。我可以改变它,但希望保留尽可能多的现有表格设置。
我一直试图弄清楚如何使用SELECT和RAND(),但不知道如何进行加权。有可能吗?
答案 0 :(得分:39)
This guy问同样的问题。他说和弗兰克一样,但权重并没有正确,并且有人建议使用ORDER BY -LOG(1.0 - RAND()) / Multiplier
评论,这在我的测试中得到了非常完美的结果。
(如果有任何数学家想解释为什么这是正确的,请赐教!但它确实有效。)
缺点是您无法将权重设置为0以暂时禁用选项,因为您最终将除以零。但您总是可以使用WHERE Multiplier > 0
过滤掉它。
答案 1 :(得分:6)
不要使用0,1和2,而是使用1,2和3.然后您可以将此值用作乘数:
SELECT * FROM tablename ORDER BY (RAND() * Multiplier);
答案 2 :(得分:6)
对于更好的性能(特别是在大表上),首先索引权重列并使用此查询:
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 。
答案 3 :(得分:3)
好吧,我会把权重的逻辑放在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
我认为它会起作用:)
答案 4 :(得分: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>
如果你想选择N行......
$rand
指向的行。应在每个选择循环中排除先前选定的行。 where ... id not in (3, 5);
答案 5 :(得分:1)
SELECT * FROM tablename ORDER BY -LOG(RAND()) / Multiplier;
是否能为您提供正确的分发。
SELECT * FROM tablename ORDER BY (RAND() * Multiplier);
给你错误的发行。
例如,表中有两个条目A和B. A的重量为100,而B的重量为200。 对于第一个(指数随机变量),它给你Pr(A获胜)= 1/3,而第二个给你1/4,这是不正确的。 我希望我能告诉你数学。但是我没有足够的代表来发布相关链接。
答案 6 :(得分:0)
无论你做什么,都是可怕的,因为它会涉及: *将所有列的总“权重”作为一个数字(包括应用乘数)。 *获得0到该总数之间的随机数。 *获取所有条目并运行它们,从随机数中扣除权重,并在用完项目时选择一个条目。
平均而言,你会跑到桌子的一半。性能 - 除非表很小,然后在内存中的mySQL之外执行 - 将是SLOW。
答案 7 :(得分:0)
伪代码(rand(1, num) % rand(1, num))
的结果将更多地朝向0并且更少地朝向num。从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");
上述代码说明:
仅使用MySQL也可以实现。
证明伪代码(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>';
答案 8 :(得分:0)
对于其他人在谷歌上搜索这个主题,我相信你也可以这样做:
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
所有记录的权重总和必须为n-1,而@RAND应为0到n-1之间的随机值。
@RAND可以在SQL中设置,也可以作为调用代码的整数值插入。
子选择将总结所有先前记录的权重,检查它是否超过所提供的随机值。
答案 9 :(得分:0)
虽然我发现这是一个关于MySQL的问题,但以下内容可能对使用 SQLite3 的人有用,后者的RANDOM和LOG实现略有不同。
SELECT * FROM table ORDER BY (-LOG(abs(RANDOM() % 10000))/weight) LIMIT 1;
weight是表中包含整数的列(我在表中使用了1-100作为范围)。
SQLite中的RANDOM()生成介于-9.2E18和+ 9.2E18之间的数字(有关详细信息,请参阅SQLite docs)。我使用模运算符来获取数字范围。
abs()将删除否定值以避免LOG仅处理非零正数的问题。
LOGite()实际上并不存在于SQLite3的默认安装中。我使用php SQLite3 CreateFunction调用来使用SQL中的php函数。有关此信息,请参阅the PHP docs。
答案 10 :(得分:0)
@ali的答案很好用,但您无法控制结果偏向较高或较低权重的程度,可以更改乘数,但这不是一种动态方法。
我通过添加POWER(weight,skewIndex)
而不是weight
对代码进行了优化,这使skewIndex的值大于1的权重更高,而0和1的值的权重更低。
SELECT * FROM tbl AS t1 JOIN (SELECT id FROM tbl ORDER BY -LOG(1-RAND())/POWER(weight,skewIndex) LIMIT 10) AS t2 ON t1.id = t2.id
您可以使用以下方式分析查询结果
SELECT AVG(weight) FROM tbl AS t1 JOIN (SELECT id FROM tbl ORDER BY -LOG(1-RAND())/POWER(weight,skewIndex) LIMIT 10) AS t2 ON t1.id = t2.id
例如,将skewIndex设置为3,我的平均值为78%,而skewIndex为1则平均值为65%