【问题标题】:One-to-many query while limiting based on distinct primary key基于不同主键限制的一对多查询
【发布时间】:2014-05-20 22:01:02
【问题描述】:

我有一张这样的桌子:

create table images (
    image_id serial primary key,
    user_id int references users(user_id),
    date_created timestamp with time zone
);

然后我有一个标签表,用于显示图像可以具有的标签:

create table images_tags (
    images_tag_id serial primary key,
    image_id int references images(image_id),
    tag_id int references tags(tag_id)       
);

为了得到我想要的结果,我运行如下查询:

select image_id,user_id,tag_id from images left join images_tags using(image_id)
where (?=-1 or user_id=?)
and (?=-1 or tag_id in (?, ?, ?, ?)) --have up to 4 tag_ids to search for
order by date_created desc limit 100;

问题是,我想根据唯一 image_ids 的数量进行限制,因为我的输出将如下所示:

{"images":[
    {"image_id":1, "tag_ids":[1, 2, 3]},
    ....
]}

请注意我如何将 tag_ids 分组到一个数组中以进行输出,即使 SQL 为每个 tag_idimage_id 组合返回一行。

所以,当我说 limit 100 时,我希望它适用于 100 个唯一的 image_ids。

【问题讨论】:

    标签: java sql postgresql left-join sql-limit


    【解决方案1】:

    也许你应该在每一行放一张图片?如果可行,您可以这样做:

    select image_id, user_id, string_agg(cast(tag_id as varchar(2000)), ',') as tags
    from images left join
         images_tags
         using (image_id)
    where (?=-1 or user_id=?) and
          (?=-1 or tag_id in (?, ?, ?, ?)) --have up to 4 tag_ids to search for
    group by image_id, user_id
    order by date_created desc
    limit 100;
    

    如果这不起作用,请使用 CTE:

    with cte as (
          select image_id, user_id, tag_id,
                 dense_rank() over (order by date_created desc) as seqnum
          from images left join
               images_tags
               using (image_id)
          where (?=-1 or user_id=?) and
                (?=-1 or tag_id in (?, ?, ?, ?)) --have up to 4 tag_ids to search for
        )
    select *
    from cte
    where seqnum <= 100
    order by seqnum;
    

    【讨论】:

    • +1,尽管有一点是你需要将 tag_id 从 int 转换为字符类型才能在 string_agg 中工作。反正要变成数组,array_agg 可能更合适。
    • 确实如此,但我也可以使用array_agg。我之前实际上并不知道那些聚合函数。谢谢!
    【解决方案2】:

    首先选择 100 张符合条件的图片,然后然后加入 images_tags。
    使用 EXISTS semi-join 来满足 images_tags 上的条件,并注意括号正确。

    SELECT i.*, t.tag_id
    FROM  (
       SELECT i.image_id, i.user_id
       FROM   images i
       WHERE (? = -1 OR i.user_id = ?)
       AND   (? = -1 OR EXISTS (
          SELECT 1
          FROM   images_tags t
          WHERE  t.image_id = i.image_id
          AND    t.tag_id IN (?, ?, ?, ?)
          ))
       ORDER  BY i.date_created DESC
       LIMIT  100
       ) i
    LEFT   JOIN images_tags t
                ON t.image_id = i.image_id
               AND (? = -1 OR t.tag_id in (?, ?, ?, ?)) -- repeat condition
    

    这应该比使用窗口函数和 CTE 的解决方案更快。
    使用EXPLAIN ANLAYZE 测试性能。一如既往地运行几次以预热缓存。

    【讨论】:

    • 这很有意义,但我在 select 子句中遇到错误,说我缺少表 i 的 from 子句。
    • @tau:对不起,错字。有表别名 sub 而不是 i。另外,您需要在最后一个连接中重复 t 上的条件以仅添加合格标签(如果适用)。
    • 哦,谢谢!现在更有意义了;我只是在查找sub 关键字的含义,呵呵。让我试试看!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-09-28
    • 1970-01-01
    • 2016-11-07
    • 2021-03-17
    • 1970-01-01
    相关资源
    最近更新 更多