繁体   English   中英

表示在关系数据库中的排序

[英]Represent Ordering in a Relational Database

我在数据库中有一组对象。 照片库中的图像,目录中的产品,书中的章节等。每个对象都表示为一行。 我希望能够任意地对这些图像进行排序,将这种排序存储在数据库中,这样当我显示对象时,它们的顺序就会正确。

例如,假设我正在写一本书,每一章都是一个对象。 我写了我的书,并按以下顺序列出章节:

简介,可访问性,表单与功能,错误,一致性,结论,索引

它转到编辑器,并返回以下建议的顺序:

简介,表格,功能,可访问性,一致性,错误,结论,索引

如何以强大,高效的方式将此排序存储在数据库中?

我有以下想法,但我对其中任何一个都不感兴趣:

  1. 阵列。 每行都有一个订单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

  2. 链接列表。 每行都有一列用于排序中下一行的id。 遍历在这里似乎很昂贵,尽管可能通过某种方式使用ORDER BY ,我没想到。

  3. 间隔阵列。 将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之后的位置)

我会做一个连续的数字,在桌子上有一个触发器,如果​​它已经存在,它会为优先级“腾出空间”。

由于我主要使用Django遇到这个问题 ,我发现这个解决方案是最可行的。 似乎在关系数据库中没有任何“正确的方法”。

我也有这个问题。 我受到了很大的压力(不是我们所有人)而且我选择了#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到父行来更新排序。

https://docs.djangoproject.com/en/dev/ref/validators/#django.core.validators.validate_comma_separated_integer_list

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM