改进SQL中的GROUP BY

Pet*_*ton 11 sql postgresql

脚本

我们有很多人,这些人带着多个阶段/州进行旅行(最初计划,然后开始,然后返回_安全或在灾难中结束).

我有一个查询可以得到正确的结果,你可以在这里看到并使用它:

http://sqlfiddle.com/#!15/2e096/1

但是,我想知道是否有更好的实现,特别是避免使用GROUP BY和postgres' bool_and,也可能避免嵌套查询.

我们想知道什么

谁从未经历过旅行,他们没有安全返回?

或者,换句话说:

谁有:1. Never planned or gone on a trip2. only ever returned safely

澄清

  • 如果在旅行表中有一个人的记录,但没有阶段,他们计划旅行.

产量

至少应该是person表中的所有列,如果其他列也出来,那很好.

设置/复制

CREATE TABLE people (person_name text, gender text, age integer);
INSERT INTO people (person_name, gender, age)
  VALUES ('pete', 'm', 10), ('alan', 'm', 22), ('jess', 'f', 24), ('agnes', 'f', 25), ('matt', 'm', 26);

CREATE TABLE trips (person_name text, trip_name text);
INSERT INTO trips (person_name, trip_name)
  VALUES ('pete', 'a'),
         ('pete', 'b'),
         ('alan', 'c'),
         ('alan', 'd'),
         ('jess', 'e'),
         ('matt', 'f');

CREATE TABLE trip_stages (trip_name text, stage text, most_recent boolean);
INSERT INTO trip_stages
  VALUES ('a', 'started', 'f'), ('a', 'disaster', 't'),
         ('b', 'started', 't'),
         ('c', 'started', 'f'), ('c', 'safe_return', 't'),
         ('e', 'started', 'f'), ('e', 'safe_return', 't');
Run Code Online (Sandbox Code Playgroud)

情况概要

  • 皮特有一次旅行以灾难结束,一次他刚开始
  • 艾伦有一次旅行,他安然返回,他正在计划一次旅行
  • 杰斯一直在旅途中,她安然返回
  • 艾格尼丝从未计划过旅行
  • 马特曾计划过一次旅行,但尚未开始

 person_name | gender | age
-------------+--------+-----
 jess        | f      | 24
 agnes       | f      | 25
Run Code Online (Sandbox Code Playgroud)
  • 杰斯(已经一次旅行,她安全返回)
  • 艾格尼丝(从未计划过旅行)

工作查询

SELECT people.* FROM people WHERE people.person_name IN (
  SELECT people.person_name FROM people
  LEFT OUTER JOIN trips
    ON trips.person_name = people.person_name
  LEFT OUTER JOIN trip_stages
    ON trip_stages.trip_name = trips.trip_name AND trip_stages.most_recent = 't'
  GROUP BY people.person_name
    HAVING bool_and(trips.trip_name IS NULL)
      OR bool_and(trip_stages.stage IS NOT NULL AND trip_stages.stage = 'safe_return')
)
Run Code Online (Sandbox Code Playgroud)

说明

SELECT people.* FROM people WHERE people.person_name IN (
  -- All the people
  SELECT people.person_name FROM people

  -- + All their trips
  LEFT OUTER JOIN trips
    ON trips.person_name = people.person_name

  -- + All those trips' stages
  LEFT OUTER JOIN trip_stages
    ON trip_stages.trip_name = trips.trip_name AND trip_stages.most_recent = 't'

  -- Group by person
  GROUP BY people.person_name
    -- Filter to those rows where either:
    --   1. trip_name is always NULL (they've made no trips)
    --   2. Every trip has been ended with a safe return
    HAVING bool_and(trips.trip_name IS NULL)
      OR bool_and(trip_stages.stage IS NOT NULL AND trip_stages.stage = 'safe_return')
)
Run Code Online (Sandbox Code Playgroud)

还有其他方法可以编写此查询吗?没有使用GROUP BYbool_and理想情况下不使用子查询?也许一些分区/窗口功能?

我正在用它来学习,所以对查询的解释/分析表示赞赏!

我对性能影响特别感兴趣.例如,如果人们进行数千次旅行会发生什么?子查询是否通过其他方法执行?

Fuz*_*ree 1

您可以使用“不”not exists来选择没有至少一次旅行但未安全返回的所有人员(这意味着他们要么没有进行任何旅行,要么从所有旅行中安全返回)并且没有至少一次计划的旅行这不在一个阶段

select * from people p
where not exists (
    select 1 from trips t
    left join trip_stages ts on ts.trip_name = t.trip_name
    where ((ts.stage <> 'safe_return' -- did not end in safe return
      and ts.most_recent = 't') 
      or ts.trip_name is null) -- or does not have a trip stage
    and t.person_name = p.person_name
)
Run Code Online (Sandbox Code Playgroud)

http://sqlfiddle.com/#!15/3416a/18