PostgreSQL 中的 Lederboards 并获得 2 个下一行和上一行

Posted

技术标签:

【中文标题】PostgreSQL 中的 Lederboards 并获得 2 个下一行和上一行【英文标题】:Lederboards in PostgreSQL and get 2 next and previous rows 【发布时间】:2022-01-24 03:35:02 【问题描述】:

我们使用 Postgresql 14.1

我有一个包含超过 5000 万条记录的示例数据。

基表:

+------+----------+--------+--------+--------+
|  id  | item_id  | battles|  wins  | damage |
+------+----------+--------+--------+--------+
|   1  |    255   |   35   | 52.08  | 1245.2 |
|   2  |    255   |   35   | 52.08  | 1245.2 |
|   3  |    255   |   35   | 52.08  | 1245.3 |
|   4  |    255   |   35   | 52.08  | 1245.3 |
|   5  |    255   |   35   | 52.09  | 1245.4 |
|   6  |    255   |   35   | 52.08  | 1245.3 |
|   7  |    255   |   35   | 52.08  | 1245.3 |
|   8  |    255   |   35   | 52.08  | 1245.7 |
|   1  |    460   |   18   | 47.35  | 1010.1 |
|   2  |    460   |   27   | 49.18  | 1518.9 |
|   3  |    460   |   16   | 50.78  | 1171.2 |
+------+----------+--------+--------+--------+

我们需要尽快获取目标行号和2下2行和2前行。

索引列:

    身份证 item_id

排序:

    伤害 (DESC) 获胜 (DESC) 战斗(ASC) id (ASC)

在示例中,我们需要找到行号和 +- 2 行,其中 id = 4 和 item_id = 255。结果表应该是:

+------+----------+--------+--------+--------+------+
|  id  | item_id  | battles|  wins  | damage | rank |
+------+----------+--------+--------+--------+------+
|   5  |    255   |   35   | 52.09  | 1245.4 |  2   |
|   3  |    255   |   35   | 52.08  | 1245.3 |  3   |
|   4  |    255   |   35   | 52.08  | 1245.3 |  4   |
|   6  |    255   |   35   | 52.08  | 1245.3 |  5   |
|   7  |    255   |   35   | 52.08  | 1245.3 |  6   |
+------+----------+--------+--------+--------+------+

如何使用行号窗口功能做到这一点?

由于其他列没有索引,是否有任何方法可以优化查询以使其更快?

CREATE OR REPLACE FUNCTION find_top(in_id integer, in_item_id integer) RETURNS TABLE (
        r_id int,
        r_item_id int,
        r_battles int,
        r_wins real,
        r_damage real,
        r_rank bigint,
        r_eff real,
        r_frags int
    )  AS $$
        DECLARE
            center_place bigint;
        BEGIN
            SELECT place INTO center_place FROM
            (SELECT 
                id, item_id,
                ROW_NUMBER() OVER (ORDER BY damage DESC, wins DESC, battles, id) AS place
            FROM 
                public.my_table
            WHERE 
                item_id = in_item_id 
            AND battles >= 20
            ) AS s
            WHERE s.id = in_id;
            
            RETURN QUERY SELECT
                    s.place, pt.id, pt.item_id, pt.battles, pt.wins, pt.damage
                FROM
                    (
                        SELECT * FROM 
                        (SELECT
                            ROW_NUMBER () OVER (ORDER BY damage DESC, wins DESC, battles, id) AS place,
                            id, item_id
                        FROM
                            public.my_table
                        WHERE 
                            item_id = in_item_id
                        AND battles >= 20) x
                        WHERE x.place BETWEEN (center_place - 2) AND (center_place + 2)
                    ) s
                JOIN 
                    public.my_table pt
                    ON pt.id = s.id AND pt.item_id = s.item_id;
        END;
$$ LANGUAGE plpgsql;

【问题讨论】:

使用 lead() 和 lag() 获取下一行和上一行的值 【参考方案1】:
CREATE OR REPLACE FUNCTION find_top(in_id integer, in_item_id integer) RETURNS TABLE (
        r_id int,
        r_item_id int,
        r_battles int,
        r_wins real,
        r_damage real,
        r_rank bigint,
        r_eff real,
        r_frags int
    )  AS $$
BEGIN
RETURN QUERY
SELECT c.*, B.ord -3 AS row_number
  FROM
     ( SELECT array_agg(id) OVER w AS id
            , array_agg(item_id) OVER w AS item_id
         FROM public.my_table
       WINDOW w AS (ORDER BY damage DESC, wins DESC, battles, id ROWS BETWEEN 2 PRECEDING AND 2 FOLLOWING)
     ) AS a
 CROSS JOIN LATERAL unnest(a.id, a.item_id) WITH ORDINALITY AS b(id, item_id, ord)
 INNER JOIN public.my_table AS c
    ON c.id = b.id
   AND c.item_id = b.item_id
 WHERE a.item_id[3] = in_item_id 
   AND a.id[3] = in_id
 ORDER BY b.ord ;
END ; $$ LANGUAGE plpgsql;

测试结果在dbfiddle

【讨论】:

解决方法很有意思,但是如何显示row_number? 通过在SELECT 子句中添加b.ord,相应更新答案。 现在显示row_number,可惜没有显示+2 -2,只有一行id = 4 我的回答有误。它现在更新了一个指向 dbfiddle 的链接以显示结果。 link 您的解决方案很好,但没有显示当前行号。我修改了你的代码,得到了我想要的。你能分析一下吗?会不会更好?

以上是关于PostgreSQL 中的 Lederboards 并获得 2 个下一行和上一行的主要内容,如果未能解决你的问题,请参考以下文章

PostgreSQL 中的并发查询 - 实际发生了啥?

如何自动关闭 PostgreSQL 中的空闲连接?

PostgreSQL:块中的无效页眉

postgresql中的范围序列

PostgreSQL 中的循环

postgresql中的关系表