Dinamik SQL filtre oluşturucu

Yazdığınız makaleleri ve üyelerimizin işine yarayacağını düşündüğünüz kodlarınızı gönderebilirsiniz. Bu foruma soru sormayın!
Cevapla
fduman
Moderator
Mesajlar: 2749
Kayıt: 17 Ara 2004 12:02
Konum: Ankara

Dinamik SQL filtre oluşturucu

Mesaj gönderen fduman »

Kendi programlarımda basitçe filtre için SQL üretmede kullandığım bir sınıf yazdım. Daha kompleks SQL'ler de üretebilir, ancak birkaç değişiklik yapmak gerekli. Bu olaya yanılmıyorsam Ad-hoc da diyorlar.

Ne için kullanıldığına gelirsek. Tutun ki bir arama dialog'unuz var. Burada isim, soyadı, sicil no, adresi gibi kriterler ile tabloda arama yapmak istiyorsunuz. Bunun SQL'ini nasıl düzenlerdiniz? İsim kutusu ve Adres kutusu doldurulduğunda ona göre SQL üretilmeli. Sadece isim kutusu doldurulduğunda da ona göre bir üretim dinamik olarak gerçekleşmeli. Bu olay FibPlus bileşenlerinde mevcut. Ben de oradan esinlenerek bunu oluşturdum. Sadece = sorgusu değil, CONTAINING, IN vs.. gibi operatorleri de desteklediğini ve olabildiğince esnek tutulduğunu göreceksiniz.

Sınıfın testini tam olarak yapmadığımdan bazı durumlarda hatalı SQL üretebilir, belki de üretmez. ;)

Kullanımını bir sonraki mesajda açıklıyorum.

Kod: Tümünü seç

{ Furkan Duman 2005, Kod LGPL lisansı ile kullanılabilir }

unit WhereFilter;

interface

uses Classes;

type
  TValueKind = (vkText, vkNumeric);
  TCompareKind = (ckEqual, ckBigger, ckSmaller, ckIN, ckNOT_IN,
    ckCONTAINING, ckBigEqual, ckSmallEqual, ckIS, ckIS_NOT, ckLIKE,
    ckSTARTING);

  TWhereField = class
  private
    FName: string;
    FValue: string;
    FValueKind: TValueKind;
    FCompareKind: TCompareKind;
    function GetClause: string;
    function GetCompareOperator: string;
    function GetValueByKind: string;
  public
    property Name: string read FName write FName;
    property Value: string read FValue write FValue;
    property ValueKind: TValueKind read FValueKind write FValueKind;
    property CompareKind: TCompareKind read FCompareKind write FCompareKind;
    property Clause: string read GetClause;
  end;

  TWhereFields = class
  private
    FList: TList;
    FSQL: TStringList;
    FChangedSQL: TStringList;
    function GetCount: Integer;
    function GetWhereField(const Index: Integer): TWhereField;
    function GetWhereText: string;
    procedure SetSQL(const Value: TStringList);
    function GetChangedSQL: TStringList;
  public
    constructor Create;
    destructor Destroy; override;
    procedure Clear;
    procedure AddField(const AName, AValue: string;
      AValueKind: TValueKind; ACompareKind: TCompareKind);
    property Count: Integer read GetCount;
    property Fields[const Index: Integer]: TWhereField read
      GetWhereField; default;
    property SQL: TStringList read FSQL write SetSQL;
    property ChangedSQL: TStringList read GetChangedSQL;
  end;

implementation

uses SysUtils;

{ TWhereField }

function TWhereField.GetClause: string;
begin
  // Value'da değer yoksa clause boş döner
  if Trim(FValue) <> '' then
    Result:= Format('%s %s %s', [FName, GetCompareOperator, GetValueByKind])
  else
    Result:= '';
end;

function TWhereField.GetCompareOperator: string;
begin
  case FCompareKind of
    ckEqual: Result:= '=';
    ckBigger: Result:= '>';
    ckSmaller: Result:= '<';
    ckBigEqual: Result:= '>=';
    ckSmallEqual: Result:= '<=';
    ckIn: Result:= 'IN';
    ckNOT_IN: Result:= 'NOT IN';
    ckIS: Result:= 'IS';
    ckIS_NOT: Result:= 'IS NOT';
    ckContaining: Result:= 'CONTAINING';
    ckLike: Result:= 'LIKE';
    ckStarting: Result:= 'STARTING';
  else
    Result:= '';
  end;
end;

function TWhereField.GetValueByKind: string;
begin
  case FValueKind of
    vkText: Result:= QuotedStr(FValue);
  else
    Result:= FValue;
  end;
end;

{ TWhereFields }

procedure TWhereFields.AddField(const AName, AValue: string;
  AValueKind: TValueKind; ACompareKind: TCompareKind);
var
  WhereField: TWhereField;
begin
  WhereField:= TWhereField.Create;
  with WhereField do
  begin
    Name:= AName;
    Value:= AValue;
    ValueKind:= AValueKind;
    CompareKind:= ACompareKind;
  end;
  FList.Add(WhereField);
end;

procedure TWhereFields.Clear;
var
  I: Integer;
  WhereField: TWhereField;
begin
  for I:= 0 to FList.Count - 1 do
  begin
    WhereField:= TWhereField(FList[I]);
    FreeAndNil(WhereField);
  end;
  FList.Clear;  
end;

constructor TWhereFields.Create;
begin
  FList:= TList.Create;
  FSQL:= TStringList.Create;
  FChangedSQL:= TStringList.Create;
end;

destructor TWhereFields.Destroy;
begin
  Clear;
  FList.Free;
  FSQL.Free;
  FChangedSQL.Free;
  inherited Destroy;
end;

function TWhereFields.GetChangedSQL: TStringList;
const
  WhereClause = 'WHERE';
var
  WhereIndex, OrderByIndex: Integer;
  TempStr: string;
  WhereText: string;
begin
  TempStr:= FSQL.Text;
  WhereText:= GetWhereText;
  if WhereText <> '' then
  begin
    WhereIndex:= Pos(WhereClause, Uppercase(TempStr));
    OrderByIndex:= Pos('ORDER BY', Uppercase(TempStr));

    if WhereIndex > 0 then
      Insert(' ' + WhereText + ' AND ', TempStr, WhereIndex + 5)
    else
      if OrderByIndex > 0 then
        Insert(WhereClause + ' ' + WhereText + #$0D#$0A, TempStr, OrderByIndex)
      else
        TempStr:= TempStr + WhereClause + ' ' + WhereText;
  end;        
  FChangedSQL.Text:= TempStr;
  Result:= FChangedSQL;
end;

function TWhereFields.GetCount: Integer;
begin
  Result:= FList.Count;
end;

function TWhereFields.GetWhereField(const Index: Integer): TWhereField;
begin
  Result:= FList[Index];
end;

function TWhereFields.GetWhereText: string;
var
  I: Integer;
begin
  for I:= 0 to Count - 1 do
  begin
    if Fields[I].Clause <> '' then
      if Result <> '' then
        Result:= Format('%s %s %s', [Result, ' AND ', Fields[I].Clause])
      else
        Result:= Fields[I].Clause;
  end;
  if Result <> '' then
    Result:= '(' + Result + ')';
end;

procedure TWhereFields.SetSQL(const Value: TStringList);
begin
  FSQL.Assign(Value);
end;

end.
En son fduman tarafından 04 Tem 2005 07:46 tarihinde düzenlendi, toplamda 1 kere düzenlendi.
fduman
Moderator
Mesajlar: 2749
Kayıt: 17 Ara 2004 12:02
Konum: Ankara

Mesaj gönderen fduman »

Bunu nasıl kullanıyoruz. Şöyle:

Kod: Tümünü seç

 FBuilder:= TWhereFields.Create;
  FBuilder.SQL.Assign(iqrSearch.SQL);
  with FBuilder do
  begin
    AddField('ADI', '', vkText, ckCONTAINING);
    AddField('SOYADI', '', vkText, ckCONTAINING);
    AddField('SICIL_NO', '', vkNumeric, ckEqual);
    AddField('BOLUM_ADI', '', vkText, ckCONTAINING);
    AddField('AYRILMA_TARIHI', 'NULL', vkNumeric, ckIS);
  end;
iqrSearch isimli bir Query componentinde 'SELECT * FROM HEBE ORDER BY ADI' gibi bir SQL yazılı olsun.

TWhereFields 'a ADI, SOYADI gibi verileri ilk başta girerek hangi field'ların filtrelemeye ne şekilde katılacağını belirtiyoruz.

Mesela "ADI" alanı CONTAINING operatorü ile SQL'e dahil edilecek ve bu bir Text alan, yani string.

Mesela SICIL_NO "=" operatörü ile dahil edilecek ve bu bir sayı gibi..

Sıra geldi bu alanlara filtre değerlerini vermeye. Siz isterseniz arama dialog'undaki ADI ve SICILNo Editbox'larının onchanged eventine şu şekilde kod yazarak hangi değerin filtrede aranacağını bildirebilirsiniz.

Kod: Tümünü seç

procedure TdlgEmployeeSearch.ediAdiChange(Sender: TObject);
begin
  FBuilder[0].Value:= Trim(TEdit(Sender).Text);
end;
0. indexte olan ADI alanına Edit box'daki değeri koyduk.

Kod: Tümünü seç

procedure TdlgEmployeeSearch.ediSicilNoChange(Sender: TObject);
begin
  FBuilder[2].Value:= Trim(TEdit(Sender).Text);
end;
2. indexte olan SICIL_NO alanına edit box değerini koyduk. gibi...

Edit lerden ADI olana "Furkan", sicil no editine de "777" yazdığımızda TWhereFields'in ChangedSQL property si şöyle bir çıktı üretir.

Kod: Tümünü seç

SELECT * FROM HEBE WHERE ADI CONTAINING 'Furkan' AND SICIL_NO=777 ORDER BY ADI
Eğer Adı edit'inin içini silersek şöyle bir çıktı oluşur.

Kod: Tümünü seç

SELECT * FROM HEBE WHERE SICIL_NO=777 ORDER BY ADI

Umarım açıklayıcı olmuştur. Sorularınız olursa buradan sorabilirsiniz.
Kullanıcı avatarı
delphist
Üye
Mesajlar: 595
Kayıt: 05 Oca 2005 04:34

Mesaj gönderen delphist »

s.a. hocam cok guzel bir ornek olmus daha denemedim ama tam istedigim olayi yapmıssınız...Böyle bir filtreleme yapmam gerekiyordu deneyecegim sunu merak ettim ama her harf girildiginde like la arama yapıyormu yoksa sadece yazılan deger kadar mı arama yapıyor onu merak ettım. biraz da acemi oldugumuz icin olup olmadıgını goremedım ben. tesekkurler
fduman
Moderator
Mesajlar: 2749
Kayıt: 17 Ara 2004 12:02
Konum: Ankara

Mesaj gönderen fduman »

Selam.

Bu kod arama yapmıyor. Arama yapabileceğiniz SQL'leri oluşturuyor.

Mesela like ile bir örnek yapalım. ADI alanında baş harfe göre SQL üretsin.

Kod: Tümünü seç

  // FormCreate de oluşturalım.
  FBuilder:= TWhereFields.Create;
  // Query'deki SQL'imizi atayalım
  FBuilder.SQL.Assign(iqrSearch.SQL);

  FBuilder.AddField('ADI', '', vkText, ckLIKE);

procedure TdlgEmployeeSearch.ediAdiChange(Sender: TObject);
begin
   // Adi hangi harf ile başlıyorsa
    FBuilder[0].Value:= Trim(ediAdi.Text) + '%';
   
  // FBuilder'ın ChangedSQL property'si yeni SQL'e göre güncellendi.
  // Yeni SQL'i Query'ye atayalım
  iqrSearch.SQL.Assign(FBuilder.ChangedSQL)
  // Query'mizi kapatıp açalım
  iqrSearch.Close;
  iqrSearch.Open;
end;
voila.. :D
Kullanıcı avatarı
kelaynak2
Üye
Mesajlar: 135
Kayıt: 30 Haz 2003 02:18

Güzel örnek

Mesaj gönderen kelaynak2 »

valla kutuların boş olup olmadığını kontrol edip bir çok dallanma yaparak bir SQL stringi oluşturmaktansa bu component rahat kullanılabilir.
Eline sağlık @coderland :?
Kullanıcı avatarı
vipaydin
Üye
Mesajlar: 82
Kayıt: 23 Ara 2004 04:52

Mesaj gönderen vipaydin »

Hocam Merhaba.

Sitedeki makaleninizi okudum. Benim yapmak istediğimin aynısı. Ama nasıl yapacağımı bilmiyorum.
Benim ekranda tam 8 adet filtreleme var. Kullanıcı hepsini de seçebilir, isterse hiç birini de seçmeyebilir.
Bir türlü SQL String i oluşturamıyorum.
Her alanın sql stringini oluşturuyorum ama aralarına AND atamıyorum. Atıyorum ama kullanıcı o alanı seçmezse bir tane AND fazla oluyor.
Kontrol etmek amacıyla MaskEdit nesnelerinin başlarına CheckBox'lar koydum. Eğer checkbox işaretliyse maskedit in içini sqlstring e almam lazım. Eğer maskedit içi boşsa ve checkbox işaretliyse sqlstring e '' gibi bir ibare konacak. Kullanıcı hem işaretliyor hem de içini boş bırakıyorsa ben ne yapayım? Sonuçta olayı checkbox ın işaretli olup olmamasına bağladım. İşaretliyse where sonuna eklenecek, işaretsiz ise eklenmeyecek.
Makalenizde biraz ağır geldi bana. Biraz açıklayarak anlatabilir misiniz?
Özellikle kodları nasıl kullanacağımı.?

Kolay gelsin, iyi çalışmalar.
Cevapla