Formülü bir tabloda depolama ve formülü bir işlevde kullanma


10

Bir kısmı ajan komisyonları işleyen bir PostgreSQL 9.1 veritabanı var. Her temsilcinin ne kadar komisyon aldıklarına dair kendi hesaplama formülü vardır. Her ajanın alması gereken komisyon miktarını üretme fonksiyonum var, ancak ajan sayısı arttıkça kullanımı imkansız hale geliyor. Son derece uzun vaka ifadeleri ve tekrarlayan kodlar yapmak zorunda kaldım, bu da fonksiyonumu çok büyük hale getirdi.

Tüm formüllerin sabit değişkenleri vardır:

d .. gün o ay çalıştı
r .. yeni düğümler accuired
l .. sadakat puanı
s .. subagent komisyonu
baz oranı
i .. kazanılan gelir

Formül şöyle olabilir:

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

Her temsilci ödeme formülünü İK borcuyla görüşür. Yani formülü aracılar tablosunda saklayabilir ve formülü tablodan alan ve değerlerle çeviren ve miktarı hesaplayan küçük bir işleve sahip olabilir miyim?

Yanıtlar:


6

Hazırlamak

Formülleriniz şöyle görünür:

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

Ben $ndoğrudan plpgsql değerleri ile değiştirilebilir böylece gösterim ile değişkenleri yerine EXECUTE(aşağıya bakınız):

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

Orijinal formüllerinizi ek olarak (insan gözü için) saklayabilir veya bu formu aşağıdaki gibi bir ifadeyle dinamik olarak oluşturabilirsiniz:

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

Sadece çevirinizin sağlam olduğundan emin olun. Normal ifade ifadeleri için bazı açıklamalar :

\ m .. sadece bir kelimenin başında
eşleşir \ M .. sadece bir kelimenin sonunda eşleşir

4. parametre 'g'.. global olarak değiştir

Çekirdek işlevi

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; 

Aramak:

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

İadeler:

29.6000000000000000

Önemli noktalar

  • Fonksiyon 6 değer parametresi ve formula text7. Formülü son koydum, $1 .. $6bunun yerine kullanabiliriz $2 .. $7. Sadece okunabilirlik uğruna.
    Uygun gördüğüm gibi değerler için veri türleri belirledim. Uygun tipler atayın (temel sağlık kontrollerini uygulamak için) veya sadece hepsini yapın numeric:

  • USINGMadde ile dinamik yürütme için değerleri iletin. Bu, ileri geri dökümü önler ve her şeyi daha basit, daha güvenli ve daha hızlı hale getirir.

  • Bir OUTparametre kullanıyorum çünkü bu daha zarif ve daha kısa sözdizimi sağlıyor. Bir sonlandırmaya RETURNgerek yoktur, OUT parametrelerinin değeri otomatik olarak döndürülür.

  • @Chris tarafından güvenlik konusundaki dersi ve kılavuzdaki "GÜVENLİK TANIMLAMA İşlevlerini Güvenle Yazma" bölümünü düşünün . Tasarımımda, tek enjeksiyon noktası formülün kendisidir.

  • Aramayı daha da basitleştirmek için bazı parametreler için varsayılanları kullanabilirsiniz .


5

Güvenlik hususlarıyla ilgili olarak lütfen bunu dikkatlice okuyun. Temelde işlevlerinize rasgele SQL enjekte etmeye çalışıyorsunuz. Sonuç olarak, bunun çok kısıtlı izinlere sahip bir kullanıcı altında çalıştırılması gerekir.

  1. Bir kullanıcı oluşturun ve ondan tüm izinleri kaldırın. Bunu yaptığınızla aynı db'de halka izin vermeyin.

  2. İfadeyi değerlendirmek, yapmak security definerve sahibi bu kısıtlı kullanıcıya değiştirmek için bir işlev oluşturun .

  3. İfadeyi önceden işleyin ve ardından yukarıda oluşturduğunuz eval () işlevine iletin. Gerekirse bunu başka bir işlevde yapabilirsiniz,

Yine unutmayın, bunun ciddi güvenlik etkileri vardır.

Düzenleme: Kısa örnek kod (denenmemiş, ancak dokümanları izlerseniz oraya ulaşmanız gerekir):

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.

"güvenlik tanımlayıcısı yap" gerçekten kafa karıştırıcı, açıklayabilir misiniz?
jcolebrand

1
PostgreSQL, bir fonksiyonun çalıştırabileceği iki güvenlik moduna sahiptir. GÜVENLİK İNVOKERİ varsayılan ayardır. GÜVENLİK TANIMLAMA, * nix'deki SETUID biti gibi "işlev sahibinin güvenlik bağlamıyla çalıştır" anlamına gelir. Bir işlev güvenlik tanımlayıcısı yapmak için bunu işlev bildiriminde ( CREATE FUNCTION foo(text) returns text IMMUTABLE LANGUAGE SQL SECURITY DEFINER AS $$...) belirtebilirsiniz veya şunları yapabilirsinizALTER FUNCTION foo(text) SECURITY DEFINER
Chris Travers

Oh, bu spesifik PG lino. Anladım. Shoulda
Cevapta

@ChrisTravers i bir formülü değerlendirmek için bazı örnek kod bekliyordum yani a+bbir tablodaki bir metin türü sütununda saklanır sonra foo(a int, b int,formula text)formül bir + b alırsa nasıl bir işlevi var nasıl yerine aslında + b yapmak yapabilirim tüm olası formüller için çok uzun bir vaka deyimi olması ve tüm segmentlerde kodu tekrarlamak zorunda?
indago

1
@indago, güvenlik endişeleri nedeniyle bunu iki katmana ayırmak istediğinizi düşünüyorum. Birincisi bir enterpolasyon katmanıdır. Bunu yapmak için PostgreSQL'de normal ifadeleri kullanabilirsiniz. Alt düzeyde, temelde bunu bir hapishane SQL işlevinde çalıştırıyorsunuz. Gerçi bunu yapacaksanız güvenliğe çok dikkat etmelisiniz ve ayrıca değerlere dönmeye de dikkat etmelisiniz. Daha fazlasını bilmeden, samople kodu ile çok şey yapmak zordur, ancak cevabı değiştirecektir.
Chris Travers

2

Sadece formülü saklamak ve daha sonra onu çalıştırmak için bir alternatif ( Chris'in belirttiği gibi, güvenlik problemleri vardır ) formula_stepsTemel olarak değişkenleri ve operatörleri ve yürütüldükleri sırayı içeren ayrı bir tabloya sahip olmak olacaktır. Bu biraz daha fazla iş olurdu, ama daha güvenli olurdu. Tablo şöyle görünebilir:

formula_steps
-------------
  formula_step_id
  formula_id (FK, aracılar tablosu tarafından başvurulur)
  INPUT_1
  INPUT_2
  operatörü (operatör simgelerini doğrudan saklamak istemiyorsanız, izin verilen operatörler tablosunun kimliği de olabilir)
  sıra

Başka bir seçenek, matematiksel ifadeleri değerlendirmek için bazı 3. taraf kütüphane / araç kullanmak olacaktır. Bu, veritabanınızı SQL enjeksiyonuna karşı daha az savunmasız hale getirir, ancak şimdi olası güvenlik sorunlarını harici aracınıza kaydırdınız (yine de oldukça güvenli olabilir).


Son seçenek, matematiksel ifadeleri değerlendiren bir prosedür yazmak (veya indirmek) olacaktır. Bu sorun için bilinen algoritmalar vardır, bu nedenle çevrimiçi bilgi bulmak zor olmamalıdır.


1
Üçüncü seçenek için +1. Tüm potansiyel girdiler biliniyorsa, girişlerin her birini belirli bir şekilde kodlayın ve bunları metin olarak depolanan formüle yerleştirin (gerekirse ve gerekirse), ardından aritmetiği değerlendirmek için bir kütüphane rutini kullanın. SQL enjeksiyon riski ortadan kaldırıldı.
Joel Brown
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.