I am trying to select three random rows from a table, where their combined item_price
column is less than a desired amount.
Imagine you have an
Another solution i get by looking Lashane's answer because each of the item's price must not be greater than the total. There should be some improvement by adding this (try EXPLAIN to the query).
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
you can get all triplets of items having sum of price <= 300 with
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
then you could sort by rand() and pick one.
there are discussions about performance of selecting random rows in mysql that you should check. the triple join will be costly if items table is big.
EDIT
as suggested in other answers, this query can be improved filtering each item by price <= 300, and using an index on items.price.
You could do it step by step. Say we have $500 ask limit. First get the min price in your DB.
select MIN(item_price) from items
Lets say this is 25.00
so for our first item we want a max from 500 plus 2 times the least value (2 * 25 = 50) so i check for the first item matching less or equal to 450 dollars
select item_id, item_price from items where item_price <= 450 order by rand() limit 1
This item now maybe is 240 dollars, so next query is:
select item_id, item_price from items where item_price <= 140 order by rand() limit 1
The next one could be 50 dollars, so the next query is:
select item_id, item_price from items where item_price <= 90 order by rand() limit 1
And there you go.
I am aware, that this is a quite simple solution and there surely could be better solutions, but using triple joins and random sorting on large tables will swallow lots of performance, and the result of the queries are not better than running these three simple queries, that will run like burst if table is indexed properly.
Doing it this way also would give you fine control on combinations returned (i.e. you could extend items with categories and reduce queries to distinct categories, so for example you could combine technical+kitchen+fun categories).
Since we are all here to learn, and we never stop learning, i believe this solution is a good basis for a flexible extension of the functionality. If you want to use a single query, then i would advise to have the query dump a large set of possible combinations into a table, so you can run your massive query maybe once a day and when you want to pick a combination, you just query your pre-rendered random table.
I was able to get the result with both these queries and a PHP version below
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;
And
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
I would only do this if the Items table is relatively small. You can do this in PHP by selecting all the items with the price less than 300 and generating the k combinations(also named nCr) of 3 and then using a filter function that returns the ones that summed together are less than 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; })
here is another solution:
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
optionally you can filter by minimal sum
Here's a SQL only (MySQL flavour) solution:
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
Not very efficient for large result sets as it creates a subquery of the different permutations of 3 items that fulfill the total criteria and then picks one of these at random. The subquery returns its result as a regular expression to allow the rows to be picked in the outer query.
See SQL Fiddle demo.