【问题标题】:SQL preserve orderSQL 保留顺序
【发布时间】:2015-04-28 10:01:20
【问题描述】:

我有一个 SQL 表 (PostgreSQL 9.3),它本质上是一个 ID 列和一个值列(例如 varchar)。我需要能够重现输入行的顺序。最天真的方法是要么依赖 ID 列的 AUTOINCREMENT 功能,要么有一个特定的列进行排序,比如说

CREATE TABLE "foo" (
    "foo_id" SERIAL,
    "order_index" int NOT NULL,
    "value" VARCHAR(128)
);

数据可能看起来像

347 1 'foo'
368 2 'bar'
511 3 'baz'

我可以使用 ORDER BY "order_index" 子句检索它们。

问题是必须有可能稍后在特定位置插入另一行,例如当一行被意外遗漏时。在上述解决方案中,我必须重新分配插入位置之后的所有 order_index 值。这不能很好地扩展,有点与优雅相反。

一种解决方案是在初始分配中留下空白,因此上面的示例看起来像

347 1000 'foo'
368 2000 'bar'
511 3000 'baz'

可以插入行直到填补空白。只有这样才能进行重新分配。这是我迄今为止最好的主意。我还考虑了一种基于链表思想的解决方案,其中每行都有一个列“successor_id”。但是,在这种情况下,我无法使用简单的 ORDER BY 子句以有序的方式检索数据。

我一直想知道是否有一个可扩展且优雅的解决方案可能涉及不需要重新分配订单索引的两列。或者是否有某种最佳实践来处理这个问题?

【问题讨论】:

  • 是什么让你想在第 1 行和第 2 行之间插入一行?我的意思是你怎么知道必须在那里放置一个新行?您的数据中是否有任何迹象表明这一点?
  • 我刚刚在“考虑我”的评论中描述了它背后的现实问题。希望可以参考不同的评论而不是复制/粘贴它。

标签: sql postgresql sql-order-by


【解决方案1】:

用户输入(工作表的)数据。当他们查找数据时,他们可能希望在其他数据行之间插入另一个数据行。

所以他们总是在整张桌子的一小部分上工作。那么对记录重新编号就不是那么大的任务了。假设您有一个工作表编号,指示他们正在处理的工作表以及每个工作表编号的订单索引。那么 dbms 处理这个就不是什么大任务了:

update sheets set order_index = order_index + 1 
where sheet_no = 1234 and order_index >= 23;

insert into sheets (order_index, ...) values (23, new data);

commit;

我什至会删除行并重新插入它们。

delete from sheets where sheet_no = 1234;

insert into sheets ...
...

commit;

【讨论】:

  • 你是对的。从性能的角度来看,这应该没什么大不了的。我曾希望在不触及其他未修改的行的情况下做到这一点。但是,最终您的建议的简单性可能会胜出。如果增益纯粹是理论上的,那么实现非常复杂的东西是没有意义的。
  • 是的,我认为您可以将输入的数据作为一个整体来查看。如果用户决定更改此数据集,只需删除旧数据集并在循环中插入新数据(order_index 是循环计数器)。我什至认为这个解决方案很优雅:-)
【解决方案2】:

您的“差距”想法的变体:使用二级排序列。

自行增加您的订单列(不要使用 AUTOINCREMENT),但将其视为自动增加列,仅增加 +1(而不是您建议的间隔)。然后,只需添加默认为 0 的第二列。对这两列进行排序。

当您遇到插入缺失行的情况时,将其插入与前一行匹配的主 ID 列,并在辅助排序列中插入“1”(或 2,或 3)。

不过,它会破坏您 ID 列的唯一性。

【讨论】:

  • 这种方法的问题是它只能工作一次。如果我想在具有排序列 (1, 0) 和 (2, 0) 的列之间插入一行,我可以使用 (1, 1) 甚至使用 (1, 1)、(1, 2) 插入多行, ... .但是,当我再次回来并想要插入例如在 (1, 0) 和 (1, 1) 之间,我在一个排序列上遇到了与以前相同的问题。我必须使用与我想要支持的修订数量一样多的排序列。
  • 然后你可以改用numeric 类型。然后你有了浮点数就可以了。
【解决方案3】:

我有点担心您将行重新插入到已分配的序列号。更好的方法是使用timestamp 列来识别插入行的时间。您还可以创建一个history table 并在table 上添加触发器,以记录一行(insert, delete, update) 中的每个更改。这样,您可以在必要时重新创建行

解决方案:

添加一个列add_date timestamp NOT NULL DEFAULT NOW(),这应该是最优雅的(我相信)。

id = 368的删除和插入操作之前

347 'foo' 2015-04-26 12:16:00
368 'bar' 2015-04-27 13:55:23
511 'baz' 2015-04-28 10:01:00

用相同的序号再次插入后

347 'foo' 2015-04-26 12:16:00
368 'bar' 2015-04-28 12:17:11
511 'baz' 2015-04-28 10:01:00

【讨论】:

  • 嗯,也许我用“输入行的顺序”的措辞具有误导性。我应该说我需要维护一个自定义顺序(它最初恰好与输入行的顺序一致)。我的问题是我需要保持上面第 1、2、3 列的顺序,但仍然能够在第 2 行和第 3 行之间插入一行。我看不出时间戳在这里有什么帮助。跨度>
  • @PowerGnom 所以你特别坚持有一个像 1,2,3 这样的序列并且能够在它们之间插入。请问为什么将这些信息明确地保存在表格中很重要?索引使用双向链表来维护它。
  • 我不一定坚持像 1、2、3 这样的顺序。问题如下:用户在一张纸上输入他们拥有的数据。出于 QA 的原因,他们希望以与输入数据相同的顺序(即与原始工作表上的顺序相同)在屏幕上查看结果表。在所述 QA 期间,他们可能会识别缺失的行,例如因为他们在输入数据时不小心跳过了该行。现在他们需要在正确的位置插入缺失的行。我现在正试图弄清楚如何在数据库中维护该顺序。
  • 我仍然会说timestamp 列解决了这个问题。然后,您可以选择并使用窗口函数在插入行时根据时间戳为您提供数字顺序。您能否针对您所描述的情况制作一个 SQL 小提琴?
  • 抱歉,我不确定你要我做什么。 “针对您所描述的情况的 SQL 小提琴”是什么意思?
【解决方案4】:

order_index 设为浮点数,或者更好的是numeric

ALTER TABLE foo ALTER COLUMN index_order TYPE NUMERIC(26, 16);

选择最适合您需要的 precisionscale 值。

【讨论】:

  • 是的,这是可能的。它本质上是差距想法的一种变体。根据精度,它允许您在必须重新调整订单索引之前进行一定数量的修订。
  • 好吧,你描述为“意外跳过”的 16 位数字应该绰绰有余,因为你可以存储的最大整数值有 10 位数字(实际上是你的表,给定foo_id的当前定义)。如果没有,您可以随时调整它。
  • 你是对的。您将插入周围索引的平均值,以便在两侧具有相等的空间。如果您从相邻的整数索引开始,那么即使在最坏的情况下,您也可以在差值降至 1e-16 以下之前进行超过 50 次插入轮次。我同意这应该绰绰有余,但这个数字并没有大到你不需要考虑它。
猜你喜欢
  • 1970-01-01
  • 2022-08-08
  • 1970-01-01
  • 2017-06-18
  • 2012-05-31
  • 2014-01-21
  • 2014-12-01
  • 2011-09-06
相关资源
最近更新 更多