Как заставить функцию postgres переупорядочивать строки, представляющие связанный список? - PullRequest
1 голос
/ 16 апреля 2020

У меня есть такая таблица, которая представляет связанный список. Когда столбец comes_after равен null, это означает, что это первая запись в связанном списке.

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

Как мне написать функцию, используя SQL или PLPG SQL для переупорядочения строк? Функция function move_id_after (id_to_move string, after_id string) имеет 2 аргумента: id_to_move - идентификатор для перемещения на новую позицию и after_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"

1 Ответ

2 голосов
/ 16 апреля 2020

Если вы хотите указать заказ, то вам нужно использовать предложение ORDER BY. Любое другое решение не должно работать. Ваш дизайн не практичен с большими данными. В вашем проекте вы должны каждый раз вычислять какое-то значение для заказа, и этот расчет должен основываться на рекурсивном вызове - это хороший дизайн для графовых баз данных и плохой для реляционных.

Отношение (таблица) не является матрицей, нет начала, нет конца. Например, когда вы хотите найти последнюю запись, вы должны использовать рекурсивный 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;

Я не знаю, какова ваша цель, но база данных отношений - плохой инструмент для этого.

Это может быть интересное школьное задание, но оно может быть ужасным в реальной жизни. Это идет вразрез с принципами реляционных баз данных.

Более обычным решением является использование специального столбца цифр c, который можно использовать для предложения 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)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...