【问题标题】:Get rows above and below (neighbouring rows) a certain row, based on two criteria SQL根据两个条件 SQL 获取某一行上方和下方(相邻行)的行
【发布时间】:2021-01-15 15:53:40
【问题描述】:

假设我有一张这样的桌子:

+---+-------+------+---------------------+
|id | level |score |      timestamp      |
+---+-------+------+---------------------+
| 4 |   1   |  70  | 2021-01-14 21:50:38 |
| 3 |   1   |  90  | 2021-01-12 15:38:0  |
| 1 |   1   |  20  | 2021-01-14 13:10:12 |
| 5 |   1   |  50  | 2021-01-13 12:32:11 |
| 7 |   1   |  50  | 2021-01-14 17:15:20 |
| 8 |   1   |  55  | 2021-01-14 09:20:00 |
| 10|   2   |  99  | 2021-01-15 10:50:38 |
| 2 |   1   |  45  | 2021-01-15 10:50:38 |
+---+-------+------+---------------------+

我想要做的是在一个表中显示其中的 5 行(在 html 中),中间有某一行(例如其中 id=5),并在其上方和下方有两行(以正确的顺序)。还有级别= 1。这就像一个记分板,但只显示用户的分数,上面两个,下面两个。 所以因为分数可以相同,所以还需要使用时间戳列 - 所以如果两个分数相等,那么第一个获得分数的人会显示在另一个人的上方。

例如说用户是id=5,我要显示

+---+-------+------+---------------------+
|id | level |score |      timestamp      |
+---+-------+------+---------------------+
| 4 |   1   |  70  | 2021-01-14 21:50:38 |
| 8 |   1   |  55  | 2021-01-14 09:20:00 |
| 5 |   1   |  50  | 2021-01-13 12:32:11 |
| 7 |   1   |  50  | 2021-01-14 17:15:20 |
| 2 |   1   |  45  | 2021-01-15 10:50:38 |
| 1 |   1   |  20  | 2021-01-14 13:10:12 |
+---+-------+------+---------------------+

注意 id=7 低于 id=5

我想知道有人知道这样做的方法吗?

我在下面尝试过,但它没有输出我需要的东西(它输出level_id = 2和id = 5,其他行不按顺序)

((SELECT b.* FROM table a JOIN table b ON b.score > a.score OR (b.score = a.score AND b.timestamp < a.timestamp)
  WHERE a.level_id = 1 AND a.id = 5 ORDER BY score ASC, timestamp DESC LIMIT 3)
 UNION ALL 
 (SELECT b.* FROM table a JOIN table b ON b.score < a.score OR (b.score = a.score AND b.timestamp > a.timestamp)
  WHERE a.level_id = 1 AND a.id = 5 ORDER BY score DESC, timestamp ASC LIMIT 2)) 
order by score 

如果更容易输出表中的所有行,说 where level = 1,所以它是一个满分板.. 然后使用 PHP 获取特定行和上下两行我也会想知道请:)! (可能认为这可能会使 SQL 更简单)?

【问题讨论】:

标签: php join mariadb union


【解决方案1】:

您可以使用 cte 和内连接,如下所示:

With cte as
(select t.*,
        dense_rank() over (order by score) as dr
   from your_table t)
Select c.*
  From cte c join cte cu on c.dr between cu.dr - 2 and cu.dr + 2
 Where cu.id = 5
Ordwr by c.dr, c.timestamp

【讨论】:

  • 嗨,我收到一个错误Unrecognized statement type. (near "WITH" at position 0)
  • 您使用的是哪个数据库?
  • MariaDB 是你的意思吗? (抱歉不完全确定“什么数据库”是什么意思)
  • MySQL 8.0 之前的版本不支持 WITH 子句。
  • 有没有办法使用标准 SQL 做到这一点?
【解决方案2】:

我会建议窗口函数:

select t.*
from (select t.*,
             max(case when id = 7 then score_rank end) over () as id_rank
      from (select t.*,
                   dense_rank() over (order by score) as score_rank
            from t
            where level = 1
           ) t
     ) t
where score_rank between id_rank - 2 and id_rank + 2;

注意:这会返回 5 个不同的分数值,这可能会导致更多行,具体取决于重复项。

Here 是一个 dbfiddle。

编辑:

如果您希望使用时间戳正好 5 行,那么:

select t.*
from (select t.*,
             max(case when id = 7 then score_rank end) over () as id_rank
      from (select t.*,
                   dense_rank() over (order by score, timestamp) as score_rank
            from t
            where level = 1
           ) t
     ) t
where score_rank between id_rank - 2 and id_rank + 2
order by score;

注意:这仍将等效时间戳视为相同,但它们在您的数据中似乎是唯一的。

【讨论】:

  • 嗨,虽然我不知道该代码是什么意思,但我试过了,它返回零行
  • @bb25 。 . .我修正了一个错字并添加了一个 dbfiddle。它有效。
  • 这为得分相同的用户提供了相同的排名 - 但我使用时间戳背后的原因是允许他们拥有不同的排名。 IE。得分第一的人排名更高
  • 编辑后的答案似乎不起作用。我得到了 level = 2 的值(我不想要)。此外,这些行的顺序也很混乱
  • 如果你只想要level = 1,那么使用where 子句。
猜你喜欢
  • 1970-01-01
  • 2013-12-03
  • 2015-06-19
  • 1970-01-01
  • 2021-09-07
  • 2021-04-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多