Mysql
 sql >> база данни >  >> RDS >> Mysql

mysql процедура за актуализиране на цифровата препратка в предишните редове, когато една се актуализира

Според мен има два случая, които трябва да се разгледат:

  1. Преместете един ред, така че да се появи по-рано в подреждането.
  2. Преместете един ред, така че да се появи по-късно в подреждането.

Така или иначе е нетривиално. Не е ясно дали има уникално ограничение за колоната „поръчка“; крайният резултат със сигурност трябва да има уникална подредба.

Забележки:

  • „Включено“ се отнася до реда със стойност „order =n“ в старите стойности
  • „Nn“ се отнася до реда с „order =n“ в новите стойности

В примера (илюстриращ случай 1):

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

Като алтернатива помислете за преместване на идентификатор =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) е:

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 на UPDATE като цяло, може да сте в състояние да премахнете втория WHEN в CASE и да го замените с обикновен 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 не са агрегати, а функции за сравнение за двойка стойности (както се намира във Fortran, наред с други езици за програмиране).

Имайте предвид, че приемам, че докато се изпълнява един SQL оператор, ограничението за уникалност (ако има такова) не се прилага, тъй като всеки ред се променя - само когато изразът завърши. Това е необходимо, тъй като всъщност не можете да контролирате реда, в който се обработват редовете. Знам за СУБД, където това би причинило проблеми; Знам за други, където не би.

Всичко може да се направи в един SQL оператор - но вие искате съхранена процедура, която да сортира параметрите на оператора. Използвам IBM Informix Dynamic Server (11.50.FC6 на MacOS X 10.6.2) и това е една от СУБД, която налага уникалното ограничение върху колоната „поръчка“ в края на изявлението. Направих разработката на 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 или друга СУБД е оставено като упражнение за читателя.



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. грешка в заявката на собствения SQL за хибернация

  2. Автоматично довършване на jQuery да работи с PHP източник

  3. Експортирайте база данни MySQL/MariaDB

  4. Подготвена декларация с НА ДУБЛИКАТ КЛЮЧ

  5. Множество клаузи ИЛИ в MySQL