如何在数据库中存储有序的数据

思考一个问题,现在需要在前端展示和管理一个数据列表,这个列表需要让用户自己来定义顺序,并不是按照添加的时间或者其他属性来排序,那么我们如何在数据库中存储这样的数据呢?
这种场景可能大部分人都遇到过,比如股票应用,每个人关注的股票都不一样,而且都会把自己最关注的放在最前面,这样在打开应用时就能以最快的速度看到自己关注的股票的涨跌情况。
在前端进行移动排序非常容易,因为在前端一般都是用数组来存储列表数据,数组元素的增加、删除、修改顺序等操作都有对应的api,操作简便,效率也高,数据库不一样,数据库里的记录都是无序的,通过修改记录的顺序来维持用户自定义的顺序代价太高,所以需要用单独的字段来存储用户自定义的股票顺序,而且在用户添加股票、修改排序、删除股票后都需要做相应的修改。

权重字段

最便捷的一个方法就是增加一个权重字段,值的类型是整数,数值越大的排序越靠前,数值小的排到最后,这个数值其实就是用户对这些股票的关注程度。
举个例子:

item1->2
item2->1
item3->3

按照权重字段降序排列读取出来后的顺序就是item3->item1->item2
这样做有个问题,如果用户想增加一个item4,并且把它排在item1item2之间,那这样item1item3的权重都得改,数据越多,操作起来越复杂。
如果我们可以明确需要排序的数据的数据量,那么可以把权重的数之间的间隔设置更大一些,比如100,每次插入都插在前后两条记录的权重的平均数的位置。我们来看一下:

item1->200
item2->100
item3->300

现在需要插入item4,那么它的权重值设置在150比较合适,方便后续的插入。
这种设置权重字段的方式适用于小数据量的情况,而且开销小,但弊端也很明显,面对大数据量非常乏力,这时我们可以考虑用另外一种方式。

单向链表

单向链表非常适用于这个场景,增加一个字段,取名叫next,指向它的下一条数据的id,即使数据库里的记录没有顺序,我们查询时也能很轻松的把数据按照用户自定义的顺序排列出来。

item1(id:1) next->2
item2(id:2) next->-1
item3(id:3) next->1

查询出来后先找到next为-1的记录(item2)作为列表的最后一个,再查找next为2的记录……这样依次排序,就得到了最终想要的顺序列表。

增加一条记录时,需要客户端把前一条数据的id传给服务端,获取这条数据的next字段,并修改这条数据的next字段,使其指向新增加的数据的id,新数据的next字段则改为前一条数据的之前的next字段指向的id。比如增加item4,插入到item1item2之间。

item1(id:1) next->4(修改)
item2(id:2) next->-1
item3(id:3) next->1
item4(id:4) next->2(新增)

删除一条数据也需要获取前一条数据,把next字段值的值修改为被删除的这条数据的next字段指向的id。

修改一条数据顺序(即移动某一条数据)的操作稍微复杂一些,需要修改在移动之前这条数据的前一条数据的next指向,还需要修改移动之后这条数据的前一条数据的next指向,最后修改它自己的next指向。
比如,移动item4item1item3之间。

item1(id:1) next->2(item4移动之前的前一条数据)
item2(id:2) next->-1
item3(id:3) next->4(item4移动之后的前一条数据)
item4(id:4) next->1(修改)

在链表的操作过程中最多需要在数据库里修改三条记录,开销并不算大。随着数据增多,操作数据库的次数并不会增加。

“链表”转换成数组

数据库中的链表并不是真正的链表,因为next字段只是记录了下一条数据的id,不是真的指针,所以在从数据库里获取到数据后还要转换成真的链表。
具体的转换过程思路是这样:

  1. 通过读库获取数据列表
  2. 建立一个字典表,以每项数据的id为key,整项数据为value
  3. 然后遍历整个数据列表,通过next字段在字典表里找到对应的元素,然后将这两个元素互相关联,形成一个双向链表
  4. 找到数据列表中没有指向前一个元素的指针的元素,即为整个双向链表的第一项。
  5. 通过指针依次读取,把所有的数据都添加到数组中

这里以JavaScript为例来实现。
从数据库中获取的数据是这样:

[
  {id:1,name:"item1",next:4},
  {id:2,name:"item2",next:-1},
  {id:3,name:"item3",next:1},
  {id:4,name:"item4",next:2},
]

具体实现代码:

function transform(data){
  var dictionary={};

  data.forEach(function(item){
    dictionary[item.id]=item;
  });

  data.forEach(function(item){
    if(dictionary.hasOwnProperty(item.next)){
      //nextItem属性指向下一个元素
      item.nextItem=dictionary[item.next];
      //previousItem属性指向前一个元素
      dictionary[item.next].previousItem=item;
    }
  });
  
  var first = data.filter(function(item){
    return item.previousItem===undefined;
  })[0];//找到第一个元素
  
  var sortedList=[first];
  while(first.nextItem){
    sortedList.push(first.nextItem);
    first=first.nextItem;
  }
  
  return sortedList;
}

这里运用到了引用类型的特性,只需要为每个元素找到对应的nextItempreviousItem,所有的元素就会根据指针串联起来。

小结

本文一共讲了两种在数据库中存储有序数据的方式,第一种适用于小数据量的数据,第二种方式无论大数量量还是小数据量都适用,当然推荐使用第二种方式,无论数据有多少。同时还介绍了一种方法,把数据库中读取的数据转换成指定顺序的数组,前后端都可以用。
还有一种情况需要跟大家分享一下,就是在树节点的排序中使用第二种方式会比较好。同级节点之间排序使用权重字段当然是没有问题,如果存在跨层级移动节点的情况,使用单向链表的方式会更方便。

你可能感兴趣的:(如何在数据库中存储有序的数据)