40

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

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

我正在尝试修改我的开发人员已经给我的内容,如果设置没有多大意义,非常抱歉。我可能会更改它,但希望尽可能多地保留现有的表设置。

我一直试图弄清楚如何使用 SELECT 和 RAND() 来做到这一点,但不知道如何进行加权。可能吗?

4

11 回答 11

44

这个人问了同样的问题。他和弗兰克说的一样,但权重不正确,在评论中有人建议使用ORDER BY -LOG(1.0 - RAND()) / Multiplier,在我的测试中给出了非常完美的结果。

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

缺点是您无法将权重设置为 0 以暂时禁用某个选项,因为您最终会被零除。但是你总是可以用WHERE Multiplier > 0.

于 2012-09-06T14:06:17.053 回答
14

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

SELECT * FROM tbl AS t1 JOIN (SELECT id FROM tbl ORDER BY -LOG(1-RAND())/weight LIMIT 10) AS t2 ON t1.id = t2.id

在 40MB 表上,通常的查询在我的 i7 机器上需要 1s ,而这个需要 0.04s

有关为什么这更快的解释,请参阅MySQL 从 600K 行中快速选择 10 个随机行

于 2017-01-10T20:07:25.707 回答
7

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

SELECT * FROM tablename ORDER BY (RAND() * Multiplier);
于 2010-03-10T14:39:44.927 回答
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

我认为它会工作:)

于 2010-03-10T14:36:03.127 回答
2

虽然我意识到这是一个关于 MySQL 的问题,但以下内容可能对使用SQLite3的人有用,该 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 文档)。我使用模运算符将数字范围缩小了一点。

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

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

于 2016-09-23T11:13:37.810 回答
1

对于其他人在谷歌上搜索这个主题,我相信你也可以这样做:

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 中设置,也可以作为调用代码中的整数值插入。

子选择将总结所有先前记录的权重,检查它是否超过提供的随机值。

于 2011-08-02T11:00:17.450 回答
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 行...

  1. 重新计算总和。
  2. 重置范围(“upto”列)。
  3. 选择$rand指向的行。

应在每个选择循环中排除先前选择的行。where ... id not in (3, 5);

于 2012-09-22T11:08:24.643 回答
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,这是不正确的。我希望我能告诉你数学。但是我没有足够的代表来发布相关链接。

于 2015-08-30T20:01:40.797 回答
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");

上面代码的解释:

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

仅通过使用 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>';
于 2010-03-10T14:56:40.987 回答
0

无论您做什么,都会很糟糕,因为它将涉及: * 将所有列的总“权重”作为一个数字(包括应用乘数)。* 获取一个介于 0 和该总数之间的随机数。* 获取所有条目并运行它们,从随机数中减去权重,并在项目用完时选择一个条目。

平均而言,你会跑到一半的桌子上。性能 - 除非表很小,否则在内存中的 mySQL 之外执行 - 会很慢。

于 2010-03-10T14:33:15.807 回答
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%

于 2020-10-26T15:58:49.610 回答