关键词:
-
引言
数据库应用中常需要在一个有序数据子集中,对指定的若干条记录进行上下移动。例如,管理员需要对新闻列表中的若干条新闻置顶,考试出卷时需要对选定题目进行上下移动重排顺序,等等。
总的应该场景在数据表中可以概括为如下模型:
数据表 TblData(id,fid,rank),id表示记录的唯一标识,fid指记录的父节点,rank代表父节点下兄弟的前后顺序,依次从1递增,没有空隙。
问题是要对相同fid下选中的若干个节点进行上下移动,如图1中的2个示例:
图 1 上移操作示例图
左侧示例是对第5、6两个连续的记录上移,右侧示例是对第4和6个有间隔的记录上移。上移后,要移动的记录间的间隔保持不变。现实中,选中记录的位置和数量都是随机的,移动方向有“上移”1位,“下移”1位,“置顶”和“置底”。该需求进入存储过程的参数可归结为 PROCEDURE Reorder(OUT out_errno INT, IN in_ids TEXT, IN in_n INT UNSIGNED, IN in_direction),其中,out_errno 用于返回状态码,in_ids 是要移动的多个 id,以“,”分隔;in_n 是要移动的 id 数量;in_direction 是移动方向,取值是"up","down","top","bottom"。
-
上移操作
以块为单位的上移
以向上移动(上移或置顶)为例(如图2所示),将连续的记录看作一个块,按升序依次对各块进行上移操作。示例中有3个块,因此需要移动3次。
图2 上移操作块示意图
特定块的上移
对具体的块的移动,需要确定移动的步长(step)和块中的记录数(size)。通过第1块可以确定移动的步长,并且该步长对所有块都适用,即,所有块移动的步长都和第1块相同。图2中,如果进行“上移”,则移动的步长是 step = 1;如果进行“置顶”,则移动的步长是 step = 4。不同块中的记录数可能不同。图2中第1块的记录是5和6,因此记录数是 size = 2。
以块1的“置顶”操作为例(如图3所示)。以要移动的记录5作为基准点,current=5,对其上面步长范围内的记录下移,并对其下面块中记录数范围内的记录上移,即,对[current-step,current-1]范围的记录作下移 (+size),对[current,current+size-1]范围的记录上移 (-step)。
图3 特定块的上移示意图
该过程通过1条SQL语句可以实现:
UPDATE TblData
SET rank=rank+IF(rank<current,size,-step)
WHERE fid=l_fid -- 其中,l_fid 是指记录共同的父节点编号
AND rank BETWEEN current-step AND current+size-1
-
下移操作
下移过程和上移过程类似,区别是移动要从下面的块开始(即,编号大的块),与下面的记录进行交换(如图4所示)。
图4 特定块的下移示意图
-
以块为操作单位的可选方案
从前文可以看出,确定移动步长(step)和块内记录数(size)起到关键作用。 移动步长根据第1块(上移操作)或最后1块(下移操作)很容易确定。 但对于块内记录数却不那么容易,需要设置游标判断相邻两条记录是否连续,是否有间隙,还要进行记数,并在存在间隙时需要暂停,转去执行具体块的移动操作。 可选方案是对要移动的记录不作分块处理,即每条要移动的记录都看作一个块进行移动。 这样带来的后果是:- 图2示例中,若按块移动,则需移3次(因为有3个块需要上移);若不分块移动,则需移动4次(因为有4条记录需要上移)。移动次数取决于块数和记录数的区别。
- 图2示例中,若按块移动,则需要确定每1块的记录数(第1块是2,第2和3块都是1);若不分块移动,则不需要确定每块记录数,或可以看成每块记录数都是1。是否省去了统计块内记录数的区别。
- 效率上,主要取决于块数和记录数;逻辑上,后者更简单清晰。因此,本人倾向后者。
-
完整实现代码
使用MySQL存储过程实现上述功能,参考代码如下。假设传入参数在数据库外的合法性已经保证,数据库内的合法性需要验证;假设要移动的记录数大于0;假设数据表中记录都是有序连续的。DROP PROCEDURE IF EXISTS Reorder;上述源代码中,上移和下移的过程可以合并,但展示的逻辑会略复杂,相对难理解。读者若希望合并,可自行改写代码。
CREATE PROCEDURE Reorder(
OUT out_errno INT,
IN in_ids TEXT,
IN in_n INT UNSIGNED,
IN in_direction VARCHAR(16)
)
-- block0 是整个程序最外层的范围,其中还包含 block1(检测合法性) 和 block2(实际移动操作)
block0: BEGIN
DECLARE
l_rank_max, -- 用于记录最大Rank值,也即记录总数
l_fid -- 用于记录父节点编号
INT UNSIGNED;
-- block1 对传入参数在数据库端合法性进行检测
block1: BEGIN
-- 将要移动的记录存入临时表 t0(id,rank,processed)
-- 其中,processed用于block2中循环处理待移动记录的标记
IF TRUE THEN
DROP TEMPORARY TABLE IF EXISTS t0;
SET @sql = CONCAT(‘
CREATE TEMPORARY TABLE t0
SELECT id, rank,‘no‘ processed
FROM TblData
WHERE id IN(‘,in_ids,‘)
ORDER BY rank ‘,IF(in_direction IN(‘up‘,‘top‘),‘ASC‘,‘DESC‘),‘ -- 要移动的记录,若上移,则按rank升序排列,否则降序
‘);
PREPARE s FROM @sql;EXECUTE PREPARE s;DEALLOCATE PREPARE s;
END IF;
-- 101 检测要移动的记录是否都存在
IF (SELECT COUNT(id) FROM t0)<>in_n THEN
SET out_errno = 101;
LEAVE block0;
END IF;
-- 102 要移动的记录都隶属于同一父节点
IF (
SELECT COUNT(DISTINCT fid) FROM t0
)>1 THEN
SET out_errno = 102;
LEAVE block0;
END IF;
SET l_fid = (SELECT fid FROM t0 LIMIT 1);
SET l_rank_max = (SELECT MAX(rank) FROM TblData WHERE fid=l_fid);
IF in_direction IN(‘up‘,‘top‘) THEN
-- 103 如果上移,是否已经在顶端
IF (
SELECT rank=1 FROM t0
LIMIT 1
) THEN
SET out_errno = 103;
LEAVE block0;
END IF;
ELSE
-- 104 如果下移,是否已经在底端
IF (
SELECT rank=l_rank_max
FROM t0
LIMIT 1
) THEN
SET out_errno = 104;
LEAVE block0;
END IF;
END IF;
END block1;
block2: BEGIN
DECLARE l_step, l_size, l_current INT UNSIGNED;
SET l_size = 1;
IF in_direction IN(‘up‘,‘top‘) THEN
-- 上移操作
SET l_step = IF(
in_direction=‘up‘,
1,
(SELECT rank-1 FROM t0 LIMIT 1)
);
myloopup: LOOP
-- 所有要移动的记录都已经处理完
IF NOT (
SELECT id FROM t0
WHERE processed=‘no‘
LIMIT 1
) THEN
LEAVE myloopup;
END IF;
-- 对要移动的记录 t0.processed=‘no‘ 的记录进行上移
SET l_current = (SELECT rank FROM t0 WHERE processed=‘no‘ LIMIT 1);
UPDATE TblData
SET rank=IF(rank<l_current,+l_size,-l_step)
WHERE fid=l_fid
AND rank BETWEEN l_current-l_step AND l_current+l_size-1;
-- 设置标记位,表示已经处理过
UPDATE t0 SET processed=‘yes‘ WHERE processed=‘no‘ LIMIT 1;
END LOOP myloopup;
ELSE
-- 下移操作
SET l_step = IF(
in_direction=‘down‘,
1,
(SELECT l_rank_max-rank FROM t0 LIMIT 1)
);
myloopdown: LOOP
IF NOT(
SELECT id FROM t0
WHERE processed=‘no‘
LIMIT 1
) THEN
LEAVE myloopdown;
END IF;
SET l_current = (SELECT rank FROM t0 WHERE processed=‘no‘ LIMIT 1);
UPDATE TblData
SET rank=rank+IF(rank>=l_current,-l_size,+l_step)
WHERE fid=l_fid
AND rank BETWEEN l_current-l_size+1 AND l_current+l_step;
-- 设置标记位,表示已经处理过
UPDATE t0 SET processed=‘yes‘ WHERE processed=‘no‘ LIMIT 1;
END loop myloopdown;
END IF;
SET out_errno = 0; -- 表示移动完成
END block2;
END block0
元素的上移下移等排序操作
...项目中,我经常遇到对元素进行排序操作的需求,包括:上移、下移、置顶、置底。那么这些操作如何实现呢? 上移,简言之就是将需要上移的元素和它前面元素交换位置,使用insertBefore(),大致思路为:vardom=需要上移的元... 查看详情
table中实现数据上移下移效果
html由于vue+Element项目中的table,没有开放的上移下移的api,但是能对数据操作,故思路为数组中的一条数据,再重新添加一条数据,办法有点笨,但是好歹也是实现了,望有好的办法的,请留言<el-table:data="tableData"style="width:100%"&... 查看详情
模仿淘宝卖家的店铺分类
功能要求:随意添加分类、移动排序(要有上移下移置顶置底)、删除按钮需要保存修改之后才可以生效、需要无子类时候才可以、页面上也是可以立即更改,难点是前端的JavaScript逻辑。效果:用的是bootstrap框架,页面所以有... 查看详情
请问如何在delphi中实现多选打印功能!
参考技术A标签打印请问如安在delphi中实现多选打印功能!具体的情况是:DBgrid傍边有很多字段,有很多记录请求做到:第一步,记录的若干是动态的,但要能选择记录打印,数量不限。第二步,字段有很多,再上一步的基本上... 查看详情
请教jquery对表格的行操作的。对页面表格进行上下移动位置,删除记录的操作。谢谢。如下补充
型号厂商数量封装批号供应商单价操作asdf原装asdf0123上移下移删除model004原装asdasda011上移下移删除model001信息技术原装dasdas025上移下移删除model002信息技术原装dasdas026上移下移删除model003信息技术原装dasdas011上移下移删除asdasd原装... 查看详情
lind.ddd.domain.isortbehavor~上移与下移
在进行列表排序时,有个“上移”和“下移”操作,这个一般在内存里完成,然后统一提交到数据库中,对于上移与下移的设计,大叔在LIND.DDD.DOMAIN里有一个ISortBehavor接口,主要是说,如果实体对象支持排序功能,... 查看详情
jqgrid上移下移单元格(代码片段)
...表中的行数保存到数据库中,取数据时按行进行排序1、上移,下移按钮<ahref="javascript:void(0)"onclick="operateWithOneRowById(up)"class="linkButton">上移</a><ahref="javascript:void(0)" 查看详情
cdatagridview中怎样把多行数据同时上移或下移
这个分两种情况:1和DataGridView没稀有据源这时就是对DataGridView本身的操作。2和DataGridView稀有据源这时操作数据源然后刷新DataGridView就可以了。---------------你可以换一种思路将多行移动改变为一行的移除与插入。假如还有不清楚... 查看详情
上移下移扩展版总结(代码片段)
需求上移下移 需求: 1)点击上移就将当前的按钮的父级上移一位 2)当点击第一个时候将当前按钮的父级移动到最后一位 3)当点击最后一个时候将当前按钮的父级移动到首位4)带过渡动画 需求分析:下移:剪切所点... 查看详情
使用主数据库在 Spring MVC 应用程序中实现多租户?
】使用主数据库在SpringMVC应用程序中实现多租户?【英文标题】:ImplementingmultitenancyinSpringMVCapplicationwithamasterdatabase?【发布时间】:2018-12-0305:39:41【问题描述】:我正在尝试使用基于此代码源demo的SpringMVC、Springsecurity、Hibernate和... 查看详情
thinkphp有啥优越性?如何使用?另外在php+mysql中如何实现置顶、收索、转跳到指定第几页的功能。
...到任意的第N页去,如何实现。 还有就是怎么样实现上移下移。 本人菜鸟尽,解答量详细点哦,谢谢!参考技术A这个没法详细,一详细就要自己写代码贴上来了,说下思路吧,TP框架要说优越性么,国产的,适合学习练手,文档丰... 查看详情
vue做一个上移和下移,删除的li功能(代码片段)
效果图:思路就是冒泡原理,把数据放到一个空数组,对其进行排序,单选框用到的是iview。具体实现代码:<divv-for="iteminsingledLists":key="item.index">//数组singledLists<Checkbox@on-change="checkSingle":disabled="isDisabled"v-model="item.isRight 查看详情
使用jquery实现option的上移和下移
基本思路: 上移:(1)获取当前选中的元素的索引值 (2)判断当前元素是否为第一个元素 (3)如果是,则不执行上移操作,如果不是,则则调用insertBefore方法插入... 查看详情
js实现数组内数据的上移和下移
varswapItems=function(arr,index1,index2){ arr[index1]=arr.splice(index2,1,arr[index1])[0] returnarr}vararr=[1,2,3]varnewArr=[]upData(arr,index){ if(this.arr.length>1&&index!==0){ newA 查看详情
angularjs实现数据列表的增加删除和上移下移等功能实例
...章给大家分享了AngularJS循环实现数据列表的增加、删除和上移下移等基础功能,对大家学习AngularJS具有一定的参考借鉴价值,有需要的朋友可以看看。 效果图实例代码<!DOCTYPEhtml><htmllang="en"ng-app="myapp"ng-c 查看详情
如何在gridview中实现多选
GridView实现跨页多选,参考如下:JS前台://GridView中实现多选效果function CheckAllC(oCheckbox) var GridView1 = document.getElementById(\'gvDataList\'); for (i = 1; i < gvDataList.rows.length; i++) GridView1.rows[i].cells[0].getEleme... 查看详情
db2数据库某一个表增加新列后,如何改变该列在表中的排序?表右方的“上移”和“下移”好像都用不了。
...列队顺序不会影响数据展示,就是想建的表中的列看起来有序一些。设计时有时候会漏了需要的字段,重建表太麻烦。为什么DB2表中有“上移”和“下移”,就是不给用?追答哈哈,感觉是强迫症哇。。。上移,下移不太清楚 查看详情
如何使用 Java Spring 在 MySql 中实现多租户 [关闭]
】如何使用JavaSpring在MySql中实现多租户[关闭]【英文标题】:HowcanIachievemultitenancyinMySqlbyusingJavaSpring[closed]【发布时间】:2018-02-2605:13:48【问题描述】:如何使用MySqlJavaSpring最佳实践实现多租户,并建议使用任何其他数据库代替MyS... 查看详情