【问题标题】:SELECT with sorting and counting by a joined table通过连接表进行排序和计数的 SELECT
【发布时间】:2019-03-22 15:59:29
【问题描述】:

我很难使用 PostgreSQL SELECT,起初看起来很简单。涉及表:

CREATE TABLE events (id INT, customers_id INT);
CREATE TABLE jobs (
  events_id INT,
  "from"    TIMESTAMP,
  until     TIMESTAMP,
  users_id  INT);
  • 每个事件可以有多个作业
  • 事件的开始和结束由相应作业的最低"from" 和最高until 定义
  • 每个作业都可以分配给一个用户

我需要如下表:

events_id | customers_id | min(from) | max(until) | total_jobs | open_jobs
        1 |            1 |  .. 08:00 |   .. 11:00 |          4 |        1

到目前为止我的选择:

SELECT e.id, e.customers_id, min(j.from) as min_from, max(j.until) as max_until, 
  count(j.id) as total_jobs
FROM events e
LEFT JOIN jobs j ON j.events_id = e.id
GROUP BY e.id, e.customers_id

这给了我前 5 列的结果,但我如何才能仅包含 users_id = NULL 的作业的第二个计数?我假设我需要第二个 LEFT JOIN 在工作表上,但不知何故我无法让它工作。

我怎样才能正确有效地实现它?

【问题讨论】:

  • 只对 users_id = NULL 的作业进行第二次计数是什么意思?什么是空缺职位?没有用户的工作?即,您想知道如何计算 open_jobs 即 user_id 为 null 的作业数?
  • 您是在查询所有事件(如您的查询所暗示的那样),还是查询一个或几个给定的events_id?请始终披露您的 Postgres 版本。 SELECT version() 有帮助。我冒昧地修复了您设置的语法。像from 这样的保留字需要双引号(除非像j.from 那样有表限定)。那么你在标题中提到的“排序”呢?
  • @KristjanKica 很抱歉没有澄清“空缺职位”。我的意思是 users_id = NULL 的工作
  • @ErwinBrandstetter 我正在查询所有事件。 Postgres 版本是 10.5。感谢您的编辑。我忘记排序了,但它只是一个ORDER BY min("from")

标签: sql postgresql eloquent left-join aggregate


【解决方案1】:

假设open job定义为until为空

WITH open_jobs_cte as (select events_id, customer_id, count(*) open_jobs FROM jobs WHERE until is null group by 1,2)
SELECT e.id, e.customers_id, min(j.from) as min_from, max(j.until) as max_until, count(j.id) as total_jobs, open_jobs
FROM events e
LEFT JOIN jobs j ON j.events_id = e.id
LEFT JOIN open_jobs_cte oj ON oj.events_id = e.id
GROUP BY e.id, e.customers_id

根据您的要求/设计,客户可以被排除在 cte 之外

【讨论】:

  • 抱歉,open jobusers_id = NULL 的工作。但这很容易改变。等我在家的时候再试试。没听说过WITH (cte),谢谢你的回答
  • 如果我删除 customer_id(因为它在 events 中)并将其更改为 users_id is null,它会给我错误 ERROR: column "oj.open_jobs" must appear in the GROUP BY clause or be used in an aggregate function
【解决方案2】:

假设 open_jobs 是 user_id 为 null 的作业数?

SELECT e.id, e.customers_id, min(j.from) as min_from, max(j.until) as max_until, 
  count(j.id) as total_jobs,sum(case user_id is null then 1 else 0) as open_jobs
FROM events e
LEFT JOIN jobs j ON j.events_id = e.id
GROUP BY e.id, e.customers_id

注意sum(case user_id is null then 1 else 0)。如果 user_id 为空,那么你有一个空缺职位,所以你加 1,否则你加 0。

EDIT:由于还需要按min_from排序,所以可以

select *from
(
SELECT e.id, e.customers_id, min(j.from) as min_from, max(j.until) as max_until, 
  count(j.id) as total_jobs,sum(case user_id is null then 1 else 0) as open_jobs
FROM events e
LEFT JOIN jobs j ON j.events_id = e.id
GROUP BY e.id, e.customers_id
) as results 
order by min_from

【讨论】:

  • 感谢您的回答。等我在家的时候试试看。
【解决方案3】:

在获取所有或大多数事件时,在连接之前聚合 n 表通常是最快的(也是最简单的):

SELECT e.id, e.customers_id
     , j.min_from
     , j.max_until
     , j.total_jobs
     , j.open_jobs
FROM   events e
LEFT   JOIN (
   SELECT events_id    AS id -- alias only to ease join syntax
        , min("from")  AS min_from
        , max(until)   AS max_until
        , count(*)     AS total_jobs
        , count(*) FILTER (WHERE users_id IS NULL) AS open_jobs
-- equivalent for old versions:
--      , count(users_id IS NULL OR NULL)          AS open_jobs
   FROM   jobs
   GROUP  BY 1
   )  j USING (id);

这样你根本不需要GROUP BY 1-table。

既然您正在考虑第二个LEFT JOIN:如果您不先聚合,您会遇到“代理交叉连接”的情况。

聚合 FILTER 子句需要 Postgres 9.4 或更高版本。

相关:

除此之外:永远不要使用 reserved words 之类的 from 作为列名,除非您喜欢复杂性...

【讨论】:

  • 稍后我会试一试。代理交叉连接可能是我遇到的情况。 FILTER 看起来不错,我以前从未听说过。
猜你喜欢
  • 2017-11-09
  • 1970-01-01
  • 2014-03-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-12-11
  • 2012-07-09
相关资源
最近更新 更多