【问题标题】:Check if related data exists within aggregated data检查聚合数据中是否存在相关数据
【发布时间】:2023-04-04 22:05:01
【问题描述】:

我正在尝试编写一个能够检查聚合数据范围内是否存在某个数据的 SQL 查询,我在查询中使用ARRAY_AGG 函数。

架构如下所示:

Schema Browser
----------------------------
  + post (TABLE)
      postid int4(10)
      name varchar(5)

  + posttag (TABLE)
      posttagid int4(10)
      postid int4(10)
      tagid int4(10)

  + tag (TABLE)
      tagid int4(10)
      name varchar(20)

这是我使用ARRAY_AGG 获取所有带有标签的帖子的查询:

SELECT * FROM post,
LATERAL (
  SELECT
    ARRAY_AGG( DISTINCT tag.name ) AS tags 
    FROM tag, posttag 
    WHERE post.postid = posttag.postid
    AND posttag.tagid = tag.tagid
) posts

它将输出这个结果集,这正是我正在寻找的:

问题是当我尝试使用标签过滤器时!例如,我想获取所有包含“node”和“test”标签的帖子。假设“test”标签的 id 为 1,而“node”标签的 id 为 2。所以我正在尝试IN 像这样的tag.tagid IN (1, 2) 条件,这就是我将如何在查询中添加 where 条件:

SELECT * FROM post,
LATERAL (
  SELECT
    ARRAY_AGG( DISTINCT tag.name ) AS tags 
    FROM tag, posttag 
    WHERE post.postid = posttag.postid
    AND posttag.tagid = tag.tagid
    AND tag.tagid IN (1, 2)
) posts

但现在它失去了所有其他标签,好像所有帖子都只有两个标签,这是不正确的。还有一些空标签!

这里是SQLFiddle游乐场。

如何简单地检查 ID 1 和 2 是否存在于 ARRAY_AGG 中,这样它就不会改变结果?

【问题讨论】:

  • 如果你想要一个逗号分隔的列表,string_agg() 可能比array_agg() 更好

标签: sql postgresql database-design


【解决方案1】:

demo:db<>fiddle

WHERE 子句在聚合之前过滤标签。在您的情况下,HAVING 子句有助于过滤分组记录。 &amp;&amp; 数组运算符检查两个数组之间的重叠:

   SELECT * FROM post,
    LATERAL (
      SELECT
        ARRAY_AGG( DISTINCT tag.name ) AS tags 
        FROM tag, posttag 
        WHERE post.postid = posttag.postid
        AND posttag.tagid = tag.tagid
        HAVING ARRAY[1,2] && ARRAY_AGG(DISTINCT tag.tagid)
    ) posts

结果:

postid | name  | tags               
-----: | :---- | :------------------
     1 | hello | {go,node,rust,test}
     2 | world | {node,test} 

【讨论】:

    【解决方案2】:

    首先按照您想要的方式将所有内容组合在一起。不要在子查询中创建标签列表并将其连接到post,而是将postposttagtag 连接在一起并使用GROUP BY 创建标签列表。我使用LEFT JOIN 而不是INNER JOIN,以防post 没有posttag

    SELECT p.postid,
      p.name,
      ARRAY_AGG(DISTINCT t.name) AS tags
    FROM post p
     LEFT JOIN posttag pt
       ON p.postid = pt.postid
     LEFT JOIN tag t
       ON pt.tagid = t.tagid
    GROUP BY p.postid,
      p.name
    

    以上将给您提供与未过滤查询相同的结果,并且它具有更好的查询计划(4.2k 与 82.5k)。使用 INNER JOIN 可以进一步提高 (3.9k)。

    至于过滤,您遇到的问题是您只过滤了posttagtag 加入,而不是实际的帖子。为此,使用子查询从posttag 中选择具有所需标签的postid。在IN 语句中使用它,如下所示:

    SELECT p.postid,
      p.name,
      ARRAY_AGG(DISTINCT t.name) AS tags
    FROM post p
     LEFT JOIN posttag pt
       ON p.postid = pt.postid
     LEFT JOIN tag t
       ON pt.tagid = t.tagid
    WHERE p.postid IN (SELECT postid FROM posttag WHERE tagid IN (1,2))
    GROUP BY p.postid,
      p.name
    

    SQLFiddle

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-10-22
      相关资源
      最近更新 更多