数据库关于拖拽排序功能的字段设计和逻辑

发表于:2020-4-22 09:08

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:小蒋不素小蒋    来源:博客园

  一、背景
  最近做的一个比较简单 CMS 项目,其中最“复杂”的功能就是要对表格中的数据实现 拖拽排序 。
  实例效果如下:
  二、前端
  用的是 AntDesign 的组件 Table 其中的 拖拽排序 的示例:
  https://ant.design/components/table-cn/#components-table-demo-drag-sorting
  传给后端的参数格式为: {dragRowId, hoverRowId} ,id 为此行在数据表里的真实 id。
  三、后端 —— 数据库结构字段设计
  (1)方案1 —— 单表单列
  给原表加入 order 字段,即原表结构: id …… order 。
  (2)方案2 —— 单表多列
  给原表加入 prevId 和 nextId 字段,即原表结构: id …… prevId nextId 。
  (3)方案3 —— 多表单列
  再开一个新表: order ,结构为: id content
  content 数据类型为 array ,格式形如: {1,3,2,4} ,里面记录了原表的 id,并且数组的顺序即排序的顺序(可以数组的首项表示最top,或者数组的尾项表示最 top,随你)。
  (4)总结
  关于这一节的讨论,让我想到了当初看《SQL 反模式》的其中一章,关于如何设计 “存放(帖子的)评论(可嵌套回复评论)” 的数据库表结构设计,思想还是挺一致的。
  感兴趣可以看我之前的那一篇: 《SQL 反模式》 学习笔记 的 “第三章 单纯的树” 。
  Result:因我们这次的需求,涉及拖动的数据量并不大,且拖拽行为并不频繁,所以这里我采用比较简单的 方案1 。
  四、后端 —— 算法逻辑
  基于上面的方案1。
  order 规则:从 1 开始,逐步递增 +1,值越大表示顺序越靠前。
  1、order 字段值用整数
  (1)增
  取出原表中最大的 order 值, order+1 即为新行的 order。
  (2)删
  直接删
  (3)查
  要按顺序 select 出来,很 方便 ,直接按 order 的值递减排序即可。
  (4)改(即拖拽排序)
  当用户进行拖拽操作,我们并不需要把原表所有行的 order 字段进行更新,而是 只更新 [dragRow, hoverRow] 之间的行的 order 。
  伪代码如下:
  1、根据 dragRowId 和 hoverRowId 取出 dragRowOrder 和 hoverRowOrder
  2、判断是向上拖拽(up)还是向下拖拽(down)
  3、取出 dragRow 与 hoverRow 之间的行列表(注意要按照 order 的顺序取出),下面待用
  4、对上面取出的结果:① 赋值:drag 行的 order = hover 行 order ② 剩余的行,order 全部 -1(up) / +1(down)
  注意:最好在整个操作期间加上表锁,例如用 事务 。
  具体代码如下:
  应用代码:Node.js(express)
  数据库 ORM:Sequelize (PostgreSQL)
async function sort(req, res, next) {

    const {dragRowId, hoverRowId} = req.body;

    let transaction;
    try {

        transaction = await models.sequelize.transaction({isolationLevel: 'SERIALIZABLE'});

        // 1、根据 dragRowId 和 hoverRowId 取出 dragRowOrder 和 hoverRowOrder
        const dragRowItem = await Designer.findOne({
            where: {
                id: dragRowId
            },
            transaction
        })
        const dragRowOrder = dragRowItem.order
        const hoverRowItem = await Designer.findOne({
            where: {
                id: hoverRowId
            },
            transaction
        })
        const hoverRowOrder = hoverRowItem.order

        // 2、判断是向上拖拽(up)还是向下拖拽(down)
        let dragType = null
        if (dragRowOrder < hoverRowOrder) {
            dragType = "up"
        } else if (dragRowOrder > hoverRowOrder) {
            dragType = "down"
        } else {
            throw new Error("您没有进行拖拽操作")
        }

        // 3、取出 dragRow 与 hoverRow 之间的行列表(注意要按照 order 的顺序取出),下面待用
        const resultList = await Designer.findAll({
            where: {
                order: dragType === "up" ?
                    {
                        [Op.gte]: dragRowOrder,
                        [Op.lte]: hoverRowOrder,
                    } : {
                        [Op.gte]: hoverRowOrder,
                        [Op.lte]: dragRowOrder,
                    }
            },
            order: [["order", "DESC"]],
            transaction
        })

        // 4、对上面取出的结果:① 赋值:drag 行的 order = hover 行 order ② 剩余的行,order 全部 -1(up) / +1(down)
        for (let i = 0; i < resultList.length; i++) {
            if (dragType === "up") {
                if (i === resultList.length - 1) {
                    await resultList[i].update({order: hoverRowOrder}, {transaction})
                } else {
                    await resultList[i].decrement('order', {transaction})
                }
            } else {
                if (i === 0) {
                    await resultList[i].update({order: hoverRowOrder}, {transaction})
                } else {
                    await resultList[i].increment('order', {transaction})
                }
            }
        }

        // commit
        await transaction.commit();

        res.json({message: 'ok'});

    } catch (error) {
        // 只要出错就回滚
        if (transaction) await transaction.rollback();
        next(error)
    }

}
  2、order 字段值用浮点数
  (1)增
  取出原表中最大的 order 值, order 向上取整并 +1 即为新行的 order。
  (2)删
  跟上面用整数的一致。
  (3)查
  跟上面用整数的一致。
  (4)改(拖拽排序)
  这里比用整数 简单,不用更新 [dragRow, hoverRow] 之间的行的 order,只需要更新一行,即把 dragRow 的 order 改成 (dragRow 上一行 order 值 + dragRow 下一行 order 值)/ 2 。
  这种方法又称 取中值法。
  缺点:因为一个数除以2,可能会让小数位+1(如 (1+2)/2=1.5 ),所以如果达到了 数据库关于浮点数的最大精度,则会有问题 。
  解决方案:
  1、如果用户的拖拽不是很频繁,可以忽略这种错误的可能性,例如 Postgres 的 demical 数据类型,小数的最大精度是 16383,绰绰有余。
  2、如果用户的拖拽很频繁,建议创建一个定时任务,把所有行的 order 值重置(用整数值)。

本文内容不用于商业目的,如涉及知识产权问题,请权利人联系博为峰小编(021-64471599-8017),我们将立即处理。
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号