【问题标题】:Ensuring no dupe ids in query return确保查询返回中没有重复的 id
【发布时间】:2022-01-14 15:38:01
【问题描述】:

所以对于以下架构:

CREATE TABLE activity (
    id integer NOT NULL,
    start_date date NOT NULL
);

CREATE TABLE account (
    id integer NOT NULL,
    name varchar NOT NULL
);

CREATE TABLE contact (
    id integer NOT NULL,
    account_id integer NOT NULL,
    name varchar NOT NULL
);

CREATE TABLE activity_contact (
    id integer NOT NULL,
    contact_id integer NOT NULL,
    activity_id integer NOT NULL
);

insert into activity(id, start_date)
values
(1, '2021-11-03'),
(2, '2021-10-03'),
(3, '2021-11-02');

insert into account(id, name)
values
(1, 'Test Account');

insert into contact(id, account_id, name)
values
(1, 1, 'John'),
(2, 1, 'Kevin');

insert into activity_contact(id, contact_id, activity_id)
values
(1, 1, 1),
(2, 2, 1),
(3, 2, 2),
(4, 1, 3);

您可以看到有 3 个活动,每个联系人有两个。我要搜索的是前两个月每个帐户的活动数量。所以我有以下查询

SELECT contact.account_id AS accountid,
    count(*) FILTER (WHERE date_trunc('month'::text, activity.start_date) = date_trunc('month'::text, CURRENT_DATE - '1 mon'::interval)) AS last_month,
    count(*) FILTER (WHERE date_trunc('month'::text, activity.start_date) = date_trunc('month'::text, CURRENT_DATE - '2 mons'::interval)) AS prev_month
FROM activity
JOIN activity_contact ON activity_contact.activity_id = activity.id
JOIN contact ON contact.id = activity_contact.contact_id
JOIN account ON contact.account_id = account.id
GROUP BY contact.account_id;

这会返回:

accountid   last_month  prev_month
    1           3           1

但是这是不正确的。只有 3 个活动,只是每个联系人都看到活动 1。所以它对该活动计数两次。有没有办法让我只计算每个活动 id 一次,这样就不会重复?

【问题讨论】:

  • count(DISTINCT activity_id)替换count(*)
  • 很棒的东西主管。谢谢

标签: sql postgresql


【解决方案1】:

count(DISTINCT activity_id) 折叠计数中的重复项,like Edouard suggested
但还有更多:

SELECT con.account_id AS accountid
     , count(DISTINCT aco.activity_id) FILTER (WHERE act.start_date >= date_trunc('month', LOCALTIMESTAMP - interval '1 mon')
                                               AND   act.start_date <  date_trunc('month', LOCALTIMESTAMP)) AS last_month
     , count(DISTINCT aco.activity_id) FILTER (WHERE act.start_date >= date_trunc('month', LOCALTIMESTAMP - interval '2 mon')
                                               AND   act.start_date <  date_trunc('month', LOCALTIMESTAMP - interval '1 mon')) AS prev_month
FROM   activity         act
JOIN   activity_contact aco ON aco.activity_id = act.id
                           AND act.start_date >= date_trunc('month', LOCALTIMESTAMP - interval '2 mon')
                           AND act.start_date <  date_trunc('month', LOCALTIMESTAMP)
RIGHT  JOIN contact     con ON con.id = aco.contact_id
-- JOIN   account       acc ON con.account_id = acc.id  -- noise
GROUP  BY 1;

db小提琴here

  • 最重要的是,在查询中添加一个外部WHERE 子句以尽早过滤不相关的行。这对于从大表中选择的一小部分可能会产生很大的不同。
    我们必须将该谓词移至JOIN 子句,以免我们排除没有活动的帐户。 (LEFT JOINRIGHT JOIN 都可以使用,相互镜像。) 见:

  • 使该过滤器“可搜索”,以便它可以使用(start_date) 上的索引(与您的原始公式不同)。同样,从一张大桌子中选择一小部分会产生很大的影响。

  • 对聚合过滤子句使用相同的表达式。影响较小,但请接受。
    与其他聚合函数不同,count() 为“无行”返回 0(而不是 NULL),因此我们不需要做任何额外的事情。

  • 假设参照完整性(通过 FK 约束强制执行),连接到表 account 只是昂贵的噪音。算了吧。 CURRENT_DATE 没错。但是由于您的表达式无论如何都会产生timestamp,因此使用LOCALTIMESTAMP 开始会更有效。

与您的原始版本相比,这要快很多。

我假设您知道此查询引入了对执行会话的TimeZone 设置的依赖。当前日期取决于我们询问的世界上的哪个地方。见:


如果您没有绑定到这种特定的输出格式,那么透视表单会更简单,因为我们会提前过滤行:

SELECT con.account_id AS accountid
     , date_trunc('month', act.start_date) AS mon
     , count(DISTINCT aco.activity_id) AS dist_count
FROM   activity         act
JOIN   activity_contact aco ON aco.activity_id = act.id
                           AND act.start_date >= date_trunc('month', LOCALTIMESTAMP - interval '2 mon')
                           AND act.start_date <  date_trunc('month', LOCALTIMESTAMP)
RIGHT  JOIN  contact    con ON con.id = aco.contact_id
GROUP  BY 1, 2
ORDER  BY 1, 2 DESC;

同样,我们可以包含没有活动的帐户。但是没有活动的月份不会出现......

【讨论】:

  • 谢谢。是的,我昨天注意到时区问题。这就是要解决的问题的清单。生病看看那个链接。干杯
  • @discodowney: 要么使用timestamptz,要么明确地定义时区(以及由此产生的日期),以便查询摆脱依赖(以及可能的角落)案例错误)。在使用时添加了一个 alt 查询。
  • 这很有趣。完成格式是为了使 go 服务器解析尽可能简单,但很高兴看到这一点。生病想想什么是最好的。感谢您的帮助
  • 还有一个问题。如果我添加了第二个帐户但未添加任何活动,但我希望它显示在结果中,两者的计数均为 0。我可以这样做吗?我没有看到我将如何管理它,我认为 id 需要单独查询该计算
  • @discodowney:我更新了答案以包括没有活动的帐户,因为它很容易适应。下次,请再问一个问题。您可以随时链接到之前的问题以了解上下文。
猜你喜欢
  • 2022-01-15
  • 2019-11-10
  • 2019-12-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多