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

时间:2008-08-21 23:01:30

标签: sql database django django-models

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

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

  

简介,辅助功能,表格与功能,错误,一致性,结论,索引

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

  

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

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

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

  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. Spaced数组。将orderingID(在#1中使用)设置为大,因此第一个对象为100,第二个为200,等等。然后,当插入发生时,您只需将其放在(objectBefore + objectAfter)/2。当然,这需要偶尔重新平衡,所以你没有太紧密的东西(即使有花车,你最终会遇到舍入错误)。

  4. 这些对我来说都不是特别优雅。有没有人有更好的方法呢?

11 个答案:

答案 0 :(得分:6)

另一种替代方法是(如果您的RDBMS支持它)使用类型为array的列。虽然这打破了规范化规则,但在这种情况下它可能很有用。我知道的一个有数组的数据库是PostgreSQL。

答案 1 :(得分:4)

Rails中的acts_as_list mixin基本上按照你在#1中概述的方式处理。它查找名为position的INTEGER列(当然可以覆盖其名称)并使用它来执行ORDER BY。当您想要重新订购商品时,您需要更新头寸。每次我使用它都能很好地为我服务。

作为旁注,您可以通过使用稀疏编号来消除总是在INSERTS / DELETES上重新定位的需要 - 有点像当天的基本回...你可以为你的位置编号10,20,30等等,如果你需要在10到20之间插入一些东西,你只需要插入一个15的位置。同样在删除时你可以删除行并留下空隙。您只需要在实际更改订单时进行重新编号,或者如果您尝试插入并且没有适当的间隙插入。

当然,根据您的具体情况(例如,您是否已将其他行加载到内存中),使用间隙方法可能有意义也可能没有意义。

答案 2 :(得分:3)

只考虑选项#1 vs#3 :没有间隔数组选项(#3)只推迟正常数组(#1)的问题?无论你选择哪种算法,要么它已经破碎了,要么你会遇到#3之后的问题,或者它有效,然后#1应该也能正常工作。

答案 3 :(得分:2)

如果对象没有被其他表严格键入,并且列表很短,则删除域中的所有内容并重新插入正确的列表是最简单的。但是如果列表很大并且你有很多限制来减慢删除,这是不切实际的。我认为你的第一种方法真的是最干净的。如果您在交易中运行它,您可以确保在更新过程中没有任何奇怪的事情发生,从而搞砸了订单。

答案 4 :(得分:2)

我在上一个项目中做到了这一点,但它只是偶尔需要特别订购的桌子,并且不经常访问。我认为间隔数组是最好的选择,因为它在一般情况下重新排序最便宜,只涉及一个值的变化和两个查询。

另外,我认为ORDER BY会被数据库供应商大大优化,因此与链表实现相比,利用该函数对性能有利。

答案 5 :(得分:2)

使用浮点数表示每个项目的位置:

项目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项之后的位置)

答案 6 :(得分:1)

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

答案 7 :(得分:1)

我也有这个问题。我受到了沉重的压力(不是我们所有人)而且我选择了#1选项,只更新了更改的行。

如果您将项目1与项目10交换,只需执行两次更新即可更新项目1和项目10的订单编号。我知道它在算法上很简单,并且它是O(n)最坏的情况,但最坏的情况是当你有一个列表的总排列。这种情况多久会发生一次?那是给你回答的。

答案 8 :(得分:1)

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

答案 9 :(得分:0)

我遇到了同样的问题,并且可能至少花了一周的时间来讨论正确的数据建模,但我想我终于得到了它。使用PostgreSQL中的数组数据类型,您可以存储每个有序项的主键,并在订单更改时使用插入或删除相应地更新该数组。引用单行将允许您根据数组列中的顺序映射所有对象。

它的解决方案仍有点不稳定,但它可能比选项#1更好,因为选项1需要在订购更改时更新所有其他行的订单号。

答案 10 :(得分:0)

除了INSERT写操作之外,方案1和方案3在每个操作中都具有相同的复杂性。方案#1在INSERT上写O(n),方案#3在INSERT上写O(1)。

对于其他所有数据库操作,复杂度是相同的。

甚至不应该考虑方案2,因为其DELETE要求O(n)读写。方案#1和方案#3的读写都具有O(1)DELETE

新方法

如果您的元素具有不同的父元素(即它们共享一个外键行),则可以尝试以下操作...

Django提供了一种与数据库无关的解决方案,用于在CharField()中存储整数列表。缺点之一是存储的字符串的最大长度不能大于max_length,这取决于数据库。

就复杂性而言,这将使Scheme#1 O(1)写入INSERT,因为订购信息将作为单个字段存储在父元素行中。

另一个缺点是,现在需要在父行上使用JOIN来更新顺序。

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