Сохраните формулу в таблице и используйте формулу в функции

10

У меня есть база данных PostgreSQL 9.1, часть которой обрабатывает агентские комиссии. Каждый агент имеет свою формулу расчета, какую комиссию они получают. У меня есть функция для генерации комиссионного вознаграждения, которое должен получить каждый агент, но его становится невозможно использовать по мере роста числа агентов. Я вынужден делать очень длинные операторы case и повторять код, что сделало мою функцию очень большой.

Все формулы имеют постоянные переменные:

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

Формула может быть что-то вроде:

d*b+(l*4+r)+(i/d)+s

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

Indago
источник

Ответы:

6

Подготовить

Ваши формулы выглядят так:

d*b+(l*4+r)+(i/d)+s

Я бы заменил переменные на $nнотации, чтобы они могли быть заменены значениями непосредственно в plpgsql EXECUTE(см. Ниже):

$1*$5+($3*4+$2)+($6/$1)+$4

Вы можете дополнительно сохранить исходные формулы (для человеческого глаза) или динамически сгенерировать эту форму с помощью следующего выражения:

SELECT regexp_replace(regexp_replace(regexp_replace(
       regexp_replace(regexp_replace(regexp_replace(
      'd*b+(l*4+r)+(i/d)+s'
      , '\md\M', '$1', 'g')
      , '\mr\M', '$2', 'g')
      , '\ml\M', '$3', 'g')
      , '\ms\M', '$4', 'g')
      , '\mb\M', '$5', 'g')
      , '\mi\M', '$6', 'g');

Просто убедитесь, что ваш перевод звучит правильно. Некоторое объяснение выражений регулярных выражений :

\ m .. соответствует только в начале слова
\ M .. соответствует только в конце слова

4-й параметр 'g'.. заменить глобально

Основная функция

CREATE OR REPLACE FUNCTION f_calc(
    d int         --  days worked that month
   ,r int         --  new nodes accuired
   ,l int         --  loyalty score
   ,s numeric     --  subagent commission
   ,b numeric     --  base rate
   ,i numeric     --  revenue gained
   ,formula text
   ,OUT result numeric
)  RETURNS numeric AS
$func$
BEGIN    
   EXECUTE 'SELECT '|| formula
   INTO   result
   USING  $1, $2, $3, $4, $5, $6;                                          
END
$func$ LANGUAGE plpgsql SECURITY DEFINER IMMUTABLE; 

Вызов:

SELECT f_calc(1, 2, 3, 4.1, 5.2, 6.3, '$1*$5+($3*4+$2)+($6/$1)+$4');

Возвращает:

29.6000000000000000

Основные моменты

  • Функция принимает 6 значений параметра и formula textкак 7-й. Я ставлю формулу последней, чтобы мы могли использовать $1 .. $6вместо $2 .. $7. Просто для удобства чтения.
    Я назначил типы данных для значений по своему усмотрению. Назначьте подходящие типы (для реализации базовых проверок работоспособности) или просто сделайте их все numeric:

  • Передайте значения для динамического выполнения с USINGпредложением. Это позволяет избежать бросания вперед и назад и делает все проще, безопаснее и быстрее.

  • Я использую OUTпараметр, потому что это более элегантно и делает для более короткого более ясного синтаксиса. Окончательный RETURNвариант не требуется, значение параметра (ов) OUT возвращается автоматически.

  • Рассмотрите лекцию по безопасности @Chris и главу «Безопасное написание функций БЕЗОПАСНОСТИ» в руководстве. В моем дизайне единственной точкой внедрения является сама формула.

  • Вы можете использовать значения по умолчанию для некоторых параметров, чтобы еще больше упростить вызов.

Эрвин Брандштеттер
источник
5

Пожалуйста, прочитайте это внимательно в отношении соображений безопасности. По сути, вы пытаетесь внедрить произвольный SQL в свои функции. Следовательно, вы должны запускать это под пользователем с ограниченными правами.

  1. Создать пользователя и отозвать у него все разрешения. Не предоставляйте разрешения для публики в том же БД, что и вы.

  2. Создайте функцию для оценки выражения, сделайте ее security definerи измените владельца для этого ограниченного пользователя.

  3. Предварительно обработайте выражение и затем передайте его функции eval (), которую вы создали выше. Вы можете сделать это в другой функции, если вам нужно,

Обратите внимание, это имеет серьезные последствия для безопасности.

Изменить: краткий пример кода (не проверен, но вы должны получить его, если вы будете следовать документам):

CREATE OR REPLACE FUNCTION eval_numeric(text) returns numeric language plpgsql security definer immutable as
$$
declare retval numeric;
begin

execute $e$ SELECT ($1)::numeric$e$ into retval;
return retval;
end;
$$;

ALTER FUNCTION eval_numeric OWNER TO jailed_user;

CREATE OR REPLACE FUNCTION foo(expression text, a numeric, b numeric) returns numeric language sql immutable as $$
select eval(regexp_replace(regexp_replace($1, 'a', $2, 'g'), 'b', '$3', 'g'));
$$; -- can be security invoker, but eval needs to be jailed.
Крис Траверс
источник
"сделать это определением безопасности" действительно сбивает с толку, вы можете объяснить?
Jcolebrand
1
PostgreSQL имеет два режима безопасности, которые может выполнять функция. SECURITY INVOKER используется по умолчанию. SEFURITY DEFINER означает «запуск в контексте безопасности владельца функции», что-то вроде бита SETUID в * nix. Чтобы определить безопасность функции, вы можете указать это в объявлении функции ( CREATE FUNCTION foo(text) returns text IMMUTABLE LANGUAGE SQL SECURITY DEFINER AS $$...) илиALTER FUNCTION foo(text) SECURITY DEFINER
Chris Travers
О, так это конкретный PG Lino. Попался. Следует ли использовать
ответные пометки
@ChrisTravers Я ожидал, что какой-то пример кода оценивает формулу, т.е. a+bхранится в столбце текстового типа в таблице, тогда у меня есть функция, foo(a int, b int,formula text)если она получает формулу a + b, как я могу заставить функцию фактически делать a + b вместо мне нужно иметь очень длинный регистр для всех возможных формул и повторять код во всех сегментах?
Индиго
1
@indago, я думаю, ты хочешь разбить это на два уровня из-за проблем безопасности. Первый - это интерполяционный слой. Вы можете использовать регулярные выражения в PostgreSQL для этого. На более низком уровне вы в основном выполняете это в заключенной в тюрьму функции SQL. Вы действительно должны уделять пристальное внимание безопасности, если вы собираетесь сделать это, и вы должны также обратить пристальное внимание на возвращаемые значения. Не зная намного больше, с кодом samople трудно что-либо сделать, но он исправит ответ.
Крис Трэверс
2

Альтернативой простому сохранению формулы и последующему ее выполнению (которая, как упоминал Крис, есть проблемы с безопасностью ) будет иметь отдельную таблицу с именем, formula_stepsкоторая в основном будет содержать переменные и операторы, а также последовательность, в которой они выполняются. Это будет немного больше работы, но будет более безопасным. Таблица может выглядеть так:

formula_steps
-------------
  formula_step_id
  Formula_id (FK, на который ссылается таблица агентов)
  INPUT_1
  INPUT_2
  оператор (также может быть идентификатором таблицы разрешенных операторов, если вы не хотите хранить символы операторов напрямую)
  последовательность

Другой вариант - использовать стороннюю библиотеку / инструмент для оценки математических выражений. Это сделает вашу базу данных менее уязвимой для внедрения SQL-кода, но теперь вы просто перенесли возможные проблемы с безопасностью на ваш внешний инструмент (который все еще может быть довольно безопасным).


Последний вариант - написать (или загрузить) процедуру, которая оценивает математические выражения. Существуют известные алгоритмы для этой проблемы, поэтому не составит труда найти информацию в Интернете.

FrustratedWithFormsDesigner
источник
1
+1 за третий вариант. Если все потенциальные входные данные известны, жестко запрограммируйте выбор каждого из входных данных и подставьте их (если и при необходимости) в формулу, сохраненную в виде текста, а затем используйте библиотечную процедуру для оценки арифметики. Устранен риск внедрения SQL.
Джоэл Браун