表示关系数据库中的排序

Posted

技术标签:

【中文标题】表示关系数据库中的排序【英文标题】:Represent Ordering in a Relational Database 【发布时间】:2010-09-06 11:44:47 【问题描述】:

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

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

简介、可访问性、形式与功能、错误、一致性、结论、索引

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

介绍、形式、功能、可访问性、一致性、错误、结论、索引

如何以稳健、高效的方式将此排序存储在数据库中?

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

    数组。每行都有一个排序 ID,当订单发生变化时(通过删除后插入),订单 ID 会更新。这使得检索变得容易,因为它只是 ORDER BY,但它似乎很容易破解。

    // REMOVALUPDATE ... SET orderingID=NULL WHERE orderingID=removedIDUPDATE ... SET orderingID=orderingID-1 WHERE orderingID > removedID// INSERTIONUPDATE ... SET orderingID=orderingID+1 WHERE orderingID > insertionIDUPDATE ... SET orderID=insertionID WHERE ID=addedID

    链接列表。每行都有一列用于排序中下一行的 id。此处的遍历似乎代价高昂,尽管可能通过某种方式使用我没有想到的ORDER BY

    间隔数组。将 orderingID(如 #1 中使用的)设置为大,因此第一个对象是 100,第二个是 200,等等。然后当插入发生时,您只需将其放置在 (objectBefore + objectAfter)/2。当然,这需要偶尔重新平衡,所以你不会让事物靠得太近(即使使用浮点数,你最终也会遇到舍入错误)。

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

【问题讨论】:

【参考方案1】:

另一种选择是(如果您的 RDBMS 支持)使用数组类型的列。虽然这违反了规范化规则,但在这种情况下它可能很有用。我知道的一个有数组的数据库是 PostgreSQL。

【讨论】:

我不明白这个解决方案显然是更好的答案。您能否详细说明如何为每一行使用数组?谢谢【参考方案2】:

Rails 中的acts_as_list mixin 基本上按照您在#1 中概述的方式处理这个问题。它查找一个名为 position 的 INTEGER 列(当然,您可以覆盖它的名称)并使用它来执行 ORDER BY。当您想重新排序时,您会更新职位。每次使用它都对我有好处。

作为旁注,您可以通过使用稀疏编号来消除始终在 INSERTS/DELETES 上重新定位的需要——有点像过去的基本操作...您可以将您的位置编号为 10、20、30等,如果您需要在 10 到 20 之间插入一些内容,您只需将其插入位置为 15。同样,在删除时您可以删除该行并留下间隙。仅在实际更改订单或尝试插入且没有适当间隙可插入时才需要重新编号。

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

【讨论】:

+1 用于提及稀疏编号。我过去曾为此使用过ranked-model gem。【参考方案3】:

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

【讨论】:

【参考方案4】:

只是考虑选项#1 vs #3的一个想法:间隔数组选项(#3)不只是推迟了普通数组(#1)的问题吗?无论您选择哪种算法,要么它已损坏,您稍后会遇到#3 的问题,要么它可以工作,然后 #1 应该也能正常工作。

【讨论】:

【参考方案5】:

我在我的上一个项目中这样做了,但它只是为了一个偶尔需要特别订购的表格,并且不经常访问。我认为间隔数组将是最好的选择,因为在平均情况下,它的重新排序是最便宜的,只涉及对一个值的更改和对两个值的查询)。

另外,我认为 ORDER BY 将由数据库供应商进行大量优化,因此与链表实现相比,利用该功能将有利于性能。

【讨论】:

【参考方案6】:

使用浮点数来表示每一项的位置:

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

【讨论】:

这个/不会/无限期地继续下去。在病理情况下,浮点数(双精度)值将在 53 轮后收敛。即使您的 DBMS 使用任意精度的小数,您也会有大量的数据结构膨胀。 好的,所以当间距低于阈值时,添加一个以 O(n) 复杂度进行重组的意外事件。现在您大约每 1/10000 次操作就会发生一次 O(n) 操作。如果你很聪明地使用它,二等分算法是最好的。【参考方案7】:

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

【讨论】:

太多被 jQuery UI 掩盖了,我不知道它遵循哪种方案。基于模型使用IntegerField 进行排序这一事实,它可能会使用 O(n) 更新,并遵循 OP 的选项 #1。【参考方案8】:

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

【讨论】:

这需要对每次插入进行 O(n) 重组!【参考方案9】:

我也有这个问题。我承受着巨大的时间压力(不是我们所有人),我选择了选项 #1,并且只更新了更改的行。

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

【讨论】:

【参考方案10】:

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

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

【讨论】:

【参考方案11】:

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

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

甚至不应该考虑方案#2,因为它的DELETE 需要O(n) 次读取和写入。 Scheme #1 和 Scheme #3 的读写时间都为 O(1) DELETE

新方法

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

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

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

另一个缺点是现在需要父行的JOIN 来更新排序。

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

【讨论】:

以上是关于表示关系数据库中的排序的主要内容,如果未能解决你的问题,请参考以下文章

查询以获取按关系中的项目数排序的数据

NSSortDescriptor 按核心数据对多关系中的项目数排序

拓扑排序问题

数据库中的自连接

图的应用——拓扑排序算法

核心数据:重启时排序的一对多关系