【问题标题】:Get N previous and next records in mariadb / mysql ordered by score table获取mariadb/mysql中按分数表排序的N条前后记录
【发布时间】:2019-07-30 22:22:31
【问题描述】:

我有一个包含分数的表(最多 20000 条记录)。我想显示一个带有延迟加载功能的记分牌:只显示玩家得分的 20 条记录,如果他向上滚动,则再显示 20 条记录,或者如果他向下滚动,则获取 20 条记录。 这个板子会经常被大量玩家同时调用,所以我必须以最轻松的方式来做。

CREATE TABLE cities (
  cityId SMALLINT UNSIGNED NOT NULL,
  points SMALLINT UNSIGNED NOT NULL, -- not unique at all
  PRIMARY KEY (cityId)
)
ENGINE = INNODB;

ALTER TABLE cities
ADD INDEX points (points);

如何有效地获取指定行 (WHERE cityId=<myCityId>) 的前 10 行和后 10 行,按 points 降序排序

我怎样才能找到下一个 20?因为使用OFFSETLIMIT 似乎不是最好的方法 https://www.eversql.com/faster-pagination-in-mysql-why-order-by-with-limit-and-offset-is-slow/

谢谢

编辑

我尝试了两种@Schwern 解决方案,但都没有按预期工作,因为我可以有相同分数的线条。

select points, cityName from (
    (
        select *  
        from cities
        where points < (select points from cities where cityName = :cityName)
        order by points desc
        limit 5
    )
    union
    select * from cities where cityName = :cityName
    union
    (
        select *
        from cities
        where points >= (select points from cities where cityName = :cityName)
          and cityName != :cityName
        order by points
        limit 5
    )
) t
order by points;

limit=5cityName=Viry 的结果:

points  cityName
0   Nantes
0   Amiens
2223    Roye
3705    Caps City
4446    Toulouse
5187    Viry
5187    Rampillon
5187    Vdr
5187    Chicago
5187    Le Village
5187    Titoucity

缺少很多相同分数的行(例如:32行,分数=4446,这里只有一个)

MariaDB / MySQL 版本翻译自Oracle solution

WITH RECURSIVE boundaries (prevr, nextr, lvl) as (
  select 
    COALESCE(
      (
        select max(c.points)
        from   cities AS c
        where  c.points < c2.points
      ), 
      c2.points
    ) AS prevr,
    COALESCE(
      (
        select min(c.points) 
        from   cities AS c 
        where  c.points > c2.points
      ),
      c2.points
    ) AS nextr,
    1 lvl
  from cities AS c2
  where  cityName = :cityName
  union all
  select 
    COALESCE(
      (
        select max(points) 
        from   cities AS c 
        where  c.points < prevr
      ),
      prevr
    ) AS prevr,
    COALESCE(
      (
        select min(points) 
        from   cities AS c 
        where  c.points > nextr
      ),
      nextr
    ) AS nextr,
    lvl+1 lvl
  from   boundaries
  where  lvl+1 <= :lvl
)
select c.points, c.cityName
from   cities AS c
join   boundaries AS b
on     c.points between b.prevr and b.nextr
and    b.lvl = :lvl
order  by c.points; 

lvl=1cityName=Viry 的结果

points  cityName
4446    Toulouse
4446    Jotown
4446    Guignes
4446    Douns
4446    Colombes
4446    Chambly
4446    Cassandra Gn
4446    Bussyland
4446    Magny Les Hameaux
4446    Palamos
4446    Ville
4446    Loujul
4446    Osny
4446    Sqy
4446    Senlis
4446    Vendres
4446    Amiens
4446    Saint Jean De Luz
4446    Senlis
4446    Abbeville
4446    Ca City
4446    Tolkien
4446    Paiementland
4446    Cash City
4446    Amiens
4446    Beauvais
4446    Kona
4446    St Petaouchnoc'
4446    Amiens
4446    Pick City
4446    Conflans
4446    Versailles          ^ +1
5187    Le Village
5187    Compiegne
5187    Titoucity
5187    Vdr
5187    Rampillon
5187    Chicago
5187    Moustache Ville
5187    Viry                ^  0
5928    Trot Ville          v -1
5928    Amiens
5928    Cityc
5928    Bakel City
5928    Rouen
5928    Noailles
5928    Caps Town
5928    Atlantis
5928    Camon
5928    Smart City
5928    Maville
5928    Azzana
5928    Strasbourg
5928    Sqy Park

它有效,但我需要决定我得到多少行,有时我可以有 50 个相同的分数,有时只有一两个。

重新编辑

使用第二个订单字段重试第一个解决方案

SET @mypoints := (select points from cities where cityId = :cityId);
select t.points, t.cityId, t.cityName from (
    (
        select *  
        from cities AS c1
        where c1.points <= @mypoints
          AND c1.cityId > :cityId
        order by c1.points DESC, c1.cityId ASC
        limit 5
    )
    union
    select * from cities AS c2 where c2.cityId = :cityId
    union
    (
        select *
        from cities AS c3
        where c3.points >= @mypoints
          AND c3.cityId < :cityId
        order by c3.points ASC, c3.cityId DESC
        limit 5
    )
) t
order by t.points;

limit=5cityId=36 的结果

points  cityId  cityName
0   49  Nantes
1482    53  Paris
1482    51  Mattown
2223    56  Haudiville
3705    37  Caps City
5187    36  Viry           < ==
6669    29  Prospercity
6669    31  Amiens
8892    22  Meteor
20007   34  Ouagadougou
20007   35  Meaux

和第一个问题一样

【问题讨论】:

  • 什么版本的 MySQL 和/或 MariaDB?较新的有window functions
  • 好的,谢谢,我总是在不知道它是否真的有用的情况下留下这个数字,知道我知道:) stackoverflow.com/questions/5634104/… 我的错误来自这里:dev.mysql.com/doc/refman/8.0/en/integer-types.html(我混淆了字节和位)和来自phpmyadmin 在字段创建时询问长度并默认放置值
  • mysql Ver 15.1 Distrib 10.1.40-MariaDB
  • 偏移和限制对我来说似乎是一种合理的方式
  • 您应该阅读LEAD()LAG() 的文档。有解释和例子。试试看。然后,如果您还有其他现有文档无法回答的问题,请回来在这里提问。

标签: mysql mariadb


【解决方案1】:

因为城市可以有相同的点,所以我们需要注意不要在前一个和下一个之间重复行。

首先,我们通过按点排序并找到具有相同或更多点的那些获得下一行,不包括所选城市。很简单。

select *
from ranking
where points >= (select points from ranking where cityId = :cityId)
  and cityId != :cityId
order by points
limit 10

然后我们得到有问题的行。

select * from ranking where cityId = :cityId

然后我们通过查找点数较少的行来获取前面的行,但我们必须按点数降序排列。这给出了相反的结果,我们稍后会解决这个问题。

select *  
from ranking
where points < (select points from ranking where cityId = :cityId)
order by points desc
limit 10

我们可以使用unions 将所有这些放在一个查询中。对组合查询进行排序可以解决先前行被反转的问题。

select * from (
    (
        select *  
        from ranking
        where points < (select points from ranking where cityId = :cityId)
        order by points desc
        limit 10
    )
    union
    select * from ranking where cityId = :cityId
    union
    (
        select *
        from ranking
        where points >= (select points from ranking where cityId = :cityId)
          and cityId != :cityId
        order by points
        limit 10
    )
) t
order by points;

我通过generating 200,000 random dates 对此与限制/偏移进行了基准测试。有显着的性能改进。不像you read on the internet那么可怕,但这可能是硬件差异。

使用 union 需要 limit 10 offset X 随 X 增加,在 50,000 时需要 20 到 120 毫秒,具体取决于它是否需要文件排序。


Fetching a Row Plus N Rows Either Side in a Single SQL Statement 中概述了另一个选项。因为它是为 Oracle 编写的,所以替换 nvl with coalesce

【讨论】:

  • 好的,谢谢! oracle 解决方案使用LEADLAG,正如@bill 所说,我想我现在将使用您的代码,稍后尝试使用窗口函数进行优化。
  • @hexaJer 文章的最终形式没有使用lead 也没有使用lag,尽管我很难理解它是如何工作的。
  • 如何转换 with 语句?
  • @hexaJer 在语法上,将nvl 替换为coalesce。除此之外我不确定。就像我说的,我很难看懂这篇文章。
  • 对于coalesce 可以,但with 必须替换为 JOIN 但存在递归调用。我不知道该怎么做:\
猜你喜欢
  • 2013-05-13
  • 1970-01-01
  • 2020-01-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-09-01
相关资源
最近更新 更多