从mysql表中的一列中找到一个等于给定和的数字组合


find a combination of numbers that equal a given sum from a single column in mysql table

我有一个MySQL表包,它有字段id, max_post。max_post中的值包含

  1,2,3,4,5,10,20,30,40,50,100,200,500,1000,2000

我想找到最合适的包裹。例如,我输入230,那么它应该去掉所有东西,选择200和30个包装。我想用SQL查询得到结果。

以下是Gordon查询的更正版本。当有一个较小的值可用时,Gordon的查询主要缺少连接一个空记录。它为230找到的最佳匹配是200-20-5-4,因为最佳解决方案200-30-null-null甚至不在我们正在评估的集合中。这是因为p3实际上从未外部连接到200-30,因为表中存在值小于30的记录。(到外部联接意味着当没有匹配时添加一个空记录。)

select 
  p1.max_post, p2.max_post, p3.max_post, p4.max_post
from packages p1 
left join (select max_post from packages union all select null) p2
  on (p1.max_post > p2.max_post or p2.max_post is null)  
left join (select max_post from packages union all select null) p3 
  on (p2.max_post > p3.max_post or p3.max_post is null)  
left join (select max_post from packages union all select null) p4 
  on (p3.max_post > p4.max_post or p4.max_post is null)  
where p1.max_post + coalesce(p2.max_post, 0) + coalesce(p3.max_post, 0) + coalesce(p4.max_post, 0) <= 230
order by 
  p1.max_post + coalesce(p2.max_post, 0) + coalesce(p3.max_post, 0) + coalesce(p4.max_post, 0) desc,
  (p2.max_post is null) desc,
  (p3.max_post is null) desc,
  (p4.max_post is null) desc
limit 1;

(这可以通过在查询中添加where max_post <= 230来稍微优化,因此,值本身已经高于所需总和的记录将立即被驳回。)

SQL fiddle:http://sqlfiddle.com/#!9/5f6d25/18。

这是一个装箱问题的例子。基本上,解决它的唯一方法是显式的。

您可以通过执行显式联接来获得所有此类组合的列表:

select p1.max_post, p2.max_post, p3.max_post, p4.max_post
from packages p1 left join
     packages p2
     on p1.max_post > p2.max_post left join
     packages p3
     on p2.max_post > p3.max_post left join
     packages p4
     on p3.max_post > p4.max_post
where (p1.max_post + coalesce(p2.max_post, 0) + coalesce(p3.max_post, 0) +
       coalesce(p4.max_post, 0)
      ) = 230
order by (p2.max_post is null) desc,
         (p3.max_post is null) desc,
         (p4.max_post is null) desc

order by将"较短"的列表放在首位。如果您愿意,可以添加一个limit

注意:这实际上是在表中的值之间创建一个笛卡尔乘积四次。随着表的大小增加,执行时间也会增加。

满足您所述偏好的替代方案483=>200,100,50,40

SELECT
    target.max_post,
    p1.id,
    p1.max_post,
    p2.id,
    p2.max_post,
    p3.id,
    p3.max_post,
    p4.id,
    p4.max_post
FROM
(
    SELECT 483 AS max_post
)
    target
LEFT JOIN
    packages p1
        ON  p1.id = (SELECT id
                       FROM packages
                      WHERE max_post <= target.max_post
                   ORDER BY max_post DESC
                      LIMIT 1
                    )
LEFT JOIN
    packages p2
        ON  p2.id = (SELECT id
                       FROM packages
                      WHERE max_post <= target.max_post - p1.max_post
                   ORDER BY max_post DESC
                      LIMIT 1
                    )
LEFT JOIN
    packages p3
        ON  p3.id = (SELECT id
                       FROM packages
                      WHERE max_post <= target.max_post - p1.max_post - p2.max_post
                   ORDER BY max_post DESC
                      LIMIT 1
                    )
LEFT JOIN
    packages p4
        ON  p4.id = (SELECT id
                       FROM packages
                      WHERE max_post <= target.max_post - p1.max_post - p2.max_post - p3.max_post
                   ORDER BY max_post DESC
                      LIMIT 1
                    )

它使用重复的相关子查询。对于非常小的packages表,笛卡尔乘积搜索可能更快(尽管不能解决上述情况),对于任何合理大小的任何表,我都希望相关的子查询更快。

对于包含10个值的表
-笛卡尔乘积搜索数百个组合(比10 choose 4=210多得多)
-相关子查询搜索40个值(10x4)

对于包含100个值的表
-Cartessian乘积搜索了数百万个组合(远远超过100 choose 4=3,921,225
-相关子查询搜索400个值(100x4)