如何使 postgres 函数重新排序表示链表的行?

How to make a postgres function reorder rows representing a linked list?

我有一个像这样的 table 表示链表。当 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"

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

关系 (table) 不是矩阵,没有开始,也没有结束。例如,当你想搜索最后一条记录时,你必须使用递归 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)