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

时间:2014-01-08 16:18:09

标签: sql algorithm sorting

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

我能想到的第一个直截了当的方法是有一个“位置”列,其中排序保存为整数。体育课。

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

4 个答案:

答案 0 :(得分:2)

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

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

确定之后,您可以使用CTE(使用UNION ALL)创建一个永远不会有限制的排序列表。

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

答案 1 :(得分:0)

链接列表的想法很简洁,但按顺序提取数据却很昂贵。如果您有一个支持它的数据库,您可以使用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之间添加一些东西,你需要引入一个子小节并获得

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数据库的任何无限精度十进制类型。但是你当然可以将你的号码保存为字符串,在这种情况下,这个解决方案变得更像你原来的那个。

答案 2 :(得分:0)

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

item  order
A     ffga
B     ffgaa
C     ffgb

这里,有限精度的问题是通过增长弦的可能性来处理的。理论上,字符串存储在数据库中是无限的,只有存储设备的大小。但绝对订购项目没有更好的解决方案。相对排序(如链接列表)可能效果更好(但您无法通过查询进行排序)。

答案 3 :(得分:-1)

将所有行设置为从1开始的唯一编号,并在开始时递增1。插入新行时,将其设置为表的计数(*)+ 1(有多种方法可以执行此操作)。

当用户更新行的顺序时,请始终通过调用存储过程来更新它,并使用此行的Id(PK)进行更新和新订单。在存储过程中,

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

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

这保证了总是有空间和连续的序列,没有重复。如果你把一行愚蠢的新订单号(例如&lt; = 0)但可能是坏事,我没有按你的方式工作。那是为了防止前端应用程序。

干杯 -