我遇到了使用窗口函数在连接值上构建聚合的问题。简化它看起来像:
我收到了以下表格:
CREATE TABLE movies (
id SERIAL,
name VARCHAR,
year INT,
genre VARCHAR,
country VARCHAR
);
CREATE TABLE tags (
id SERIAL,
name VARCHAR
);
CREATE TABLE movies_tags (
id SERIAL,
movie_id INT,
tag_id INT
);
现在我想做以下声明:
SELECT m.*, array_agg(t.name) AS tags
FROM movies m
LEFT JOIN movies_tags mt ON mt.movie_id = m.id
LEFT JOIN tags t ON t.id = mt.tag_id
ORDER BY m.name
LIMIT 10
由于选择中的聚合,所有电影都会加入所有标签,然后再选择大型连接中的前10名。我的目标是出于性能原因仅在前10部电影上进行聚合。所以我做的是这个:
WITH top_movies AS (
SELECT m.*
FROM movies m
ORDER BY m.name
LIMIT 10
)
SELECT tm.*, array_agg(t.name) AS tags
FROM top_movies tm
LEFT JOIN movies_tags mt ON mt.movie_id = tm.id
LEFT JOIN tags t ON t.id = mt.tag_id
表现要好得多。但是我遇到了另一个问题。最终的目标是创建一个可重用的组件形式,如Postgres中的函数或ORM中的命名查询,如Rails的Active Record,我可以根据我的需要动态修改,例如:
SELECT * FROM my_top_movies_with_tags() AS tm
WHERE tm.country = 'USA' AND tm.year <= 1995
LIMIT 10;
因此,我必须修改我的SQL语句,即电影选择是外部查询,但仍然限制了标签加入我想要的前n部电影。
为了做到这一点,我尝试了横向连接并做了这个:
SELECT m.*, lat.tags FROM movies m
LATERAL (
SELECT array_agg(t.name) AS tags
FROM movies_tags mt
JOIN tags t ON t.id = mt.tag_id
WHERE mt.movie_id = m.id
) AS lat
ORDER BY m.name
LIMIT 10;
这使我可以灵活地随后动态修改它,但性能明显更差。
还有其他方法可以实现我不了解的目标吗?
我的目标总结如下:
array_agg
电影集上制作聚合(LIMIT
),而不是在整个电影桌上。WHERE
,ORDER
和LIMIT
声明来保持可修改。答案 0 :(得分:1)
如何使用row_number模拟LIMIT
?
SELECT * FROM (
SELECT
m.*,
array_agg(t.name) AS tags,
row_number() OVER(ORDER BY m.name) AS rownum
FROM
movies m
LEFT JOIN movies_tags mt ON mt.movie_id = m.id
LEFT JOIN tags t ON t.id = mt.tag_id
--There're must be a GROUP BY here
) AS tmp
WHERE rownum <= 10;
此外,在使用CTE进行性能关键查询时,请考虑this article。
答案 1 :(得分:0)
You can add form inputs to a temporary table and use your this table for filtering.
CREATE TEMP TABLE temp_inputs
(
country VARCHAR(80),
year int
)
ON COMMIT DELETE ROWS;
WITH top_movies AS (
SELECT m.*
FROM movies m
ORDER BY m.name
LIMIT 10
)
SELECT tm.*, array_agg(t.name) AS tags
FROM tmovies tm, temp_inputs
LEFT JOIN movies_tags mt ON mt.movie_id = tm.id
LEFT JOIN tags t ON t.id = mt.tag_id
and tm.country = temp_inputs.country AND tm.year <= temp_inputs.year