Preparar
Suas fórmulas são assim:
d*b+(l*4+r)+(i/d)+s
Eu substituiria as variáveis pela $n
notação para que elas possam ser substituídas pelos valores diretamente no plpgsql EXECUTE
(veja abaixo):
$1*$5+($3*4+$2)+($6/$1)+$4
Você pode armazenar suas fórmulas originais adicionalmente (para o olho humano) ou gerar esse formulário dinamicamente com uma expressão como:
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');
Apenas certifique-se de que sua tradução é sólida. Alguma explicação para as expressões regexp :
\ m .. corresponde apenas ao início de uma palavra
\ M .. corresponde apenas ao final de uma palavra
Quarto parâmetro 'g'
.. substituir globalmente
Função principal
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;
Ligar:
SELECT f_calc(1, 2, 3, 4.1, 5.2, 6.3, '$1*$5+($3*4+$2)+($6/$1)+$4');
Devoluções:
29.6000000000000000
Pontos principais
A função assume o parâmetro 6 value e formula text
como 7th. Coloquei a fórmula por último, para que possamos usar em $1 .. $6
vez de $2 .. $7
. Apenas por uma questão de legibilidade.
Atribuí tipos de dados para os valores como achar melhor. Atribua tipos adequados (para implementar verificações básicas de sanidade) ou apenas faça todos eles numeric
:
Passe valores para execução dinâmica com a USING
cláusula Isso evita a transmissão para frente e para trás e torna tudo mais simples, seguro e rápido.
Eu uso um OUT
parâmetro porque é mais elegante e cria uma sintaxe mais clara e clara. Uma final RETURN
não é necessária, o valor do (s) parâmetro (s) OUT é retornado automaticamente.
Considere a palestra sobre segurança de @Chris e o capítulo "Como escrever com segurança as funções do DEFINER DE SEGURANÇA" no manual. Na minha concepção, o único ponto de injeção é a própria fórmula.
Você pode usar padrões para alguns parâmetros para simplificar ainda mais a chamada.
CREATE FUNCTION foo(text) returns text IMMUTABLE LANGUAGE SQL SECURITY DEFINER AS $$...
) ou podeALTER FUNCTION foo(text) SECURITY DEFINER
a+b
é armazenado em uma coluna de tipo de texto em uma tabela, então eu tenho uma funçãofoo(a int, b int,formula text)
se obtiver a fórmula é a + b, como posso fazer a função realmente fazer a + b em vez de eu tendo que ter uma declaração de caso muito longa para todas as fórmulas possíveis e repetir o código em todos os segmentos?Uma alternativa para apenas armazenar a fórmula e executá-la (que, como Chris mencionou, tem problemas de segurança ) seria ter uma tabela separada chamada
formula_steps
que basicamente conteria as variáveis e operadores e a sequência na qual eles são executados. Isso seria um pouco mais trabalhoso, mas seria mais seguro. A tabela pode ficar assim:Outra opção seria usar alguma biblioteca / ferramenta de terceiros para avaliar expressões matemáticas. Isso tornaria seu banco de dados menos vulnerável à injeção de SQL, mas agora você acabou de mudar os possíveis problemas de segurança para sua ferramenta externa (que ainda pode ser bastante segura).
A opção final seria escrever (ou baixar) um procedimento que avalie expressões matemáticas. Existem algoritmos conhecidos para esse problema, portanto, não deve ser difícil encontrar informações on-line.
fonte