为什么我必须为group by子句提供items.id列?

时间:2013-07-15 23:58:53

标签: postgresql

我想根据condition返回基于price asc排序的唯一项目。我的查询失败,因为Postgres希望items.id子句中存在group by。如果包含该查询,则返回与where子句匹配的所有内容,这不是我想要的。为什么我需要包含该列?

select items.*
from items
where product_id = 1 and items.status = 'in_stock'
group by condition /* , items.id returns everything */
order by items.price asc

| id | condition | price |
--------------------------
| 1 | new        | 9     |
| 2 | good       | 5     |
| 3 | good       | 3     |

我只想要带有ID 1和3的项目。

更新:使用下面的答案,这是一个小提琴,仍然会产生错误:

http://sqlfiddle.com/#!1/33786/2

3 个答案:

答案 0 :(得分:3)

问题在于PostgreSQL无法知道您想从哪个items记录中获取值;也就是说,它无法说明你想要这个:

| id | condition | price |
--------------------------
|  1 | new       |     9 |
|  3 | good      |     3 |

而不是这个:

| id | condition | price |
--------------------------
|  1 | new       |     9 |
|  2 | good      |     5 |

要解决此问题,您需要使用某种聚合函数,例如MAX

SELECT MAX(id) AS id,
       condition,
       MAX(price) AS price
  FROM items
 WHERE product_id = 1
   AND status = 'in_stock'
 GROUP BY condition
 ORDER BY price ASC

给出:

| id | condition | price |
--------------------------
|  1 | new       |     9 |
|  3 | good      |     5 |

(这个限制是SQL标准的一部分,大多数DBMS都强制执行它。一个例外是MySQL,它允许你的查询,但有一点需要注意“服务器可以自由选择每个组的任何值,所以除非他们如果是相同的,所选择的值是不确定的“[link]。”

答案 1 :(得分:3)

SQL Fiddle

select *
from (
    select distinct on (cond)
        id, cond, price
    from items
    where product_id = 1 and items.status = 'in_stock'
    order by cond, price
) s
order by price

答案 2 :(得分:2)

SQL标准需要这种行为,尽管像MySQL这样的某些数据库会忽略它,而是返回不可预测的结果

如果“cond = good”有多行,你要求“cond = good”行的“id”,数据库应该给你哪一行? id = 3或id = 2的行?怎么知道选哪个? MySQL picks an arbitrary row if there are multiple candidates,但标准不允许这样做。

在您的情况下,您似乎想为每种情况选择价格最低的行。

PostgreSQL提供了一个扩展名DISTINCT ON ...来帮助解决这个问题。 Clodaldo在他的回答中证明了这一点,所以我在此不再重复。使用DISTINCT ON将比下面的示例更有效。

SQL标准方法是使用窗口对结果进行排名,然后对排名数据进行过滤。不幸的是,这是非常低效的,因为它需要收集和排序匹配内部where子句的所有行。

SELECT *
FROM (
  SELECT *, dense_rank() OVER w AS itemrank
  FROM items
  WHERE product_id = 1 AND items.status = 'in_stock'
  WINDOW w AS (PARTITION BY cond ORDER BY price ASC)
) ranked_items
WHERE itemrank = 1;

http://sqlfiddle.com/#!1/33786/19

另一种SQL标准方法是使用聚合子查询来查找每个类别的最小价格,然后以最小价格显示所有行:

SELECT *
FROM items INNER JOIN (
  SELECT cond, min(price) AS minprice
  FROM items
  WHERE product_id = 1 AND items.status = 'in_stock'
  GROUP BY cond
) minprices(cond, price)
ON (items.price = minprices.price AND items.cond = minprices.cond)
ORDER BY items.price;

DISTINCT ON版本不同,如果最低价格的商品具有多个具有相同条件和价格的条目,则会显示多个条目

所以..你应该真的使用DISTINCT ON方法,但你需要理解它。开始with the PostgreSQL documentation here

另外,较新的PostgreSQL版本允许您引用您在GROUP BY中列出主键的表的任何列;它们识别主键上其他列的功能依赖性。因此,如果您在较新版本中提到过PK,则不必聚合其他cols。这就是标准所要求的,但旧版本不够聪明,无法明确地列出所有列。

这就是提出此问题的人通常想知道的内容,但并不严格适用于您的问题,因为事实证明您正在尝试使用GROUP BY来过滤行。