数据调整顺序方案
在业务场景中,经常会有数据需要调整顺序的需求,例如菜单上、下移动,表格数据拖动排序等等。
这个需求前后端实现起来都有一定的难度,本文是在开发全栈应用时,对后端调整顺序处理的思考和记录。
在下文中,以 D(data)指代全部数据排序字段值组成的数组,以 C(current index)指代需要移动的数据的当前下标,以 N(new index)指代需要移动的数据的目标下标
1. 区间数据整体移动
这是最容易想到的一种方案,分为两种情况:
- 数据向下移动
此时数据库中的修改应该是:遍历 D[C]
到 D[N]
之间的数据,执行 -1
操作,D[C]
的值改为 D[N]
的值
- 数据向上移动
此时数据库中的修改应该是:遍历 D[N]
到 D[C]
之间的数据,执行 +1
操作,D[C]
的值改为 D[N]
的值
这种方案实现起来较为容易,但缺点也很明显。如果数据量过大,极端情况下需要全表进行更新,性能较为低下。
2. 浮点数作为排序字段
以浮点数作为排序字段的操作方式是:
- 将
D[C]
的值改为D[N]
与D[N-1]
的中间数,即D[C] = D[N - 1] + (D[N] - D[N - 1]) / 2
这样做就只需要修改一条数据便能完成改变位置。但这个方案的问题也显而易见,一直取中间数迟早会让排序值超过数据精度范围,导致出现相同的排序值。
所以这种方案只适合短时间内操作量不大的情况,且基本需要配合定时操作来重置排序值,避免出现超出精度的情况。
3. 链表数据结构
链表数据结构会在每一条数据中存储与它相连接数据的地址(单向链表只存储下一条数据的地址,双向链表则同时存储前后两条数据的地址),这种结构非常适合调整位置的情况。
如果数据不需要经常查询前一条数据,可以使用单项链表,否则推荐使用双向链表。
例如在数据表中增加 prev
和 next
字段,分别存储链表中前一条数据和后一条数据的 id
,第一条数据的 prev
为空,最后一条数据的 next
为空。
在改变位置时,如果为单向链表,只需要:
- 修改前一条数据的
next
为后一条数据:D[C].prev.next = D[C].next
- 修改目标数据前一条数据的
next
为当前数据:D[N].prev.next = D[C]
- 修改当前数据的
next
为目标数据:D[C].next = D[N]
如果为双向链表,则需要:
- 修改前一条数据的
next
为后一条数据:D[C].prev.next = D[C].next
- 修改后一条数据的
prev
为前一条数据:D[C].next.prev = D[C].prev
- 修改目标数据前一条数据的
next
为当前数据:D[N].prev.next = D[C]
- 修改当前数据的
prev
为目标数据的前一条数据:D[C].prev = D[N].prev
- 修改当前数据的
next
为目标数据:D[C].next = D[N]
- 修改目标数据的
prev
为当前数据:D[N].prev = D[C]
可以看到链表结构中,只需要三步或六步就能完成顺序调整操作。
但链表结构也有自己的问题,SQL 语句中无法直接对链表进行排序,所以获取数据时需要先找到第一条数据,再根据第一条数据的 next 字段依次查询后面的数据(可以查出全部数据后在内存中操作),如果数据量过大,或读取频繁的话会有一定性能问题。
4. 单独存储排列顺序
可以新建一张表,用一个字段存储数据的排列顺序,例如将数据 id 以数组方式存储,数组中的顺序就代表数据的顺序。
查询时按照排序值对数据进行排序即可,mysql 5.0.2
中提供了 field
函数可以对排序字段按给出值的顺序进行排列:
SELECT * FROM table ORDER BY FIELD(order, 1, 3, 5, 2, 6, 4);
上面的查询语句中,FIELD
函数第一个参数为需要排序的字段名,后续参数为字段值,可以有任意个,代表数据排列的方式。
更新时可以修改排序值,也可以新增一条排序数据,且始终都只需要操作数据表一次。
这种方案查询和更新都很方便,缺点是需要新增一张表,以及低版本数据库中查询时会较为麻烦。
总结
上面介绍了我在解决后端数据排序时收集到的四种方案,这里做一个总的对比:
方案 | 增 | 删 | 查 | 改 | 其他 |
---|---|---|---|---|---|
区间数据整体移动 | 排序值设为最大排序值+1 | 直接删除 | 按排序字段排序 | 修改当前位置到目标位置区间数据的排序值 | |
浮点数作为排序字段 | 排序值设为最大排序值+1 | 直接删除 | 按排序字段排序 | 修改排序值为目标位置数据和前一条数据排序值的中间数 | 存在排序值重复的可能,需要经常整理数据 |
链表结构 | 与最后一条数据互相关联 | 直接删除 | 从第一条数据开始依次查找 | 分别修改当前位置和目标位置前后数据的关联信息 | |
单独存储排列顺序 | 增加数据并修改排序值 | 删除数据并修改排序值 | 查找排序值后按值顺序排序 | 修改数据值 | 需要增加表 |