【问题标题】:SQL select elements where sum of field is less than NSQL 选择字段总和小于 N 的元素
【发布时间】:2012-07-26 04:58:36
【问题描述】:

鉴于我有一个包含以下内容的表格,非常简单:

# select * from messages;
  id | verbosity 
 ----+-----------
   1 |        20
   2 |        20
   3 |        20
   4 |        30
   5 |       100
 (5 rows)

我想选择 N 条消息,它们的详细度总和低于 Y(出于测试目的,假设它应该是 70,那么正确的结果将是 id 为 1、2、3 的消息)。 对我来说非常重要的是,该解决方案应该独立于数据库(它应该至少在 Postgres 和 SQLite 上工作)。

我正在尝试类似的东西:

SELECT * FROM messages GROUP BY id HAVING SUM(verbosity) < 70;

但它似乎没有按预期工作,因为它实际上并没有对详细度列中的所有值求和。

我将非常感谢任何提示/帮助。

【问题讨论】:

  • 想象一下,如果您有 10 行都带有verbosity=20,那么数据库应该将哪三行报告为您的 SELECT 的结果?看起来这个查询在当前形式下是模糊的。也许您需要添加更多标准。比如你有1: 29, 2: 30, 3: 20, 4: 20, 5: 10,数据库应该给你1, 2吗?它应该给1, 3, 4吗?或者1, 2, 5
  • 为什么你只期待 1,2,3 - 为什么不是 4?

标签: sql sqlite postgresql aggregate-functions sql-limit


【解决方案1】:
SELECT m.id, sum(m1.verbosity) AS total
FROM   messages m
JOIN   messages m1 ON m1.id <= m.id
WHERE  m.verbosity < 70    -- optional, to avoid pointless evaluation
GROUP  BY m.id
HAVING SUM(m1.verbosity) < 70
ORDER  BY total DESC
LIMIT  1;

这假定一个唯一的、升序的id,就像您在示例中那样。


在现代 Postgres 中 - 或者通常使用 现代标准 SQL(但在 SQLite 中不是):

简单 CTE

WITH cte AS (
   SELECT *, sum(verbosity) OVER (ORDER BY id) AS total
   FROM   messages
   )
SELECT *
FROM   cte
WHERE  total <= 70
ORDER  BY id;

递归 CTE

对于只检索小集合的大表应该更快。

WITH RECURSIVE cte AS (
   (  -- parentheses required
   SELECT id, verbosity, verbosity AS total
   FROM   messages
   ORDER  BY id
   LIMIT  1
   )

   UNION ALL 
   SELECT c1.id, c1.verbosity, c.total + c1.verbosity 
   FROM   cte c
   JOIN   LATERAL (
      SELECT *
      FROM   messages
      WHERE  id > c.id
      ORDER  BY id
      LIMIT  1
      ) c1 ON  c1.verbosity <= 70 - c.total
   WHERE c.total <= 70
   )
SELECT *
FROM   cte
ORDER  BY id;

所有标准功能,LIMIT 除外。

严格来说,不存在“独立于数据库”之类的东西。有各种 SQL 标准,但没有一个 RDBMS 完全符合。 LIMIT 适用于 PostgreSQL 和 SQLite(以及其他一些)。对 SQL Server 使用 TOP 1,对 Oracle 使用 rownum。这是comprehensive list on Wikipedia.

SQL:2008 standard 将是:

...
FETCH  FIRST 1 ROWS ONLY

... PostgreSQL 支持 - 但几乎没有任何其他 RDBMS。

适用于更多系统的纯粹替代方法是将其包装在子查询中并

SELECT max(total) FROM <subquery>

但这既慢又笨重。

SQL Fiddle.

【讨论】:

  • @Erwin 您介意将递归 CTE 查询转换为 SQL Server 吗?除了 TOP 1 之外,还需要进行更多语法更改,例如语句中的 LATERAL 和 RECURSIVE 词。
【解决方案2】:

这会起作用...

select * 
from messages
where id<=
(
    select MAX(id) from
    (
        select m2.id, SUM(m1.verbosity) sv 
        from messages m1
        inner join messages m2 on m1.id <=m2.id
        group by m2.id
    ) v
    where sv<70
)

但是,您应该了解,SQL 被设计为一种基于集合的语言,而不是一种迭代语言,因此它旨在将数据视为一个集合,而不是逐行处理。

【讨论】:

    猜你喜欢
    • 2021-03-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-07
    • 1970-01-01
    • 2011-08-03
    • 1970-01-01
    相关资源
    最近更新 更多