【问题标题】:Selecting single and only single rows from GROUP BY从 GROUP BY 中选择单行且仅单行
【发布时间】:2016-03-01 02:38:31
【问题描述】:

我有两个表:parcelstructure,它们之间是一对多的关系:structure.parcel_id 指向 parcel.id

我想选择所有单个结构。我目前的解决方案有效,但非常怪诞:

SELECT 
max(column_1),
max(column_2),
max(column_3),
...
(twenty+ columns)

FROM structure
GROUP BY parcel_id
HAVING count(structure.id) = 1;

因为structure.id 不可为空并且上面的HAVING 子句,根据定义,每个组中只有一行。不幸的是 Postgres 没有意识到这一点,所以如果我说:

SELECT *    
FROM structure
GROUP BY parcel_id
HAVING count(structure.id) = 1;

然后我得到关于需要对列使用聚合函数的预期错误。我用任意的max() 函数解决了这个问题,但这会让试图理解代码的其他人感到困惑,它迫使我明确列出所有列,这意味着我必须重新潜入并编辑这段代码添加列。 (不幸的是,这在我的环境中经常发生。)

我有这个替代解决方案,它解决了我的大部分问题:

SELECT * FROM STRUCTURE
WHERE id IN (
    SELECT
        max(id) as id
    FROM structure
    GROUP by structure.parcel_id
    HAVING count(structure.id)  = 1
    );

但这显然给我的查询增加了不必要的缓慢,由于查询的频率和表的大小,我想避免这种情况。

This question 与我的要求非常相似,但它会抓取每个组的第一行,而不是第一行(也是唯一的)单数组。

有没有优雅的方法来解决这个问题?

每个请求的示例数据:

structure表:

id | parcel_id | column_1 | column_2 | ...
------------------------------------------
1  |   536     |   ...    | ....     | ...
2  |   536     |   ...    | ....     | ...
3  |   537     |   ...    | ....     | ...
4  |   538     |   ...    | ....     | ...
5  |   538     |   ...    | ....     | ...
6  |   539     |   ...    | ....     | ...
7  |   540     |   ...    | ....     | ...
8  |   541     |   ...    | ....     | ...
9  |   541     |   ...    | ....     | ...

想要的结果:

id | parcel_id | column_1 | column_2 | ...
------------------------------------------
3  |   537     |   ...    | ....     | ...
6  |   539     |   ...    | ....     | ...
7  |   540     |   ...    | ....     | ...

请注意,537、539 和 540 是唯一不重复的 parcel_id

两个表都有约 150 万行和约 25 列。

【问题讨论】:

  • 能否请您发布一些有代表性的样本数据和想要的结果。
  • 所以,structure 表有 150 万行。 parcel 表中有多少行? structure 表中有多少个地块只有一个对应的行?换句话说,最终查询将返回多少行?
  • 我不太熟悉 SQL,所以在评论中发表我的建议。您可以将自联接用作此 'SELECT S1.* FROM STRUCTURE S1, STRUCTURE S2 WHERE S1.parcel_id = S2.parcel_id GROUP BY S2.parcel_id HAVING count(S2.parcel_id) = 1'

标签: sql postgresql aggregate postgresql-performance


【解决方案1】:

使用窗口函数怎么样?

SELECT s.*    
FROM (SELECT s.*, COUNT(*) OVER (PARTITION BY parcel_id) as cnt
      FROM structure s
     ) s
WHERE cnt = 1;

但是,更有效的方法可能是:

select s.*
from structure s
where not exists (select 1
                  from structure s2
                  where s2.parcel_id = s.parcel_id and s2.id<> s.id
                 );

特别是,这可以利用structure(parcel_id, id) 上的索引。

【讨论】:

  • 你的第二种方法很聪明!我喜欢它,但经过一些(尽管相当粗糙)基准测试和查询计划分析后,它看起来会比我的怪诞解决方案慢。我希望找到一个至少在速度方面可以与之匹敌的解决方案。
  • @inhubbell 。 . .你有合适的索引吗?我希望第二条消息对索引来说非常快。
【解决方案2】:

这应该会快很多:

SELECT s.*
FROM  (
   SELECT parcel_id
   FROM   structure
   GROUP  BY 1
   HAVING count(*) = 1
   ) s1
JOIN structure s USING (parcel_id);

您所需要的只是(parcel_id) 上的索引。

  • 由于查询仅限于唯一的parcel_id,因此无需在子查询中涉及id。因此,我们可以从 (parcel_id) 上的普通索引中获取 index-only scan - 并使用相同的索引进行连接。

  • 连接应该比带有大子选择的IN 快一点。 (尽管它们在现代 Postgres 中大多产生相同的查询计划。)

  • count(*)count(&lt;expression&gt;) 快一点,因为只需要确定行的存在。

旁白:

@Gordon's 2nd queryNOT EXISTS 反半连接也应该很快。你只需要(parcel_id, id) 上的多列索引。

question you linked to 用于 SQL Server。这是 Postgres 的一个更相关的相关问题:

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-11-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-10-19
    • 2022-01-03
    • 2019-08-23
    • 2014-07-31
    相关资源
    最近更新 更多