这是一个粗略的架构:
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;
答案 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中添加其他级别。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)