Изменить нумерацию ссылочных переменных в текстовых столбцах - PullRequest
3 голосов
/ 06 января 2012

Фон

Для проекта ввода данных пользователь может вводить переменные, используя сокращенную запись:

"Pour i1 into a flask."
"Warm the flask to 25 degrees C."
"Add 1 drop of i2 to the flask."
"Immediately seek cover."

В этом случае i1 и i2 являются ссылочными переменнымигде число относится к ингредиенту.Текстовые строки находятся в таблице INSTRUCTION, ингредиенты - в таблице INGREDIENT.

Каждый ингредиент имеет порядковый номер для сортировки.

Задача

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

seq | label
1   | water
2   | sodium

Пользователь добавляет еще один ингредиент:

seq | label
1   | water
2   | sodium
3   | francium

Пользователь переупорядочивает список:

seq | label
1   | water
2   | francium
3   | sodium

На этом этапе следующая строка теперь неверна:

"Add 1 drop of i2 to the flask."

Необходимо изменить нумерацию i2 (поскольку ингредиент # 2 был перемещен в позицию # 3), чтобы указывать на исходную ссылочную переменную:

"Add 1 drop of i3 to the flask."

Обновленные сведения

Это упрощенная версия проблемы.Полная проблема может иметь строки, такие как:

"Add 1 drop of i2 to the o3 of i1."

, где o3 - объект (колба), а i1 и i2 - вода и натрий соответственно.

Структура таблицы

Таблица ингредиентов имеет следующую структуру:

id | seq | label

Таблица инструкций имеет следующую структуру:

step

Алгоритм

Алгоритм, который я имею в виду:

  1. Повторите для всех step с, которые соответствуют выражению '\mi([0-9]+)':
  2. Разбейте шаг на жетоны слова.
  3. Длякаждый токен:
    1. Если числовая часть токена соответствует старому порядковому номеру, замените его новым порядковым номером.
    2. Перекомбинируйте токены и обновите инструкцию.
    3. Обновите номер ингредиента.

Обновление

Алгоритм может быть неверным, как написано.Могут быть две ссылочные переменные, которые должны измениться.Рассмотрим до:

seq | label
1   | water
2   | sodium
3   | caesium
4   | francium

И после (замена натрия и цезия):

seq | label
1   | water
2   | caesium
3   | sodium
4   | francium

Каждый i2 на каждом шаге должен стать i3;аналогично i3 должно стать i2.Итак

"Add 1 drop of i2 to the flask, but absolutely do not add i3."

Становится:

"Add 1 drop of i3 to the flask, but absolutely do not add i2."

Код

Код для выполнения первых двух частей алгоритма напоминает:

CREATE OR REPLACE FUNCTION
  renumber_steps(
    p_ingredient_id integer,
    p_old_sequence integer,
    p_new_sequence integer )
  RETURNS void AS
$BODY$
DECLARE
  v_tokens text[];

BEGIN
  FOR v_tokens IN
    SELECT
      t.tokens
    FROM (
      SELECT
        regexp_split_to_array( step, '\W' ) tokens,
        regexp_matches( step, '\mi([0-9]+)' ) matches
      FROM
        instruction
      ) t
  LOOP
    RAISE NOTICE '%', v_tokens;
  END LOOP;
END;
$BODY$
  LANGUAGE plpgsql VOLATILE
  COST 100;

Вопрос

Какой более эффективный способ решения этой проблемы (т. Е. Как бы вы исключили циклические конструкции), возможно, с использованием возможностей, специфичных для PostgreSQL, без серьезного пересмотра модели данных?

Спасибо!

Сведения о системе

PostgreSQL 9.1.2.

Ответы [ 2 ]

1 голос
/ 06 января 2012

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

Может работать так:

CREATE OR REPLACE FUNCTION renumber_steps(_old int[], _new int[])
  RETURNS void AS
$BODY$
DECLARE
    _prefix     CONSTANT text := ' i';    -- prefix, incl. leading space
    _new_prefix CONSTANT text := ' ###';  -- temp prefix, incl. leading space
    i           int;
    o           text;
    n           text;
BEGIN

IF array_upper(_old,1) <> array_upper(_new,1) THEN
    RAISE EXCEPTION 'Array length mismatch!';
END IF;

FOR i IN 1  .. array_upper(_old,1) LOOP
    IF _old[i] <> _new[i] THEN
        o := _prefix || _old[i] || ' ';        -- leading and trailing blank!
        -- new instruction are temporarily prefixed with new_marker
        n := _new_prefix || _new[i]  || ' ';

        UPDATE instruction
        SET    step = replace(step, o, n)      -- replace all instances
        WHERE  step ~~ ('%' || o || '%');

        UPDATE ingredient
        SET    seq = _new[i] * -1              -- temporarily negative
        WHERE  seq = _old[i];
    END IF;
END LOOP;

-- finally replace temp. prefix
UPDATE instruction
SET    step = replace(step, _new_prefix, _prefix)
WHERE  step ~~ ('%' || _new_prefix || '%');

-- .. and temp. negative seq numbers
UPDATE ingredient
SET    seq = seq * -1
WHERE  seq < 0;

END;
$BODY$
  LANGUAGE plpgsql VOLATILE STRICT;

Звоните:

SELECT renumber_steps('{2,3,4}'::int[], '{4,3,2}'::int[]);

Алгоритм требует ...
... чтобы ингредиенты в шагах были разделены пробелами.
... чтобы не было постоянных отрицательных последовательных чисел.

_old и _new являются массивами старого и нового instruction.seq ингредиентов, которые меняют положение.Длина обоих массивов должна совпадать, иначе будет сгенерировано исключение.Может содержать seq, которые не меняются.С ними ничего не случится.

Требуется PostgreSQL 9.1 или более поздняя версия.

0 голосов
/ 06 января 2012

Я думаю, что ваша модель проблематична ... у вас должно быть "реальное имя (id)" (i1, o3 и т. Д.), ИСПРАВЛЕННОЕ после создания и второе поле в таблице ingredientобеспечение "сортировки".Пользователь вводит «имя сортировки», и вы сразу заменяете его «настоящим именем» (id) при сохранении введенных данных в таблицу step.

Когда вы читаете их из step таблицу, которую вы просто замените / отобразите «настоящее имя» (id) на текущее «имя сортировки» для целей отображения, если это необходимо ...

Таким образом, вам не нужно изменять данныеуже в таблице step каждый раз, когда кто-то меняет сортировку, которая является сложной и дорогой операцией ИМХО - она ​​также подвержена проблемам с параллелизмом ...

Приведенная выше опция сводит всю проблему к операции отображения (таблица ingredient) для INSERT / UPDATE / SELECT (таблица step) для одной записи, над которой в данный момент работает - она ​​не мешает другим уже существующим записям.

...