Postgres LEFT JOIN与SUM,缺少记录

时间:2014-10-17 22:26:23

标签: sql postgresql left-join aggregate-functions

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

所以我有一个不太正确的查询和一个返回正确结果的查询。正确的结果查询具有更高的执行成本。我喜欢使用第一种方法,如果我可以纠正结果。 (见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版本正常工作。

3 个答案:

答案 0 :(得分: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条件。

相关答案:

答案 1 :(得分:0)

您的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;

答案 2 :(得分:0)

加入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

这很顺利,但是您应该理解,由于内部连接,结果将错过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);

REF:https://postgresql.org/docs/11/indexes-multicolumn.html 当然,应该定义您的主键。默认情况下,主键将被索引。