【问题标题】:How to represent and insert into an ordered list in SQL?如何在 SQL 中表示并插入有序列表?
【发布时间】:2011-02-25 19:07:15
【问题描述】:

我想在 SQL 表中表示列表“hi”、“hello”、“goodbye”、“good day”、“howdy”(按此顺序):

pk | i | val
------------
1  | 0 | hi
0  | 2 | hello
2  | 3 | goodbye
3  | 4 | good day
5  | 6 | howdy

'pk' 是主键列。忽略它的价值。

'i' 是定义 'val' 列中值的顺序的“索引”。它用于建立顺序,其他值不重要。

我遇到的问题是在保持顺序的同时将值插入列表。例如,如果我想插入“hey”并且我希望它出现在“hello”和“goodbye”之间,那么我必须改变“goodbye”和“good”的“i”值day”(但最好不要“howdy”)为新条目腾出空间。

那么,是否有标准的 SQL 模式来执行移位操作,但只移位必要的元素? (请注意,简单的“UPDATE table SET i=i+1 WHERE i>=3”不起作用,因为它违反了 'i' 的唯一性约束,而且它还不必要地更新了“howdy”行。)

或者,有没有更好的方法来表示有序列表?我想您可以将“i”设为浮点值并在它们之间选择值,但是当不存在此类值时,您必须进行单独的重新平衡操作。

或者,如果我要让 'i' 成为 varchar,是否有一些标准算法可以在任意其他字符串之间生成字符串值?

或者我应该将它表示为一个链接列表?我避免这样做是因为我还希望能够执行 SELECT .. ORDER BY 以按顺序排列所有元素。

【问题讨论】:

  • 顺便说一句:您的评论 "UPDATE table SET i=i+1 WHERE i>=3" 不起作用,因为它绝对违反了 'i' 的唯一性约束在 SQL Server 中不成立。最后检查约束。你用的是什么关系型数据库?

标签: sql sqlite


【解决方案1】:

如果你不使用数字,而是使用字符串,你可能有一个表格:

pk | i | val
------------
1  | a0 | hi
0  | a2 | hello
2  | a3 | goodbye
3  | b  | good day
5  | b1 | howdy

您可以在 a3 和 b 之间插入 a4,在 a2 和 a3 之间插入 a21,在 a0 和 a2 之间插入 a1,依此类推。您需要一个聪明的函数,为 p 和 n 之间的新值 v 生成 i,并且索引可能会变得越来越长,或者您需要不时进行大的重新平衡。

另一种方法可能是,在表中实现(双)链表,您不保存索引,但链接到上一个和下一个,这意味着您通常必须更新 1-2元素:

pk | prev | val
------------
1  |   0  | hi
0  |   1  | hello
2  |   0  | goodbye
3  |   2  | good day
5  |   3  | howdy

你好和再见之间:

嘿,得到 pk 6,

pk | prev | val
------------
1  |   0  | hi
0  |   1  | hello 
6  |   0  | hi <- ins
2  |   6  | goodbye <- upd
3  |   2  | good day
5  |   3  | howdy

前一个元素是hello,pk=0,而goodbye,现在链接到hello,将来必须链接到hey

但我不知道,是否有可能为许多数据库实现找到“排序依据”机制。

【讨论】:

  • 使用字符串是可怕的(但相当聪明)。它甚至可能很快;在您的 i 列上使用索引,有序查询应该很便宜,并且由于数据局部性,可能比链表方法更有优势。
【解决方案2】:

当我阅读您的帖子时,我一直在想“链表” 最后,我仍然认为这是要走的路。

如果您使用的是 Oracle,并且链表是一个单独的表(甚至是具有自引用 id 的同一个表 - 我会避免),那么您可以使用 CONNECT BY 查询和伪列 LEVEL 来确定排序顺序。

【讨论】:

  • btw - 这让我想起了过去的美好时光,当你需要重新编号你的 BASIC 程序时……这通常是作为一个单独的命令/努力完成的,只要你之间的数字用完了(你会以防万一,基本上从 10 开始计数...)
  • 只要您可以使用您的应用程序来计算排序顺序,一个简单的 NextNode 列(为 NULL 或指向下一个条目)将为您提供所需的内容。您将无法利用 ORDER BY,但数据就在那里。 INSERTS 就像修改行的 NextNode 列一样简单,该列曾经指向您想要在新条目之后出现的行。
【解决方案3】:

您可以通过使用级联触发器轻松实现此目的,该触发器将插入/更新操作中等于新条目的任何“索引”条目更新为索引值 +1。这将级联所有行,直到第一个间隙停止级联 - 有关 PostgreSQL 实现,请参阅this blog entry 中的第二个示例。

这种方法应该独立于所使用的 RDBMS,只要它支持触发器在更新/插入之前触发。如果你在代码中实现了你想要的行为,它基本上会做你会做的事情(增加所有后续索引值直到你遇到间隙),但是以更简单和更有效的方式。

或者,如果您可以忍受对 SQL Server 的限制,请查看hierarchyid type。虽然主要用于定义嵌套层次结构,但您也可以将其用于平面排序。它有点类似于您使用浮点数的方法,因为它允许通过分配小数值在两个位置之间插入,从而避免更新其他条目的需要。

【讨论】:

  • 我使用的是 SQLite,它显然不支持级联/递归触发器(尽管它支持 'before' 触发器)。
  • @Travis:似乎他们从 3.6.18 版本开始增加了对递归触发器的支持,但您必须明确打开它:sqlite.org/pragma.html#pragma_recursive_triggers - 我不确定是否“级联” ' 在你的情况下算作递归,因为它不应该影响插入/更新的行本身,但总是不同的行,所以无论如何它可能值得一试。
  • 谢谢,没看到。但是,递归深度有一个编译上限(默认为 1000),我不知道它是否可以处理非常高的值,这对我来说可能是个问题。我最终决定做的是删除对“i”的唯一性约束,现在只使用“i=i+1 WHERE i>=new_i”方法。如果不必要地更新额外的 i 值实际上会成为性能问题,我会重新讨论这个问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-11-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-09-12
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多