【问题标题】:How to make a postgres function reorder rows representing a linked list?如何使 postgres 函数重新排序表示链表的行?
【发布时间】:2020-07-29 23:14:04
【问题描述】:

我有一个这样的表,它代表一个链表。当comes_after 列是null 时,表示它是链表中的第一条记录。

id      | comes_after
--------+------------
"one"   | null
"two"   | "one"
"three" | "two"
"four"  | "three"

如何使用 SQL 或 PLPGSQL 编写函数来重新排序行?函数 function move_id_after (id_to_move string, after_id string) 有 2 个参数,id_to_move 是要移动到新位置的 id,after_id 是要移动该行的 id。如果after_id 为空,则表示将其移至列表的开头。

这是我的尝试,但它不起作用,而且似乎不是理想的方法。如示例案例所示,我还希望能够将一行移动到列表的开头或末尾,并处理不需要更改的情况。

create function move_id_after (id_to_move string, after_id string) language plpgsql as $$
declare
  AFTER_id_to_move string;
  AFTER_after_id string;
  id_to_move_used_to_follow string;
begin
  select id from mytable where comes_after = id_to_move into AFTER_id_to_move;
  select id from mytable where comes_after = after_id into AFTER_after_id;
  update mytable set comes_after = id_to_move where id = AFTER_after_id;
  update mytable set comes_after = AFTER_after_id where id = id_to_move returning id into id_to_move_used_to_follow;
  update mytable set comes_after = id_to_move_used_to_follow where id = id_to_move_after;
end $$;

下面是一些结果应该如何的例子。

将记录移动到另一个位置

select move_id_after("two", "three") 应该变成:

id      | comes_after
--------+------------
"one"   | null
"three" | "one"
"two"   | "three"
"four"  | "two"

将记录移动到它已经在的位置

select move_id_after("three", "two") 应该没有变化:

id      | comes_after
--------+------------
"one"   | null
"two"   | "one"
"three" | "two"
"four"  | "three"

将第一条记录移到最后一个位置

select move_id_after("one", "four") 应该变成:

id      | comes_after
--------+------------
"two"   | null
"three" | "two"
"four"  | "three"
"one"   | "four"

将最后一条记录移动到第一个位置

select move_id_after("four", null) 应该变成:

id      | comes_after
--------+------------
"four"  | null
"one"   | "four"
"two"   | "one"
"three" | "two"

【问题讨论】:

  • 如果记录的状态依赖于其他记录,这通常是设计不良的标志。 [即使这个问题是关于tabbing order,也不清楚]

标签: sql postgresql linked-list plpgsql sql-function


【解决方案1】:

如果要指定顺序,则必须使用 ORDER BY 子句。任何其他解决方案都不应该起作用。您的设计不适用于更大的数据。在您的设计中,您每次都必须为 order 计算一些值,并且该计算应该基于递归调用——这对于图形数据库来说是好的设计,而对于关系数据库来说是不好的。

关系(表)不是矩阵,没有开始,也没有结束。例如,当你想搜索最后一条记录时,你必须使用递归 CTE

 -- searching last record in list
with recursive x as (select 0 l, id 
                       from mytable 
                      where comes_after is null 
                     union all 
                     select l + 1, mytable.id 
                       from x join mytable on x.id = mytable.comes_after) 
  select id 
    from x 
   order by l desc 
    limit 1;

我不知道你的目标是什么,但关系数据库是一个不好的工具。

这可能是有趣的学校任务,但在现实生活中可能很糟糕。它违反了关系数据库原则。

更常用的解决方案是使用可用于 ORDER BY 子句的特殊数字列。有些人喜欢

CREATE SEQUENCE test_o START WITH 1;

CREATE TABLE test(id SERIAL, v varchar, o numeric DEFAULT nextval('test_o'));

-- insert at end
INSERT INTO test(v) VALUES('ahoj');
INSERT INTO test(v) VALUES('nazdar');
INSERT INTO test(v) VALUES('bazar');

-- sort data by o
SELECT * FROM test ORDER BY o;
INSERT INTO test(v, 

SELECT * FROM test ORDER BY o;
┌────┬────────┬───┐
│ id │   v    │ o │
╞════╪════════╪═══╡
│  1 │ ahoj   │ 1 │
│  2 │ nazdar │ 2 │
│  3 │ bazar  │ 3 │
└────┴────────┴───┘

id=2之后插入:

INSERT INTO test(v, o)
  SELECT 'HELLO', 
         (SELECT (o +  lead(o,1) OVER (ORDER BY o))/2 
            FROM test 
           WHERE o >= (SELECT o 
                         FROM test 
                        WHERE id = 2) 
           ORDER BY o 
           LIMIT 1);

postgres=# SELECT * FROM test ORDER BY o;
┌────┬──────────┬────────────────────┐
│ id │    v     │         o          │
╞════╪══════════╪════════════════════╡
│  1 │ ahoj     │                  1 │
│  2 │ nazdar   │                  2 │
│  6 │ HELLO    │ 2.5000000000000000 │
│  3 │ bazar    │                  3 │
└────┴──────────┴────────────────────┘
(4 rows)

【讨论】:

  • 对于需要让用户重新排序列表的用例,链接列表是softwareengineering.stackexchange.com/questions/195308/… 中讨论的几个可行选项之一。对于订购,我会在应用程序而不是数据库中进行订购,因为它适用于我的用例,就记录数而言(尽管递归 CTE 也是该帖子中描述的一个选项)。问题是我无法使用我发布的 postgres 函数来处理我描述的案例,所以希望有人能帮助找出我哪里出错了。
  • @user779159 - 为此目的,postgres 没有什么特别的功能。干净的 SQL 解决方案基于一些行号、可用于排序的字符串,以及您每次在其中生成新数字、某些两个值之间的新值。
  • 有趣,您能否为我的问题创建一个带有该解决方案的答案?如果它实现了相同的目标,那么即使它是一种不同的方式,它也将是公认的答案。
  • 谢谢!关于这个解决方案的一些问题。文档说numeric 类型的存储大小是可变的。有没有办法可以计算给定数字占用多少存储空间,例如“2.500”或“123.456789”,因为大量重新排序可能会有很多小数位?而不是在插入时使用nextval,我可以得到o列的max并将其加1吗?
  • @user779159 - 您可以使用函数pg_column_size,您的值是 4、6 和 11 个字节。 nextval 在更高的桌子上比 max 快得多,但您更喜欢什么取决于更多因素。
猜你喜欢
  • 2018-12-06
  • 1970-01-01
  • 2022-01-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-08-17
  • 2023-03-06
相关资源
最近更新 更多