Users
可以有很多Articles
。
我想获得 20 个最近的用户,每个用户有 5 篇文章。
我正在阅读http://wiki.postgresql.org/wiki/Find_recent_activity但这对于我的场景来说似乎过于复杂。但是,文章中提到的查询速度惊人!所以我被告知。也许有一种方法可以合并查询中使用的一些方法?
我正在使用 Postgres 9.2
Users
可以有很多Articles
。
我想获得 20 个最近的用户,每个用户有 5 篇文章。
我正在阅读http://wiki.postgresql.org/wiki/Find_recent_activity但这对于我的场景来说似乎过于复杂。但是,文章中提到的查询速度惊人!所以我被告知。也许有一种方法可以合并查询中使用的一些方法?
我正在使用 Postgres 9.2
假设“最近的”用户是指“最近创建的”:
首先,生成一些虚拟数据。我没有费心格式化这个:
create table users ( id serial primary key, username text not null,created_at timestamptz not null default current_timestamp );
create table articles (id serial primary key, user_id integer not null references users(id), created_at timestamptz not null default current_timestamp);
insert into users (username) values ('todd'),('bob'),('janet'),('joan'),('jim'),('jolly'),('roger'),('yarr'),('fred');
update users set created_at = current_timestamp + id * INTERVAL '1' MINUTE;
insert into articles(user_id, created_at) select u.id, x from users u cross join generate_series(current_timestamp, current_timestamp + INTERVAL '1' HOUR, INTERVAL '1' MINUTE) x;
LATERAL
现在,这是一个遗憾的是你不在 9.3 上;在那里,您可以使用横向子查询来很好地完成所有这些工作:
SELECT u.username, a.id AS article_id
FROM (
SELECT u1.id, u1.username
FROM users u1
ORDER BY u1.created_at DESC LIMIT 5
) u,
LATERAL (
SELECT a1.id
FROM articles a1
WHERE a1.user_id = u.id
ORDER BY a1.created_at DESC LIMIT 5
) a;
见: http: //www.depesz.com/2012/08/19/waiting-for-9-3-implement-sql-standard-lateral-subqueries/
但是,由于 9.3 尚未完全发布,因此您不使用它并不奇怪。
对于 9.2 和更早的版本,您必须使用另一层子查询来解决LATERAL
您对一些涉及row_number
窗口函数和嵌套子查询的丑陋解决方法缺乏支持的问题。请参阅PostgreSQL 中的分组限制:显示每个组的前 N 行?, http://www.postgresql.org/message-id/4CD0B077.2080700@ateb.com , http://troels.arvin.dk/db/rdbms/#select-top-n
就像是:
WITH
last_five_users AS (
SELECT u1.id, u1.username FROM users u1 ORDER BY u1.created_at DESC LIMIT 5
)
SELECT
lfa.username, lfa.article_id
FROM
(
SELECT lfive.username, lfive.id, a.id, row_number() OVER (PARTITION BY a.user_id ORDER BY created_at)
FROM articles a
INNER JOIN last_five_users lfive ON (a.user_id = lfive.id)
) AS lfa(username, user_id, article_id, rownum)
WHERE lfa.rownum <= 10;
(在这种情况下,五个用户和每个用户 10 篇文章)。
如果选择的用户有很多文章,效率会很糟糕,因为它会为这些用户获取所有文章并为其编号,而不仅仅是第一个n
,然后在外部查询中丢弃大部分文章。
如果这是一个问题,您可以创建一个 SQL 集返回函数:
CREATE OR REPLACE FUNCTION last_n_articles_for_user(user_id integer, n_articles integer)
RETURNS SETOF articles AS $$
SELECT * FROM articles WHERE articles.user_id = $1 ORDER BY created_at LIMIT $2
$$ LANGUAGE sql STABLE;
然后在您的主查询中使用它:
SELECT
o.username,
(o.last_articles).*
FROM (
SELECT
u.username,
last_n_articles_for_user(u.id, 10) AS last_articles
FROM (
SELECT *
FROM users u1
ORDER BY u1.created_at DESC
LIMIT 5
) u
) AS o;
这可能会在有索引的情况下执行得更好created_at
,尽管不如LATERAL
9.3 中启用的方法。额外的子查询层是必需的,因为last_n_articles_for_user
直接使用.*
语法调用将 - 由于内部 PostgreSQL 对行返回函数的限制 - 导致它为每一列调用一次。