MySQL随机选择三行,其中三行之和小于值


MySQL Select 3 random rows where sum of three rows is less than value

我试图从一个表中随机选择三行,其中它们的组合item_price列小于所需数量。

想象一下,你有一个金额为1美元的<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中有更好(甚至牺牲性能)的方法来实现这一点,我并不介意。我只是没想到这个问题会这么难。

一如既往,感谢任何人的帮助。

这里有另一个解决方案:

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

可选地,您可以通过最小和进行筛选

您可以一步一步地完成。假设我们有$500请求限额。首先在数据库中获取最低价格。

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

就这样。

我知道,这是一个非常简单的解决方案,当然还有更好的解决方案。在大型表上使用三重联接和随机排序会降低很多性能,查询的结果并不比运行这三个简单的查询好,如果表的索引正确,这三个查询将像突发一样运行。

这样做也可以让你对返回的组合进行精细控制(即,你可以用类别扩展项目,并将查询减少到不同的类别,例如,你可以组合技术+厨房+有趣的类别)。

既然我们都是来学习的,而且我们从未停止学习,我相信这个解决方案是灵活扩展功能的良好基础。如果你想使用单个查询,那么我建议让查询将一大组可能的组合转储到一个表中,这样你就可以每天运行一次大规模查询,当你想选择一个组合时,你只需查询预渲染的随机表。

您可以获得所有具有价格总和<=的项目的三元组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中随机选择行的性能的讨论,您应该进行检查。如果items表很大,那么三重联接将是昂贵的。

编辑

如其他答案中所建议的,可以改进该查询,按价格<=过滤每个项目300,并使用items.price上的索引。

我能够通过这些查询和低于的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

只有当Items表相对较小时,我才会这样做。您可以在PHP中这样做,方法是选择价格低于300的所有项目,生成3的k个组合(也称为nCr),然后使用一个过滤函数返回加起来小于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; })

这里有一个仅SQL(MySQL风格)的解决方案:

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演示。

我通过查看Lashane的答案得到的另一个解决方案,因为每个项目的价格都不能大于总数。添加此项应该会有所改进(请尝试将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