使用数据库时,通常需要按顺序访问记录。例如,如果我有一个博客,我希望能够以任意顺序重新排列我的博客文章。这些条目通常具有很多关系,因此关系数据库似乎很有意义。

常见的解决方案是添加一个整数列order

CREATE TABLE AS your_table (id, title, sort_order)
AS VALUES
  (0, 'Lorem ipsum',   3),
  (1, 'Dolor sit',     2),
  (2, 'Amet, consect', 0),
  (3, 'Elit fusce',    1);


然后,我们可以按order对行进行排序,以使其按正确的顺序排列。

但是,这看起来很笨拙:


如果要将记录0移到开头,则必须对每个记录重新排序
如果要在中间插入新记录,则必须对每个记录重新排序
如果要删除一条记录,我必须重新排序每条记录

很容易想到这样的情况:


两个记录具有相同的order
< br记录之间在order中存在间隙

由于多种原因,这些间隙很容易发生。

Joomla等应用程序采用的方法是:
/>


您可能会争辩说这里的界面不好,不是人类直接编辑数字,则应使用箭头或拖放操作-您可能是对的。但是在幕后,发生了同样的事情。

有人建议使用小数存储顺序,以便您可以使用“ 2.5”将记录插入顺序2和顺序之间的记录中。 3.虽然有一点帮助,但可以说它甚至更混乱,因为您可能会得到奇怪的小数点(在哪里停止?2.75?2.875?2.8125?)

是否有更好的存储订单的方法在桌子上?

评论

请注意。 。 。 “这种系统之所以被称为“关系式”,是因为术语“关系”基本上只是表的数学术语。 -数据库系统简介,CJ Date,第7版。第25页

用于管理订购列表的功能和模式的可能副本

我没听懂的@ MikeSherrill'CatRecall',我已经用旧订单和ddl解决了这个问题。

#1 楼


如果要将记录0移到开头,则必须对每个记录重新排序

不,有一种更简单的方法。
update your_table
set order = -1 
where id = 0;


如果我想在中间插入一条新记录,我必须对它之后的每条记录重新排序。

是的,除非您使用支持“介于”值之间的数据类型。浮点数和数字类型使您可以将值更新为2.5。但是varchar(n)也可以。 (考虑“ a”,“ b”,“ c”;然后考虑“ ba”,“ bb”,“ bc”。)

如果要删除记录,则必须对每个记录重新排序记录之后

不,有一种更简单的方法。只需删除该行。其余行仍将正确排序。

很容易想到这样的情况:
两个记录具有相同的顺序

唯一的约束可以防止这种情况。

记录之间的顺序存在差距

空白对dbms如何对列中的值进行排序没有影响。

有人建议使用一个小数来存储顺序,以便您可以使用“ 2.5”在第2个和第3个顺序的记录之间插入一条记录。虽然这样做有一点帮助,但它甚至可能更混乱,因为您可能会得到奇怪的小数(在哪里可以你停了吗?2.75?2.875?2.8125?)

你必须停止才停止。 dbms可以毫无问题地对小数点后第2、7或15位的值进行排序。您可以做到。
create table your_table (
  id int primary key, 
  title varchar(13), 
  sort_order float
);
    
insert into your_table values
(0, 'Lorem ipsum', 2.0),
(1, 'Dolor sit', 1.5),
(2, 'Amet, consect', 0.0),
(3, 'Elit fusce', 1.0);

-- This windowing function will "transform" the floats into sorted integers.
select id, title,
       row_number() over (order by sort_order)
from your_table


评论


为了简洁起见,您可以使用cte之类的方法完成此工作(在测试中,选择*,row_number()超过(按sort_order desc排序)作为测试行)

– Manngo
17年3月28日在11:23

这是一个附加提示:如果您希望它真的很完美,则应检查是否要移动更多行,然后希望保持不变。如果是这样,则更新数量较少的“未触动”的那些; D

– Ruben Boeck
18年5月12日11:00



#2 楼

这很简单。您需要具有“基数孔”结构:

您需要具有2列:


pk = 32位integer

order = 64位bigint(不是double

插入/更新


插入第一个新记录时,设置order = round(max_bigint / 2)
在开始插入时在表的末尾,设置order = round("order of first record" / 2)

在表尾插入时,设置order = round("max_bigint - order of last record" / 2)
4)在中间插入时,设置order = round("order of record before - order of record after" / 2)


此方法具有很大的基数。如果遇到约束错误,或者如果您认为基数很小,则可以重建订单列(规范化)。

在具有规范化的最大情况下(采用这种结构),您可以在32中设置“基数漏洞”位。

请记住不要使用浮点类型-顺序必须是精确值!

#3 楼

通常,根据记录,标题,ID或适用于该特定情况的任何信息来进行排序。

如果您确实需要特殊的排序,那么使用整数列并不像看起来那样糟糕。例如,要为记录排在第5位留出空间,您可以执行以下操作:

update table_1 set place = place + 1 where place > 5

希望您可以将该列声明为unique,也许具有使“原子”重排的程序。具体细节取决于系统,但这是基本概念。

#4 楼


…甚至可以说是更加混乱,因为您最终可能会得到奇怪的小数点(在哪里停止?2.75?2.875?2.8125?)


谁在乎?这些数字仅由计算机处理,因此它们有多少小数位或我们看上去多么难看都无关紧要。

使用十进制值意味着要在项目J和K之间移动项目F,您需要做的就是选择J和K的顺序值,然后取它们的平均值,然后更新F。两个SELECT语句和一个UPDATE语句(可能使用可序列化隔离来完成以避免死锁)。

如果要在输出中查看整数而不是分数,则可以在客户端应用程序中计算整数或使用ROW_NUMBER()或RANK()函数(如果您的RDBMS包含它们)。

#5 楼

在我自己的项目中,我打算尝试一个类似于十进制数的解决方案,但改用字节数组:

def pad(x, x_len, length):
    if x_len >= length:
        return x
    else:
        for _ in range(length - x_len):
            x += b"\x00"
        return x

def order_index(_from, _to, count, length=None):
    assert _from != _to
    assert _from < _to

    if not length:
        from_len = len(_from)
        to_len = len(_to)
        length = max(from_len, to_len)

        _from = pad(_from, from_len, length)
        _to = pad(_to, to_len, length)

    from_int = int.from_bytes(_from, "big")
    to_int = int.from_bytes(_to, "big")
    inc = (to_int - from_int)//(count + 1)
    if not inc:
        length += 1
        _from += b"\x00"
        _to += b"\x00"
        return order_index(_from, _to, count, length)

    return (int.to_bytes(from_int + ((x+1)*inc), length, "big") for x in range(count))


>>> index = order_index(b"A", b"Z", 24)
>>> [x for x in index]
[b'B', b'C', b'D', b'E', b'F', b'G', b'H', b'I', b'J', b'K', b'L', b'M', b'N', b'O', b'P', b'Q', b'R', b'S', b'T', b'U', b'V', b'W', b'X', b'Y']
>>> 
>>> index = order_index(b"A", b"Z", 25)
>>> [x for x in index]
[b'A\xf6', b'B\xec', b'C\xe2', b'D\xd8', b'E\xce', b'F\xc4', b'G\xba', b'H\xb0', b'I\xa6', b'J\x9c', b'K\x92', b'L\x88', b'M~', b'Nt', b'Oj', b'P`', b'QV', b'RL', b'SB', b'T8', b'U.', b'V$', b'W\x1a', b'X\x10', b'Y\x06']


这个想法是,您永远不会耗尽中间的值,因为如果需要更多的值,您只需在相关记录中附加一个b"\x00"。 (int在Python 3中是不受限制的,否则,您必须在末尾选择一个字节的切片进行比较,假设是在两个相邻值之间,差异将被压缩到末尾。)

例如,假设您有两个记录b"\x00"b"\x01",并且您希望在它们之间有一条记录。在0x000x01之间没有任何可用的值,因此您将b"\x00"都附加到这两个值之间,现在您可以在它们之间添加一堆值,可以用来插入新值。
该数据库可以轻松地对其进行排序,因为所有内容最终都按字典顺序排列。如果您删除一条记录,它仍然是有序的。但是,在我的项目中,我将b"\x00"b"\xff"分别作为FIRSTLAST记录,以便将它们用作虚拟的“ from”和“ to”值来添加/添加新记录:

>>> records = [b"\x00", b"\x01", b"\x02"]
>>> values = [x for x in order_index(records[0], records[1], 3)]
>>> records = records + values
>>> records.sort()
>>> records
[b'\x00', b'\x00@', b'\x00\x80', b'\x00\xc0', b'\x01', b'\x02']


#6 楼

我发现这个答案要好得多。完全引用它:


数据库针对某些事物进行了优化。快速更新很多行是其中之一。当您让数据库完成其工作时,这一点尤其成立。

考虑:您将有两个查询:



 Beat It 


它。这可以很好地按比例放大。尝试将几千首歌曲放入数据库的假设播放列表中,查看将歌曲从一个位置移动到另一个位置需要多长时间。由于它们具有非常标准化的形式:

 update table 
  set order = order - 1
  where order >= 2 and order <= 5;

update table
  set order = 5
  where song = 'Beat It'
 


您有两个准备好的语句可以重复使用非常有效。

这提供了一些显着的优点-您可以推断表格的顺序。第三首歌曲的update table set order = order - 1 where order >= ? and order <= ?; update table set order = ? where song = ? 始终为3。保证这一点的唯一方法是使用连续整数作为顺序。使用伪链接列表,十进制数字或带空格的整数不能保证您具有此属性。在这种情况下,获取第n首歌曲的唯一方法是对整个表进行排序并获取第n条记录。

实际上,这比您想象的要容易得多。很容易弄清楚您想做什么,生成两个更新语句,然后让其他人查看这两个更新语句并了解正在执行的操作。