Lưu trữ một công thức trong một bảng và sử dụng công thức trong một hàm


10

Tôi có một cơ sở dữ liệu PostgreSQL 9.1 trong đó một phần của nó xử lý hoa hồng đại lý. Mỗi đại lý có công thức tính toán riêng của họ bao nhiêu hoa hồng. Tôi có một chức năng để tạo ra số tiền hoa hồng mà mỗi đại lý sẽ nhận được, nhưng nó trở nên không thể sử dụng khi số lượng đại lý tăng lên. Tôi buộc phải thực hiện một số báo cáo trường hợp cực kỳ dài và mã lặp lại, điều này làm cho chức năng của tôi rất lớn.

Tất cả các công thức có các biến không đổi:

d .. ngày làm việc trong tháng đó
r .. các nút mới được tích lũy
l .. điểm trung thành
s .. hoa hồng phụ
b .. lãi suất cơ bản
i .. doanh thu đạt được

Công thức có thể là một cái gì đó như:

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

Mỗi đại lý đàm phán công thức thanh toán với phòng nhân sự. Vì vậy, tôi có thể lưu trữ công thức trong bảng đại lý sau đó có một hàm nhỏ chỉ lấy công thức từ bảng và dịch nó với các giá trị và tính toán số lượng không?

Câu trả lời:


6

Chuẩn bị

Công thức của bạn trông như thế này:

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

Tôi sẽ thay thế các biến bằng $nký hiệu để chúng có thể được thay thế bằng các giá trị trực tiếp trong plpgsql EXECUTE(xem bên dưới):

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

Bạn có thể lưu trữ thêm các công thức ban đầu của mình (đối với mắt người) hoặc tạo biểu mẫu này một cách linh hoạt với một biểu thức như:

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

Chỉ cần chắc chắn, bạn dịch là âm thanh. Một số giải thích cho các biểu thức regrec :

\ m .. chỉ khớp ở đầu một từ
\ M .. chỉ khớp ở cuối từ

Tham số thứ 4 'g'.. thay thế trên toàn cầu

Chức năng cốt lõi

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; 

Gọi:

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

Trả về:

29.6000000000000000

Những điểm chính

  • Hàm lấy 6 tham số giá trị và formula textlà thứ 7. Tôi đặt công thức cuối cùng, vì vậy chúng ta có thể sử dụng $1 .. $6thay vì $2 .. $7. Chỉ vì mục đích dễ đọc.
    Tôi đã gán các kiểu dữ liệu cho các giá trị mà tôi thấy phù hợp. Chỉ định các loại thích hợp (để thực hiện kiểm tra vệ sinh cơ bản) hoặc chỉ thực hiện tất cả numeric:

  • Truyền vào các giá trị để thực hiện động với USINGmệnh đề. Điều này tránh việc truyền qua lại và làm cho mọi thứ đơn giản hơn, an toàn hơn và nhanh hơn.

  • Tôi sử dụng một OUTtham số vì điều đó thanh lịch hơn và làm cho cú pháp rõ ràng ngắn hơn. RETURNKhông cần một bản cuối cùng , giá trị của (các) tham số OUT được trả về tự động.

  • Hãy xem xét bài giảng về bảo mật của @Chris và chương "Viết các chức năng DEFINER AN NINH một cách an toàn" trong hướng dẫn. Trong thiết kế của tôi, điểm tiêm duy nhất là chính công thức.

  • Bạn có thể sử dụng mặc định cho một số tham số để đơn giản hóa cuộc gọi hơn nữa.


5

Xin vui lòng đọc kỹ điều này về các cân nhắc bảo mật. Về cơ bản, bạn đang cố gắng đưa SQL tùy ý vào các hàm của mình. Do đó, bạn cần phải chạy nó dưới một người dùng có quyền hạn chế cao.

  1. Tạo một người dùng và thu hồi tất cả các quyền từ nó. Không cấp quyền cho công chúng trong cùng một db như bạn làm điều này.

  2. Tạo một hàm để đánh giá biểu thức, tạo nó security definervà thay đổi chủ sở hữu thành người dùng bị hạn chế đó.

  3. Tiền xử lý biểu thức và sau đó chuyển nó đến hàm eval () mà bạn đã tạo ở trên. Bạn có thể làm điều này trong một chức năng khác nếu bạn cần,

Lưu ý một lần nữa, điều này có ý nghĩa bảo mật nghiêm trọng.

Chỉnh sửa: Mã mẫu ngắn gọn (chưa được kiểm tra nhưng sẽ đưa bạn đến đó nếu bạn theo tài liệu):

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.

"Làm cho nó definer bảo mật" thực sự khó hiểu, bạn có thể giải thích?
jcolebrand

1
PostgreSQL có hai chế độ bảo mật mà một chức năng có thể chạy là. InvOKER BẢO MẬT là mặc định. DEFINER AN NINH có nghĩa là "chạy với bối cảnh bảo mật của chủ sở hữu hàm" giống như bit SETUID trên * nix. Để thực hiện một trình bảo vệ chức năng bảo mật chức năng, bạn có thể chỉ định điều này trong khai báo hàm ( CREATE FUNCTION foo(text) returns text IMMUTABLE LANGUAGE SQL SECURITY DEFINER AS $$...) hoặc bạn có thểALTER FUNCTION foo(text) SECURITY DEFINER
Chris Travers

Ồ, vậy đó là PG lino cụ thể. Gotcha. Shoulda đã sử dụng backticks trong Câu trả lời ;-)
jcolebrand

@ChrisTravers Tôi đã mong đợi một số mã mẫu để đánh giá một công thức tức a+blà được lưu trữ trong một cột loại văn bản trong một bảng sau đó tôi có một hàm foo(a int, b int,formula text)nếu nó có công thức là + b, làm thế nào tôi có thể thực hiện hàm thực sự làm a + b thay vì tôi phải có một tuyên bố trường hợp rất dài cho tất cả các công thức có thể và lặp lại mã trong tất cả các phân đoạn?
indago

1
@indago, tôi nghĩ bạn muốn chia nó thành hai lớp vì những lo ngại về bảo mật. Đầu tiên là một lớp nội suy. Bạn có thể sử dụng regexes trong PostgreSQL để làm điều này. Ở cấp độ thấp hơn, về cơ bản, bạn đang chạy cái này trong một hàm SQL bị bỏ tù. Bạn thực sự cần phải hết sức chú ý đến bảo mật nếu bạn sẽ làm điều này và bạn cũng phải hết sức chú ý để trả về các giá trị. Nếu không biết nhiều hơn, thật khó để làm nhiều với mã samople nhưng sẽ sửa đổi câu trả lời.
Chris Travers

2

Một cách khác để chỉ lưu trữ công thức và sau đó thực thi nó (như Chris đã đề cập, có vấn đề về bảo mật ) sẽ có một bảng riêng gọi là formula_stepsvề cơ bản sẽ chứa các biến và toán tử và chuỗi trong đó chúng được thực thi. Đây sẽ là một công việc nhiều hơn một chút, nhưng sẽ an toàn hơn. Bảng có thể trông như thế này:

công thức_ bước
-------------
  công thức_step_id
  Formula_id (FK, được tham chiếu bởi bảng đại lý)
  đầu vào_1
  đầu vào_2
  toán tử (cũng có thể là ID cho bảng các toán tử được phép, nếu bạn không muốn lưu trữ trực tiếp các ký hiệu toán tử)
  sự nối tiếp

Một lựa chọn khác là sử dụng một số thư viện / công cụ của bên thứ 3 để đánh giá các biểu thức toán học. Điều này sẽ làm cho cơ sở dữ liệu của bạn ít bị tổn thương hơn khi tiêm SQL, nhưng giờ bạn đã chuyển các vấn đề bảo mật có thể xảy ra sang công cụ bên ngoài (vẫn có thể khá an toàn).


Tùy chọn cuối cùng sẽ là viết (hoặc tải xuống) một thủ tục đánh giá các biểu thức toán học. Có các thuật toán đã biết cho vấn đề này, vì vậy không khó để tìm thấy thông tin trực tuyến.


1
+1 cho tùy chọn thứ ba. Nếu tất cả các đầu vào tiềm năng đã biết, mã cứng sẽ chọn từng đầu vào và thay thế chúng (nếu cần thiết) vào công thức được lưu dưới dạng văn bản, sau đó sử dụng thói quen thư viện để đánh giá số học. SQL đã loại bỏ rủi ro.
Joel Brown
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.