【问题标题】:SELECT fields from one table with aggregates from related table从一个表中选择字段,并从相关表中聚合
【发布时间】:2016-05-01 05:14:21
【问题描述】:

这里是 2 个表格的简化描述:

CREATE TABLE jobs(id PRIMARY KEY, description);
CREATE TABLE dates(id PRIMARY KEY, job REFERENCES jobs(id), date);

每个工作可能有一个或多个日期。

我想创建一个生成以下内容的查询(在 pidgin 中):

jobs.id, jobs.description, min(dates.date) as start, max(dates.date) as finish

我尝试过这样的事情:

SELECT id, description,
      (SELECT min(date) as start  FROM dates d WHERE d.job=j.id),
      (SELECT max(date) as finish FROM dates d WHERE d.job=j.id)
FROM jobs j;

可行,但看起来效率很低。

我尝试了INNER JOIN,但看不到如何将jobsdates 上的合适聚合查询结合起来。

谁能提出一个干净有效的方法来做到这一点?

【问题讨论】:

  • 您可以聚合已经加入的“表”。很可能会使用GROUP BY
  • @PM77-1 类似这样的东西:SELECT min(jobs.id),min(jobs.description), min(dates.date) AS start, max(dates.data) AS finish FROM obs INNER JOIN dates ON jobs.id=dates.job GROUP BY jobs.id。我想到了这一点,但想知道将min 用于job 项目是否浪费。
  • 您不必汇总用于分组的字段。
  • @PM77-1 你是对的,当然。更正后的语句为:SELECT jobs.id,min(jobs.description), min(dates.date) AS start, max(dates.data) AS finish FROM obs INNER JOIN dates ON jobs.id=dates.job GROUP BY jobs.id。但是,在我的实际应用程序中,jobs 表中还有其他字段也需要列出,类似于上面的description
  • 与往常一样,应该声明您的 Postgres 版本。

标签: sql postgresql subquery inner-join aggregate


【解决方案1】:

在检索所有行时:先聚合,后加入:

SELECT id, j.description, d.start, d.finish
FROM   jobs j
LEFT   JOIN (
   SELECT job AS id, min(date) AS start, max(date) AS finish 
   FROM   dates 
   GROUP  BY job
   ) d USING (id);

相关:

关于JOIN .. USING

这不是“不同类型的连接”。 USING (col)ON a.col = b.col标准 SQL (!) 语法快捷方式。更准确地说,quoting the manual:

USING 子句是一种简写形式,可让您利用 join双方同名的具体情况 用于连接列。它需要一个逗号分隔的列表 共享列名并形成一个连接条件,其中包括 每个人的平等比较。例如,将 T1T2USING (a, b) 产生连接条件 ON *T1*.a = *T2*.a AND *T1*.b = *T2*.b

此外,JOIN USING 的输出抑制了冗余列: 不需要打印两个匹配的列,因为它们必须 具有相等的值。而JOIN ON 产生所有来自 T1 的列 通过 T2 的所有列,JOIN USING 为每个列生成一个输出列 列出的列对(按列出的顺序),后跟任何 T1 中的剩余列,然后是 T2 中的任何剩余列。

特别方便,可以写SELECT * FROM ...,加入列只列出一次。

【讨论】:

  • 我以前没见过这种类型的加入。 USING 是什么,为什么 `ON d.job=j.id) 不起作用?
  • @Manngo:考虑添加的解释。 ON d.job=j.id 在这里不起作用,因为在我应用列别名 id(带有 job AS id)之后,派生表 d 中没有列 job。如果您不熟悉方便的快捷方式USING (id),请删除别名或改用:ON d.id = j.id
  • 好吧,我以这里的价格学到了两件事。在连接之前聚合,以及 USING 子句。似乎您也可以避免将列别名为与列名相同的列名,这样您就可以包含 SELECT id, job AS id FROM dates 这样的怪异现象,这可能完全没用,但这有助于解释为什么您不能在计算表达式。感谢您的出色解决方案。
【解决方案2】:

除了Erwin's solution,还可以使用window clause

SELECT j.id, j.description,
       first_value(d.date) OVER w AS start,
       last_value(d.date) OVER w AS finish
FROM jobs j
JOIN dates d ON d.job = j.id
WINDOW w AS (PARTITION BY j.id ORDER BY d.date
            ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING);

窗口函数有效地按一个或多个列(PARTITION BY 子句)和/或ORDER BY 其他一些列进行分组,然后您可以对其应用一些window function,甚至是常规聚合函数,而不会影响分组或任何其他列的排序(在您的情况下为description)。它需要一种稍微不同的查询构造方式,但一旦你明白了,它就非常棒了。

在您的情况下,您需要获取 partition 的第一个值,这很容易,因为默认情况下可以访问它。您还需要查看 window frame(默认以当前行结束)到 partition 中的最后一个值,然后您需要 ROWS 子句。由于您使用相同的窗口定义生成两列,因此此处使用WINDOW 子句;如果它适用于单个列,您可以在选择列表中编写窗口函数,后跟 OVER 子句和不带名称的窗口定义 (WINDOW w AS (...))。

【讨论】:

  • 这超出了我自己对窗口函数的经验(仅限于小计和获取行号)。谢谢你——我需要花一些时间来了解更多。
猜你喜欢
  • 1970-01-01
  • 2021-04-28
  • 1970-01-01
  • 1970-01-01
  • 2020-07-16
  • 1970-01-01
  • 2012-04-24
  • 2017-10-31
  • 2016-04-16
相关资源
最近更新 更多