具有空值的游标分页(上一个/下一个)

Cursor Pagination (prev / next) with null values

我有一个使用 MySQL(8.0 版)实现的游标分页,只要不涉及 null 值,它就可以正常工作。

这是我的示例数据(id 是随机 UUID,date 是日期,time 是时间):

id | date       | time
--------------------------
68 | 2017-10-28 | 22:00:00
d3 | 2017-11-03 | null
dd | 2017-11-03 | 21:45:00
62 | 2017-11-04 | 14:00:00
a1 | 2017-11-04 | 19:40:00

我使用的 cursor 总是包含所有三列。

我使用此查询获取 下一个 结果(在 cursor 之后):

SELECT * FROM table
WHERE (date > cursor.date)
    OR (date = cursor.date AND time > cursor.time)
    OR (date = cursor.date AND time = cursor.time AND id > cursor.id)
ORDER BY date ASC, time ASC, id ASC

此查询 prev 结果(在 cursor 之前):

SELECT * FROM table
WHERE (date < cursor.date)
    OR (date = cursor.date AND time < cursor.time)
    OR (date = cursor.date AND time = cursor.time AND id < cursor.id)
ORDER BY date DESC, time DESC, id DESC

当使用 prev 查询和 cursor [id = dd, date = 2017-11-03, time = 21:45:00] 时,它不会 return 带有 id = d3 的行,因为 timenulltime < cursor.time 不会选择它。

虽然我尝试使用 time < cursor.time OR time IS NULL 而不是 time < cursor.time 来包含具有 null 值的行。这似乎解决了这个特定问题,但随后又产生了一个新问题:当使用 prev 查询和 cursor [id = d3, date = 2017-11-03, time = null] 时,因为现在结果包含所提供游标的行。

我希望有一个简单的解决方案。网络上似乎没有处理游标分页中 null 值的示例或教程。

注意: 对于解决方案,null 是否在 non-null 值之前或之后排序并不重要,只要它是一致的。 (MySQL 的默认顺序是 null < non-null

我不会触及使用游标进行分页的话题。还有其他选择,例如 limit/offset.

但我对您的查询的建议是使用 coalesce(),为比较分配一个假时间。 MySQL 使这有点简单,因为它支持 time 超过 24 小时的值。对于 date/time 组合,这些值不是有效值。

所以:

SELECT *
FROM table
WHERE (date > cursor.date) OR
      (date = cursor.date AND COALESCE(time, '24:00:00') > COALESCE(cursor.time, '24:00:00')) OR
      (date = cursor.date AND COALESCE(time, '24:00:00') = COALESCE(cursor.time, '24:00:00') AND id > cursor.id)
ORDER BY date ASC, time ASC, id ASC

更简洁的 WHERE 子句是:

WHERE (date, COALESCE(time, '24:00:00'), id) > (cursor.date, COALESCE(cursor.time, '24:00:00'), cursor.id)

向 table 添加另一列。将其设为 DATETIME。不为NULL时将datetime合并进去;当 NULL 时,将 date 与某个特定时间结合起来。然后你的光标有两列可以使用并且没有空值。

如果您有相当新的 MySQL 版本,您可以使用“生成的存储”列,从而避免任何代码更改。

并且一定要INDEX(datetime, id).

如果您使用的是 MySQL 8.0,那么您可以考虑使用 row_number() window 函数为每一行创建一个唯一的顺序 ID (rn)。然后只需为当前行传递 rn 即可获取前几行。

架构和插入语句:

 create table cursortable( id varchar(10), date date, time time);

 insert into cursortable values('68' , '2017-10-28' , '22:00:00');
 insert into cursortable values('d3' , '2017-11-03' ,  null);
 insert into cursortable values('dd' , '2017-11-03' , '21:45:00');
 insert into cursortable values('62' , '2017-11-04' , '14:00:00');
 insert into cursortable values('a1' , '2017-11-04' , '19:40:00');

第一次查询得到结果:

 select *,row_number()over(order by date,time,id)rn from cursortable

输出:

id date time rn
68 2017-10-28 22:00:00 1
d3 2017-11-03 null 2
dd 2017-11-03 21:45:00 3
62 2017-11-04 14:00:00 4
a1 2017-11-04 19:40:00 5

查询以获取 cursor [id = dd, date = 2017-11-03, time = 21:45:00, rn=3] 的前几行,只有 cursor [rn=3]:

 with cte as
 (
   select *,row_number()over(order by date,time,id)rn from cursortable
 )
 select * from cte where rn<3

输出:

id date time rn
68 2017-10-28 22:00:00 1
d3 2017-11-03 null 2

db<>fiddle here

如果您不想在代码中引入计算列,请考虑所有三列尝试以下解决方案 cursor [id = dd, date = 2017-11-03, time = 21:45:00]

查询:

with cte as
 (
   select *,row_number()over(order by date,time,id)rn from cursortable
 )
 ,cte2 as
 (
   select * from cte where id='dd' and date=  '2017-11-03' and time= '21:45:00'
 )
 select cte.id,cte.date,cte.time from cte inner join cte2 on cte.rn<cte2.rn

输出:

id date time
68 2017-10-28 22:00:00
d3 2017-11-03 null

db<>fiddle here

您的代码如下所示:

with cte as
 (
   select *,row_number()over(order by date,time,id)rn from cursortable
 )
 ,cte2 as
 (
   select * from cte where id=cursor.id and date=  cursor.date and time= cursor.time
 )
 select cte.id,cte.date,cte.time from cte inner join cte2 on cte.rn<cte2.rn