У меня есть стол
items
id int unsigned auto_increment primary key,
name varchar(255)
price DECIMAL(6,2)
Я хочу получить по крайней мере 30 случайных предметов из этой таблицы, где общая цена равна 500, как лучше всего это сделать?
Я видел это решение, которое, похоже, имеет аналогичную проблему MySQL Выберите 3 случайные строки, где сумма трех строк меньше значения
И мне интересно, есть ли другие решения, которые проще реализовать и / или более эффективные
Как выбирается сумма? Пользователем? Есть ли ограничения? Или самые популярные ценности?
У меня еще нет самой популярной реализации, пока я просто хочу получить 30 случайных элементов с фиксированной суммой, которую я даю запросу (я мог бы дать эту возможность пользователям в будущем)
Я не думаю, что есть эффективный способ добиться этого. Если бы сумма всегда была одинаковой, вы могли бы заранее сгенерировать комбинации, а затем просто случайным образом выбрать одну из них.
Это слишком много комбинаций, чтобы охватить их, и нужно много обновлять, когда добавляются новые элементы.
Есть ли гарантия, что результат существует? Что вы имеете в виду под «случайным»? Должен ли алгоритм быть недетерминированным? Или вы просто имеете в виду «любые 30 предметов»? В заголовке написано «выберите 30 случайных строк» - в теле вы пишете «не менее 30». Что правильно? Должна ли сумма быть ровно 500,00?
Как правило, это «проблема с рюкзаком», возможно, ограниченная (BKP), если вы хотите использовать продукт только один раз. Для этого есть несколько алгоритмов, хотя мне не удалось найти реализацию php (для ограниченной), но это не должно быть слишком сложно реализовать (но вы определенно не захотите делать это в MySQL). Вы можете предварительно рассчитать (все) решения (потому что это будет быстро замедляться с увеличением количества различных цен на продукты). Они будут действительны до тех пор, пока цены на товары, используемые в наборах, не изменятся (и нет товаров с такой же ценой.
заменить его). Кроме того, во многих случаях вам нужно только одно решение (которое вы можете предварительно рассчитать с помощью такого алгоритма), а затем сгенерировать больше комбинаций путем повторного случайного обмена двух или трех продуктов, которые в сумме имеют одинаковую цену (например, 4,50 и 9,99 могут быть заменены на 0,99 и 13,50), что часто подтверждается тем фактом, что цены в магазине часто имеют структуру (например, .00 или .99 гораздо более распространены, чем .83).
База данных должна быть только хранилищем для списка; Для алгоритма следует использовать SQL нет. Это займет слишком много времени.
@PaulSpiegel Да, результат существует ... Я могу вручную выбрать 30 элементов и получить результат ... Я говорю как минимум 30 элементов, потому что это требование может измениться в будущем ... И да, сумма должна быть ровно 500, иначе я бы просто выберите любые 30 случайных предметов и продолжайте.
@Solarflare Я посмотрю и посмотрю, какие есть решения.
Пусть бэкэнд выполняет большую часть поиска
Может быть, глупый вопрос, но это можно сделать в два шага: вы выбираете всю строку, тогда в PHP вы сохраняете только 30 строк, где сумма равна тому, что вы хотите? Я не уверен в производительности, но простой запрос "select" с некоторым php после может быть быстрее, чем очень сложный запрос, не так ли?
И еще вопрос: у вашего товара есть лимит (мин, макс)? Они целые или у вас может быть десятичная дробь?






В зависимости от средней цены и распределения цен вы можете попробовать что-то вроде этого:
Случайным образом выберите в сумме несколько элементов меньше, чем вы хотите (например, 25). Повторите попытку, пока их общее количество не станет меньше x.
Затем используйте концепцию, указанную в вашем вопросе, чтобы найти комбинацию, которая обеспечивает оставшуюся сумму.
Я попробовал решение в своем сообщении для 5 элементов, и его выполнение занимает от 2 до 5+ секунд в зависимости от общей суммы.
У вас действительно много предметов ... Анализировали ли вы свой запрос с помощью EXPLAIN?
Да, я сделал ... он говорит, что использование индекса для первого вхождения таблицы, а затем использование where, использование индекса, использование буфера соединения ... количество комбинаций, которые могут удовлетворить запрос, слишком велико, чтобы он мог быстро вернуться
Самый близкий ответ, который я могу дать, - это
set @cnt = 0;
set @cursum = 0;
set @cntchanged = 0;
set @uqid = 1;
set @maxsumid = 1;
set @maxsum = 0;
select
t.id,
t.name,
t.cnt
from (
select
id + 0 * if (@cnt = 30, (if (@cursum > @maxsum, (@maxsum := @cursum) + (@maxsumid := @uqid), 0)) + (@cnt := 0) + (@cursum := 0) + (@uqid := @uqid + 1), 0) id,
name,
@uqid uniq_id,
@cursum := if (@cursum + price <= 500, @cursum + price + 0 * (@cntchanged := 1) + 0 * (@cnt := @cnt + 1), @cursum + 0 * (@cntchanged := 0)) as cursum, if (@cntchanged, @cnt, 0) as cnt
from (select id, name, price from items order by rand() limit 10000) as orig
) as t
where t.cnt > 0 and t.uniq_id = @maxsumid
;
Итак, как это работает? Сначала мы выбираем из элементов 10k произвольно упорядоченных строк. После этого мы суммируем цены на предметы, пока не дойдем до 30 предметов с суммой меньше 500. Когда мы находим 30 предметов, мы повторяем процесс, пока не пройдемся по всем 10 тысячам выбранных предметов. Находя эти 30 предметов, мы сохраняем максимальную найденную сумму. Итак, в конце мы выбираем 30 элементов с наибольшей суммой (т.е. самые близкие к целевым 500). Не уверен, что вы изначально хотели этого, но нахождение суммы точный, равной 500, потребует слишком больших усилий со стороны БД.
Если вы хотите быть эффективный, перестаньте тратить свое время и выберите возможная согласованность. Создайте консольный сценарий, который делает то, что вы хотите выполнить, любыми необходимыми средствами, затем запускайте этот сценарий в CRON или с любым программным обеспечением для планирования время от времени.
Имея 100, 1000 посетителей, вы бы хотели, чтобы ваш запрос выполнялся каждый раз? Это требует времени и ресурсов. Запросы, упорядоченные случайным образом, также не могут кэшироваться СУБД. Перейдите к возможная согласованность: создайте таблицу для хранения этих записей и очищайте ее каждый раз, блокируйте запись, затем загружайте новый набор, например, каждые 5 минут.
По крайней мере, так я делаю в сильно загруженных приложениях. В коде это вопрос выполнения простого запроса SELECT.
Если вы читали руководство MySQL, вы могли видеть ЗАКАЗАТЬ СЛУЧАЙ () для рандомизации строк.
Этот пример работает нормально и быстро, если вы только скажем, 1000 строк. Как только у вас есть 10000 строк, накладные расходы на сортировку строк становятся важными. Не забывайте: мы сортируем только для того, чтобы выбросить почти все строки.
Отличный Почта, обрабатывающий несколько случаев, от простых до пробелов, до неоднородных с пробелами.
Вот как это сделать идеально:
SELECT id, name, price
FROM `items` AS i1 JOIN
(SELECT CEIL(RAND() *
(SELECT MAX(id)
FROM `items`)) AS id) AS i2
WHERE i1.id >= i2.id AND i1.price = 500
ORDER BY i1.id ASC
LIMIT 30;
порядок по ранду не является проблемой, мне нужно 30 строк, которые в сумме составляют до 500, а не 30 элементов с ценой 500
затем выполните следующий код
$arr = array();
$num = 0;
while($row = mysqli_fetch_array($result))
{
array_push($arr,$row['id']);
}
$arr2= array();
while(count($arr2!=30)
{
$cnt = random(0,count($arr));
if (in_array($arr[$cnt],$arr2);
{
array_push($arr2,$arr[$cnt]);
}
}
print_r($arr2);
здесь $ arr2 - это требуемый массив
Есть решение, если ваш список продуктов удовлетворяет следующему предположение:
У вас есть товары по всем ценам от 0,00 до 500,00. например. 0,01, 0,02 и т. д. До 499,99. или, может быть, от 0,05, 0,10 и т. д. до 499,95.
Алгоритм основан на следующем:
В наборе из n положительных чисел, сумма которых равна S, по крайней мере одно из них будет меньше, чем S, деленное на n (S / n).
В этом случае шаги следующие:
Повторите это 29 раз и получите 29 продуктов. Для последнего продукта выберите тот, где цена = оставшаяся цена. (или цена <= оставшаяся цена и порядок по убыванию цены, и, надеюсь, вы сможете подойти достаточно близко).
Для элементов стола:
Получите случайную максимальную цену товара:
CREATE PROCEDURE getRandomProduct (IN maxPrice INT, OUT productId INT, productPrice DECIMAL(8,2))
BEGIN
DECLARE productId INT;
SET productId = 0;
SELECT id, price INTO productId, productPrice
FROM items
WHERE price < maxPrice
ORDER BY RAND()
LIMIT 1;
END
Получите 29 случайных товаров:
CREATE PROCEDURE get29products(OUT str, OUT remainingPrice DECIMAL(8,2))
BEGIN
DECLARE x INT;
DECLARE id INT;
DECLARE price DECIMAL(8,2);
SET x = 30;
SET str = '';
SET remainingPrice = 500.00;
REPEAT
CALL getRandomProduct(remainingPrice/x, @id, @price);
SET str = CONCAT(str,',', @id);
SET x = x - 1;
SET remainingPrice = remainingPrice - @price;
UNTIL x <= 1
END REPEAT;
END
Вызов процедуры:
CALL `get29products`(@p0, @p1); SELECT @p0 AS `str`, @p1 AS `remainingPrice`;
и, в конце концов, попробуйте найти последний продукт, чтобы добраться до 500.
В качестве альтернативы вы можете выбрать 28 и использовать решение для связанного вопроса, который вы предоставили, чтобы получить пару продуктов, сумма которых равна оставшейся цене.
Обратите внимание, что разрешены продукты дублировать. Чтобы избежать дублирования, вы можете расширить getRandomProduct с помощью дополнительного параметра IN для уже найденных продуктов и добавить условие НЕ В для их исключения.
Обновлять: вы можете преодолеть вышеуказанное ограничение, чтобы вы всегда находите коллекции на сумму до 500 использовали процесс cron, как описано во 2-м разделе ниже.
Основываясь на предложении @Michael Zukowski, вы могли бы
Таким образом вы можете найти коллекции, которые всегда суммируйте ровно до 500. Когда пользователь делает запрос, вы можете выбрать случайную коллекцию из новой таблицы.
Даже с коэффициентом совпадения 20%, процесс cron, который запускает алгоритм 10 раз каждые 5 минут в течение 24 часов, вы можете собрать более 500 коллекций.
На мой взгляд, использование процесса cron имеет следующие преимущества и недостатки:
Преимущества
недостатки
Новинки добавляются каждые несколько дней, но цены почти не меняются.