-4

Users可以有很多Articles

我想获得 20 个最近的用户,每个用户有 5 篇文章。

我正在阅读http://wiki.postgresql.org/wiki/Find_recent_activity但这对于我的场景来说似乎过于复杂。但是,文章中提到的查询速度惊人!所以我被告知。也许有一种方法可以合并查询中使用的一些方法?

我正在使用 Postgres 9.2

4

1 回答 1

3

假设“最近的”用户是指“最近创建的”:

虚拟数据设置

首先,生成一些虚拟数据。我没有费心格式化这个:

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;

PostgreSQL 9.3beta1 及以上版本: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 尚未完全发布,因此您不使用它并不奇怪。

PostgreSQL 9.2 及更早版本:

对于 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,尽管不如LATERAL9.3 中启用的方法。额外的子查询层是必需的,因为last_n_articles_for_user直接使用.*语法调用将 - 由于内部 PostgreSQL 对行返回函数的限制 - 导致它为每一列调用一次。

于 2013-06-03T13:19:23.080 回答