40

多数のエントリを含む MySQL テーブルと、"Multiplier" という列があります。この列のデフォルト (および最も一般的な) 値は 0 ですが、任意の数値にすることができます。

私がする必要があるのは、そのテーブルからランダムに 1 つのエントリを選択することです。ただし、行は「乗数」列の数値に従って重み付けされます。値 0 は、まったく重み付けされていないことを意味します。値 1 は、エントリがテーブルに 2 回あったかのように、重みが 2 倍であることを意味します。値 2 は、エントリがテーブルに 3 回存在するかのように、重みが 3 倍であることを意味します。

開発者から既に提供されているものを変更しようとしているので、セットアップがあまり意味をなさない場合は申し訳ありません. おそらく変更できますが、既存のテーブル設定をできるだけ維持したいと考えています。

SELECT と RAND() を使用してこれを行う方法を理解しようとしていますが、重み付けの方法がわかりません。出来ますか?

4

11 に答える 11

44

この男は同じ質問をします。彼はフランクと同じことを言っていますが、重み付けが適切に行われず、コメントで誰かが を使用することを提案してORDER BY -LOG(1.0 - RAND()) / Multiplierいます。私のテストでは、ほぼ完璧な結果が得られました。

(もしこれが正しい理由を説明したい数学者がいたら、教えてください! しかし、うまくいきます。)

不利な点は、オプションを一時的に無効にするために重み付けを 0 に設定できないことです。ゼロで除算することになります。ただし、 . でいつでも除外できますWHERE Multiplier > 0

于 2012-09-06T14:06:17.053 に答える
14

(特に大きなテーブルで)パフォーマンスを大幅に向上させるには、最初に weight 列にインデックスを付けて、次のクエリを使用します。

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 マシンでは通常のクエリに 1 秒かかり、これには 0.04 秒かかります

なぜこれが速いのかについては、MySQL select 10 random rows from 600K rows fastを参照してください。

于 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 に関する質問であることは認識していますが、RANDOM と LOG の実装が微妙に異なるSQLite3を使用している人にとっては、次のことが役立つ場合があります。

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. 範囲をリセットします (「上限」列)。
  3. $rand指している行を選択します。

以前に選択された行は、各選択ループで除外する必要があります。where ... id not in (3, 5);

于 2012-09-22T11:08:24.643 に答える
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. Multiplier 列の最大値を取得します
  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

あなたが何をするにしても、それには次のことが含まれるため、ひどいことになるでしょう: * すべての列の合計「重み」を 1 つの数値として取得する (乗数の適用を含む)。* 0 からその合計までの乱数を取得します。* すべてのエントリを取得して実行し、乱数から重量を差し引いて、アイテムがなくなったときに 1 つのエントリを選択します。

平均すると、テーブルの半分を走ります。パフォーマンス - テーブルが小さい場合を除き、メモリ内の mySQL の外側で実行すると、遅くなります。

于 2010-03-10T14:33:15.807 に答える