[英]Represent Ordering in a Relational Database
我在数据库中有一组对象。 照片库中的图像,目录中的产品,书中的章节等。每个对象都表示为一行。 我希望能够任意地对这些图像进行排序,将这种排序存储在数据库中,这样当我显示对象时,它们的顺序就会正确。
例如,假设我正在写一本书,每一章都是一个对象。 我写了我的书,并按以下顺序列出章节:
简介,可访问性,表单与功能,错误,一致性,结论,索引
它转到编辑器,并返回以下建议的顺序:
简介,表格,功能,可访问性,一致性,错误,结论,索引
如何以强大,高效的方式将此排序存储在数据库中?
我有以下想法,但我对其中任何一个都不感兴趣:
阵列。 每行都有一个订单ID,当订单更改时(通过删除后插入),订单ID会更新。 这使得检索变得容易,因为它只是ORDER BY
,但它似乎很容易破解。
// REMOVAL
UPDATE ... SET orderingID=NULL WHERE orderingID=removedID
UPDATE ... SET orderingID=orderingID-1 WHERE orderingID > removedID
// INSERTION
UPDATE ... SET orderingID=orderingID+1 WHERE orderingID > insertionID
UPDATE ... SET orderID=insertionID WHERE ID=addedID
链接列表。 每行都有一列用于排序中下一行的id。 遍历在这里似乎很昂贵,尽管可能通过某种方式使用ORDER BY
,我没想到。
间隔阵列。 将orderingID(在#1中使用)设置为大,因此第一个对象是100,第二个是200,等等。然后当插入发生时,只需将它放在(objectBefore + objectAfter)/2
。 当然,这需要偶尔重新平衡,所以你没有太紧密的东西(即使有花车,你最终会遇到舍入误差)。
这些对我来说都不是特别优雅。 有没有人有更好的方法呢?
另一种替代方法是(如果您的RDBMS支持它)使用类型为array的列。 虽然这打破了规范化规则,但在这种情况下它可能很有用。 我知道有一个数组的数据库是PostgreSQL。
Rails中的acts_as_list mixin基本上按照您在#1中概述的方式处理。 它查找名为position的INTEGER列(当然可以覆盖其名称)并使用它来执行ORDER BY。 当您想要重新订购商品时,您需要更新头寸。 每次我使用它都很适合我。
作为旁注,您可以通过使用稀疏编号来消除总是在INSERTS / DELETES上重新定位的需要 - 类似于当天的基本类型...您可以编号您的位置10,20,30等。如果你需要在10到20之间插入一些东西,你只需要插入一个位置为15.同样在删除时你可以删除该行并留下空隙。 您只需要在实际更改订单时进行重新编号,或者如果您尝试执行插入操作,并且没有适当的间隙插入。
当然,根据您的具体情况(例如,您是否已将其他行已加载到内存中),使用间隙方法可能有意义也可能没有意义。
只考虑选项#1 vs#3 :没有间隔数组选项(#3)只推迟正常数组(#1)的问题? 无论你选择哪种算法,要么它已经坏了,要么你会在#3之后遇到问题,或者它可以工作,然后#1应该也能正常工作。
如果对象没有被其他表严格键入,并且列表很短,则删除域中的所有内容并重新插入正确的列表是最简单的。 但是如果列表很大并且你有很多限制来减慢删除,这是不切实际的。 我认为你的第一种方法真的是最干净的。 如果你在一个交易中运行它,你可以确定当你处于更新中间以搞砸订单时,没有什么奇怪的事情发生。
我在上一个项目中做到了这一点,但它只是偶尔需要特别订购的表,并且不经常访问。 我认为间隔数组是最好的选择,因为它在一般情况下重新排序最便宜,只涉及一个值的变化和两个查询。
此外,我认为ORDER BY将由数据库供应商进行相当大的优化,因此与链接列表实现相比,利用该功能对于性能将是有利的。
使用浮点数表示每个项目的位置:
第1项 - > 0.0
第2项 - > 1.0
第3项 - > 2.0
第4项 - > 3.0
您可以通过简单的二分法在任何其他两个项目之间放置任何项目:
第1项 - > 0.0
第4项 - > 0.5
第2项 - > 1.0
第3项 - > 2.0
(在第1项和第2项之间移动了第4项)。
由于浮点数在计算机系统中编码的方式,二分过程几乎可以无限期地继续。
第4项 - > 0.5
第1项 - > 0.75
第2项 - > 1.0
第3项 - > 2.0
(将项目1移动到项目4之后的位置)
我会做一个连续的数字,在桌子上有一个触发器,如果它已经存在,它会为优先级“腾出空间”。
我也有这个问题。 我受到了很大的压力(不是我们所有人)而且我选择了#1,只更新了更改的行。
如果将项目1与项目10交换,只需执行两次更新即可更新项目1和项目10的订单号。我知道它在算法上很简单,并且它是O(n)最坏的情况,但最坏的情况是当你有列表的总排列。 这种情况多久会发生一次? 那是给你回答的。
我有同样的问题,可能至少花了一周的时间来讨论正确的数据建模,但我想我终于明白了。 使用PostgreSQL中的数组数据类型,您可以存储每个有序项的主键,并在订单更改时使用插入或删除相应地更新该数组。 引用单行将允许您根据数组列中的顺序映射所有对象。
它仍然有点不稳定的解决方案,但它可能比选项#1更好,因为选项1需要在订购更改时更新所有其他行的订单号。
除INSERT
写入之外,方案#1和方案#3在每个操作中具有相同的复杂性。 上方案#1具有O(N)写入INSERT
和方案#3具有O(1)上写入INSERT
。
对于每个其他数据库操作,复杂性是相同的。
甚至不应该考虑方案#2,因为它的DELETE
需要O(n)读写。 方案#1和方案#3对读和写都有O(1) DELETE
。
如果您的元素具有不同的父元素(即它们共享外键行),那么您可以尝试以下方法...
Django提供了一种与数据库无关的解决方案,用于在CharField()
存储整数列表。 一个缺点是存储的字符串的最大长度不能大于max_length
,这取决于DB。
就复杂性而言,这将为INSERT
提供Scheme#1 O(1)写入,因为排序信息将作为单个字段存储在父元素的行中。
另一个缺点是现在需要JOIN
到父行来更新排序。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.