Borç Alacak Bakiye Üzerine ...

Firebird ve Interbase veritabanları ve SQL komutlarıyla ilgli sorularınızı sorabilirsiniz. Delphi tarafındaki sorularınızı lütfen Programlama forumunda sorunuz.
Cevapla
omurolmez
Üye
Mesajlar: 187
Kayıt: 31 Eki 2012 11:41

Borç Alacak Bakiye Üzerine ...

Mesaj gönderen omurolmez »

Arkadaşlar, forumlarda çok tartışılan bir konudur borç alacak bakiye / yürüyen bakiye. Aynı şey defalarca denenir, tartışılır. Umarım bu yazım zaman kaybını önler. Hatalarım var ise, forum adminleri veya benden iyi bilen arkadaşlar uyaracaklardır.

Yürüyen bakiyenin (gridin her satırında o satıra / o tarihe ait bakiyeyi göstermesi) tek bir sql sorgusu ile (ki sorgu nasıl olursa olsun) yapılması mümkün değildir. Gerçi subselect ler ve subselect içinde sum lar ile buna yakın görünümler (veya belkide tamamen istenen) teoride sağlanabiliyor ancak pratikte anlaşılması, hata ayıklanması, bakımı çok zor bir kod çıkıyor ortaya ve performans da tartışılır.

Bunun çözümü için rdbms lerde cursor denen bir olay var.

cursor, tıpkı delphi deki tquery nesnesi gibi çalışıyor. Yani bir rdbms de bir stored procedure içinde bir select sorgusu yazıyorsunuz ve (where ile süzerek veya süzmeyerek) çektiğiniz veriyi bir cursor a bağlıyorsunuz. Bundan sonra bu cursor üzerinde ileri geri (Tquery nin First, Last, Next, Prev ine bezer şekilde) hareket ediyorsunuz (Oracle, MsSql, Firebird) (Mysql şu an durum nedir bilmiyorum).

Bu durumda, bakiye hesap kodunuz tıpkı client daki gibi oluyor.
Bunun avantajı hiç hata oluşturmayacak sağlam bir kodunuz oluyor çünki sonuçlarınız her seferinde hesaplanıyor ve kodunuz çok sade oluyor.
Ancak performans dezavantajı var. Ben bunu ilk denediğimde, çok uzun sorgu süreleri yüzünden vazgeçtim. O zamanlar indeksleri nasıl kullanacağımı bilmiyordum.
Zaman içinde fb gelişti ve hızlandı. Bir müsait zamanda tekrar deneyeceğim. Belki de artık performans sıkıntısı yoktur.

{ Buna sağlıksız bir alternatif, (benim de zamanında yaptığım bir hata), tabloda bakiye sütununun olması ve her insert işleminde o satırın bakiyesinin hesaplanıp kaydedilmesi. Bu yapının kusuru, araya kayıt eklemelerde kodun kırılması halinde sonuçlar tutarsız oluyor. İşlemler mutlaka transaction ile yapılmalı. Bir de bu kayıt eklemeler sırasında büyük miktarda veri güncellendiği (ve bu yüzden kilitlendiği) için lock conflict hataları oluyor. Eğer yine de destek vermek zorunda olduğunuz böyle bir kodunuz varsa, lock conflictlere çözüm olarak transaction parametrelerinde wait ile nowait arasındaki farka bakmanızı öneririm.}

Firebird site kaynak: http://www.firebirdsql.org/refdocs/lang ... are-cursor

Yine fikir açısından yazdığım yürüyen bakiye kodu (Benzer bir mantık kullansa da cursor kullanmadığına dikkat). Daha iyilerini bu veya başka forumlarda bulmak mümkün olabilir :

Kod: Tümünü seç

SET TERM ^ ;
CREATE PROCEDURE SP_BILESEN_HAREKET (
    BILESEN_ID_ Integer,
    TARIH1 Timestamp,
    TARIH2 Timestamp )
RETURNS (
    BILESEN_ID Integer,
    TARIH_SAAT Timestamp,
    MIKTAR Decimal(18,4),
    GIR Decimal(18,4),
    CIK Decimal(18,4),
    BAKIYE_GIR Decimal(18,4),
    BAKIYE_CIK Decimal(18,4),
    BAKIYE_GEN Decimal(18,4) )
AS
begin
 bakiye_gen = 0;
 bakiye_gir = 0;
 bakiye_cik = 0;
 bilesen_id = bilesen_id_;
 if(:tarih1 is null) then
   select min(tarih_saat) 
     from bilesen_hareket
     where bilesen_id = :bilesen_id
     into :tarih1; 
   else begin
   select 
     sum(miktar)
     from bilesen_hareket
     where miktar>0 and bilesen_id = :bilesen_id and tarih_saat < :tarih1
     into :bakiye_gir;
   select  
     sum(miktar)
     from bilesen_hareket
     where miktar<0 and bilesen_id = :bilesen_id and tarih_saat < :tarih1
     into :bakiye_cik;
   end --else begin
     
 if(:tarih2 is null) then
   tarih2 = CURRENT_TIMESTAMP;
 for 
   select 
     tarih_saat, miktar
     from bilesen_hareket
     where bilesen_id = :bilesen_id and tarih_saat between :tarih1 and :tarih2
     order by tarih_saat
     into :tarih_saat, :miktar
 do begin
   if(miktar>0) then
     gir =miktar; else
     gir =0;
   if(miktar<0) then
     cik =miktar; else
     cik =0;
   bakiye_gir =bakiye_gir + gir;
   bakiye_cik =bakiye_cik + cik;
   bakiye_gen =bakiye_gen + miktar;
   suspend;
   end
end^
SET TERM ; ^
Ömür Ölmez
Kullanıcı avatarı
Kuri_YJ
Moderator
Mesajlar: 2248
Kayıt: 06 Ağu 2003 12:07
Konum: İstanbul
İletişim:

Re: Borç Alacak Bakiye Üzerine ...

Mesaj gönderen Kuri_YJ »

Allah razı olsun kardeş,

Senin sayende benim de yıllardır kafamı kurcalayan ve bir türlü beceremediğim bir şeyi hallettim.

CURSOR kullanımını FB hariç her yerde kullanıyordum ama bir türlü FB'de kullanamamıştım. Verdiğin linkteki örnek benim nerede hata yaptığımı gösterdi. Ben hep DECLARE CURSOR'ı kod bloğunun içinde kullanmaya çalışmıştım her seferinde de hata verip duruyordu, bir türlü de çalışan bir örnek bulamamıştım. İçimden bir ses de sürekli olarak DECLERE'i başa yaz başa yaz deyip duruyordu ama her ne hikmetse öyle yapıp denememiştim, şimdi denedim oldu. Meğer DECLARE CURSOR'ı ana kod bloğunun dışında Declarasyon bölümüne koyunca oluyormuş... Vay tospağalar vay... :)

Sağolasın ;)

Bu arada CURSOR veya FOR SELECT kullanırsan tahmin ettiğin gibi yavaş olmaz. Benim yaptığım yürüyen bakiyeli procedureler cayır cayır çalışıyor, 15-20 saniye içerisinde 2000,3000 sayfayı bulan yürüyen bakiyeli cari hareket dökümleri oluşturabiliyor, yani yeterince hızlı.


Kolay Gelsin
Adnan
Kuri Yalnız Jedi
Harbi Özgürlük İçin Pisi http://www.pisilinux.org/
Cevapla