仅返回符合所有条件的行

时间:2014-05-21 09:02:52

标签: sql postgresql

这是一个粗略的架构:

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)       
);

输出应如下所示:

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

允许用户根据用户ID,标签和偏移image_id过滤图像。例如,某人可以说"user_id":1, "tags":[1, 2], "offset_image_id":500,这将为他们提供来自user_id 1的所有图片,标记为1和2,image_id为500或更少。

棘手的部分是"标记为1和2"。返回所有具有1,2或两者的图像更直接(也更快)。

,除了聚合之外,我没有看到任何其他方法

快速提供帮助吗?

这是我正在使用的当前查询非常慢:

select * from (
    select i.*,u.handle,array_agg(t.tag_id) as tag_ids, array_agg(tag.name) as tag_names from (
        select i.image_id, i.user_id, i.description, i.url, i.date_created from images i
        where (?=-1 or i.user_id=?)
        and (?=-1 or i.image_id <= ?)
        and exists(
            select 1 from image_tags t
            where t.image_id=i.image_id
            and (?=-1 or user_id=?)
            and (?=-1 or t.tag_id in (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?))
        )
        order by i.image_id desc
    ) i
    left join image_tags t on t.image_id=i.image_id
    left join tag using (tag_id) --not totally necessary
    left join users u on i.user_id=u.user_id --not totally necessary
    group by i.image_id,i.user_id,i.description,i.url,i.date_created,u.handle) sub
where (?=-1 or sub.tag_ids @> ?)
limit 100;

3 个答案:

答案 0 :(得分:1)

当确定本声明的执行计划时,在准备时,PostgresSQL规划人员不知道这些?=-1条件中的哪一个是真的。

所以它必须制定一个计划,可以过滤特定的user_id,或者可能不会,并且可能会对image_id上的范围进行过滤,也可能不过滤,并且可能会过滤特定的一组tag_id,或许不是。它可能是一个愚蠢的,未经优化的计划,无法利用索引。

虽然您当前涵盖所有案例的大型通用查询策略的正确性是正确的,但对于性能而言,您可能需要放弃它,或者根据实际填充的参数化条件生成最小查询。

在这样生成的查询中,?=-1 or ...将消失,只有实际需要的连接才会出现,而可疑的t.tag_id in (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)将被删除或被简化为必要的SELECT image_id FROM image_tags WHERE tag_id in (?,?,...) GROUP BY image_id HAVING count(*)=?

如果在给定某些参数的情况下它仍然很慢,那么您将有一个更容易优化的起点。


至于问题的要点,测试所有标签上的完全匹配,你可能想在内部子查询中尝试惯用形式:

?

其中最后sub.tag_ids @> ?是作为参数传递的标记数。

(并完全删除{{1}}作为外部条件)。

答案 1 :(得分:1)

除此之外,您的GROUP BY子句可能比您的任何索引更宽(和/或包括不太可能的组合中的列)。我可能会按如下方式重新编写您的查询(将@ Daniel的子查询转换为CTE):

WITH Tagged_Images (SELECT Image_Tags.image_id, ARRAY_AGG(Tag.tag_id) as tag_ids,
                                                ARRAY_AGG(Tag.name) as tag_names
                    FROM Image_Tags
                    JOIN Tag
                      ON Tag.tag_id = Image_Tags.tag_id
                    WHERE tag_id IN (?, ?)
                    GROUP BY image_id
                    HAVING COUNT(*) = ?)

SELECT Images.image_id, Images.user_id, 
       Images.description, Images.url, Images.date_created,
       Tagged_Images.tag_ids, Tagged_Images.tag_names,
       Users.handle
FROM Images
JOIN Tagged_Images
  ON Tagged_Images.image_id = Images.image_id
LEFT JOIN Users
       ON Users.user_id = Images.user_id
WHERE Images.user_id = ?
      AND Images.date_created < ?
ORDER BY Images.date_created, Images.image_id
LIMIT 100

(未经测试 - 没有提供数据集。请注意,我假设您正在动态构建条件,以避免条件标记)

以下是其他一些内容:

  • 请注意Tagged_Images 至少指示的标签,但可能有更多。如果您想要这些标签的图片(正好为2,不多,不少),则需要在CTE中添加其他级别。
  • 在存储过程周围有许多示例,它们将逗号分隔的列表转换为虚拟表(哎呀,我用递归CTE完成它),您可以将其用于IN()子句。但是,由于需要动态SQL,所以在这里并不重要......
  • 假设Images.image_id是自动生成的,执行范围搜索或按顺序排序在很大程度上是没有意义的。 关注此处所持有的值的情况相对较少。除非您正在搜索一个特定行(用于更新/删除/无论如何),概念数据集也不关心;它本身的价值在很大程度上毫无意义。 image_id < 500实际告诉我什么?没什么 - 只是分配了一个给定的数字。您是否使用它来限制“早期”与“晚期”图像?然后使用适当的数据,即date_created。分页?好吧,你必须在之后所有其他条件,或者你得到奇怪的页面长度(在某些情况下像0)。应仅依赖于一个属性生成的密钥:唯一性。这就是我把它放在ORDER BY末尾的原因 - 以确保一致的排序。假设date_created具有足够高的分辨率作为时间戳,即使这是不必要的。
  • 我非常确定您的LEFT JOIN Users可能应该是常规(INNER) JOIN,但您没有提供足够的信息让我确定。

答案 2 :(得分:0)

聚合不太可能减缓你的速度。查询如:

select images.image_id
  from images
  join images_tags on (images.image_id=images_tags.image_id)
 where images_tags.tag_id in (1,2)
group by images.image_id
having count(*) = 2

将为您提供包含标签1和2的所有图像,如果您在两个image_tags列上都有索引,它将快速运行:

create index on images_tags(tag_id);
create index on images_tags(image_id);

查询中最慢的部分可能是 where 子句中的 部分。如果您准备在以下位置创建包含目标标记的临时表,则可以加快速度:

create temp table target_tags(tag_id int primary key);
insert into target_tags values (1);
insert into target_tags values (2);

select images.image_id
  from images
  join images_tags on (images.image_id=images_tags.image_id)
  join target_tags on images_tags.tag_id=target_tags.tag_id
group by images.image_id
having count(*) = (select count(*) from target_tags)