我有一张桌子
items
id int unsigned auto_increment primary key,
name varchar(255)
price DECIMAL(6,2)
我希望从这张表中获得至少30个随机商品,其中总价格等于500,实现这一目标的最佳方法是什么?
我看过这个看似有类似问题的解决方案MySQL Select 3 random rows where sum of three rows is less than value
我想知道是否有更容易实施和/或更有效的其他解决方案
答案 0 :(得分:6)
我能提供的最接近的答案是
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项时,我们重复这个过程,直到我们遍历所有10k选定的项目。在找到这30个项目时,我们可以节省最多的总和。因此,最后我们选择30个具有最大总和的项目(意味着最接近目标500)。 不确定这是否是您最初想要的,但找到500的精确总和需要在DB端付出太多努力。
答案 1 :(得分:3)
如果您希望高效停止浪费您的时间并去最终的合作。创建控制台脚本,以任何必要的方式完成您想要完成的任务,然后在CRON中运行此脚本或偶尔使用任何计划软件。
每次有100,1000名访问者,您是否希望执行查询?这是耗费时间和资源的。 DBMS也不能缓存随机排序的查询。转到最终一致性:创建一个表来保存记录并每次清除它,锁定写入,然后加载新设置,例如每5分钟。
至少这是我在负载很重的应用程序中的方式。在代码中,运行简单的SELECT
查询。
答案 2 :(得分:1)
如果您的产品列表满足以下假设:
,则有一种解决方案您的产品价格介于0.00到500.00之间。例如。 0.01,0.02等到499.99。或者0.05,0.10等等到499.95。
该算法基于以下内容:
在总和为S的n个正数的集合中,其中至少有一个将小于S除以n(S / n)
在这种情况下,步骤是:
重复29次,获得29件产品。对于最后一个产品,请选择价格=剩余价格的产品。 (或价格&lt; =剩余价格和按订单价格排序,希望你能够足够接近)。
表格项目:
随机产品最高价格:
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并在您提供的链接问题上使用解决方案,以获得总计剩余价格的几种产品。
请注意,允许使用重复产品。为避免重复,您可以使用已找到的产品的附加IN参数扩展getRandomProduct
,并添加条件 NOT IN 以排除它们。
更新:您可以克服上述限制,以便始终使用所述的cron流程查找总计为500 的集合在下面的第二部分。
基于@Michael Zukowski的建议,你可以
通过这种方式,您可以找到总是精确到500 的集合。当用户发出请求时,您可以从新表中选择一个随机集合。
即使匹配率为20%,一个cron进程在24小时内每5分钟运行一次算法10次,你可以收集500多个。
在我看来,使用cron进程有以下优点和缺点:
<强>优点强>
<强>缺点强>
答案 3 :(得分:0)
根据平均价格和价格分布,你可以尝试这样的事情:
随机选择少于您想要的项目(例如25)。重试,直到其总金额小于x。
然后使用您问题中链接的概念来查找提供剩余金额的组合。
答案 4 :(得分:0)
然后执行以下代码
$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是必需的数组
答案 5 :(得分:0)
令我感到惊讶的是,没有人建议,作为记录,蛮力解决方案:
SELECT
i1.id,
i2.id,
...,
i30.id,
i1.price + i2.price + ... + i30.price
FROM items i1
INNER JOIN items i2 ON i2.id NOT IN (i1.id)
...
INNER JOIN items i30 ON i30.id NOT IN (i1.id, i2.id, ..., i29.id)
ORDER BY ABS(x - (i1.price + i2.price + ... + i30.price))
这样的请求可以由程序生成以避免错误。这几乎是一个笑话,因为时间是O(n ^ 30)(泛型https://en.wikipedia.org/wiki/Subset_sum_problem是NP完整的,但是如果你修复子集的大小,则不是。 ),但它可能并且可能对预计算有意义。当价格集没有变化时,使用预先计算的价格集并找到价格过高的随机物品。
有一个动态编程解决方案(请参阅维基百科),但可能需要很长时间才能满足您的需求。还有一个多项式时间近似算法,但天真的实现将是查询中的O(n)(我没有搜索另一个实现)。
我提出另一种可能性,没有Jannes Botis的假设原则是贪婪的“爬山”,有一些撤退,因为贪婪的方法不适合所有情况。
首先,摘要:取30个最便宜的物品的总和,然后通过用昂贵的物品替换廉价物品,尽可能快地进步到x(贪婪);如果你超越x,那么最大限度地退一步并恢复攀爬,除非你已经完成或累了。
现在,细节(应该使用PHP + MySQL,而不仅仅是MySQL):
设N = 30
按升价对商品进行排序,然后选择前N个
对于价格的B树索引,它应该是快速的
因此,x - 总&gt; 0,我们希望差异最接近0.
选择每对项目(带连接),其中:
按升序排序(x - 总) - (p1 - p2)。
如果没有匹配的行,则有两种情况(如果允许N增长,可以使用两个查询):
否则取第一行(最接近峰值)并在项目中用i2替换i1:新总数为总数 - p1 + p2,现在x - 总数> = 0且你离得更近了到0。
*连接将采用一些O(n):N项目i1 * [(n-N)项目i2减去具有p2&gt;的项目; P1] *
有很多方法可以撤退。这是一个。
对于价格的B树索引,它应该是快速的
我希望这很清楚。您可以调整它以决定何时做得足够多并使用预先计算的30个项目,总价格为x。我认为时间复杂度在平均情况下是O(n)。我做了一些测试(python + sqlite),有200个项目,0到1000之间的随机价格和没有撤退。在1000次测试中,22次失败达到5000次(0.44%),3次尝试成功708次,4次尝试成功139次,3次尝试取得126次成功,5次尝试取得4次成功,1次尝试成功1次(“尝试”是尝试一组与30个最便宜的项目不同的项目:k次尝试表示步骤2)的查询次数。这取决于物品的数量,价格,......
您还可以制作变体,例如从随机的一组项目开始,尝试缩小x,围绕x振荡而不是后退,......
答案 6 :(得分:-1)
如果你阅读了MySQL手册,你可能已经看到了 ORDER BY RAND()来随机化这些行。
这个例子工作正常,如果你只说1000行就很快。只要有10000行,排序行的开销就变得很重要。不要忘记:我们只会扔掉几乎所有的行。
一个很棒的post处理了几个案例,从简单到间隙,再到不均匀的差距。
以下是如何完美地完成这项工作:
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;