Как запросить ранжированную таблицу лидеров для определения c пользовательской записи и подмножества записей «вокруг» пользователя? - PullRequest
0 голосов
/ 06 апреля 2020

У меня есть следующие таблицы, которые составляют рейтинговую таблицу лидеров:

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);

Я могу присвоить специфическую c таблицу лидеров этим запросом 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    |
+----------------+--------------------+------+------------+-------+---------+--------------------+

Однако я не могу запросить указанный c идентификатор пользователя, чтобы узнать его ранг в таблице лидеров. Всегда говорится, что они ранжируются 1. Я предполагаю, что это потому, что фильтр для идентификатора пользователя применяется перед оконной функцией RANK (). Как выполнить запрос, чтобы он возвращал правильный ранг для указанного c идентификатора пользователя?

Это не работает:

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 |
+----------------+--------------------+------+------------+-------+---------+-------------------+

Правильный ранг для Идентификатор пользователя 3 должен быть 8, а не 1.

Кроме того, я хотел бы иметь возможность отфильтровывать указанный идентификатор пользователя c и возвращать записи «вокруг» этого пользователя в таблице лидеров. Поэтому, если пользователь занимает 5 место, и я хочу показать 4 записи вокруг них, я бы запросил идентификатор пользователя, а также выделил 2 строки перед ними и 2 строки за ними.

Любой помощь приветствуется. Спасибо!

Ответы [ 2 ]

1 голос
/ 06 апреля 2020

Вы можете использовать подзапрос с оконными функциями и затем фильтровать пользователя во внешнем запросе:

select . . . 
from (. . .
      where "leaderboard_entry"."leaderboard_id" = 1
     )
where "user_id" = 3 
1 голос
/ 06 апреля 2020

Оконные функции работают с результирующим набором данных, после фильтрации с предикатами 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 больше не требуется, поскольку запрос возвращает только одну строку. В противном случае вам также потребуется переместить его во внешний запрос.

...