數據庫關於拖拽排序功能的字段設計和邏輯


一、背景


最近做的一個比較簡單 CMS 項目,其中最“復雜”的功能就是要對表格中的數據實現拖拽排序

實例效果如下:

2021-04-07-00-00-20

二、前端


用的是 AntDesign 的組件 Table 其中的 拖拽排序 的示例:

https://ant.design/components/table-cn/#components-table-demo-drag-sorting

傳給后端的參數格式為:{dragRowId, hoverRowId},id 為此行在數據表里的真實 id。

三、后端 —— 數據庫結構字段設計


(1)方案1 —— 單表單列

給原表加入 order 字段,即原表結構:id …… order

(2)方案2 —— 單表多列

給原表加入 prevIdnextId 字段,即原表結構: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 值重置(用整數值)。

參考資料


一個基本的用戶排序功能為什么這么難?—— 知乎


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM