процедура mysql для обновления числовой ссылки в предыдущих строках при ее обновлении - PullRequest
6 голосов
/ 16 марта 2010

Вот такая таблица

 ______________________
| id  |  title | order |
|----------------------|
|  1  |  test1 |   1   |
|-----|--------|-------|
|  2  |  test2 |   2   |
|-----|--------|-------|
|  3  |  test3 |   3   |
|-----|--------|-------|
|  4  |  test4 |   4   |
'----------------------'

когда я добавляю в свою оболочку mysql одно обновление строки

 $sql> UPDATE `table` SET order=1 WHERE id=3; 

И затем процедура или метод столбец заказа повторных выборок в нижних значениях перед обновлением, чтобы обновить его порядок следующим образом

 ______________________
| id  |  title | order |
|----------------------|
|  1  |  test1 |   2   |
|-----|--------|-------|
|  2  |  test2 |   3   |
|-----|--------|-------|
|  3  |  test3 |   1   |
|-----|--------|-------|
|  4  |  test4 |   4   |
'----------------------'

Любая помощь будет оценена, спасибо!

Ответы [ 7 ]

4 голосов
/ 16 марта 2010

Я думаю, есть два случая:

  1. Переместить одну строку, чтобы она отображалась раньше в порядке.
  2. Переместить одну строку, чтобы она появилась позже в порядке.

Это в любом случае нетривиально. Не ясно, существует ли уникальное ограничение на столбец «порядок»; конечный результат, безусловно, должен иметь уникальный порядок.

Обозначения:

  • «Вкл» относится к строке со значением «порядок = n» в старых значениях
  • «Nn» относится к строке с «order = n» в новых значениях

В примере (иллюстративный пример 1):

  • O3 -> N1
  • O1 -> N2
  • O2 -> N3

В качестве альтернативы рассмотрим перемещение id = 2, чтобы он имел порядок = 4:

  • O2 -> N4
  • O3 -> N2
  • O4 -> N3

Вы в основном добавляете или вычитаете одну из «других» строк, где это строки в старом порядке между старой позицией перемещенной строки и новой позицией перемещенной строки. В псевдокоде, используя $ old и $ new для определения позиций перемещенной строки до и после, и обрабатывая случай 1 ($ old> $ new):

UPDATE AnonymousTable
   SET order = CASE
               WHEN order = $old THEN $new
               WHEN order >= $new AND order < $old THEN order + 1
               END CASE
 WHERE order BETWEEN $new AND $old;

Соответствующий код для случая 2 ($ old <$ new): </p>

UPDATE AnonymousTable
   SET order = CASE
               WHEN order = $old THEN $new
               WHEN order > $new AND order <= $old THEN order - 1
               END CASE
 WHERE order BETWEEN $old AND $new;

Учитывая предложение WHERE в ОБНОВЛЕНИИ в целом, вы можете удалить второе КОГДА в СЛУЧАЕ и заменить его простым ELSE.

UPDATE AnonymousTable
   SET order = CASE
               WHEN order = $old THEN $new
               ELSE                   order + 1
               END CASE
 WHERE order BETWEEN $new AND $old;

UPDATE AnonymousTable
   SET order = CASE
               WHEN order = $old THEN $new
               ELSE                   order - 1
               END CASE
 WHERE order BETWEEN $old AND $new;

Я думаю, что хранимая процедура в порядке - выбор между двумя операторами на основе входных параметров $ old, $ new. Вы могли бы сделать что-то с разумным сочетанием выражений, таких как '($old - $new) / ABS($old - $new)' и 'MIN($old, $new)' и 'MAX($old, $new)', где MIN / MAX не агрегаты, а функции компаратора для пары значений (как находится в Фортране, среди других языков программирования).

Обратите внимание, что я предполагаю, что во время выполнения одного оператора SQL ограничение уникальности (если оно есть) не применяется при изменении каждой строки - только после завершения оператора. Это необходимо, поскольку вы не можете контролировать порядок обработки строк. Я знаю о СУБД, где это может вызвать проблемы; Я знаю о других, где это не будет.


Все это можно сделать одним оператором SQL, но вы хотите, чтобы хранимая процедура сортировала параметры в операторе. Я использую IBM Informix Dynamic Server (11.50.FC6 в MacOS X 10.6.2), и это одна из СУБД, которая применяет уникальное ограничение для столбца 'order' в конце оператора. Я занимался разработкой SQL без ограничения UNIQUE; это тоже сработало, конечно. (И да, IDS позволяет вам откатывать операторы DDL, такие как CREATE TABLE и CREATE PROCEDURE. Что вы сказали? Ваша СУБД нет? Как странно!)

BEGIN WORK;
CREATE TABLE AnonymousTable
(
    id      INTEGER NOT NULL PRIMARY KEY,
    title   VARCHAR(10) NOT NULL,
    order   INTEGER NOT NULL UNIQUE
);
INSERT INTO AnonymousTable VALUES(1, 'test1', 1);
INSERT INTO AnonymousTable VALUES(2, 'test2', 2);
INSERT INTO AnonymousTable VALUES(3, 'test3', 3);
INSERT INTO AnonymousTable VALUES(4, 'test4', 4);

SELECT * FROM AnonymousTable ORDER BY order;

CREATE PROCEDURE move_old_to_new(old INTEGER, new INTEGER)
    DEFINE v_min, v_max, v_gap, v_inc INTEGER;
    IF old = new OR old IS NULL OR new IS NULL THEN
        RETURN;
    END IF;
    LET v_min = old;
    IF new < old THEN
        LET v_min = new;
    END IF;
    LET v_max = old;
    IF new > old THEN
        LET v_max = new;
    END IF;
    LET v_gap = v_max - v_min + 1;
    LET v_inc = (old - new) / (v_max - v_min);
    UPDATE AnonymousTable
       SET order = v_min + MOD(order - v_min + v_inc + v_gap, v_gap)
     WHERE order BETWEEN v_min AND v_max;
END PROCEDURE;

EXECUTE PROCEDURE move_old_to_new(3,1);
SELECT * FROM AnonymousTable ORDER BY order;
EXECUTE PROCEDURE move_old_to_new(1,3);
SELECT * FROM AnonymousTable ORDER BY order;

INSERT INTO AnonymousTable VALUES(5, 'test5', 5);
INSERT INTO AnonymousTable VALUES(6, 'test6', 6);
INSERT INTO AnonymousTable VALUES(7, 'test7', 7);
INSERT INTO AnonymousTable VALUES(8, 'test8', 8);

EXECUTE PROCEDURE move_old_to_new(3,6);
SELECT * FROM AnonymousTable ORDER BY order;
EXECUTE PROCEDURE move_old_to_new(6,3);
SELECT * FROM AnonymousTable ORDER BY order;
EXECUTE PROCEDURE move_old_to_new(7,2);
SELECT * FROM AnonymousTable ORDER BY order;
EXECUTE PROCEDURE move_old_to_new(2,7);
SELECT * FROM AnonymousTable ORDER BY order;

ROLLBACK WORK;

Пары вызовов хранимой процедуры с обращенными числами каждый раз восстанавливали первоначальный порядок. Ясно, что я мог бы переопределить переменную v_inc, чтобы вместо просто ± 1 это было «LET v_inc = v_inc - v_min + v_gap;», а затем выражение MOD было бы просто «MOD(order + v_inc, v_gap)». Я не проверял, работает ли это с отрицательными числами.

Адаптация к MySQL или другим СУБД оставлена ​​читателю в качестве упражнения.

2 голосов
/ 16 марта 2010

Другой подход заключается в использовании чисел с плавающей точкой вместо целых чисел для сортировки.В этой настройке вам нужно обновить только одну строку при изменении сортировки.Давайте начнем с этого:

id  order
 1     1
 2     2
 3     3
 4     4

Теперь вы хотите изменить порядок, чтобы элемент 2 появлялся между пунктами 3 и 4. Все, что вам нужно сделать, это обновить элемент 2, чтобы его новый orderзначение от 3 до 4, например 3,5:

id  order
 1     1
 2    3.5
 3     3
 4     4
0 голосов
/ 01 февраля 2016

Для всех, кто имеет ту же проблему, что и автор потока, но не использует IDS в качестве СУБД, как @Jonathan Leffler: Вот пастбина из этих процедур для MySQL http://pastebin.com/AxkJQmAH

0 голосов
/ 16 марта 2010

код псевдо:

CREATE TRIGGER reorder AFTER UPDATE ON `table` 
  FOR EACH ROW BEGIN
    UPDATE `table` SET order=order+1 WHERE id < 3 ORDER BY id DESC LIMIT 1; 
  END;
|

delimiter ;

Предыдущий идентификатор:

UPDATE `table` SET order=order+1 WHERE id < 3 ORDER BY id DESC LIMIT 1; 
0 голосов
/ 16 марта 2010

Полагаю, что вы пытаетесь достичь, лучше сделать не с помощью запроса к БД, а с помощью простой конструкции сортировки.

Вы должны сохранить свои записи БД как объекты в коллекции или структуре / массиве / списке какого-либо рода, это зависит от вашего языка.

Затем вы создаете простой алгоритм сортировки, сортируете все записи на основе порядка, изменяющего все порядки других строк, и создаете процедуру, которая автоматически обновляет все измененные строки после прохождения им той же collection .

0 голосов
/ 16 марта 2010

Предполагается, что вы передали идентификатор строки для изменения как change_row_id и новый порядок как new_order:

current_order = SELECT order from 'table' WHERE id=change_row_id;
if (current_order > new_order)
  UPDATE `table` SET order=order+1 WHERE (order > new_order AND 
    order < current_order);
else
  [you'll have to figure out how you want to handle this.  Do you want the 
   orders to all be sequential with no breaks?]
ENDYIF;

UPDATE 'table' SET order=new_order WHERE id=change_row_id;

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

0 голосов
/ 16 марта 2010

Возможно сделать 2 оператора обновления:

UPDATE `table` SET ord=ord+1 WHERE ord >= 1 order by ord desc;
UPDATE `table` SET ord=1 WHERE id=3;

(вероятно, вы хотите сгруппировать эти две операции в одну транзакцию вместо использования автоматической фиксации)

РЕДАКТИРОВАТЬ: добавьте "order by" к первому обновлению, чтобы управлять порядком обновления, избегая проблемы "дубликата ключа" (также избегая ключевого слова "order" в именах столбцов: -)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...