Выберите переменное количество случайных записей из MySQL - PullRequest
3 голосов
/ 18 февраля 2009

Я хочу показать случайную запись из базы данных. Я хотел бы иметь возможность показать X количество случайных записей, если я выберу. Поэтому мне нужно выбрать топ X записей из случайно выбранного списка идентификаторов

(Никогда не будет более 500 вовлеченных записей на выбор, если только Земля не сильно увеличится в размерах. В настоящее время существует 66 возможных вариантов.)

Эта функция работает, но как мне сделать ее лучше?

/***************************************************/
/* RandomSite */
//****************/
//  Returns an array of random site IDs or NULL
/***************************************************/   
function RandomSite($intNumberofSites = 1) {
    $arrOutput = NULL;
    //open the database
    GetDatabaseConnection('dev');

    //inefficient
    //$strSQL = "SELECT id FROM site_info WHERE major <> 0 ORDER BY RAND() LIMIT ".$intNumberofSites.";";

    //Not wonderfully random
    //$strSQL = "SELECT id FROM site_info WHERE major <> 0 AND id >= (SELECT FLOOR( COUNT(*) * RAND()) FROM site_info ) ORDER BY id LIMIT ".$intNumberofSites.";";

    //Manual selection from available pool of candidates  ?? Can I do this better ??
    $strSQL = "SELECT id FROM site_info WHERE major <> 0;";

    if (is_numeric($intNumberofSites))
    {
        //excute my query
        $result = @mysql_query($strSQL);
        $i=-1;

        //create an array I can work with  ?? Can I do this better ??
        while ($row = mysql_fetch_array($result, MYSQL_NUM))
        {
            $arrResult[$i++] = $row[0];
        }

        //mix them up
        shuffle($arrResult);

        //take the first X number of results  ?? Can I do this better ??
        for ($i=0;$i<$intNumberofSites;$i++)
        {
            $arrOutput[$i] = $arrResult[$i];
        }
    }   

    return $arrOutput;
    }

ВОПРОС ОБНОВЛЕНИЯ: Я знаю об ORDER BY RAND (), я просто не хочу его использовать, потому что ходят слухи, что он не самый лучший в масштабировании и производительности. Я слишком критично отношусь к своему коду. Что у меня работает, ORDER BY RAND () работает, но можно ли сделать его лучше?

БОЛЬШЕ ОБНОВЛЕНИЙ В удостоверениях есть дыры. Там нет тонны оттока, но любая отток, который случается, должен быть одобрен нашей командой, и, следовательно, может быть обработан, чтобы сбросить любое кэширование.

Спасибо за ответы!

Ответы [ 8 ]

3 голосов
/ 18 февраля 2009

Если вы не хотите выбирать с помощью rand ().

Вместо перемешивания используйте array_rand для результата:

$randKeys = array_rand($arrResult, $intNumberofSites);
$arrOutput = array_intersect_key(array_flip($randKeys), $arrResult);

edit: вернуть массив ключей, а не новый массив с ключом => значение

3 голосов
/ 18 февраля 2009

Почему бы не использовать функцию Rand в порядке в запросе вашей базы данных? Тогда вам не нужно заниматься рандомизацией и т. Д. В коде ...

Что-то вроде (я не знаю, законно ли это)

Select *
from site_info
Order by Rand()
LIMIT N

где N - количество записей, которое вы хотите ...

EDIT
Вы профилировали свой код против решения запроса? Я думаю, что вы просто предварительно оптимизируете здесь.

1 голос
/ 18 февраля 2009

Вот три функции, которые я написал и протестировал

Мой ответ

/***************************************************/
/* RandomSite1 */
//****************/
//  Returns an array of random rec site IDs or NULL
/***************************************************/   
function RandomSite1($intNumberofSites = 1) {
    $arrOutput = NULL;
    GetDatabaseConnection('dev');
    $strSQL = "SELECT id FROM site_info WHERE major <> 0;";
    if (is_numeric($intNumberofSites))
    {
        $result = @mysql_query($strSQL);
        $i=-1;
        while ($row = mysql_fetch_array($result, MYSQL_NUM)) {
            $arrResult[$i++] = $row[0]; }
        //mix them up
        shuffle($arrResult);
        for ($i=0;$i<$intNumberofSites;$i++) {
            $arrOutput[$i] = $arrResult[$i]; }
    }   
    return $arrOutput;
    }

JPunyon и многие другие

/***************************************************/
/* RandomSite2 */
//****************/
//  Returns an array of random rec site IDs or NULL
/***************************************************/   
function RandomSite2($intNumberofSites = 1) {
    $arrOutput = NULL;
    GetDatabaseConnection('dev');
    $strSQL = "SELECT id FROM site_info WHERE major<>0 ORDER BY RAND() LIMIT ".$intNumberofSites.";";
    if (is_numeric($intNumberofSites))
    {
        $result = @mysql_query($strSQL);
        $i=0;
        while ($row = mysql_fetch_array($result, MYSQL_NUM)) {
            $arrOutput[$i++] = $row[0]; }
    }   
    return $arrOutput;
    }

OIS с креативным решением, отвечающим цели моего вопроса.

/***************************************************/
/* RandomSite3 */
//****************/
//  Returns an array of random rec site IDs or NULL
/***************************************************/   
function RandomSite3($intNumberofSites = 1) {
    $arrOutput = NULL;
    GetDatabaseConnection('dev');
    $strSQL = "SELECT id FROM site_info WHERE major<>0;";
    if (is_numeric($intNumberofSites))
    {
        $result = @mysql_query($strSQL);
        $i=-1;
        while ($row = mysql_fetch_array($result, MYSQL_NUM)) {
            $arrResult[$i++] = $row[0]; }
        $randKeys = array_rand($arrResult, $intNumberofSites);
        $arrOutput = array_intersect_key($randKeys, $arrResult);
    }   
    return $arrOutput;
    }

Я сделал простой цикл из 10 000 итераций, где я выбрал 2 случайных сайта. Я закрыл и открыл новый браузер для каждой функции, и очистил кеширование между запусками. Я выполнил тест 3 раза, чтобы получить простое среднее значение.

ПРИМЕЧАНИЕ. - Третьему решению не удалось получить менее 2 сайтов, так как функция array_rand имеет другой вывод, если она возвращает набор или единственный результат. Я стал ленивым и не полностью реализовал условное решение для этого случая.

  • 1 в среднем: 12,38003755 секунд
  • 2 в среднем: 12,47702177 секунд
  • 3 в среднем: 12,7124153 секунд
1 голос
/ 18 февраля 2009

Ну, я не думаю, что ORDER BY RAND () будет настолько медленным в таблице с 66 строками, но в любом случае вы можете найти несколько разных решений.

Действительно ли данные редки и / или часто обновляются (поэтому в идентификаторах есть большие пробелы)?

Предполагая, что это не очень разреженно, вы можете выбрать максимальный идентификатор из таблицы, использовать встроенную случайную функцию PHP, чтобы выбрать N различных чисел между 1 и максимальным идентификатором, а затем попытаться извлечь строки с этими идентификаторами из Таблица. Если вы вернули меньше строк, чем выбрали числа, получите больше случайных чисел и повторите попытку, пока у вас не будет нужного количества строк. Это также может быть не очень быстро.

Если данные редки, я бы настроил дополнительный столбец «id-type», который вы убедитесь, что он последовательный. Поэтому, если в таблице 66 строк, убедитесь, что новый столбец содержит значения 1-66. Всякий раз, когда строки добавляются или удаляются из таблицы, вам нужно будет поработать над настройкой значений в этом столбце. Затем используйте ту же технику, что и выше, выбирая случайные идентификаторы в PHP, но вам не нужно беспокоиться о случае «пропущенного идентификатора? Повторить».

0 голосов
/ 18 февраля 2009

Я с JPunyon. Используйте ORDER BY RAND() LIMIT $N. Я думаю, что вы получите больший прирост производительности от $arrResult с большим количеством (неиспользованных) записей, чем от использования функции MySQL RAND ().

function getSites ( $numSites = 5 ) {

    // Sanitize $numSites if necessary

    $result = mysql_query("SELECT id FROM site_info WHERE major <> 0 "
                         ."ORDER BY RAND() LIMIT $numSites");

    $arrResult = array();

    while ( $row = mysql_fetch_array($result,MYSQL_NUM) ) {
        $arrResult[] = $row;
    }

    return $arrResult;
}
0 голосов
/ 18 февраля 2009

Попробуйте это:

SELECT
  @nv := @min + (RAND() * (@max - @min)) / @lc,
  (
  SELECT
    id
  FROM  site_info
  FORCE INDEX (primary)
  WHERE id > @nv
  ORDER BY
    id
  LIMIT 1
  ),
  @max,
  @min := @nv,
  @lc := @lc - 1
FROM
  (
  SELECT @min := MIN(id)
  FROM site_info
  ) rmin,
  (
  SELECT @max := MAX(id)
  FROM site_info
  ) rmax,
  (
  SELECT @lc := 5
  ) l,
  site_info
LIMIT 5

Это выберет случайный идентификатор на каждой итерации с использованием индекса в порядке убывания.

Однако существует небольшая вероятность того, что вы получите меньше результатов, чем хотели, поскольку это не дает второго шанса пропущенным идентификаторам.

Чем больше процентов строк вы выберете, тем больше шанс.

0 голосов
/ 18 февраля 2009

Я бы просто использовал функцию rand () (я полагаю, вы используете MySQL) ...

SELECT id, rand() as rand_idx FROM site_info WHERE major <> 0 ORDER BY rand_idx LIMIT x;
0 голосов
/ 18 февраля 2009
mysql_query("SELECT id FROM site_info WHERE major <> 0 ORDER BY RAND() LIMIT $intNumberofSites")

EDIT Блин, JPunyon был немного быстрее:)

...