如何在没有 HAVING 子句的情况下改进此查询

How can I improve this query without HAVING Clause

有两个表:

  1. 用户
  2. 文件

users:

CREATE TABLE IF NOT EXISTS users (
    id         SERIAL PRIMARY KEY,
    username   TEXT NOT NULL UNIQUE
)

documents:

CREATE TABLE IF NOT EXISTS documents (
    id         SERIAL PRIMARY KEY,
    user_id    INT NOT NULL REFERENCES users,
    name       TEXT NOT NULL,
    value      INT NOT NULL 
)

我想按文档 namevalue 过滤 users。通常,过滤 2-5 个文档 namevalue 之间。每个用户大约有 6-10 个文档。

我有一个庞大的数据库,想改进这个查询。我想我可以在没有 HAVING 子句的情况下获得更快的查询。非常感谢任何帮助。我使用 PostgreSQL 13.

我正在使用的查询:

SELECT
    users.username,
    jsonb_agg(jsonb_strip_nulls(jsonb_build_object('name', documents.name, 'value', documents.value))) AS docs
FROM
    users
JOIN
    documents
ON
    users.id = documents.user_id
GROUP BY
    users.username
HAVING
    jsonb_agg(jsonb_build_object('name', documents.name, 'value', documents.value)) @? '$[*] ? (@.name == "doc1") ? (@.value == "2")'

对于大表,在最终过滤少数符合条件的行之前加入和聚合所有非常昂贵

首先过滤符合条件的文档,然后抓取同一用户的所有文档,聚合,最后加入用户应该快几个数量级:

SELECT u.username, d.docs
FROM  (
   SELECT user_id, jsonb_agg(jsonb_build_object('name', d.name, 'value', d.value)) AS docs
   FROM   documents d1
   JOIN   documents d USING (user_id)
   WHERE  d1.name = 'doc1'
   AND    d1.value = 2
   -- AND    d.name  IS NOT NULL  -- strip NULLs early 
   -- AND    d.value IS NOT NULL  -- if not defined NOT NULL anyway
   GROUP  BY 1
   ) d
JOIN   users u ON u.id = d.user_id;

同时,我删除了 jsonb_strip_nulls(),因为所有已处理的列都定义为 NOT NULL。还便宜。

可能会简化为 jsonb_build_object(d.name, d.value)

对于第一步,documents(name, value) 上的 索引 将帮助 很多.甚至可能在 documents(name, value, user_id) 上获得 index-only scans(取决于)。

假设 documents(user_id) 上也有一个索引应该是安全的。有助于下一步。同样,documents(user_id, name, value) 用于仅索引扫描。

最后,users(id) 上的索引。应该是给定的。同样,users(id, username) 用于仅索引扫描。

如果每个用户 (name, value) 不是 UNIQUE(好像是这种情况),请改用 EXISTS 以避免重复:

SELECT u.username, d.docs
FROM  (
   SELECT user_id, jsonb_agg(jsonb_build_object('name', d.name, 'value', d.value)) AS docs
   FROM   documents d
   WHERE  EXISTS (
      SELECT FROM documents d1
      WHERE  d1.user_id = d.user_id
      AND    d1.name = 'doc1'
      AND    d1.value = 2
      )
   GROUP  BY 1
   ) d
JOIN   users u ON u.id = d.user_id;

类似的查询计划,可以使用相同的索引。

相关: