MySQL: выберите произвольную запись, но вес зависит от определенных записей - PullRequest
35 голосов
/ 10 марта 2010

У меня есть таблица MySQL с кучей записей в ней и столбец с именем «Multiplier».Значение по умолчанию (и наиболее распространенное) для этого столбца - 0, но это может быть любое число.

Что мне нужно сделать, это выбрать одну запись из этой таблицы случайным образом.Однако строки взвешиваются в соответствии с числом в столбце «Множитель».Значение 0 означает, что оно вообще не взвешено.Значение 1 означает, что оно взвешено вдвое больше, чем если бы запись была в таблице дважды.Значение 2 означает, что оно взвешено в три раза больше, как если бы запись была в таблице три раза.

Я пытаюсь изменить то, что мои разработчики уже дали мне, извините, если установка нене имеет большого смысла.Я, возможно, мог бы изменить его, но хотел бы сохранить как можно большую часть существующих настроек таблицы.

Я пытался выяснить, как это сделать с помощью SELECT и RAND (), но не знаю, каксделать взвешивание.Возможно ли это?

Ответы [ 10 ]

40 голосов
/ 06 сентября 2012

Этот парень задает тот же вопрос. Он говорит так же, как и Фрэнк, но весовые коэффициенты не получаются правильными, и в комментариях кто-то предлагает использовать ORDER BY -LOG(1.0 - RAND()) / Multiplier, что в моем тестировании дало почти идеальные результаты.

(Если кто-то из математиков хочет объяснить, почему это правильно, пожалуйста, просветите меня! Но это работает.)

Недостатком было бы то, что вы не могли бы установить весовое значение 0, чтобы временно отключить опцию, так как в итоге вы бы делили на ноль. Но вы всегда можете отфильтровать его с помощью WHERE Multiplier > 0.

6 голосов
/ 10 января 2017

Для гораздо лучшей производительности (особенно для больших таблиц), сначала индексируйте весовой столбец и используйте этот запрос:

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

Используются два подзапроса, поскольку MySQL еще не поддерживает LIMIT в первом подзапросе.

В таблице 40 МБ обычный запрос занимает 1 с на моем компьютере с i7, а этот запрос занимает 0,04 с .

6 голосов
/ 10 марта 2010

Не используйте 0, 1 и 2, но 1, 2 и 3. Тогда вы можете использовать это значение как множитель:

SELECT * FROM tablename ORDER BY (RAND() * Multiplier);
3 голосов
/ 10 марта 2010

Ну, я бы поставил логику весов в 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

Я думаю, что это будет работать:)

1 голос
/ 30 августа 2015
SELECT * FROM tablename ORDER BY -LOG(RAND()) / Multiplier;

Это тот, который дает вам правильное распределение.

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

Дает вам неправильный дистрибутив.

Например, в таблице есть две записи A и B. А весит 100, а В весит 200. Для первого (экспоненциальная случайная величина) он дает Pr (выигрыш) = 1/3, а второй дает 1/4, что не правильно. Я хотел бы показать вам математику. Однако мне не хватает представителя для публикации соответствующей ссылки.

1 голос
/ 22 сентября 2012
<?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);

0 голосов
/ 23 сентября 2016

Хотя я понимаю, что это вопрос к MySQL, следующее может быть полезно для тех, кто использует SQLite3 , у которого слегка различаются реализации RANDOM и LOG.

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

weight - это столбец в таблице, содержащий целые числа (я использовал диапазон от 1 до 100 в своей таблице).

RANDOM () в SQLite создает числа от -9.2E18 до + 9.2E18 (для получения дополнительной информации см. Документы SQLite ). Я использовал оператор по модулю, чтобы немного уменьшить диапазон чисел.

abs () удалит негативы, чтобы избежать проблем с LOG, который обрабатывает только ненулевые положительные числа.

LOG () на самом деле отсутствует при установке SQLite3 по умолчанию. Я использовал вызов php SQLite3 CreateFunction, чтобы использовать функцию php в SQL. См. PHP документы для получения информации об этом.

0 голосов
/ 02 августа 2011

Для других, прибегая к помощи этой темы, я думаю, вы также можете сделать что-то вроде этого:

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 или вставлен как целочисленное значение из вызывающего кода.

Подвыбор суммирует веса всех предыдущих записей, проверяя, что он превышает предоставленное случайное значение.

0 голосов
/ 10 марта 2010

Результат псевдокода (rand(1, num) % rand(1, num)) будет больше к 0 и меньше к 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>';
0 голосов
/ 10 марта 2010

Что бы вы ни делали, это ужасно, потому что это будет включать в себя: * Получение общего «веса» для всех столбцов в виде ОДНОГО числа (включая применение множителя). * Получение случайного числа от 0 до этой суммы. * Получение всех записей и их прогон, вычитание веса из случайного числа и выбор одной записи, когда у вас закончились предметы.

В среднем вы будете бегать по половине стола. Производительность - если таблица не мала, а затем делать это вне mySQL в памяти - будет медленным.

...