PostgreSQL 在 json 属性 上加入 table 并首先从结果中获取最旧的结果,首先是空值

PostgreSQL join table on json property and get oldest first from result with nulls first

我得到了 2 table;域和事件。我正在尝试创建一个查询,该查询 returns 一个不同域的列表,该列表按最旧的事件(对于该域)排序,首先是空值,然后是不同的域。

基本上这个查询就可以完成工作:

SELECT * FROM domains left join events on events.attributes ->> 'domain' = domains.domain AND events.name = 'event1' WHERE parent is null ORDER BY domain, moment asc nulls first;

但是 'domain' 上的输出并不明显。 使用 'distinct on' 时,它会给出错误的输出,其中时间戳(时刻)不是事件中该域的最新时间戳 table:

SELECT distinct on (domain) domain,moment FROM domains left join events on events.attributes ->> 'domain' = domains.domain AND events.name = 'event1' WHERE parent is null ORDER BY domain, moment asc nulls first;

这似乎也行不通:

SELECT * FROM (SELECT DISTINCT on (domain) domain,moment,parent FROM "domains" left join events on events.attributes ->> 'domain' = domains.domain AND events.name = 'event1') AS domains ORDER BY moment asc nulls first;

下面的代码模拟了数据库和我需要不同的查询,而没有不同的修改行的顺序:

(哦,正如您将看到的,父级必须为空,因此它只选择顶级域。但这是一个简单的 'where'。)

create table domains (domain text, parent text);
        insert into domains (domain, parent) values ('whosebug.com', null);
        insert into domains (domain, parent) values ('test.whosebug.com', 'whosebug.com');
        insert into domains (domain, parent) values ('github.com', null);
        insert into domains (domain, parent) values ('example.com', null);
        insert into domains (domain, parent) values ('google.com', null);
        
create table events (name text, attributes jsonb, moment timestamp with time zone);
        insert into events (name, attributes, moment) values('event1', '{"domain": "example.com"}', '2011-01-01');
        insert into events (name, attributes, moment) values('event1', '{"domain": "github.com"}', '2012-01-01');
        insert into events (name, attributes, moment) values('event1', '{"domain": "whosebug.com"}', '2013-01-01');
        insert into events (name, attributes, moment) values('event1', '{"domain": "example.com"}', '2014-01-01');
        insert into events (name, attributes, moment) values('event1', '{"domain": "whosebug.com"}', '2015-01-01');

SELECT * FROM domains left join events on events.attributes ->> 'domain' = domains.domain AND events.name = 'event1' WHERE parent is null ORDER BY domain, moment asc nulls first;

如何让它工作?

您是否考虑过使用 row_number window 函数?像

SELECT *
FROM
(
SELECT *, row_number() OVER (PARTITION BY events.attributes->>'domain' ORDER BY moment ASC) rn FROM domains left join events on events.attributes ->> 'domain' = domains.domain AND events.name = 'event1' WHERE parent is null
) a
WHERE rn = 1
ORDER BY domain, moment asc nulls first

内部查询使用 row_number() OVER (PARTITION BY events.attributes->>'domain' ORDER BY moment ASC) rn 创建一个字段,对于每个 events.attributes->>'domain' 分组,按 events.moment 的顺序编号。外部查询仅限制为每个分组的第一个并进行最终排序。

with nulls first

这是模棱两可的,因为您查询中的 moment 有两个不同的 NULL 来源,而 events.moment 可以是 NULL - 我们不知道没有 table 定义:

  • LEFT JOIN 找不到右侧符合条件的行。
  • LEFT JOIN 在右侧找到符合条件的行,但是 moment IS NULL.

假设 events.moment 被定义为 NOT NULLdomains.domainUNIQUE,并且您想要列出没有符合条件的事件的域首先,其余的按最旧的事件优先。

Select 子查询中每个合格域的最旧事件。在开始的连接 before 聚合速度更快。参见:

  • Query with LEFT JOIN not returning rows for count of 0
SELECT d.domain, e.moment
FROM   domains d
LEFT   JOIN (
   SELECT DISTINCT ON (1)
          attributes ->> 'domain' AS domain, moment
   FROM   events
   WHERE  name = 'event1'
   ORDER  BY 1, moment
   ) e USING (domain)
WHERE  d.parent IS NULL
ORDER  BY e.moment NULLS FIRST, d.domain;

我在 ORDER BY 中添加了 domain,以按字母顺序对领带进行排序。
或者,虽然我们不需要 events 中的任何其他内容,但只需要:

SELECT d.domain, e.moment
FROM   domains d
LEFT   JOIN (
   SELECT attributes ->> 'domain' AS domain, min(moment) AS moment
   FROM   events
   WHERE  name = 'event1'
   GROUP  BY 1
   ) e USING (domain)
WHERE  d.parent IS NULL
ORDER  BY e.moment NULLS FIRST, d.domain;

db<>fiddle here

参见:

  • Select first row in each GROUP BY group?

如果事件中有许多域具有 name = 'event1',而这些域对该查询没有贡献,那么 LATERAL 子查询会更快。参见:

根据未公开的数据分布和基数,可能会有(多)更快的解决方案。 row_number() 从来没有 最快。 I have tried many times.