Зберігайте формулу в таблиці та використовуйте формулу у функції


10

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

Усі формули мають постійні змінні:

д .. дні працювали в тому місяці
р .. звинувачуються нові вузли
л .. оцінка лояльності
с .. підконтрольна комісія
б .. базовий розмір
i .. отриманий дохід

Формула може бути приблизно такою:

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

Кожен агент узгоджує формулу оплати з відділом кадрів. Тож чи можу я зберігати формулу в таблиці агентів, а потім мати як маленьку функцію, яка просто отримує формулу з таблиці та переводить її зі значеннями та обчислює суму?

Відповіді:


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');

Просто переконайтесь, що ви переклад є звуковим. Деякі пояснення виразів regexp :

\ 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 має два режими захисту, з якими функція може працювати. БЕЗПЕЧНИЙ ІНВОКЕР за замовчуванням DEFINER SECURITY означає "запуск із контекстом безпеки власника функції", подібний до біта SETUID на * nix. Щоб визначити безпеку функції, ви можете вказати це в декларації функції ( CREATE FUNCTION foo(text) returns text IMMUTABLE LANGUAGE SQL SECURITY DEFINER AS $$...) або ви можетеALTER FUNCTION foo(text) SECURITY DEFINER
Chris Travers

О, так це специфічний PG lino. Готча. Shoulda використовував зворотні посилання у відповіді ;-)
jcolebrand

@ChrisTravers Я очікував, що якийсь зразок коду оцінить формулу, тобто a+bвін зберігається у стовпці тексту тексту в таблиці, тоді у мене є функція, foo(a int, b int,formula text)якщо вона отримує формулу - + b, як я можу зробити так, щоб функція фактично робила + b замість мені потрібно мати дуже довгий випадок випадку для всіх можливих формул і повторення коду у всіх сегментах?
індаго

1
@indago, я думаю, ви хочете розбити це на два шари через проблеми безпеки. Перший - інтерполяційний шар. Для цього можна використовувати регулярні вирази в PostgreSQL. На нижньому рівні ви в основному виконуєте це у в'язничній функції SQL. Вам дійсно потрібно приділяти дуже пильну увагу безпеці, якщо ви збираєтесь це робити, хоча вам також слід приділити пильну увагу поверненню значень. Не знаючи багато іншого, важко зробити багато з кодом samople, але виправите відповідь.
Кріс Траверс

2

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

formula_steps
-------------
  formula_step_id
  formula_id (FK, на який посилається таблиця агентів)
  введення_1
  введення_2
  оператор (також може бути ідентифікатор таблиці дозволених операторів, якщо ви не хочете зберігати символи оператора безпосередньо)
  послідовність

Іншим варіантом було б використання якоїсь сторонньої бібліотеки / інструменту для оцінки математичних виразів. Це зробить вашу базу даних менш вразливою до ін'єкції SQL, але тепер ви просто перенесли можливі проблеми безпеки на свій зовнішній інструмент (який все ще може бути досить безпечним).


Остаточним варіантом було б написати (або завантажити) процедуру, яка оцінює математичні вирази. Для цієї проблеми відомі алгоритми, тому знайти інформацію в Інтернеті не повинно.


1
+1 для третього варіанту. Якщо всі потенційні введення відомі, жорсткий код виділіть кожен із входів і замініть їх (якщо і в міру необхідності) формулою, що зберігається як текст, а потім використовуйте процедуру бібліотеки для оцінки арифметики. Виключається ризик введення SQL.
Джоел Браун
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.