在sql数据库中使用实数进行显式排序

Posted

技术标签:

【中文标题】在sql数据库中使用实数进行显式排序【英文标题】:Using Real numbers for explicit sorting in sql database 【发布时间】:2014-01-08 16:18:08 【问题描述】:

我正面临一个反复出现的问题。我必须让用户重新排序一些存储在数据库中的列表。

我能想到的第一个直接的方法是有一个“位置”列,其中的排序保存为整数。 p.e.

Data, Order
A     1
B     2
C     3
D     4

这里的问题是,如果我必须在位置 2 中插入 FOO,现在我的表变成了

Data, Order
A     1
FOO   2
B     3
C     4
D     5

所以要插入一个新行,我必须对一个包含五个元素的表执行一个 CREATE 和三个 UPDATE。

所以我的新想法是使用实​​数而不是整数,我的新表变成了

Data, Order
A     1.0
B     2.0
C     3.0
D     4.0

如果我想在 A 之后插入一个元素 FOO,这就变成了

Data, Order
A     1.0
FOO   1.5
B     2.0
C     3.0
D     4.0

只执行了一个 SQL 查询。

这适用于理论实数,但浮点数的精度有限,我想知道这是多么可行,我是否以及如何优化它以避免通过合理数量的修改来避免超过双精度

编辑:

这就是我现在在 python 中实现它的方式

@classmethod
def get_middle_priority(cls, p, n):
    p = Decimal(str(p))
    n = Decimal(str(n))
    m = p + ((n - p)/2)

    i = 0
    while True:
        m1 = round(m, i)
        if m1 > p and m1 < n:
            return m1
        else:
            i += 1

@classmethod
def create(cls, data, user):
    prev = data.get('prev')

    if prev is None or len(prev)<1:
        first = cls.list().first()

        if first is None:
            priority = 1.0
        else:
            priority = first.priority - 1.0
    else:
        prev = cls.list().filter(Rotator.codice==prev).first()
        next = cls.list().filter(Rotator.priority>prev.priority).first()

        if next is None:
            priority = prev.priority + 1.0
        else:
            priority = cls.get_middle_priority(prev.priority, next.priority)

    r = cls(data.get('codice'),
        priority)

    DBSession.add(r)

    return r

【问题讨论】:

用户对数据应该如何排序的解释是什么? 您实际期望插入多少次? 对我来说,这听起来像是您试图创建一个链接列表?为什么不存储 parentId 并使用 CTE 递归生成排序列表? 这不仅关乎插入,还关乎利用现有数据。我想要尽可能强大的东西。 如果你想控制一些不是 ORDER BY 的自然结果的用户定义的订单,那么你需要指向下一个项目。 【参考方案1】:

如果您想控制位置并且没有 ORDER BY 解决方案,那么一个相当简单且稳健的方法是指向下一个或上一个。更新/插入/删除(除了第一个和最后一个)将需要 3 次操作。

Insert the new Item
Update the Item Prior the New Item
Update the Item After the New Item

确定后,您可以使用 CTE(带有 UNION ALL)来创建一个无限制的排序列表。

我已经看到了通过触发器完成的相当大的实现,以保持列表的完美形式。但是,我不喜欢触发器,只是将整个操作的逻辑放在存储过程中。

【讨论】:

【参考方案2】:

你可以使用字符串而不是数字:

item  order
A     ffga
B     ffgaa
C     ffgb

这里,有限精度的问题是通过增加字符串的可能性来处理的。理论上,数据库中的字符串存储是无限的,仅取决于存储设备的大小。但是对于绝对排序项没有更好的解决方案。相对排序,如链表,可能效果更好(但你不能按查询排序)。

【讨论】:

【参考方案3】:

链表的想法很简洁,但是按顺序提取数据的成本很高。如果你有一个支持它的数据库,你可以使用connect by 之类的东西来把它拉出来。 linked list in sql 是专门针对该问题的问题。

现在,如果您不这样做,我正在考虑如何实现无限可分范围,并想到了一本书中的部分。最初将列表存储为

1
2
3

然后在 1 和 2 之间插入一个“1 下的小节”,这样你的列表就变成了

1
1.1
2
3

如果你想在 1.1 和 2 之间插入另一个小节,你可以在 1 下放置第二个小节并得到

1
1.1
1.2
2
3

最后,如果你想在 1.1 和 1.2 之间添加一些东西,你需要引入一个 subsubsection 并得到

1
1.1
1.1.1
1.2
2
3

也许使用字母而不是数字会更容易混淆。

我不确定 sql 数据库中是否有任何标准的字典顺序可以正确排序这种类型的列表。但是我认为您可以通过一些“按案例排序”和子字符串来滚动自己。编辑:我发现了一个与此相关的问题:linky

另一个缺点是此解决方案的最坏情况字段大小会随着输入项的数量呈指数增长(您可能会得到像 1.1.1.1.1.1 这样的长行)。但在最好的情况下,它将是线性的或几乎恒定的(行如 1.934856.1)。

这个解决方案也非常接近您的想法,我不确定它是否是一种改进。使用您提到的二进制分区策略的十进制数可能会将每个插入之间的小数点数增加一位,对吗?所以你会得到

1,2 -> 1,1.5,2 -> 1,1.25,1.5,2 -> 1,1.125,1.25,1.5,2

因此,分段策略的​​最佳情况似乎更好,但最坏的情况要糟糕得多。

我也不知道任何用于 sql 数据库的无限精度十进制类型。但是你当然可以将你的号码保存为一个字符串,在这种情况下,这个解决方案变得更加类似于你原来的解决方案。

【讨论】:

【参考方案4】:

将所有行设置为一个唯一的数字,从 1 开始,并在开始时递增 1。插入新行时,将其设置为表格的 count(*) + 1(有多种方法)。

当用户更新行的顺序时,始终通过调用存储过程来更新它,该存储过程具有要更新的行的此 ID (PK) 和新顺序。在存储过程中,

update tableName set Order = Order + 1 where Order >= @updatedRowOrder;

update tablename set Order = @updatedRowOrder where Id = @pk;

这保证了总会有空间和一个没有重复的连续序列。如果您将愚蠢的新订单号连续放入(例如

干杯-

【讨论】:

那将非常昂贵.. 如果有一百万行并且中间发生了插入怎么办?我的朋友不是很健壮。 是的。然而,问题中没有说明表格的大小,用户手动订购一百万行表格的想法表明他们可能有比这更严重的问题。 他想要一个稳健/长期的解决方案。 OP 表示“显式”排序顺序,表示顺序是受控的。 “我必须让用户重新排序一些列表”。但你是对的;有一张相当大的桌子就行不通了。有了 I 会让用户重新排序的东西。

以上是关于在sql数据库中使用实数进行显式排序的主要内容,如果未能解决你的问题,请参考以下文章

为了在 PL/SQL 中对这些数据进行排序,使用啥数据结构?

浅析SQL查询语句未显式指定排序方式,无法保证同样的查询每次排序结果都一致的原因

在 SQL 中对星期几进行排序

sql server中的事务是啥意思

显式指定mysql查询的排序顺序?

将列值显式设置为空 SQL Developer