Выберите 30 случайных строк, где сумма = x

У меня есть стол

items
id int unsigned auto_increment primary key,
name varchar(255)
price DECIMAL(6,2)

Я хочу получить по крайней мере 30 случайных предметов из этой таблицы, где общая цена равна 500, как лучше всего это сделать?

Я видел это решение, которое, похоже, имеет аналогичную проблему MySQL Выберите 3 случайные строки, где сумма трех строк меньше значения

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

Новинки добавляются каждые несколько дней, но цены почти не меняются.

Frank 10.03.2018 17:32

Как выбирается сумма? Пользователем? Есть ли ограничения? Или самые популярные ценности?

Jonas Staudenmeir 10.03.2018 17:36

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

Frank 10.03.2018 17:59

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

Jonas Staudenmeir 10.03.2018 18:18

Это слишком много комбинаций, чтобы охватить их, и нужно много обновлять, когда добавляются новые элементы.

Frank 10.03.2018 18:29

Есть ли гарантия, что результат существует? Что вы имеете в виду под «случайным»? Должен ли алгоритм быть недетерминированным? Или вы просто имеете в виду «любые 30 предметов»? В заголовке написано «выберите 30 случайных строк» ​​- в теле вы пишете «не менее 30». Что правильно? Должна ли сумма быть ровно 500,00?

Paul Spiegel 12.03.2018 21:30

Как правило, это «проблема с рюкзаком», возможно, ограниченная (BKP), если вы хотите использовать продукт только один раз. Для этого есть несколько алгоритмов, хотя мне не удалось найти реализацию php (для ограниченной), но это не должно быть слишком сложно реализовать (но вы определенно не захотите делать это в MySQL). Вы можете предварительно рассчитать (все) решения (потому что это будет быстро замедляться с увеличением количества различных цен на продукты). Они будут действительны до тех пор, пока цены на товары, используемые в наборах, не изменятся (и нет товаров с такой же ценой.

Solarflare 13.03.2018 00:22

заменить его). Кроме того, во многих случаях вам нужно только одно решение (которое вы можете предварительно рассчитать с помощью такого алгоритма), а затем сгенерировать больше комбинаций путем повторного случайного обмена двух или трех продуктов, которые в сумме имеют одинаковую цену (например, 4,50 и 9,99 могут быть заменены на 0,99 и 13,50), что часто подтверждается тем фактом, что цены в магазине часто имеют структуру (например, .00 или .99 гораздо более распространены, чем .83).

Solarflare 13.03.2018 00:23

База данных должна быть только хранилищем для списка; Для алгоритма следует использовать SQL нет. Это займет слишком много времени.

Rick James 13.03.2018 03:26

@PaulSpiegel Да, результат существует ... Я могу вручную выбрать 30 элементов и получить результат ... Я говорю как минимум 30 элементов, потому что это требование может измениться в будущем ... И да, сумма должна быть ровно 500, иначе я бы просто выберите любые 30 случайных предметов и продолжайте.

Frank 13.03.2018 15:10

@Solarflare Я посмотрю и посмотрю, какие есть решения.

Frank 13.03.2018 15:18

Пусть бэкэнд выполняет большую часть поиска

Richard 15.03.2018 08:17

Может быть, глупый вопрос, но это можно сделать в два шага: вы выбираете всю строку, тогда в PHP вы сохраняете только 30 строк, где сумма равна тому, что вы хотите? Я не уверен в производительности, но простой запрос "select" с некоторым php после может быть быстрее, чем очень сложный запрос, не так ли?

Mickaël Leger 16.03.2018 10:39

И еще вопрос: у вашего товара есть лимит (мин, макс)? Они целые или у вас может быть десятичная дробь?

Mickaël Leger 16.03.2018 10:49
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Symfony Station Communiqué - 7 июля 2023 г
Symfony Station Communiqué - 7 июля 2023 г
Это коммюнике первоначально появилось на Symfony Station .
Оживление вашего приложения Laravel: Понимание режима обслуживания
Оживление вашего приложения Laravel: Понимание режима обслуживания
Здравствуйте, разработчики! В сегодняшней статье мы рассмотрим важный аспект управления приложениями, который часто упускается из виду в суете...
Установка и настройка Nginx и PHP на Ubuntu-сервере
Установка и настройка Nginx и PHP на Ubuntu-сервере
В этот раз я сделаю руководство по установке и настройке nginx и php на Ubuntu OS.
Коллекции в Laravel более простым способом
Коллекции в Laravel более простым способом
Привет, читатели, сегодня мы узнаем о коллекциях. В Laravel коллекции - это способ манипулировать массивами и играть с массивами данных. Благодаря...
Как установить PHP на Mac
Как установить PHP на Mac
PHP - это популярный язык программирования, который используется для разработки веб-приложений. Если вы используете Mac и хотите разрабатывать...
16
14
966
6
Перейти к ответу Данный вопрос помечен как решенный

Ответы 6

В зависимости от средней цены и распределения цен вы можете попробовать что-то вроде этого:

  1. Случайным образом выберите в сумме несколько элементов меньше, чем вы хотите (например, 25). Повторите попытку, пока их общее количество не станет меньше x.

  2. Затем используйте концепцию, указанную в вашем вопросе, чтобы найти комбинацию, которая обеспечивает оставшуюся сумму.

Я попробовал решение в своем сообщении для 5 элементов, и его выполнение занимает от 2 до 5+ секунд в зависимости от общей суммы.

Frank 12.03.2018 15:19

У вас действительно много предметов ... Анализировали ли вы свой запрос с помощью EXPLAIN?

Jonas Staudenmeir 12.03.2018 15:23

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

Frank 12.03.2018 18:06

Самый близкий ответ, который я могу дать, - это

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

Frank 14.03.2018 13:37
  1. сначала выберите все значения, где сумма = 500
  2. использовать mysql_query

затем выполните следующий код

$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).

В этом случае шаги следующие:

  1. Случайным образом выберите товар с ценой <500/30. Получите его цену, скажем, X.
  2. Случайным образом выберите товар, цена которого <(500 - X) / 29. Получите его цену, предположим, Y.
  3. Выберите продукт случайным образом, если цена <(500 - X - Y) / 28.

Повторите это 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-м разделе ниже.

2-й раздел: Использование процесса cron

Основываясь на предложении @Michael Zukowski, вы могли бы

  • создать таблицу для хранения найденных коллекций
  • определите процесс cron, который запускает вышеуказанный алгоритм несколько раз (в примере 10 раз), например. каждые 5 минут
  • если найдена коллекция, соответствующая сумме, добавить ее в новую таблицу

Таким образом вы можете найти коллекции, которые всегда суммируйте ровно до 500. Когда пользователь делает запрос, вы можете выбрать случайную коллекцию из новой таблицы.

Даже с коэффициентом совпадения 20%, процесс cron, который запускает алгоритм 10 раз каждые 5 минут в течение 24 часов, вы можете собрать более 500 коллекций.

На мой взгляд, использование процесса cron имеет следующие преимущества и недостатки:

Преимущества

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

недостатки

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

Другие вопросы по теме