在表中任意排序记录


28

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

我见过的常见解决方案是添加一个整数列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
  • order记录之间存在差距

这些可能很容易发生,原因有很多。

这是Joomla之类的应用程序采用的方法:

Joomla订购方法的示例

您可能会争辩说这里的界面很糟糕,他们应该使用箭头或拖放操作来代替人类直接编辑数字,而您可能是正确的。但是在幕后,发生了同样的事情。

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

有没有更好的方法将订单存储在表中?


5
请注意。。。“之所以这样的系统被称为‘关系’是该术语的关系基本上是一个数学术语,表示。” - 数据库系统简介,CJ Date,第7版。第25页
Mike Sherrill'猫召回'


@ MikeSherrill'CatRecall'我没听懂,我已经用旧的orders和ddl 解决了这个问题。
埃文·卡罗尔

Answers:


17

如果我想将记录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?)

你不停止,直到你来。数据库管理系统有没有问题排序是有小数点后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

为了整洁起见,你可以用像完成任务with cte as (select *,row_number() over (order by sort_order desc) as row from test) update cte set sort_order=row;
Manngo

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

7

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

您需要有2列:

  1. pk = 32位 integer
  2. 顺序= 64bit bigint不是 double

插入/更新

  1. 插入第一个新记录时,设置order = round(max_bigint / 2)
  2. 在表格的开头插入时,请设置 order = round("order of first record" / 2)
  3. 在表格的末尾插入时,设置order = round("max_bigint - order of last record" / 2) 4)在中间插入时,设置order = round("order of record before - order of record after" / 2)

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

在具有标准化的最大情况下(使用此结构),您可以使用32位的“基数孔”。

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


4

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

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

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包含它们)。


1

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

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"如果需要更多的值,只需将a附加到所涉及的记录中即可。(int在Python 3中是无边界的,否则,您必须在末尾选择一个字节的片段进行比较,假设是在两个相邻值之间,差异将被压缩到末尾。)

例如,假设您有两条记录,b"\x00"b"\x01",并且您希望一条记录在它们之间。0x00和之间没有任何可用的值0x01,因此您可以将b"\x00"它们附加到两者之间,现在它们之间有一堆值可用于插入新值。

>>> 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']

数据库可以轻松对它进行排序,因为所有内容都按字典顺序排列。如果您删除一条记录,它仍然是有序的。但是,在我的项目中,我制作了as b"\x00"b"\xff"as FIRSTLASTrecords,以便将它们用作虚拟的“ from”和“ to”值来添加/添加新记录:

>>> records = []
>>> value = next(order_index(FIRST, LAST, 1))
>>> value
b'\x7f'
>>> records.append(value)
>>> value = next(order_index(records[0], LAST, 1))
>>> value
b'\xbf'
>>> records.append(value)
>>> records.sort()
>>> records
[b'\x7f', b'\xbf']
>>> value = next(order_index(FIRST, records[0], 1))
>>> value
b'?'
>>> records.append(value)
>>> records.sort()
>>> records
[b'?', b'\x7f', b'\xbf']

0

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

数据库针对某些事物进行了优化。快速更新许多行是其中之一。当您让数据库完成其工作时,情况尤其如此。

考虑:

order song
1     Happy Birthday
2     Beat It
3     Never Gonna Give You Up
4     Safety Dance
5     Imperial March

而您想移到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 = ?

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

这提供了一些显着的优势-您可以推断出表格的顺序。第三首歌曲order总是带有3。保证这一点的唯一方法是使用连续整数作为顺序。使用伪链接列表,十进制数字或带空格的整数将不能保证此属性。在这些情况下,获取第n首歌曲的唯一方法是对整个表进行排序并获取第n条记录。

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.