【问题标题】:Postgres LEFT JOIN with SUM, missing recordsPostgres LEFT JOIN with SUM,缺少记录
【发布时间】:2014-10-17 22:26:22
【问题描述】:

我正在尝试获取相关表中某些类型记录的计数。我正在使用左连接。

所以我有一个不太正确的查询并且返回了正确的结果。正确的结果查询具有更高的执行成本。如果我能纠正结果,我想使用第一种方法。 (见http://sqlfiddle.com/#!15/7c20b/5/2

CREATE TABLE people(
  id SERIAL,
  name varchar not null
);

CREATE TABLE pets(
  id SERIAL,
  name varchar not null, 
  kind varchar not null,
  alive boolean not null default false,
  person_id integer not null
);

INSERT INTO people(name) VALUES
('Chad'),
('Buck'); --can't keep pets alive

INSERT INTO pets(name, alive, kind, person_id) VALUES
('doggio', true, 'dog', 1),
('dog master flash', true, 'dog', 1),
('catio', true, 'cat', 1),
('lucky', false, 'cat', 2);

我的目标是找回一张桌子,里面有所有的人和他们活着的宠物的种类:

| ID | ALIVE_DOGS_COUNT | ALIVE_CATS_COUNT |
|----|------------------|------------------|
|  1 |                2 |                1 |
|  2 |                0 |                0 |

我让这个例子变得更简单了。在我们的生产应用程序(不是真正的宠物)中,每人大约有 100,000 只死狗和猫。我知道搞砸了,但这个例子更容易传递;)我希望在计数之前过滤掉所有“死”的东西。我现在生产中的查询速度较慢(来自上面的 sqlfiddle),但希望让 LEFT JOIN 版本正常工作。

【问题讨论】:

    标签: sql postgresql left-join aggregate-functions


    【解决方案1】:

    如果您获取所有或大多数行,通常最快:

    SELECT pp.id
         , COALESCE(pt.a_dog_ct, 0) AS alive_dogs_count
         , COALESCE(pt.a_cat_ct, 0) AS alive_cats_count
    FROM   people pp
    LEFT   JOIN (
       SELECT person_id
            , count(kind = 'dog' OR NULL) AS a_dog_ct
            , count(kind = 'cat' OR NULL) AS a_cat_ct
       FROM   pets
       WHERE  alive
       GROUP  BY 1
       ) pt ON pt.person_id = pp.id;
    

    索引在这里无关紧要,全表扫描将是最快的。 除了如果活着的宠物是罕见的情况,那么partial index 应该会有所帮助。喜欢:

    CREATE INDEX pets_alive_idx ON pets (person_id, kind) WHERE alive;
    

    我包含了查询 (person_id, kind) 所需的所有列,以允许仅索引扫描。

    SQL Fiddle.

    小子集或单行通常最快:

    SELECT pp.id
         , count(kind = 'dog' OR NULL) AS alive_dogs_count
         , count(kind = 'cat' OR NULL) AS alive_cats_count
    FROM   people pp
    LEFT   JOIN pets pt ON pt.person_id = pp.id
                       AND pt.alive
    WHERE  <some condition to retrieve a small subset>
    GROUP  BY 1;
    

    您至少应该在 pets.person_id 上有一个索引(或上面的部分索引) - 可能还有更多,这取决于 WHERE 条件。

    相关答案:

    【讨论】:

    • 很棒,而且信息量大。我想我已经选择了正确的答案。看看我在那里做了什么。
    • @Cory:您可能对添加的有关索引的内容感兴趣。
    • 不活宠物的数量随着时间的推移而增加,该表有相关索引,但它有大约 1.5% 的记录“活着”。我实际上忽略了这一点,但是表是根据“活动”标志进行分区的,并且当状态更改时,记录会移动到死对象的分区。这里非常病态。
    【解决方案2】:

    您的WHERE alive=true 实际上正在过滤掉person_id = 2 的记录。使用下面的查询,将WHERE alive=true 条件推送到CASE 条件中,可以在此处注意到。查看您修改后的Fiddle

    SELECT people.id,
    pe.alive_dogs_count,
    pe.alive_cats_count
    FROM people
    LEFT JOIN 
    (
    select person_id, 
      COALESCE(SUM(case when pets.kind='dog' and alive = true then 1 else 0 end),0) as alive_dogs_count,
      COALESCE(SUM(case when pets.kind='cat' and alive = true then 1 else 0 end),0) as alive_cats_count
    from pets
    GROUP BY person_id
    ) pe on people.id = pe.person_id
    

    (或)你的版本

    SELECT 
      people.id,
      COALESCE(SUM(case when pets.kind='dog' and alive = true then 1 else 0 end),0) as alive_dogs_count,
      COALESCE(SUM(case when pets.kind='cat' and alive = true then 1 else 0 end),0) as alive_cats_count
    FROM people
      LEFT JOIN pets on people.id = pets.person_id
    GROUP BY people.id;
    

    【讨论】:

    • 在这种情况下它的成本要高得多。这是我发布的 sqlfiddle 中的第三个。
    • 在第一个查询中,如果一个人没有宠物,我们仍然会得到NULL 值。 COALESCE 需要从子查询移动到外部查询。并且可以进一步简化。
    【解决方案3】:

    加入 SUM

    我认为您的原始查询是这样的:

    SELECT people.id, stats.dog, stats.cat
      FROM people
      JOIN (SELECT person_id, count(kind)filter(where kind='dog') dog, count(kind)filter(where kind='cat') cat FROM pets WHERE alive GROUP BY person_id) stats
        ON stats.person_id = people.id
    

    这很顺利,但你应该明白,结果会因为inner join而错过宠物数为0的人。 为了包括想念宠物的人,您可以:

    1. 首先左连接,
    2. 然后 GROUP BY 加入结果
    3. 并准备好使用 NULL 值而不是计数。

    请参阅上面接受的答案。 感谢@ErwinBrandstetter

    缓慢

    与其他 DBMS 相比,Postgresql 不为外键创建索引。 一个多列索引将比三个单一索引更有效。以正确的顺序使用 WHERE 和 JOIN ON 列中的额外列扩展外键索引:

    CREATE INDEX people_fk_with_kind_alive ON test2 (person_id, alive, kind);
    

    参考号:https://postgresql.org/docs/11/indexes-multicolumn.html 当然,应该定义您的主键。主键默认会被索引。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-03-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-12-13
      • 1970-01-01
      • 2017-12-07
      相关资源
      最近更新 更多