# MySQL 中自定义排序,上移、下移功能的设计

# 1、背景

指定数据列表的排序顺序、上移下移、指定位置插入是日常开发中一些比较常见的需求,比如:支付宝中用户扣款银行卡的先后顺序,QQ 好友分组的按序展示、上移下移调整

本文以一个用户表为例,假定用户的展示顺序有严格要求,并且支持上移下移自定义调整。

探讨在 MySQL 中设计和实现该功能,建表语句如下:

create table sys_user
(
    id                   bigint not null auto_increment comment '主键',
    username             varchar(100) not null comment '用户名',
    sort                 bigint not null comment '排序字段',
    deleted              bigint not null default 0 comment '删除标识(0存在,非0删除)',
    create_time          datetime default current_timestamp comment '创建时间',
    update_time          datetime default current_timestamp on update current_timestamp comment '更新时间',
    primary key (id),
    unique key uk_username (username, deleted)
) comment '用户表';

其中的 sort 字段为排序字段,预定最小值为 1,按 sort 降序排序;deleted 为逻辑删除字段。

# 2、初始化数据

insert into sys_user(id, username, sort) values (1, 'zhangsan', 1);
insert into sys_user(id, username, sort) values (2, 'lisi', 2);
SELECT * FROM sys_user order by sort desc

# 3、删除数据,逻辑删除

update sys_user set deleted = 1 where id = #{id}

# 4、插入数据

# 4.1 不指定排序位置插入数据(默认)

insert into sys_user(username, sort)
select 'wangwu', (select ifnull(max(sort), 0) + 1 from sys_user where deleted = 0)

# 4.2 指定排序位置插入数据

新增时指定插入到 sort = 2021 的位置

step1:原有数据调整顺序,sort >= 2001 的数据项 sort 加一

update sys_user
set sort = sort + 1
where sort >= 2021
  and deleted = 0

step2:将新的数据插入到 sort = 2001 的位置上

insert into sys_user(username, sort)
select 'zhaoliu', (select least(2021, (select ifnull(max(sort), 0) + 1 from sys_user where deleted = 0)))

如果表中排序最大值未达到 2021,将取表中最大排序值加 1

# 4.3 插入语句在 MyBatis 中合并处理

insert into sys_user(username, sort)
select 'zhangsan',
<choose>
    <when test="sort != null and sort > 0">
        (select least(#{sort}, (select ifnull(max(sort), 0) + 1 from sys_user where deleted = 0)))
    </when>
    <otherwise>
        (select ifnull(max(sort), 0) + 1 from sys_user where deleted = 0)
    </otherwise>
</choose>

# 5、上移下移功能

# 5.1 上移

step1:根据当前数据行 ID 查询上一条有效记录的 ID

select id
from sys_user
where sort > (select sort from sys_user where id = #{id})
  and deleted = 0
  order by sort
  limit 1

step2:使用自连接交换两条数据的 sort

update sys_user t1
join sys_user t2
on (t1.id = #{oneId} and t2.id = #{otherId}) or (t1.id = #{otherId} and t2.id = #{oneId})
set t1.sort = t2.sort,
t2.sort = t1.sort

# 5.2 下移

step1:根据当前数据行 ID 查询下一条有效记录的 ID

select id
from sys_user
where sort < (select sort from sys_user where id = #{id})
  and deleted = 0
  order by sort desc
  limit 1

step2:使用自连接交换两条数据的 sort(附带更新 updateUid 字段)

update sys_user t1
join sys_user t2
on (t1.id = #{oneId} and t2.id = #{otherId}) or (t1.id = #{otherId} and t2.id = #{oneId})
set t1.sort = t2.sort,
t1.update_uid = #{updateUid},
t2.sort = t1.sort,
t2.update_uid = #{updateUid}