2015-04-06 2 views
5

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

Представьте, что у вас есть <input> за сумму в долларах. Когда вы вводите сумму в долларах, база данных возвращает три случайных элемента, где их комбинированная цена меньше или равна сумме доллара, которую вы вводите.

Если я ввожу 300 долларов США, вы можете купить эти три предмета: 150 долларов, 100 долларов США и 50 долларов США. Мне трудно создать запрос, который будет возвращать три элемента, отвечающие этим критериям.

SELECT t1.item_id, t1.item_price 
FROM items t1 
INNER JOIN items t2 ON (t1.item_id = t2.item_id) 
GROUP BY t1.item_id, t1.item_name, t1.item_price 
HAVING SUM(t2.item_price) <=300 
ORDER BY RAND() 
LIMIT 3 

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

Я также попробовал этот запрос:

SELECT t1.item_id, t1.item_price 
FROM items t1 
JOIN items t2 ON t2.item_id <= t1.item_id 
WHERE t2.item_price <= 500 
GROUP BY t1.item_id 
HAVING SUM(t2.item_price) <= 500 
ORDER BY RAND() 
LIMIT 3 

Опять же, казалось, работать сначала, но потом начали возвращаться пункты за $ 2000.

Если в PHP есть лучший (даже жертвующий производительность) способ, я не против. Я просто не думал, что запрос будет таким сложным.

Как всегда, благодарите любого за помощь.

+0

Как близко вы хотите, чтобы общее количество трех предметов находилось до введенной суммы? Например, если вы введете сумму в 300 и 150 долларов, то это полезно? –

+0

@BitwiseCreative: Как я себе это представлял, если бы у меня был предел в 300 долларов, теоретически он мог бы вернуть 30 долларов США, если каждый из них составляет 10 долларов. Я бы не стал против этого, особенно если это проще. Я думаю, что практически, приблизиться к пределу было бы полезно, но это было не мое первоначальное намерение. Во-вторых, я не возражаю против возвращения 1 предмета за полные 300 долларов. Спасибо за вопрос. – dcclassics

+0

@BitwiseCreative Кроме того, я добавлю, что, вероятно, не будет много предметов стоимостью 10 долларов.Было бы проще не устанавливать ограничение на предмет, а вместо этого давать мне неопределенное количество случайных строк, если оно ниже предела? – dcclassics

ответ

3

здесь другое решение:

SELECT t1.item_id as id1, t2.item_id as id2, t3.item_id as i3 
FROM items t1, items t2, items t3 
WHERE 
t1.item_id <> t2.item_id and 
t1.item_id <> t3.item_id and 
t2.item_id <> t3.item_id and 
(t1.item_price + t2.item_price + t3.item_price) <= 300 
order by rand() 
limit 1 

необязательно можно фильтровать по минимальной сумме

+0

это, кажется, ближе всего к моему первоначальному намерению. Не могли бы вы указать какие-либо ошибки? – dcclassics

+0

@ dcclassics этот запрос очень медленный на больших таблицах, триплеты вроде 1,2,3 и 1,3,2 разные –

+1

@ Lashane вы можете попробовать с помощью 't1.item_id fthiella

3

Вы можете сделать это шаг за шагом. Скажем, у нас есть $ 500 ask limit. Сначала получите минимальную цену в своей БД.

select MIN(item_price) from items 

Допустим, это 25.00 так что для нашего первого пункта мы хотим максимум от 500 плюс 2 раза наименьшее значение (2 * 25 = 50), так что я могу проверить для первого элемента, соответствующего меньше или равно 450 долларов

select item_id, item_price from items where item_price <= 450 order by rand() limit 1 

Этот предмет теперь может быть 240 долларов, так что следующий запрос является:

select item_id, item_price from items where item_price <= 140 order by rand() limit 1 

Следующим может быть 50 долларов, так что следующий запрос:

select item_id, item_price from items where item_price <= 90 order by rand() limit 1 

И вот вы идете.

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

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

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

+0

Является ли это хорошим окончательным решением? Похоже, что это ограничило бы случайность полученных результатов. – dcclassics

+0

Это может быть неудачно, если есть только один предмет с ценой между минимальным и минимальным значением 2 *, а первый выбор имеет цену 450. – 1010

+0

@ dcclassics не очень, в конце концов, это «просто» rand() и rand() не будет лучше или хуже, если вы звоните один или три раза ... и вы также можете точно настроить диапазон, чтобы не только иметь выбор rand(), но и хорошо взвешенный –

2

вы можете получить все тройки элементов, имеющих сумму цены < = 300 с

SELECT a.item_id, a.item_price, b.item_id, b.item_price, c.item_id, c.item_price 
    FROM items a 
     JOIN items b ON a.item_id < b.item_id 
     JOIN items c ON b.item_id < c.item_id 
WHERE a.item_price + b.item_price + c.item_price <= 300 

затем вы можете сортировать по rand() и выбрать один.

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

РЕДАКТИРОВАТЬ

как предложено в других ответов, этот запрос может быть улучшена фильтрация каждого элемента по цене < = 300, а также с использованием индекса на items.price.

1

Я был в состоянии получить результат с обоих этих запросов и версии PHP ниже

SET @MaxAmount = 5; 
SELECT FirstItem.id, SecondItem.id, ThirdItem.id, FirstItem.amount + SecondItem.amount + ThirdItem.amount as Total 
FROM Items as FirstItem 
CROSS JOIN Items as SecondItem ON SecondItem.id <> FirstItem.id and FirstItem.amount + SecondItem.amount < @MaxAmount 
CROSS JOIN Items as ThirdItem ON ThirdItem.id <> FirstItem.id and ThirdItem.id <> SecondItem.id and FirstItem.amount + SecondItem.amount + ThirdItem.amount < @MaxAmount 
ORDER BY RAND() 
LIMIT 3; 

И

SET @MaxAmount = 5; 
SELECT FirstItem.id as id1, SecondItem.id as id2, ThirdItem.id as i3, FirstItem.amount + SecondItem.amount + ThirdItem.amount as Total 
FROM Items FirstItem, Items SecondItem, Items ThirdItem 
WHERE FirstItem.amount + SecondItem.amount < @MaxAmount 
AND FirstItem.amount + SecondItem.amount + ThirdItem.amount < @MaxAmount 
AND SecondItem.id != FirstItem.id -- Prevent Same Id from showing up 
AND ThirdItem.id != FirstItem.id and ThirdItem.id != SecondItem.id 
ORDER BY RAND() 
LIMIT 3; 

http://sqlfiddle.com/#!9/0e1c8/3

я бы это, только если таблица элементов является относительно маленький. Вы можете сделать это в PHP, выбрав все элементы с ценой менее 300 и генерации k combinations(also named nCr) из 3, а затем, используя функцию фильтра, который возвращает те, которые суммируют являются менее 300.

$rows = $db->query("Select FirstItem.amount as amount1, SecondItem.amount as amount2, ThirdItem.amount as amount3 (.. and also the ids) from Items where amount < 300"); 
$ncr = getCombinations($rows, 3); 
$filtered = array_filter($ncr, function($row) { return $row['amount1'] + $row['amount2'] + $row['amount3'] < 300; }) 
+0

Когда вы говорите, что делаете это только в том случае, если таблица Items относительно невелика, вы имеете в виду все три варианта или только для одного PHP? – dcclassics

+0

Зависит от текущей нагрузки сервера MySQL. Если ваше конкретное приложение не получает много писем, скорее всего, вы сможете с ним справиться. Если вы запустите объяснение по обоим запросам, вы увидите, что они практически идентичны. Если бы это был я, я бы запустил его на PHP и сохранил результаты, а затем запросил бы набор результатов (возможно, другую таблицу) напрямую. Если ваши изменения низкие, вы можете выполнить операцию за минимальное время использования (часовой пояс в полночь), таким образом, амортизируя операцию только на чтение. – Andre

1

Вот решение только (MySQL вкус) SQL:

SELECT i.* 
FROM items i 
CROSS JOIN 
    (SELECT CONCAT('^(', t1.item_id, '|', t2.item_id, '|', t3.item_id, ')$') AS regex 
    FROM items t1 
    CROSS JOIN items t2 
    CROSS JOIN items t3 
    WHERE t1.item_id < t2.item_id 
     AND t2.item_id < t3.item_id 
     AND t1.item_price + t2.item_price + t3.item_price <= 300 
    ORDER BY RAND() 
    LIMIT 1) s 
WHERE i.item_id REGEXP s.regex 

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

См. SQL Fiddle demo.

+0

Был бы способ вернуть случайное количество предметов тоже? – dcclassics

+0

Извините, не используя этот метод, поскольку он полагается на фиксированное количество элементов для CROSS JOIN. –

0

Другое решение, которое я получаю, смотря на ответ Лашане, потому что каждая цена товара не должна превышать сумму. Это должно быть некоторое улучшение, добавив это (попробуйте EXPLAIN для запроса).

SELECT t1.item_id as id1, t2.item_id as id2, t3.item_id as i3 
FROM items t1, items t2, items t3 
WHERE 
t1.item_price <= 300 AND 
t2.item_price <= 300 AND 
t3.item_price <= 300 AND 
t1.item_id <> t2.item_id AND 
t1.item_id <> t3.item_id AND 
t2.item_id <> t3.item_id AND 
(t1.item_price + t2.item_price + t3.item_price) <= 300 
ORDER BY RAND() 
LIMIT 1 
Смежные вопросы