【问题标题】:How should I handle "ranked x out of y" data in PostgreSQL?我应该如何处理 PostgreSQL 中的“从 y 中排名 x”的数据?
【发布时间】:2010-11-30 19:16:08
【问题描述】:

我有一个表格,我希望能够为其呈现“Y 中排名 X”的数据。特别是,我希望能够以相对有效的方式呈现单个行的数据(即不选择表中的每一行)。排名本身非常简单,它是表中单个列的直接 ORDER BY。

Postgres 似乎在这方面提出了一些独特的挑战; AFAICT 它没有 RANK 或 ROW_NUMBER 或等效功能(至少在 8.3 中,我暂时坚持使用它)。邮件列表档案中的规范答案似乎是创建一个临时序列并从中选择:

test=> create temporary sequence tmp_seq;
CREATE SEQUENCE
test=*> select nextval('tmp_seq') as row_number, col1, col2 from foo;

当我只想从表中选择一行时(并且我想按 PK 选择它,而不是按排名),这个解决方案似乎仍然无济于事。

我可以对排名进行非规范化并将其存储在单独的列中,这使得呈现数据变得微不足道,但只是重新定位了我的问题。 UPDATE 不支持 ORDER BY,所以我不确定如何构建一个 UPDATE 查询来设置排名(没有选择每一行并为每一行运行一个单独的 UPDATE,这似乎太多的数据库活动每次排名需要更新时触发)。

我是否遗漏了一些明显的东西?这样做的正确方法是什么?

编辑:显然我不够清楚。我知道 OFFSET/LIMIT,但我看不出它如何帮助解决这个问题。我不是想选择排名第 X 的项目,而是尝试选择任意项目(例如,通过它的 PK),然后能够向用户显示“在 312 中排名第 43 位”之类的内容。

【问题讨论】:

    标签: sql postgresql ranking


    【解决方案1】:

    如果您想要排名,请执行以下操作

    SELECT id,num,rank FROM (
      SELECT id,num,rank() OVER (ORDER BY num) FROM foo
    ) AS bar WHERE id=4
    

    或者如果你真的想要行号,使用

    SELECT id,num,row_number FROM (
      SELECT id,num,row_number() OVER (ORDER BY num) FROM foo
    ) AS bar WHERE id=4
    

    当您在某处具有相同的值时,它们会有所不同。如果需要,还可以使用 dense_rank()。

    这当然需要 PostgreSQL 8.4。

    【讨论】:

    • 这种语法肯定好很多。也许我必须考虑升级需要什么。
    • 就目前而言,我从未遇到过使用预先设计的数据库进行升级的问题,但好处很多。然而,我的一位客户发现,使用新的HashAggregate 方法,DISTINCT 不一定再排序,这破坏了他的一些查询。当然,这要怪他,但请确保您的查询不依赖这些技巧。
    • 最初的问题指定了 8.3,但我认为值得升级到 8.4 以访问这些功能。工作得很好,感谢您的回答!
    【解决方案2】:

    不就是这样吗:

    SELECT  *
    FROM    mytable
    ORDER BY
            col1
    OFFSET X LIMIT 1
    

    或者我错过了什么?

    更新:

    如果要显示排名,请使用:

    SELECT  mi.*, values[1] AS rank, values[2] AS total
    FROM    (
            SELECT  (
                    SELECT  ARRAY[SUM(((mi.col1, mi.ctid) < (mo.col1, mo.ctid))::INTEGER), COUNT(*)]
                    FROM    mytable mi
                    ) AS values
            FROM    mytable mo
            WHERE   mo.id = @myid
            ) q
    

    【讨论】:

    • 看来是我被打败了:你的答案 id1 小 :)
    • 见上面的评论;我认为你没有理解我的问题。如果我想选择排名第 12 的项目,OFFSET/LIMIT 非常好。但我没有。我想选择 id 为 37 的项目,并在网站上显示“该项目在 312 中排名第 43”。我看不出 OFFSET/LIMIT 有什么帮助。
    • 哇,令人印象深刻。我稍后会尝试,看看它是否符合要求。
    • 希望我能接受两个答案 - 这是一个令人印象深刻的问题。但是,我最终决定升级到 8.4 并访问 rank() 会更容易,所以我实际上并没有尝试过;觉得我应该接受我实际使用的答案。
    【解决方案3】:

    PostgreSQL 中的ROW_NUMBER 功能是通过LIMIT n OFFSET skip 实现的。

    编辑:由于您要求ROW_NUMBER() 而不是简单的排名:row_number() 在 8.4 版中被引入 PostgreSQL。所以你可能会考虑更新。否则this workaround 可能会有所帮助。

    【讨论】:

    • 我知道重复的问题并采取了预防措施。这根本不能回答我的问题。使用 LIMIT 和 OFFSET 很容易,但没有给我一个在网站上显示的排名数字(“这个项目在 312 中排名第 43”),这就是重点。
    【解决方案4】:

    以前的回复解决了“选择所有行并获得他们的排名”的问题,这不是您想要的......

    • 你有一行
    • 你想知道它的排名

    只要做:

    SELECT count(*) FROM table WHERE score > $1

    其中 $1 是您刚刚选择的行的分数(我想您想显示它以便选择它...)。

    或者做:

    SELECT a., (SELECT count() FROM table b WHERE score > b.score) AS rank FROM table as a WHERE pk = ...

    但是,如果您选择排在最后的一行,是的,您需要计算排在它之前的所有行,因此您需要扫描整个表,而且速度会很慢。

    解决方案:

    SELECT count(*) FROM (SELECT 1 FROM table WHERE score > $1 LIMIT 30)

    您将获得 30 个最佳分数的精确排名,而且速度很快。 谁在乎失败者?

    好的,如果你真的关心失败者,你需要做一个直方图:

    假设得分可以从 0 到 100,并且您有 1000000 个得分 80 的获胜者。

    您制作了一个有多少行得分为 X 的直方图,它是一个有 100 行的简单小表。向主表添加触发器以更新直方图。

    现在,如果你想对得分为 X 的失败者进行排名,他的排名是 sum( histo ) where histo_score > X。

    由于您的分数可能不在 0 到 100 之间,而是(例如)在 0 到 1000000000 之间,因此您需要稍微捏造一下,例如扩大直方图箱。所以你最多只需要 100 个 bin,或者使用一些对数直方图分布函数。

    顺便说一句,当你分析表时 postgres 会这样做,所以如果你将 statistics_target 设置为 100 或 1000 的分数,分析,然后运行:

    EXPLAIN SELECT * FROM table WHERE score > $1

    你会得到一个不错的行数估计值。

    谁需要准确的答案?

    【讨论】:

      猜你喜欢
      • 2023-02-15
      • 1970-01-01
      • 2013-09-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-03-26
      • 2021-01-09
      相关资源
      最近更新 更多