如何查询排名排行榜 table 以获取特定用户条目和条目子集 "around" 用户?

How to query a ranked leaderboard table for specific user entry and subset of entries "around" the user?

我有以下构成排名排行榜的表格:

CREATE TABLE IF NOT EXISTS "user" (
    "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
    "username" varchar(200) NOT NULL UNIQUE
);

CREATE TABLE IF NOT EXISTS "leaderboard" (
    "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
    "name" varchar(200) NOT NULL UNIQUE
);

CREATE TABLE IF NOT EXISTS "leaderboard_entry" (
    "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
    "score" integer unsigned NOT NULL CHECK ("score" >= 0),
    "leaderboard_id" integer NOT NULL REFERENCES "leaderboard" ("id") DEFERRABLE INITIALLY DEFERRED,
    "user_id" integer NOT NULL REFERENCES "user" ("id") DEFERRABLE INITIALLY DEFERRED
);

CREATE INDEX "score_idx" ON "leaderboard_entry" ("score" DESC);
CREATE UNIQUE INDEX "leaderboard_id_user_id_idx" ON "leaderboard_entry" ("leaderboard_id", "user_id");
CREATE INDEX "leaderboard_id_idx" ON "leaderboard_entry" ("leaderboard_id");
CREATE INDEX "user_id_idx" ON "leaderboard_entry" ("user_id");

-- Create a leaderboard
INSERT INTO "leaderboard" ("name") VALUES ('Global Leaderboard');

-- Create some users
INSERT INTO "user" ("username") VALUES ('Extreme Hawk');
INSERT INTO "user" ("username") VALUES ('Screaming Whistler');
INSERT INTO "user" ("username") VALUES ('Crashing Underdog');
INSERT INTO "user" ("username") VALUES ('Burly Creature');
INSERT INTO "user" ("username") VALUES ('Snarky Acrobat');
INSERT INTO "user" ("username") VALUES ('Deadly Striker');
INSERT INTO "user" ("username") VALUES ('Dark Zebra');
INSERT INTO "user" ("username") VALUES ('Eager Raptor');
INSERT INTO "user" ("username") VALUES ('Snarky Leader');
INSERT INTO "user" ("username") VALUES ('Keen Joker');

-- Add some leaderboard entries with random scores
INSERT INTO "leaderboard_entry" ("leaderboard_id", "user_id", "score") VALUES (1, 1, 15);
INSERT INTO "leaderboard_entry" ("leaderboard_id", "user_id", "score") VALUES (1, 2, 80);
INSERT INTO "leaderboard_entry" ("leaderboard_id", "user_id", "score") VALUES (1, 3, 45);
INSERT INTO "leaderboard_entry" ("leaderboard_id", "user_id", "score") VALUES (1, 4, 55);
INSERT INTO "leaderboard_entry" ("leaderboard_id", "user_id", "score") VALUES (1, 5, 95);
INSERT INTO "leaderboard_entry" ("leaderboard_id", "user_id", "score") VALUES (1, 6, 90);
INSERT INTO "leaderboard_entry" ("leaderboard_id", "user_id", "score") VALUES (1, 7, 90);
INSERT INTO "leaderboard_entry" ("leaderboard_id", "user_id", "score") VALUES (1, 8, 25);
INSERT INTO "leaderboard_entry" ("leaderboard_id", "user_id", "score") VALUES (1, 9, 60);
INSERT INTO "leaderboard_entry" ("leaderboard_id", "user_id", "score") VALUES (1, 10, 55);

我可以使用此 SQL 查询对特定排行榜进行排名:

SELECT "leaderboard_entry"."id",
       "leaderboard_entry"."leaderboard_id",
       "leaderboard_entry"."user_id",
       "leaderboard_entry"."score",
       RANK() OVER (PARTITION BY "leaderboard_entry"."leaderboard_id" ORDER BY "leaderboard_entry"."score" DESC) AS "rank",
       PERCENT_RANK() OVER (PARTITION BY "leaderboard_entry"."leaderboard_id" ORDER BY "leaderboard_entry"."score" DESC) AS "percentile_rank",
       "leaderboard"."id",
       "leaderboard"."name",
       "user"."id",
       "user"."username"
  FROM "leaderboard_entry"
 INNER JOIN "leaderboard"
    ON ("leaderboard_entry"."leaderboard_id" = "leaderboard"."id")
 INNER JOIN "user"
    ON ("leaderboard_entry"."user_id" = "user"."id")
 WHERE "leaderboard_entry"."leaderboard_id" = 1
 ORDER BY "leaderboard_entry"."score" DESC

这会产生正确的结果,其中每个条目都被正确排名:

+----------------+--------------------+------+------------+-------+---------+--------------------+
| Leaderboard ID |  Leaderboard Name  | Rank | Percentile | Score | User ID |     User Name      |
+----------------+--------------------+------+------------+-------+---------+--------------------+
|       1        | Global Leaderboard |  1   |   0.000    |   95  |    5    |   Snarky Acrobat   |
|       1        | Global Leaderboard |  2   |   0.111    |   90  |    6    |   Deadly Striker   |
|       1        | Global Leaderboard |  2   |   0.111    |   90  |    7    |     Dark Zebra     |
|       1        | Global Leaderboard |  4   |   0.333    |   80  |    2    | Screaming Whistler |
|       1        | Global Leaderboard |  5   |   0.444    |   60  |    9    |   Snarky Leader    |
|       1        | Global Leaderboard |  6   |   0.556    |   55  |    4    |   Burly Creature   |
|       1        | Global Leaderboard |  6   |   0.556    |   55  |    10   |     Keen Joker     |
|       1        | Global Leaderboard |  8   |   0.778    |   45  |    3    | Crashing Underdog  |
|       1        | Global Leaderboard |  9   |   0.889    |   25  |    8    |    Eager Raptor    |
|       1        | Global Leaderboard |  10  |   1.000    |   15  |    1    |    Extreme Hawk    |
+----------------+--------------------+------+------------+-------+---------+--------------------+

但是,我无法查询特定用户 ID 以了解他们在排行榜中的排名。它总是说他们排名第一。我认为这是因为在 RANK() window 函数之前应用了用户 ID 过滤器。我如何进行查询才能使它 return 是特定用户 ID 的正确排名?

这行不通:

SELECT "leaderboard_entry"."id",
       "leaderboard_entry"."leaderboard_id",
       "leaderboard_entry"."user_id",
       "leaderboard_entry"."score",
       RANK() OVER (PARTITION BY "leaderboard_entry"."leaderboard_id" ORDER BY "leaderboard_entry"."score" DESC) AS "rank",
       PERCENT_RANK() OVER (PARTITION BY "leaderboard_entry"."leaderboard_id" ORDER BY "leaderboard_entry"."score" DESC) AS "percentile_rank",
       "leaderboard"."id",
       "leaderboard"."name",
       "user"."id",
       "user"."username"
  FROM "leaderboard_entry"
 INNER JOIN "leaderboard"
    ON ("leaderboard_entry"."leaderboard_id" = "leaderboard"."id")
 INNER JOIN "user"
    ON ("leaderboard_entry"."user_id" = "user"."id")
 WHERE ("leaderboard_entry"."user_id" = 3 AND "leaderboard_entry"."leaderboard_id" = 1)
 ORDER BY "leaderboard_entry"."score" DESC
+----------------+--------------------+------+------------+-------+---------+-------------------+
| Leaderboard ID |  Leaderboard Name  | Rank | Percentile | Score | User ID |     User Name     |
+----------------+--------------------+------+------------+-------+---------+-------------------+
|       1        | Global Leaderboard |  1   |   0.000    |   45  |    3    | Crashing Underdog |
+----------------+--------------------+------+------------+-------+---------+-------------------+

用户 ID 3 的正确排名应该是 8,而不是 1。

此外,我希望能够过滤特定用户 ID 和 return 排行榜上该用户的条目 "around"。因此,如果一个用户排名第 5,我想显示他们周围的 4 个条目,我将查询用户 ID 以及 select 他们前面的 2 行和他们后面的 2 行。

感谢任何帮助。谢谢!

Window 函数对生成的数据集进行操作,after 使用 where 谓词进行过滤。所以过滤后数据集中只剩下一条记录,它永远排在第一位。

您需要将现有查询转换为子查询,然后 对给定用户进行过滤:

SELECT *
FROM (
    SELECT "leaderboard_entry"."id",
           "leaderboard_entry"."leaderboard_id",
           "leaderboard_entry"."user_id",
           "leaderboard_entry"."score",
           RANK() OVER (PARTITION BY "leaderboard_entry"."leaderboard_id" ORDER BY "leaderboard_entry"."score" DESC) AS "rank",
           PERCENT_RANK() OVER (PARTITION BY "leaderboard_entry"."leaderboard_id" ORDER BY "leaderboard_entry"."score" DESC) AS "percentile_rank",
           "leaderboard"."id",
           "leaderboard"."name",
           "user"."id",
           "user"."username"
      FROM "leaderboard_entry"
     INNER JOIN "leaderboard"
        ON ("leaderboard_entry"."leaderboard_id" = "leaderboard"."id")
     INNER JOIN "user"
        ON ("leaderboard_entry"."user_id" = "user"."id")
     WHERE "leaderboard_entry"."leaderboard_id" = 1
) t
WHERE "user_id" = 3

请注意,不再需要 ORDER BY 子句,因为查询 returns 只是一行。否则,您也需要将其移动到外部查询。

您可以使用带有 window 函数的子查询,然后在外部查询中过滤用户:

select . . . 
from (. . .
      where "leaderboard_entry"."leaderboard_id" = 1
     )
where "user_id" = 3