CREATE TABLE inventory_box (
box_id varchar(10),
value integer
);
INSERT INTO inventory_box VALUES ('1', 10), ('2', 15), ('3', 20);
我准备了一个sql fiddle的架构。
我想选择一个合并值大于20的库存盒列表
possible result 1. box 1 + box 2 (10 + 15 >= 20)
这就是我现在正在做的事情:
SELECT * FROM inventory_box LIMIT 1 OFFSET 0;
-- count on the client side and see if I got enough
-- got 10
SELECT * FROM inventory_box LIMIT 1 OFFSET 1;
-- count on the client side and see if I got enough
-- got 15, add it to the first query which returned 10
-- total is 25, ok, got enough, return answer
我正在寻找一种解决方案,扫描一旦达到目标值就会停止
答案 0 :(得分:3)
一种可能的方法是以box_id顺序扫描表,直到总数大于30,然后返回所有先前的行加上超过限制的总和的行。请注意,当达到总和时,扫描不会停止,它将整个表格合计,然后返回结果以选择结果。
http://sqlfiddle.com/#!15/1c502/4
SELECT
array_agg(box_id ORDER BY box_id) AS box_ids,
max(boxsum) AS boxsum
FROM
(
SELECT
box_id,
sum(value) OVER (ORDER BY box_id) AS boxsum,
sum(value) OVER (ORDER BY box_id ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING) AS prevboxsum
FROM
inventory_box
) x
WHERE prevboxsum < 30 OR prevboxsum IS NULL;
但实际上,在SQL中(或者根本不是)以一般和可靠的方式做这将是非常可怕的。
如果您愿意,可以ORDER BY value ASC
代替ORDER BY box_id
;这将添加从最小到最大的框。但是,如果您从池中删除所有小盒子并再次运行它,那么这将会灾难性地失败,并重复。很快就会把两个大盒子混在一起效率低下。
为了解决这个问题,一般情况下,找到最小的组合是一个很难的优化问题,可能会受益于不精确的基于样本和概率的方法。
要按顺序扫描表,直到总和到达目标,然后锁定表,然后使用PL / PgSQL从游标中读取行,这些行返回value
顺序加array_agg(box_id) OVER (ORDER BY value)
和{sum(value) OVER (order by value)
行的行{1}}。达到所需总和后,返回当前行的数组。这不会产生最佳解决方案,但它会生成 a 解决方案,如果有合适的索引,我认为如果没有全表扫描,它就会这样做的地方。
答案 1 :(得分:1)
您的问题更新澄清了您的实际要求比成熟"subset sum problem" suspected by @GhostGambler简单得多: 只需获取行,直到总和足够大。
我按box_id
排序以获得确定性结果。您甚至可以完全放弃ORDER BY
以更快地获得任何有效结果。
WITH RECURSIVE i AS (
SELECT *, row_number() OVER (ORDER BY box_id) AS rn
FROM inventory_box
)
, r AS (
SELECT box_id, val, val AS total, 2 AS rn
FROM i
WHERE rn = 1
UNION ALL
SELECT i.box_id, i.val, r.total + i.val, r.rn + 1
FROM r
JOIN i USING (rn)
WHERE r.total < 20
)
SELECT box_id, val, total
FROM r
ORDER BY box_id;
使用sum()作为窗口聚合函数(这种方式最便宜)。
CREATE OR REPLACE FUNCTION f_shop_for(_total int)
RETURNS TABLE (box_id text, val int, total int) AS
$func$
BEGIN
total := 0;
FOR box_id, val, total IN
SELECT i.box_id, i.val
, sum(i.val) OVER (ORDER BY i.box_id) AS total
FROM inventory_box i
LOOP
RETURN NEXT;
EXIT WHEN total >= _total;
END LOOP;
END
$func$ LANGUAGE plpgsql STABLE;
SELECT * FROM f_shop_for(35);
我用100万行的大表测试了两者。该函数仅从索引和表中读取必要的行。 CTE非常慢,似乎扫描整个桌子......
除此之外:按包含数字数据的varchar
列(box_id
)排序会产生可疑结果。也许这应该是一个数字类型,真的吗?