PDA

Просмотр полной версии : [Без оффтопа] Развлекаемся с SQL


Alisher Umarov
13.10.2013, 15:50
Ставим задачи - находим эффектные решения.
(В базе MySQL)

1. Виртуальный колхоз "Кизил пиёз. Пять бригад каждый день сдают на склад пиёз в кг.
Текущий отчёт:
За день. За неделю. За месяц. За квартал. За год.

ps. Без флейма плиз.
Красивые решения плюсуем.

JackDaniels
13.10.2013, 16:26
2. Дано 1М записей;
Не самый производительный сервер;

Нужно: ORDER BY RAND() LIMIT 10;


В качестве временного решения работает:

Первый запрос: узнаем количество записей в базе ($min, $max)
В PHP генерим 10 "случайных" индексов: $id[$i] = rand($min, $max);
Второй запрос: UNION (SELECT * FROM `a` WHERE `id` = $id[0] LIMIT 1) UNION (SELECT * FROM `a` WHERE `id` = $id[1] LIMIT 1)… и так далее.
Проверяем результат, если записи по одному из ID не было (в ID бывают "дырки"), и получили меньше чем 10, добираем нужное количество запросов.

Такая штуковина работает быстро (в сотни, и даже тысячи раз быстрее родного BY RAND()), но неэстетично. :)

Denis Shlyapnikov
13.10.2013, 16:54
`id` in уже не ?

Denis Shlyapnikov
13.10.2013, 16:57
Я как-то работал с записями больше чем 1М, там я реализовал "сложную (с relations)" выборку через временные таблицы. Работает не то что быстро, а моментально.

JackDaniels
13.10.2013, 17:14
там я реализовал "сложную (с relations)" выборку через временные таблицы
Чем это поможет при сортировке?
Или это к вопросу №1 было?

Denis Shlyapnikov
13.10.2013, 17:20
Нет, Руслан. Это к вашей задачи решение. Спокойно можно использовать ORDER BY RAND() + дополнительные маневры в виде where, group, join

JackDaniels
13.10.2013, 17:38
к вашей задачи решение. Спокойно можно использовать ORDER BY RAND() + дополнительные маневры в виде where, group, join
Так хотелось бы решение как раз и увидеть.
Здесь его нет, только слово «маневры». :)

Alisher Umarov
13.10.2013, 19:08
Зачем узнавать max если дано 1М записей?

Какое практическое значение у order by rand()? Сортировка по случайному значению?

Ну в общем читабельно тут

http://hudson.su/2010/09/16/mysql-optimizaciya-order-by-rand/

JackDaniels
13.10.2013, 19:12
Зачем узнавать max если дано 1М записей?
Это ведь приблизительно.
Элементы добавляются/удаляются.

Какое практическое значение у order by rand()? Сортировка по случайному значению?
Порой нужно показать что-то в случайном порядке.

Denis Shlyapnikov
13.10.2013, 19:15
Примерно так можно работать с большим объемом данных, где нужно делать выборки:
create temporary table`temp` select * `db` .... , дальше уже обращаться ко временной таблице select .. from `temp`

JackDaniels
13.10.2013, 19:20
Примерно так можно работать с большим объемом данных, где нужно делать выборки:
create temporary table`temp` select * `db` .... , дальше уже обращаться ко временной таблице select .. from `temp`

Реально не понимаю, как это поможет при сортировке 1М полей с данными.
Можно подробнее?

Denis Shlyapnikov
13.10.2013, 19:22
Скорость Руслан, Скорость. Это альтернатива вашему временному решению.
После чего можно смело писать
select * fromt temp order by rand()

JackDaniels
13.10.2013, 19:34
Скорость Руслан, Скорость. Это альтернатива вашему временному решению.
После чего можно смело писать
select * fromt temp order by rand()

Так сортировка будет так же в памяти, что и вызывает основные тормоза при by rand().
Но за идею спасибо, нужно будет апробировать на практике. :187:

Alisher Umarov
13.10.2013, 19:39
Мне понравился ответ с выборкой по номерам строк, которые не могут быть с дырами.

Предложение. Давайте примеры ближе к жизни. Нас люди (молодые патриоты) читают. ;)
Для специфики есть гугл или хабр.

Denis Shlyapnikov
13.10.2013, 19:43
Перебор базы в памяти работает во много раз быстрее, попробуйте, возможно, вы и не заметите задержки при рандомной выборки

Alisher Umarov
13.10.2013, 19:43
Примерно так можно работать с большим объемом данных, где нужно делать выборки:
create temporary table`temp` select * `db` .... , дальше уже обращаться ко временной таблице select .. from `temp`

На сколько я въехал для этого ещё базу подготовить стоит. Памяти для temp может не хватить.

JackDaniels
13.10.2013, 19:46
Перебор базы в памяти работает во много раз быстрее, попробуйте, возможно, вы и не заметите задержки при рандомной выборки

Не все так просто: http://habrahabr.ru/post/54176/

В процессе выполнения этого запроса MySQL записывает во временную таблицу все (!!!) строки исходной таблицы, с одним новым полем, в которое записываются результаты функции RAND () — т.е. набор произвольных значений. Затем эта временная таблица сортируется filesort по добавленному полю с произвольными значениями и далее выбираются первые 10 записей. Полный ппц. А теперь представтье что будет если в исходной таблице 10 000 записей. А что если 1 000 000? А что если эту выборку надо делать раз десять в секунду. Да тут любой супер-пупер сервер надолго уйдет в раздумья.

JackDaniels
13.10.2013, 19:47
Давайте примеры ближе к жизни.
Задача, кстати, из практики.

Denis Shlyapnikov
13.10.2013, 19:51
ну, это да, к ~ для 1млн записей (таблица, в которой есть поле `text`) требуется 1GB RAM.
Мой способ это скорее исключение, чем правило для использования. Он будет оправдан, если с этой таблицой будут происходит несколько манипуляций, но и для order by random() вполне подойдет, мне кажется.

Denis Shlyapnikov
13.10.2013, 19:53
Руслан, у меня как раз есть подопытная таблица от форума gamertalk.uz (около 0.5М постов)
Завтра потестю и ваш и свой вариант. Результат скинут сюды.

Alisher Umarov
13.10.2013, 19:55
Давайте примеры ближе к жизни.
Задача, кстати, из практики.

Не спорю. Просто с трудом представляю что в поле ID можно допустить "дыры".

Denis Shlyapnikov
13.10.2013, 19:56
имеется ввиду "дыры" - что запись удалили, при условии что там AI в таком режиме работает, что не заполняет удаленные ID

Alisher Umarov
13.10.2013, 20:01
имеется ввиду "дыры" - что запись удалили, при условии что там AI в таком режиме работает, что не заполняет удаленные ID

А, понятно. Операции в живой базе? Ясно.

Кстати есть скрипты забить базу данными для теста? С возможностью выбора кол строк?

JackDaniels
13.10.2013, 20:02
Руслан, у меня как раз есть подопытная таблица от форума gamertalk.uz (около 0.5М постов)
Завтра потестю и ваш и свой вариант. Результат скинут сюды.

Было бы шикарно — благодарю!

Denis Shlyapnikov
13.10.2013, 21:05
имеется ввиду "дыры" - что запись удалили, при условии что там AI в таком режиме работает, что не заполняет удаленные ID

А, понятно. Операции в живой базе? Ясно.

Кстати есть скрипты забить базу данными для теста? С возможностью выбора кол строк?
Да в принципе, вот, написал сейчас небольшой скрипт, который генерирует и заполняет базу данными:

<?php
/**
* @author Denis
* @copyright 2013
*/

// HEADER
header('Content-type: text/html; charset=utf-8');
mb_http_input('UTF-8');
mb_http_output('UTF-8');
mb_internal_encoding("UTF-8");

set_time_limit(0);

if(ini_get('max_execution_time') != 0)
exit('Время выполнения ограничено!');

error_reporting(E_ALL | E_STRICT) ;
ini_set('display_errors', 'On');

$sample_text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas, arcu at gravida bibendum, mi sapien tempor nisl, non malesuada justo nibh ac elit. Aenean ullamcorper felis at augue viverra porttitor ac sit amet orci. Donec rutrum suscipit interdum. Suspendisse molestie, diam et faucibus placerat, augue libero sollicitudin tortor, vel viverra justo justo a mi. Donec tempor nunc semper massa mattis tempus. Donec viverra, metus id porta auctor, ligula neque rutrum magna, sit amet vulputate ligula augue nec erat. Nulla facilisi. Phasellus adipiscing risus at dolor molestie commodo. Curabitur placerat, orci ut mollis tempus, urna enim sollicitudin felis, at facilisis tortor metus nec nulla.
Nulla sit amet quam diam. Phasellus rutrum magna eu enim tincidunt, et porta tortor hendrerit. Aliquam dapibus justo erat, nec rhoncus nunc ornare eget. Praesent eget augue vehicula, pulvinar diam a, rutrum orci. Donec tincidunt, elit vitae volutpat dictum, ligula nisl ornare nulla, et volutpat sem nisi blandit quam. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Interdum et malesuada fames ac ante ipsum primis in faucibus. Quisque semper magna lacinia fringilla viverra.
In eget ligula sapien. Praesent eget ligula egestas, ullamcorper arcu id, suscipit massa. Nam sagittis luctus vulputate. Nam id sodales nibh, at ultricies velit. Duis pretium bibendum justo nec molestie. Nullam adipiscing auctor ante, dignissim tincidunt odio scelerisque et. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum et diam et arcu convallis tempor in pulvinar ante. Proin tempor, augue non interdum elementum, est ligula lacinia tellus, non laoreet turpis lectus ac mi. Duis vel dictum augue. Fusce viverra nisl a ipsum tristique, aliquam gravida eros semper. Aenean sem turpis, sagittis vel luctus nec, molestie eget velit.
Quisque faucibus, velit id sagittis pulvinar, erat augue tempus nisl, vitae ullamcorper risus arcu at massa. Duis convallis, tellus eu aliquam volutpat, turpis est lobortis nibh, eu lacinia orci risus et elit. Nunc et nunc nisi. Suspendisse sit amet vestibulum enim, id ullamcorper velit. Sed pulvinar velit eget venenatis pharetra. Aenean in enim vehicula, blandit sapien eu, ullamcorper sapien. In ut nunc a justo consectetur egestas. Proin vel orci semper, gravida felis nec, mattis orci.
Nullam et elit vitae mi venenatis dignissim et nec ligula. Phasellus sagittis velit eu consectetur tempor. Maecenas lacinia consequat risus, a dignissim justo volutpat vestibulum. Vivamus faucibus sed lacus et viverra. Sed rutrum tortor tortor, eget hendrerit mauris suscipit sed. Suspendisse laoreet rhoncus dolor, at semper arcu sodales vitae. Nulla id enim sit amet justo placerat accumsan nec quis nisl. Donec et justo ac purus venenatis sollicitudin nec id ipsum. Duis consequat lorem erat, sed molestie sapien iaculis et. In laoreet aliquam purus, fermentum laoreet orci euismod at. Cras ultrices pulvinar tempus. Suspendisse malesuada eleifend leo, sit amet laoreet diam cursus a. Duis metus diam, ultrices a erat a, ultrices tempor lectus. Donec facilisis fermentum diam a porta. Maecenas volutpat risus eget ornare vestibulum.";

/* Connecting, selecting database */
$db_link = mysql_connect("localhost", "root", "my_password");
if (!$db_link) {
die("Could not connect: " . mysql_error());
}

mysql_query("CREATE DATABASE IF NOT EXISTS `test_db`", $db_link) or die("Error creating database: " . mysql_error($db_link));
mysql_select_db("test_db", $db_link) or die("Could not select database");
mysql_query("
CREATE TABLE IF NOT EXISTS `test` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(250) NOT NULL,
`text` text NOT NULL,
`category_id` int(10) unsigned NOT NULL DEFAULT '0',
`user_id` int(10) unsigned NOT NULL DEFAULT '0',
`created` int(10) unsigned NOT NULL DEFAULT '0',
`modified` int(10) unsigned NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
KEY `category_id` (`category_id`),
KEY `user_id` (`user_id`),
KEY `created` (`created`),
KEY `modified` (`modified`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=0;
", $db_link) or die("ERROR Create Table: " . mysql_error($db_link));

/* Performing SQL query */

class SampleText
{
var $text = '';
var $min = 1;
var $max = 0;
var $iter = 1000000;
var $db_link;

public function __construct($sample_text, $db_link)
{
$this->text = $sample_text;
$this->max = strlen($this->text);
$this->db_link = $db_link;
}

private function randText($max = 0, $min = 0)
{
$max = intval($max);
$min = intval($min);

$min = ($min > 0 && $min >= $this->min) ? $min : $this->randNum();
$max = ($max > 0 && $max <= $this->max) ? $max : $this->randNum();

return substr($this->text, $min, $max);
}

private function randNum($s = 0, $e = 0)
{
$s = intval($s);
$e = intval($e);

$s = $s > 0 ? $s : $this->min;
$e = $e > 0 ? $e : $this->max;

return rand($s, $e);
}

public function insert($iter = 0)
{
$iter = intval($iter);
$iter = $iter > 0 && $iter <= $this->iter ? $iter : $this->iter;
$sql = array();
$i = 0;
$time = time();

while($iter > 0)
{
if($i > 10)
{
if(count($sql))
mysql_query('
insert into `test`
(`name`, `text`, `category_id`, `user_id`, `created`, `modified`)
values '.implode(',', $sql),
$this->db_link
) or die("Insert ERROR: " . mysql_error($this->db_link));

$i = 0;
$sql = array();
}

array_push($sql, "('" . mysql_real_escape_string($this->randText(120)) . "', '" . mysql_real_escape_string($this->randText()) . "', " . $this->randNum(0, 10000) . ", " . $this->randNum(0, 10000) . ", " . $time . ", " . $time . ")");

$i++ ;
$iter--;
}
}
}

$s = new SampleText($sample_text, $db_link);
$s->insert(100);

/* Closing connection */
mysql_close($db_link);
?>


Интересные строки тут: 28 и 124. В 28-ой строчке настраиваем доступ к базе (обязательно рут, либо чтобы были права на создание базы и таблиц, ну и собственно на инсерт)
На 124 строке указываем кол-во строк, которое надо вбить в базу. :187:

Alisher Umarov
13.10.2013, 22:05
О! Респект!
Сейчас прогоним.

Alisher Umarov
13.10.2013, 22:34
На 124 строке указываем кол-во строк, которое надо вбить в базу.

Интересно.

Создано 99 строк при 100.
Создано 990 строк при 1000. ;)

JackDaniels
13.10.2013, 22:49
Создано 990 строк при 1000.
Пофиг, генерьте 990 000 :)

Denis Shlyapnikov
13.10.2013, 23:04
Итак, результаты по трем методам выборок из базы,в которой > 1млн записей, и имеется поле text:
http://i.pro-wizard.ru/test/select_db_tablerows.png



Обычный способ order by rand() http://i.pro-wizard.ru/test/select_db_method-rand.png
Выборка через `id` in - сначала получаем рандонмные ID из таблицы, потом их просто вытаскиваем через where http://i.pro-wizard.ru/test/select_db_method-rand_by_script.png
Решение со временными таблицами: :116:http://i.pro-wizard.ru/test/select_db_method-rand_by_temptable.png

Победитель очевиден ))


Вот скрипт для тестов:

<?php
/**
* @author Denis
* @copyright 2013
*/

// HEADER
header('Content-type: text/html; charset=utf-8');
mb_http_input('UTF-8');
mb_http_output('UTF-8');
mb_internal_encoding("UTF-8");

set_time_limit(0);

if(ini_get('max_execution_time') != 0)
exit('Время выполнения ограничено!');

error_reporting(E_ALL | E_STRICT) ;
ini_set('display_errors', 'On');

/* Connecting, selecting database */
$db_link = mysql_connect("localhost", "root", "my_password");
if (!$db_link) {
die("Could not connect: " . mysql_error());
}

// rand , rand_by_script, rand_by_temptable
$method = 'rand_by_temptable'; // test method
$limit = 10;

mysql_select_db("test_db", $db_link) or die("Could not select database");

$time_init = microtime(true);

function convert($size)
{
$unit = array('b','kb','mb','gb','tb','pb');
return @round($size/pow(1024,($i=floor(log($size,1024)))),2).' '.$unit[$i];
}

echo '<!doctype html><html><head><meta charset="UTF-8"/><title>test</title></head><body>';
echo "<span style='color:blue'>START MEMORY:</span> <strong>" . convert(memory_get_usage()) . '</strong>';
echo "<hr/>";

$limit = intval($limit);
$rows = array();

function print_array($rows)
{
if(is_array($rows) && count($rows))
{
foreach($rows as $key => $row)
{
echo '<div>';
echo '<h2>NUM: <span style="color:green">';
echo $key + 1 . '</span>.';
echo $row['name'] . '</h2>';
echo '<p>' . $row['text'] . '</p>';
echo '<ul>';
echo '<li>CATEGORY_ID: ' . $row['category_id'] . '</li>';
echo '<li>USER_ID: ' . $row['user_id'] . '</li>';
echo '<li>CREATE TIME: ' . $row['created'] . '</li>';
echo '<li>MODIFIE TIME: ' . $row['modified'] . '</li>';
echo '</ul>';
echo '<hr/>';
echo '</div>';
}
}
}

switch($method)
{
case 'rand':

$sql = 'select * from `test` order by rand() limit ' . $limit;
$query = mysql_query($sql, $db_link) or die("Mysql query ERROR: " . mysql_error());

while($row = mysql_fetch_assoc($query))
$rows[] = $row;

break;

case 'rand_by_script':

$query = mysql_query('select max(`id`) as `max` from `test`', $db_link) or die("Mysql query ERROR: " . mysql_error());
$max = mysql_fetch_assoc($query);
$max = $max['max'];

$query = mysql_query('select min(`id`) as `min` from `test`', $db_link) or die("Mysql query ERROR: " . mysql_error());
$min = mysql_fetch_assoc($query);
$min = $min['min'];

$ids = array();

function getRandId()
{
global $min, $max;

return rand($min, $max);
}

function getIds($iter, $ids)
{
global $limit;

while($iter > 0)
{
array_push($ids, getRandId());
$iter--;
}

$ids = array_unique($ids);
$sum = count($ids) - $limit;

if($sum > 0)
return getIds($sum, $ids);

return $ids;
}

function rquery($sql_limit, $ids)
{
global $db_link, $limit;
static $q;

$query = mysql_query('select * from `test` where `id` in (' . implode(',', $ids) . ') limit ' . $sql_limit, $db_link) or die("Mysql query ERROR: " . mysql_error());
$sum = $limit - mysql_num_rows($query);

$q[] = $query;

if($sum > 0)
return rquery($sum, getIds($sum, array()));

return $q;
}

$querys = rquery($limit, getIds($limit, $ids));

if(count($querys))
{
foreach($querys as $query)
{
if(!is_resource($query))
continue;

while($row = mysql_fetch_assoc($query))
$rows[] = $row;
}
}

break;

case 'rand_by_temptable':

mysql_query('create temporary table `temp_db` select * from `test`', $db_link) or die("Mysql query ERROR: " . mysql_error());

$sql = 'select * from `temp_db` order by rand() limit ' . $limit;
$query = mysql_query($sql, $db_link) or die("Mysql query ERROR: " . mysql_error());

while($row = mysql_fetch_assoc($query))
$rows[] = $row;

break;
}

print_array($rows);

echo "<span style='color:blue'>METHOD:</span> <strong>" . $method . '</strong>';
echo '<br/>';
echo "<span style='color:blue'>SQL_LIMIT:</span> <strong>" . $limit . '</strong>';
echo "<hr/>";
echo "<span style='color:blue'>END MEMORY:</span> <strong>" . convert(memory_get_usage()) . '</strong>';
echo '<br/>';
echo "<span style='color:blue'>PEAK MEMORY:</span> <strong>" . convert(memory_get_peak_usage()) . '</strong>';
echo "<hr/>";
echo "<span style='color:blue'>TIME:</span> <strong>" . round(microtime(true) - $time_init, 2) . ' sec.</strong>';

echo '</body></html>';

JackDaniels
13.10.2013, 23:08
Победитель очевиден ))
1. 28.5 с
2. 0.14 с
3. 207.8 с

Вполне логично, спасибо.

Denis Shlyapnikov
13.10.2013, 23:11
Победитель очевиден ))
1. 28.5 с
2. 0.14 с
3. 207.8 с

Вполне логично, спасибо.
Вторым методом и более лучший результат можно получить, например 0.03 было )) и это на win, думаю, что на linux еще быстрее :)

Denis Shlyapnikov
13.10.2013, 23:13
Может кто затестит все три теста на мощном linux сервере? Интересно результаты посмотреть ))

JackDaniels
13.10.2013, 23:17
Победитель очевиден ))
1. 28.5 с
2. 0.14 с
3. 207.8 с

Вполне логично, спасибо.
Вторым методом и более лучший результат можно получить, например 0.03 было )) и это на win, думаю, что на linux еще быстрее :)

Ну второй будет, конечно, самым быстрым, там же MySQL просто делает банальную выборку.
Минус в том, что если "дырок" в ID много, то пока получишь 10 (а если нужно 100-1000) элементов, то цикл может повторяться значительное количество раз…
Еще минус, что нет возможности добавить дополнительное условие, ведь вытаскивается конкретная запись по ID.

В итоге имеем, что часто оптимален родной RAND(). :187:

Denis Shlyapnikov
13.10.2013, 23:19
Если это условие одинаков для всех найденных айди, то его можно использовать. Если нет, то тогда и запрос надо по-другому строить.

Котофей
14.10.2013, 00:04
Я так и не понял, почему создание временной таблицы должно было уменьшить время выборки?
Мускуль ведь для этого создает временный файл и пишет туда данные. Отсюда и временные затраты

Denis Shlyapnikov
14.10.2013, 00:07
файл != оперативная память (ram)

Котофей
14.10.2013, 00:29
файл != оперативная память (ram)

Да что вы говорите. А я то думал... А вон оно как оказывается!

Denis Shlyapnikov
14.10.2013, 00:39
Тогда наверное я неправильно вашего вопроса понял

Alisher Umarov
14.10.2013, 00:55
На
mysql> select max(id) as maxid from test;
+-------+
| maxid |
+-------+
| 1980 |
+-------+
1 row in set (0.00 sec)

1. Rand = 0.09
2. Script = 0
3. Temptable =0.31


Для

mysql> select max(id) as maxid from test;
+-------+
| maxid |
+-------+
| 99 |
+-------+
1 row in set (0.00 sec)

1. Rand = 0.02
2. Script = 0
3. Temptable =0.04

Котофей
14.10.2013, 01:43
Тогда наверное я неправильно вашего вопроса понял

Вы это к чему написали:
файл != оперативная память (ram)
??

Denis Shlyapnikov
14.10.2013, 10:53
Ни к чему.

У кого еще есть варианты по рандомной выборки из базы ?)
Необязательно на php, но обязательно при работе с MySQL

DarkUser
14.10.2013, 12:39
Второй запрос: UNION (SELECT * FROM `a` WHERE `id` = $id[0] LIMIT 1) UNION (SELECT * FROM `a` WHERE `id` = $id[1] LIMIT 1)… и так далее.
Проверяем результат, если записи по одному из ID не было (в ID бывают "дырки"), и получили меньше чем 10, добираем нужное количество запросов.
Зачем извращяться с поиском ID-шника, если можно сразу выбрать запись с нужным порядковым номером?

Да, и если выбирать нужно часто, а меняется таблица редко, то есть смысл сразу ID-шники в массив выгрузить, и потом уже по нему рандомом выбирать...

JackDaniels
14.10.2013, 13:18
Зачем извращяться с поиском ID-шника, если можно сразу выбрать запись с нужным порядковым номером?
Разве перед получением строки по порядковому номеру ненужно сортировать таблицу по какому-то из признаков?

Rooslan Khayrov
14.10.2013, 13:27
array_push($sql, "('" . mysql_real_escape_string($this->randText(120)) . "', '" . mysql_real_escape_string($this->randText()) . "', " . $this->randNum(0, 10000) . ", " . $this->randNum(0, 10000) . ", " . $time . ", " . $time . ")");
Мои глаза... Генерация SQL конкатенацией строк до сих пор в моде?

Поднял для экспериментов облачный сервер на selectel.ru: 8 GB RAM, 8 потоков (Xeon E5-2630@2.3 GHz), Debian 7 amd64, MySQL 5.5.31.

В my.cnf поправил следующее:
key_buffer_size = 128M
sort_buffer_size = 64M
query_cache_limit = 4M
query_cache_size = 512M
innodb_file_per_table
innodb_buffer_pool_size = 4G
innodb_log_file_size = 512M

Скриптом Дениса сгенерил базу на миллион записей, погонял запросы, чтобы поднять таблицу в кэш, и попробовал самый простой вариант:
SELECT * FROM test ORDER BY RAND() LIMIT 10;
Время выполнения 9-11 сек.
Попробовал помочь планировщику, чтобы не сортировал строки целиком:
SELECT test.* FROM test JOIN
(SELECT id FROM test ORDER BY RAND() LIMIT 10) sub
ON test.id = sub.id;
800-1000 мс, куда лучше.

Погасил MySQL, поднял PostgreSQL. Версия 9.1.9, в конфиге поправил только:
shared_buffers = 4G
work_mem = 64M
maintenance_work_mem = 64MB
Создал базу с такой же схемой и залил те же самые данные, что и в MySQL.
SELECT * FROM test ORDER BY random() LIMIT 10;
1200-1300 мс.
SELECT test.* FROM test JOIN
(SELECT id FROM test ORDER BY random() LIMIT 10)
sub ON test.id = sub.id;
600-650 мс
Кто интересовался, чем постгрес лучше — в частности вот.

Причину колоссального отрыва на первом запросе легко выяснить в плане:
QUERY PLAN
-----------
Limit (cost=308654.61..308654.63 rows=10 width=1060) (actual time=2989.145..2989.171 rows=10 loops=1)
-> Sort (cost=308654.61..311154.60 rows=999999 width=1060) (actual time=2989.141..2989.150 rows=10 loops=1)
Sort Key: (random())
Sort Method: top-N heapsort Memory: 38kB
-> Seq Scan on test (cost=0.00..287044.99 rows=999999 width=1060) (actual time=81.551..1435.582 rows=999999 loops=1)
Для запросов вида ORDER BY ... LIMIT N, где N << count(*) постгрес использует потоковый алгоритм с приоритетной очередью (bounded heap), в результате чего в плане доминируют затраты на sequential scan, а сортировка обходится минимальной памятью.

При достаточно равномерном распределении автоинкрементного ключа способ с выборкой N случайных ID в диапазоне min..max, наверное, самый практичный.
Если с ключом проблемы, а запросы на N случайных записей ну очень важны, я бы попробовал материализовать это так:
ALTER TABLE test ADD COLUMN rndkey double precision NOT NULL DEFAULT random();
CREATE INDEX test_rndkey ON test(rndkey);
Выбор одной случайной записи:
SELECT * FROM test WHERE rndkey > random() ORDER BY rndkey LIMIT 1;
(Не LIMIT N потому что в таком случае в выборке рядом всегда будут оказываться одни и те же записи. В принципе, решаемо регулярным обновлением колонки с перестройкой индекса — смотря что нам важнее).
Цена такого запроса — один проход по хорошо сбалансированному индексу. На «горячих» данных — единицы миллисекунд в любой базе. Запрос с некоторой вероятностью может не вернуть данных вообще, поэтому его нужно повторять, пока не наберётся N строк. Все равно дешевле, чем full scan. Для минимизации накладных расходов по общению с базой можно завернуть в хранимку.

Самый эффективный способ, пожалуй, должен быть реализован напрямую в базе, как TABLESAMPLE в SQL Server: имея карту данных на диске, выбрать некоторое количество случайных страниц указанной таблицы, и вытянуть строки оттуда — минуя индексы и всё остальное.

Denis Shlyapnikov
14.10.2013, 14:40
Т.е. получается, что если mysql, то лучший способ это через помощь скриптов, а если протектед, то можно спокойно и через сам запрос решать?

Мои глаза... Генерация SQL конкатенацией строк до сих пор в моде?
Да фиг его знает, что сейчас в моде. Я уже забыл кодла последний раз писал нативно, сейчас в основном фреймворки yii, kohana. Поэтому, как вспомнил так и записал ))

spykee
14.10.2013, 14:54
Oracle

Случайная 10 выборка из более 60 миллионов записей без доп массивов, временных таблиц, одним селектом занял 16-20 сек.

А если известны значения мах и мин значений ключа таблицы то - 0.5 сек.

JackDaniels
14.10.2013, 15:02
Случайная 10 выборка из более 60 миллионов записей без доп массивов, временных таблиц, одним селектом занял 16-20 сек
Если у вас интернет-магазин и в секунду страничку со случайным товаром дергает 100-200 человек, то такой Селект прибьет сервер и разгонит клиентов. :)

shumbola
14.10.2013, 15:05
Самый эффективный способ, пожалуй, должен быть реализован напрямую в базе, как TABLESAMPLE в SQL Server: имея карту данных на диске, выбрать некоторое количество случайных страниц указанной таблицы, и вытянуть строки оттуда — минуя индексы и всё остальное.
Зависит от задачи, TABLESAMPLE не дает действительно случайных результатов. Прежде чем использовать TABLESAMPLE, изучайте область применения.
Думаю Rooslan Khayrov знает об этом, и мое сообщение не ему адресовано. ;-)

DarkUser
14.10.2013, 15:06
Разве перед получением строки по порядковому номеру ненужно сортировать таблицу по какому-то из признаков?Если делать запрос вида Select * from table limit 1 offset N (где N - заранее сгенерированный случайный номер из [0 .. count - 1]), то - нет. А так, запрос без указания Order By обычно инвариантен и порядок строк соответствует порядку хранения данных в таблице.

spykee
14.10.2013, 15:08
Если у вас интернет-магазин и в секунду страничку со случайным товаром дергает 100-200 человек, то такой Селект прибьет сервер и разгонит клиентов. :)

Нет уж.. это ж был пример выборки из всех записей.. а так обычно выборки делаются только из части всех записей.

Alisher Umarov
15.10.2013, 00:19
Ok. С вторым вопросом разобрались.
Так как с первым?

зы. В практике отчёты по периодам очень часто применяются.

Dolphin
15.10.2013, 00:52
Ok. С вторым вопросом разобрались.
Так как с первым?
Нет структуры базы - нет запроса...

Поступления от пяти бригад можно разложить по таблицам десятками способов разной степени упоротости.

Alisher Umarov
15.10.2013, 09:27
Ok. С вторым вопросом разобрались.
Так как с первым?
Нет структуры базы - нет запроса...

Поступления от пяти бригад можно разложить по таблицам десятками способов разной степени упоротости.

А?

id, product_name, br1, br2, br3, br4, br5, date

sleepy dragon
15.10.2013, 12:31
SELECT
'day' AS 'time',
SUM(br1) AS 'br1',
SUM(br2) AS 'br2',
SUM(br3) AS 'br3',
SUM(br4) AS 'br4',
SUM(br5) AS 'br5'
FROM
table_name
WHERE
DAY(date) = DAY(NOW())
UNION
SELECT
'week' AS 'time',
SUM(br1) AS 'br1',
SUM(br2) AS 'br2',
SUM(br3) AS 'br3',
SUM(br4) AS 'br4',
SUM(br5) AS 'br5'
FROM
table_name
WHERE
WEEK(date) = WEEK(NOW())
UNION
SELECT
'month' AS 'time',
SUM(br1) AS 'br1',
SUM(br2) AS 'br2',
SUM(br3) AS 'br3',
SUM(br4) AS 'br4',
SUM(br5) AS 'br5'
FROM
table_name
WHERE
MONTH(date) = MONTH(NOW())
UNION
SELECT
'quarter' AS 'time',
SUM(br1) AS 'br1',
SUM(br2) AS 'br2',
SUM(br3) AS 'br3',
SUM(br4) AS 'br4',
SUM(br5) AS 'br5'
FROM
table_name
WHERE
QUARTER(date) = QUARTER(NOW())
UNION
SELECT
'year' AS 'time',
SUM(br1) AS 'br1',
SUM(br2) AS 'br2',
SUM(br3) AS 'br3',
SUM(br4) AS 'br4',
SUM(br5) AS 'br5'
FROM
table_name
WHERE
YEAR(date) = YEAR(NOW())

Nestik
16.10.2013, 01:09
А?

id, product_name, br1, br2, br3, br4, br5, date

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

Итого:
1. Номенклатурный справочник
2. Справочник по складам
3. Справочник по бригадам
4. Справочник по типам транзакций
5. Ну и сами транзакции

И это ещё без конкретного описания структуры таблиц.

OmoN
16.10.2013, 02:25
Если у вас интернет-магазин и в секунду страничку со случайным товаром дергает 100-200 человек, то такой Селект прибьет сервер и разгонит клиентов.По этому делают так что бы такие странички получали данные не прямо из базы.

Dmitriy Nikolaev
16.10.2013, 03:30
юзайте правильные индексы и будет вам щастье.

Dolphin
16.10.2013, 17:26
id, product_name, br1, br2, br3, br4, br5, date
Такую таблицу я даже трехметровой палкой трогать не буду.

SELECT
'day' AS 'time',
SUM(br1) AS 'br1',
SUM(br2) AS 'br2',
...
SUM(br1) AS 'br1',
SUM(br2) AS 'br2',
SUM(br3) AS 'br3',
SUM(br4) AS 'br4',
SUM(br5) AS 'br5'
FROM
table_name
WHERE
YEAR(date) = YEAR(NOW())
Имхо, в таких случаях практичней и быстрей несколько мелких запросов сделать.

Rooslan Khayrov
17.10.2013, 14:04
Продолжаем развлекаться?

В процессе одного обсуждения родилась пара задачек. Самый редуцированный вариант: для заданной таблицы посчитать некую агрегатную функцию для каждых N строк. Для наглядности примем простейшую схему с двумя колонками: id (целое автоинкремент) и value (вещественное). Надо посчитать суммы value по 10 значений: т.е. сумма первых 10, с 11-го по 20-е и т.д.

Связанная задача с большим реальным смыслом: для той же таблицы найти перцентили (http://ru.wikipedia.org/wiki/Квантиль#.D0.9F.D0.B5.D1.80.D1.86.D0.B5.D0.BD.D1.8 2.D0.B8.D0.BB.D1.8C) распределения value, с 1-й по 100-ю.

Решения для PostgreSQL:
SELECT sum(value) FROM (
SELECT value, row_number() OVER (ORDER BY id) AS i
FROM data ORDER BY id) AS numbered
GROUP BY (i - 1) / 10;

SELECT ceil(percent) AS n, max(value) AS percentile FROM (
SELECT value, 100.0 * row_number() OVER (ORDER BY value) / count(id) OVER () AS percent
FROM data) AS sub
GROUP BY n ORDER BY n;

OmoN
18.10.2013, 15:33
Самый редуцированный вариант: для заданной таблицы посчитать некую агрегатную функцию для каждых N строк.
Решение на Oracle:
create table TASK_3
(
id NUMBER,
val NUMBER
)
id, val: 0-99
Если в поле Id нет дырки:
Select 0, Id, Val
From Task_3
Union
Select 1,
Max(Id) Over(Partition By Trunc(Id / 10)),
Sum(Val) Over(Partition By Trunc(Id / 10))
From Task_3
Order By 2, 1

Если в поле Id дырки придется поработать с Rownum:
Select 0, Id, Val
From Task_3
Union
Select 1,
Max(Rn) Over(Partition By Trunc(Rn / 10)),
Sum(Val) Over(Partition By Trunc(Rn / 10))
From (Select Id, Val, Rownum Rn From Task_3 Order By 1)
Order By 2, 1

OmoN
18.10.2013, 18:07
1. Виртуальный колхоз "Кизил пиёз. Пять бригад каждый день сдают на склад пиёз в кг.
Текущий отчёт:
За день. За неделю. За месяц. За квартал. За год.Если структура таблицы так:
id, product_name, br1, br2, br3, br4, br5, date
В оракле:
Select 0,
'Daily',
To_Char(Date_Ini, 'YYYY-MM-DD') Date_Ini,
Sum(Br_1) Br_1,
Sum(Br_2) Br_2,
Sum(Br_3) Br_3,
Sum(Br_4) Br_4,
Sum(Br_5) Br_5,
Date_Ini Sort_Date
From Task_1
Group By Date_Ini
Union
Select 1,
'Weekly',
To_Char(Max(Date_Ini) Over(Partition By To_Char(Date_Ini, 'WWYYYY')),
'WW-YYYY') Date_Ini,
Sum(Br_1) Over(Partition By To_Char(Date_Ini, 'WWYYYY')) Br1,
Sum(Br_2) Over(Partition By To_Char(Date_Ini, 'WWYYYY')) Br2,
Sum(Br_3) Over(Partition By To_Char(Date_Ini, 'WWYYYY')) Br3,
Sum(Br_4) Over(Partition By To_Char(Date_Ini, 'WWYYYY')) Br4,
Sum(Br_5) Over(Partition By To_Char(Date_Ini, 'WWYYYY')) Br5,
Max(Date_Ini) Over(Partition By To_Char(Date_Ini, 'WWYYYY')) Sort_Date
From Task_1
Union
Select 2,
'Monthly',
To_Char(Max(Date_Ini) Over(Partition By To_Char(Date_Ini, 'MMYYYY')),
'YYYY-MM') Date_Ini,
Sum(Br_1) Over(Partition By To_Char(Date_Ini, 'MMYYYY')) Br1,
Sum(Br_2) Over(Partition By To_Char(Date_Ini, 'MMYYYY')) Br2,
Sum(Br_3) Over(Partition By To_Char(Date_Ini, 'MMYYYY')) Br3,
Sum(Br_4) Over(Partition By To_Char(Date_Ini, 'MMYYYY')) Br4,
Sum(Br_5) Over(Partition By To_Char(Date_Ini, 'MMYYYY')) Br5,
Max(Date_Ini) Over(Partition By To_Char(Date_Ini, 'MMYYYY')) Sort_Date
From Task_1
Union
Select 3,
'Quarter',
To_Char(Max(Date_Ini) Over(Partition By To_Char(Date_Ini, 'QYYYY')),
'Q-YYYY') Date_Ini,
Sum(Br_1) Over(Partition By To_Char(Date_Ini, 'QYYYY')) Br1,
Sum(Br_2) Over(Partition By To_Char(Date_Ini, 'QYYYY')) Br2,
Sum(Br_3) Over(Partition By To_Char(Date_Ini, 'QYYYY')) Br3,
Sum(Br_4) Over(Partition By To_Char(Date_Ini, 'QYYYY')) Br4,
Sum(Br_5) Over(Partition By To_Char(Date_Ini, 'QYYYY')) Br5,
Max(Date_Ini) Over(Partition By To_Char(Date_Ini, 'QYYYY')) Sort_Date
From Task_1
Union
Select 4,
'Year',
To_Char(Max(Date_Ini) Over(Partition By To_Char(Date_Ini, 'YYYY')),
'YYYY') Date_Ini,
Sum(Br_1) Over(Partition By To_Char(Date_Ini, 'YYYY')) Br1,
Sum(Br_2) Over(Partition By To_Char(Date_Ini, 'YYYY')) Br2,
Sum(Br_3) Over(Partition By To_Char(Date_Ini, 'YYYY')) Br3,
Sum(Br_4) Over(Partition By To_Char(Date_Ini, 'YYYY')) Br4,
Sum(Br_5) Over(Partition By To_Char(Date_Ini, 'YYYY')) Br5,
Max(Date_Ini) Over(Partition By To_Char(Date_Ini, 'YYYY')) Sort_Date
From Task_1
Order By 9, 1

OmoN
19.10.2013, 01:27
Если окончательно извращаться первую задачу можно оптимизировать так:
Select Distinct d.Id,
d.Str,
To_Char(Max(Date_Ini) Over(Partition By To_Char(Date_Ini, d.Format)),
d.Format) Date_Ini,
Sum(Br_1) Over(Partition By To_Char(Date_Ini, d.Format)) Br1,
Sum(Br_2) Over(Partition By To_Char(Date_Ini, d.Format)) Br2,
Sum(Br_3) Over(Partition By To_Char(Date_Ini, d.Format)) Br3,
Sum(Br_4) Over(Partition By To_Char(Date_Ini, d.Format)) Br4,
Sum(Br_5) Over(Partition By To_Char(Date_Ini, d.Format)) Br5,
Max(Date_Ini) Over(Partition By To_Char(Date_Ini, d.Format)) Sort_Date
From Task_1,
(Select 0 Id, 'Daily' Str, 'YYYY-MM-DD' Format From Dual
Union
Select 1 Id, 'Weekly' Str, 'YYYY-WW' Format From Dual
Union
Select 2 Id, 'Monthly' Str, 'YYYY-MM' Format From Dual
Union
Select 3 Id, 'Quarter' Str, 'YYYY-Q' Format From Dual
Union
Select 4 Id, 'Year' Str, 'YYYY' Format From Dual) d
Order By 9,1

OmoN
19.10.2013, 02:23
2. Дано 1М записей;
Не самый производительный сервер;
Нужно: ORDER BY RAND() LIMIT 10;
Таблица:
create table TASK_2
(
id NUMBER,
val NUMBER
)
Запрос:
Select Id, Val
From (Select Distinct Id, Val, Trunc(Dbms_Random.Value(1, 1000000)) Rand
From Task_2
Order By Rand)
Where Rownum < 11
ПС: Прощу прощение что все решение на оракле. У меня нет MySQL.

Alisher Umarov
19.10.2013, 09:27
Небольшая придирка к первой задаче, которая вылезла в практике. (Даёт хорошее представление чем отличается мышления дева и юзера)
Неделя - у юзера, это количество сданного лука за последние СЕМЬ дней.
А вот месяц, квартал и год - календарные.
Конечно это спорный вопрос. Но чтобы избежать этого, в отчетах следует разбивать вариации запросов в малых периодах.
Это легко представить когда week попадёт между двух месяцев.

Alisher Umarov
19.10.2013, 09:38
OmoN
Все же Dolphin прав. Такие отчёты желательно разбивать по периодам. Зачем пересчитывать данные за год если нужен всего лишь отчёт за день? А если база за десять лет?

OmoN
19.10.2013, 10:49
Такие отчёты желательно разбивать по периодам. Зачем пересчитывать данные за год если нужен всего лишь отчёт за день? А если база за десять лет?Я думал нужен весь отчет вместе. Какие передаваемые параметры?
Upd: Решение для первой задачи не верна. Заполнял таблицу данными на 2 года.
Procedure Fill_Task1 Is
v_Date Date := To_Date('20110101', 'YYYYMMDD');
i Number := 0;
Begin
Loop
Insert Into Task_1
(Id, Product, Date_Ini, Br_1, Br_2, Br_3, Br_4, Br_5)
Values
(i,
'Onion',
Trunc(v_Date),
Trunc(Dbms_Random.Value(1, 100)),
Trunc(Dbms_Random.Value(1, 100)),
Trunc(Dbms_Random.Value(1, 100)),
Trunc(Dbms_Random.Value(1, 100)),
Trunc(Dbms_Random.Value(1, 100)));
v_Date := v_Date + 1;
i := i + 1;
If (Trunc(v_Date) > To_Date('20121231', 'YYYYMMDD')) Then
Exit;
End If;
End Loop;
Commit;
End;
Результат не верен. Игнор.

OmoN
19.10.2013, 11:36
Результат не верен. Игнор.Точнее сортировка хромает.

Nestik
19.10.2013, 20:25
OmoN
Все же Dolphin прав. Такие отчёты желательно разбивать по периодам. Зачем пересчитывать данные за год если нужен всего лишь отчёт за день? А если база за десять лет?

Ну пля так и сделайте тогда во where :date_from :date_to группировка по продукту и сумма по бригадам, и нехай юзверь со своими закидонами сам решает чё у него там день, 7 дней или неделя, месяц, квартал, год, 100 лет.

Задача вообще какая-то сумбурная.

Alisher Umarov
20.10.2013, 01:14
OmoN
Все же Dolphin прав. Такие отчёты желательно разбивать по периодам. Зачем пересчитывать данные за год если нужен всего лишь отчёт за день? А если база за десять лет?

Ну пля так и сделайте тогда во where :date_from :date_to группировка по продукту и сумма по бригадам, и нехай юзверь со своими закидонами сам решает чё у него там день, 7 дней или неделя, месяц, квартал, год, 100 лет.

Задача вообще какая-то сумбурная.

Ещё один типичный пример dev подхода против юзерского.
Доказано много раз - юзеры чрезвычайно редко знают что им нужно в конечном виде. К этому давно пора привыкнуть.
(Но в данном примере именно показано что к датам в SQL нужно относится осторожно)

OmoN
20.10.2013, 09:07
В предедущем запросе формат недели YYYY-WW дал неправильные результаты. Он не показывал первые 4 недели года. Причину не знаю. Если формат недели YYYY.WW работает нормально. И тут добавил фильтр по дата начало и дата окончание отчетного периода.
Select Distinct d.Id,
d.Str,
To_Char(Max(Date_Ini) Over(Partition By To_Char(Date_Ini, d.Format)),
d.Format) Date_Ini,
Sum(Br_1) Over(Partition By To_Char(Date_Ini, d.Format)) Br1,
Sum(Br_2) Over(Partition By To_Char(Date_Ini, d.Format)) Br2,
Sum(Br_3) Over(Partition By To_Char(Date_Ini, d.Format)) Br3,
Sum(Br_4) Over(Partition By To_Char(Date_Ini, d.Format)) Br4,
Sum(Br_5) Over(Partition By To_Char(Date_Ini, d.Format)) Br5,
Max(Date_Ini) Over(Partition By To_Char(Date_Ini, d.Format)) Sort_Date
From Task_1,
(Select 0 Id, 'Daily' Str, 'YYYY-MM-DD' Format From Dual
Union
Select 1 Id, 'Weekly' Str, 'YYYY.WW' Format From Dual
Union
Select 2 Id, 'Monthly' Str, 'YYYY-MM' Format From Dual
Union
Select 3 Id, 'Quarter' Str, 'YYYY-Q' Format From Dual
Union
Select 4 Id, 'Year' Str, 'YYYY' Format From Dual) d
Where Trunc(Date_Ini) >= To_Date('20110101','YYYYMMDD')
And Trunc(Date_Ini) < To_Date('20130101','YYYYMMDD')
Order By 9,1

Nestik
24.10.2013, 16:59
Есть диапазоны

Диапозон1;1;10
Диапазон2;11;20
Диапозон3;21;30

Есть нагенеренные числа внутри диапазонов впринципе по порядку но возможны дырки. что-то вроде этого:
Диапазон1;1
Диапазон1;3
Диапазон1;4
Диапазон1;5
Диапазон1;6
Диапазон1;8
Диапазон1;9
Диапазон2;12
Диапазон2;14
Диапазон2;15
Диапазон2;16
Диапазон2;17
Диапазон2;18
Диапазон2;20
Диапазон3;21
Диапазон3;22
Диапазон3;24
Диапазон3;27
Диапазон3;28
Диапазон3;29
Диапазон3;30


Усть Юзеры которым надо раздать нагенеренные числа в зависимости от принадлежности юзера к диапазону.
Юзер1;Диапозон1
Юзер2;Диапозон1
Юзер3;Диапозон1
Юзер4;Диапозон1
Юзер5;Диапозон2
Юзер6;Диапозон2
Юзер7;Диапозон2
Юзер8;Диапозон2
Юзер9;Диапозон2
Юзер10;Диапозон2
Юзер11;Диапозон3
Юзер12;Диапозон3
Юзер13;Диапозон3
Юзер14;Диапозон3
Юзер15;Диапозон3
Юзер16;Диапозон3

Должно получится следующее:
Юзер1;Диапозон1;1
Юзер2;Диапозон1;3
Юзер3;Диапозон1;4
Юзер4;Диапозон1;5
Юзер5;Диапозон2;12
Юзер6;Диапозон2;14
Юзер7;Диапозон2;15
Юзер8;Диапозон2;16
Юзер9;Диапозон2;17
Юзер10;Диапозон2;18
Юзер11;Диапозон3;21
Юзер12;Диапозон3;22
Юзер13;Диапозон3;24
Юзер14;Диапозон3;27
Юзер15;Диапозон3;28

Дополнительные условия. Только SQL.

Igor Tsingalov
24.10.2013, 17:37
Дак вот как биллинг билайновский работает!!! А я-то никак не мог понять, почему с опозданием трафик начисляется!
Всё, спалил Nestik корпоративные секреты... спалил...
:clapping::mosking:

Nestik
24.10.2013, 19:03
Эмм соррии за ошибки в "Диапазон". Исходные данные лепил на скорую руку. :)

Igor Tsingalov, Угу можете пожаловаться в ЕСПЧ. )))

Nestik
24.10.2013, 21:26
Нуже Господа поактивнее, у мну только одно решение с помощью аналитики Oracle.

Rooslan Khayrov
25.10.2013, 14:38
Nestik, без оконных функций как-то не получается (постгрес).


CREATE TABLE ranges (
id serial PRIMARY KEY,
start integer NOT NULL,
stop integer NOT NULL,
CHECK (start < stop),
UNIQUE (start, stop));

CREATE TABLE values (
id serial PRIMARY KEY,
range_id integer NOT NULL REFERENCES ranges(id),
value integer UNIQUE NOT NULL);

CREATE TABLE users (
id serial PRIMARY KEY,
range_id integer NOT NULL REFERENCES ranges(id));


SELECT u.id, v.value FROM
(SELECT id, range_id,
row_number() OVER (PARTITION BY range_id ORDER BY id) AS i
FROM users) AS u
LEFT OUTER JOIN
(SELECT value, range_id,
row_number() OVER (PARTITION BY range_id ORDER BY value) AS i
FROM values) AS v
ON u.range_id = v.range_id AND u.i = v.i;

Nestik
25.10.2013, 15:10
Nestik, без оконных функций как-то не получается (постгрес).Угу мне такое же решение пришло. По другому вроде никак. Аналитика таки вещь. :)

Dolphin
27.10.2013, 07:00
Дополнительные условия. Только SQL.
Ммм... возможно, я не так понял задачу, но я проблемы вообще не вижу:

SELECT user.id, range.id, value.id
FROM user
LEFT JOIN range ON user.rangeid=range.id
LEFT JOIN value ON range.id=value.rangeid
ORDER BY user.id ASC, range.id ASC, value.id ASC

Rooslan Khayrov
27.10.2013, 12:34
Dolphin, в результате запроса должно быть столько строк, сколько есть юзеров. А тут получается декартово произведение всех трёх таблиц.

Dolphin
27.10.2013, 21:35
Dolphin, в результате запроса должно быть столько строк, сколько есть юзеров. А тут получается декартово произведение всех трёх таблиц.

Т.е. юзер должен получить одно число из своего диапазона? А принцип? Рандомно или числа для всех юзеров должны быть разными? Если второе, то я вижу только вариант с временной таблицей.

Rooslan Khayrov
27.10.2013, 21:57
Рандомно или числа для всех юзеров должны быть разными?
Каждый должен получить своё уникальное значение. Если юзеров в диапазоне больше, чем значений — думаю, дубликаты выдавать нельзя, но пусть Nestik уточнит.
Если второе, то я вижу только вариант с временной таблицей.
Продемонстрируй, интересно.

Nestik
28.10.2013, 01:56
Каждый должен получить своё уникальное значение. Если юзеров в диапазоне больше, чем значений — думаю, дубликаты выдавать нельзя, но пусть Nestik уточнит.
Именно так:
1. Каждый должен получить своё уникальное значение.
2. Нагенеренные числа все уникальные, нагенеренные подрят. 1,2,3,4,5 но есть дырки.
3. Юзеров больше быть не может их может быть только меньше, даже внутри одного диапазона. В результате должно-быть столько записей сколько юзеров.
4. Не принципиально но желательно, меньший айди юзера, должен получить меньшее число из диапазона.

Тут вся проблема в том что у юзеров и нагенеренных чисел нет общего ключа для объединения, поэтому данный ключ создаётся искуственно, это номер строки внтури отсортированной группы(диапазона) как у нагенеренных чисел так и у юзеров, а потом они объеденяются. Но такой финт возможен только с помощью аналитических функций.

P/S нагенеренные цифры 1,2,3,4 и такие же ИД юзеров Взяты для примера, в реальных данных они абсолютно разные. Поэтому к ним привязываться нельзя.

Тоже интересно что за решение с темповой таблицей?

Alisher Umarov
28.10.2013, 04:12
Думаю можно пойти от обратного. Перед присвоением заполнить дыры в нагенеринных числах и пробелы в диапазонах юзеров опорным, уникальным значением. Выравнить таблицы. Присвоить по условиям. С фиксированной базой естественно.

metamod
10.11.2013, 14:34
Сомнительное у вас развлечение с этим SQL...

Constantin
11.11.2013, 09:29
Писал как-то скрипт для интернет-магазина, там тоже была задача вывести случайный товар, каждый раз рандомить похожие товары - слишком много времени. Пришлось создать отдельную таблицу, в которую генерировались случайные id товаров, таблица обновлялась каждые 15 минут, товаров в таблице было 10% от общего количества, всё это поставил ессно на крон.
--------------------------------
Если времени не жалко, и мешают "дырки" в id, то генерьте рандомно скажем, 30 id, а выводите первые 10))) уж 10 то должны найтись)
Я так думаю, что эти рандомные 10 должны выводиться и генериться не один раз, а постоянно. То для реального решения и употребления эта функция не катит.