การใช้ฐานข้อมูล SQLite กับ Lazarus ในเบื้องต้น (ตอนที่ 1)

  • ตอนก่อนหน้านี้ผมพูดเรื่อง SQLite พร้อมทั้ง tools สำหรับ admin ที่หาได้ในวินโดส์ พร้อมทั้งการ Pump ข้อมูลจากไฟล์ CSV เข้าฐานข้อมูล ต่อไปนี้จะมาดูลึกเข้าไปอีกนิด จะมาเขียนโปรแกรมเพื่อติดต่อกับฐานข้อมูล ด้วย Lazarus
  • ปัญหาของการพัฒนาโปรแกรมฐานข้อมูล SQLite ด้วย Lazarus ก็คือถ้าใช้ object จำพวก Data-aware(แทนฟิลด์)  เช่น  DBNavigator, DBEdit, DBGrid เป็นต้นพวกนี้อิงอยู่กับ Datasource และ Datasource อ้างอิงไปที่ชุด Dataset เช่น Table, Query, StoreProc และสุดท้าย Dataset อ้างอิงไปยัง Database ดูรูปโมเดลด้านล่างแสดงความสัมพันธ์
Database components model.
  • แต่ปัญหาของ SQLite  บน Lazarus นั้นเท่าที่พบใน Lazarus คือใช้ data-aware เช่น วาง DBNavigator, DBEdit, DBGrid ลงบนฟอร์ม วาง Datasource และวาง Dataset คือ TSQLite3Dataset ป้อนชื่อไฟล์ฐานข้อมูลให้กับ TSQLite3Dataset เมื่อทำการแก้ไขเช่นแก้ไขค่าในฟิลด์ ลบ record ทำการ Post ด้วย DBNavigator แต่เมื่อเปิดโปรแกรมมาใหม่ ข้อมูลจะไม่ update ยังเหมือนเดิม อืม………..ผมยังไม่รู้ว่าเป็นที่ Components ติดต่อฐานข้อมูลของ Lazarus หรือว่าเป็นที่ Library ของ SQLite แต่ความคิดผมน่าจะเป็นที่ Lazarus วิธีนี้ไม่ work
  • เอาใหม่วิธีนี้ต้องเขียน code มากขึ้น แต่ถ้าเข้าใจจะเป็นวิธีที่มีประสิทธิภาพมากที่สุด คือสามารถควบคุม บริหารฐานข้อมูลได้ลึกซึ้งกว่า และถ้าเปลี่ยนฐานข้อมูลจาก SQLite ไปเป็นฐานข้อมูลอื่นๆ จะแก้ code น้อยมาก เพราะโ้ค้ดทั้งหมดจะเป็นภาษา SQL ผมเขียนหัวข้อว่าเบื้องต้น แต่ในความเป็นจริงก็ไม่ยากเพราะภาษา SQL เป็นภาษามาตรฐานและง่ายต่อการเรียนรู้

เตรียมฐานข้อมูล SQLite

  • ตอนก่อนหน้านี้ ที่ผมแนะนำ tools ไปแล้วคือ SQLite Administrator เราสร้างไฟล์ฐานข้อมูล ผมตั้งชื่อว่า datums.s3db ตอนนี้มี table อยู่ 1 table คือ Ellipsoidas อนาคตเราจะสร้าง table เพิ่มคือ เส้นโครงแผนที่ (Map Projection) เข้ามาอยู่ด้วยกัน ดูรูปด้านล่าง
SQLite Administrator จัดการฐานข้อมูล

สร้าง New Project

  • เปิด Lazarus คลิกที่เมนู Project > New Project… เลือก Application วาง object เช่น ปุ่ม New…, Delelte…, Apply เป็นต้น ต่อจากนั้น  เราจะวางวัตถุจำพวก data-aware ตามปกติเช่น DBCombobox, DBText, DBEdit แล้ววาง DBNavigator ดูรูปด้านล่างประกอบ
วาง object เช่น controls data-aware ตั้งชื่อดังรูป
  • ตั้งชื่อ unit เป็น main ตั้งชื่อฟอร์มว่า frmSetEllipsoid แล้วแก้ไข uses ดังโค๊ดด้านล่าง อย่าลืมติดตั้งสอง component ที่ผมเคยกล่าวไปแล้วคือ sqlitedb.lpk กับ sqlitelaz.lpk ด้วยเมนูหลักคือ Package > Open package file (*.lpk) ซึ่งสอง component นี้อยู่ในโฟลเดอร์ c:\lazarus\components\sqldb และ c:\lazarus\components\sqlite ตามลำดับ
unit main;
{$mode objfpc}{$H+}

interface

Classes, SysUtils, FileUtil, LResources, Forms, Controls, Graphics, Dialogs,StdCtrls, ExtCtrls, sqldb, DbCtrls, db, SQLite3Conn;

จุดประสงค์ของโปรแกรม

  • ต้องการอ่านฐานข้อมูล SQLite ที่เตรียมไว้มาแสดงบน data-aware ต่างๆ เช่น DBCombobox, DBEdit แล้วทำการ Edit ข้อมูลเช่นเพิ่ม record ลบ record ด้วยการเขียนโค๊ดคุมเป็นหลัก ต่อไปผมจะ post โค๊ดทั้งหมดที่อยู่บนยูนิต main (main.pas)
unit main;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, FileUtil, LResources, Forms, Controls, Graphics, Dialogs,
  StdCtrls, ExtCtrls, sqldb, DbCtrls, db, SQLite3Conn;

type

  { TfrmSetEllipsoid }

  TfrmSetEllipsoid = class(TForm)
    cmdDelete: TButton;
    cmdNew: TButton;
    cmdApply: TButton;
    cmdOK: TButton;
    cmdCancel: TButton;
    dbtxtFlattening: TDBEdit;
    dbtxtSemiMajorAxis: TDBEdit;
    dbtxtEllipsoidCode: TDBEdit;
    DBNavigator1: TDBNavigator;
    dbcboEllipsoidName: TDBComboBox;
    GroupBox1: TGroupBox;
    Image1: TImage;
    Label1: TLabel;
    Label2: TLabel;
    Label3: TLabel;
    Label4: TLabel;

    procedure cmdApplyClick(Sender: TObject);
    procedure cmdCancelClick(Sender: TObject);
    procedure cmdDeleteClick(Sender: TObject);
    procedure cmdNewClick(Sender: TObject);
    procedure cmdOKClick(Sender: TObject);
    procedure FormClose(Sender: TObject; var CloseAction: TCloseAction);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    { private declarations }
    FDBCon : TSQLite3Connection;
    FTrans : TSQLTransaction;
    FSql : TSQLQuery;
    FDatasource : TDatasource;
    FDatabaseFile : string;
    FNewRecordState : boolean;
  public
    { public declarations }
  end;

var
  frmSetEllipsoid: TfrmSetEllipsoid;

implementation

{ TfrmSetEllipsoid }

procedure TfrmSetEllipsoid.cmdOKClick(Sender: TObject);
begin
  FSql.Close;
  FDBCon.Close;
  FTrans.EndTransaction;
  Close;
end;


procedure TfrmSetEllipsoid.FormClose(Sender: TObject;
  var CloseAction: TCloseAction);
begin
  FTrans.EndTransaction;
  FSql.Close;
  FDBCon.Close;
end;

procedure TfrmSetEllipsoid.FormCreate(Sender: TObject);
begin
  //เปลี่ยน path ที่อยู่ของฐานข้อมูลให้ตรงกับโฟลเดอร์ที่เก็บ
  FDatabaseFile := 'e:\sourcecodes\lazarus\sqliteproj2\datums.s3db';
  //FDatabaseFile := '/media/PJ320GMISC/SourceCodes/Lazarus/SQLiteProj2/datums.s3db';

  FDBCon := TSQLite3Connection.Create(NIL);
  FTrans := TSQLTransaction.Create(NIL);
  FSql := TSQLQuery.Create(NIL);
  FDatasource := TDatasource.Create(NIL);

  FDBCon.DatabaseName := FDatabaseFile;
  FDBCon.Transaction := FTrans;
  FTrans.DataBase := FDBCon;
  FSql.DataBase := FDBCon;
  FSql.Transaction := FTrans;
  FDBCon.Connected := true;
  FTrans.Active := true;
  FDatasource.Dataset := FSql;
  FDatasource.AutoEdit := true;

  FSql.Close;
  FSql.SQL.Clear;
  FSql.SQL.Text := 'SELECT * FROM ELLIPSOIDS';
  FSql.Open;
  //FTrans.CommitRetaining; {This line does not work in win32. But OK in linux???}

  dbcboEllipsoidName.DataSource := FDatasource;
  dbcboEllipsoidName.DataField := 'NAME';
  dbtxtEllipsoidCode.DataSource := FDatasource;
  dbtxtEllipsoidCode.DataField := 'CODE';
  dbtxtSemiMajorAxis.DataSource := FDatasource;
  dbtxtSemiMajorAxis.DataField := 'A';
  dbtxtFlattening.DataSource := FDatasource;
  dbtxtFlattening.DataField := 'INV_F';
  DBNavigator1.DataSource := FDatasource;

  FNewRecordState := false;
end;

procedure TfrmSetEllipsoid.FormDestroy(Sender: TObject);
begin
  FTrans.Free;
  FSql.Free;
  FDatasource.Free;
  //Make sure TSQLite3Connection is the last.
  FDBCon.Free;
end;

procedure TfrmSetEllipsoid.cmdCancelClick(Sender: TObject);
begin
  Close;
end;

procedure TfrmSetEllipsoid.cmdDeleteClick(Sender: TObject);
var
  szECode : string;
  sql : TSQLQuery;

begin
  try
    sql := TSQLQuery.Create(nil);
    sql.Database := FDBCon;
    sql.Transaction := FTrans;
    szECode := dbtxtEllipsoidCode.Text;

    if (MessageDlg ('คำเตือน','ยืนยันว่าต้องการลบทรงรี', mtConfirmation,
               [mbOK, mbCancel],0) = mrOK) then
    begin
      sql.Close;
      sql.SQL.Text := 'DELETE FROM ELLIPSOIDS WHERE CODE = :CODE';
      sql.Params.ParamByName('CODE').AsString := szECode;
      sql.ExecSQL;
      FSql.ApplyUpdates;
      FTrans.CommitRetaining;
      FSql.Close;
      FSql.SQL.Clear;
      FSql.SQL.Text := 'SELECT * FROM ELLIPSOIDS';
      FSql.Open;
      FTrans.CommitRetaining;
  end;

  finally
    sql.Free;
  end;
end;

procedure TfrmSetEllipsoid.cmdNewClick(Sender: TObject);
begin
  //Make its to ready for editable.
  dbcboEllipsoidName.ReadOnly := false;
  dbtxtSemiMajorAxis.ReadOnly := false;
  dbtxtflattening.ReadOnly := false;
  dbtxtEllipsoidCode.ReadOnly := false;
  dbcboEllipsoidName.Text := '';
  dbtxtSemiMajorAxis.Text := '';
  dbtxtFlattening.Text := '';
  dbtxtEllipsoidCode.Text := '';
  cmdApply.Enabled := true;
  cmdNew.Enabled := false;
  DBNavigator1.BtnClick(nbInsert);
  FNewRecordState := true;
end;


//The big problem that DBNavigator1.Post method doesn't work.
//using query to solve the problem with ExeSql method.
procedure TfrmSetEllipsoid.cmdApplyClick(Sender: TObject);
var
  szEName, szECode : string;
  dA, dB, dF : extended;
  f1, f2 : boolean;
  sql : TSQLQuery;
begin
  try
    sql := TSQLQuery.Create(nil);
    sql.Database := FDBCon;
    sql.Transaction := FTrans;
    szEName := trim(dbcboEllipsoidName.Text);
    szECode := trim(dbtxtEllipsoidCode.Text);
    if (szEName <> '') and (szECode <> '') then
    begin
      szECode := Trim(dbtxtEllipsoidCode.Text);
      f1 := trystrtofloat(dbtxtSemiMajorAxis.Text, dA);
      f2 := trystrtofloat(dbtxtFlattening.Text, dF);
      dB := dA - dA * 1 / dF;
      if (FNewRecordState) then
      begin
        if (f1 and (dA > 6377000)) and (f2 and (df > 293)) then
        begin
          sql.Close;
          sql.SQL.Clear;
          sql.SQL.Text := 'INSERT INTO ELLIPSOIDS (NAME,CODE,A,B,INV_F)'
                        + ' VALUES (:NAME,:CODE,:A,:B,:INV_F)';
          sql.Params.ParamByName('NAME').AsString := szEName;
          sql.Params.ParamByName('CODE').AsString := szECode;
          sql.Params.ParamByName('A').AsFloat := dA;
          sql.Params.ParamByName('B').AsFloat := dB;
          sql.Params.ParamByName('INV_F').AsFloat := dF;

          sql.ExecSQL;
          cmdApply.Enabled := False;
          cmdNew.Enabled := true;
          FTrans.CommitRetaining;
          FNewRecordState := false;
        end
        else if not(f1 and (dA > 6377000)) then
        begin
          MessageDlg ('Warning', 'Semi-major axis must be the number and its value must > 6,377,000 m.?', mtConfirmation,
                     [mbOK],0);
          dbtxtSemiMajorAxis.SetFocus;
        end
        else if not (f2 and (df > 297)) then
        begin
          MessageDlg ('Warning', 'Flattening must be the number and its value must > 297 m.?', mtConfirmation,
                     [mbOK],0);
          dbtxtFlattening.SetFocus;
        end;
      end
      else
      begin
        sql.Close;
        sql.SQL.Clear;
        sql.SQL.Text := 'UPDATE ELLIPSOIDS SET NAME=:NAME,'
                      + 'A=:A,B=:B,INV_F=:INV_F WHERE CODE=:CODE';
        sql.Params.ParamByName('NAME').AsString := szEName;
        sql.Params.ParamByName('CODE').AsString := szECode;
        sql.Params.ParamByName('A').AsFloat := dA;
        sql.Params.ParamByName('B').AsFloat := dB;
        sql.Params.ParamByName('INV_F').AsFloat := dF;

        sql.ExecSQL;
        cmdApply.Enabled := False;
        FTrans.CommitRetaining;

      end;
    end;
    finally
      sql.Free;
    end;
end;

initialization
  {$I main.lrs}

end.
  • ติดตามตอนหน้าผมจะอธิบายโค๊ดที่สำคัญ เราจะทดลองเพิ่มและลบ record ที่อยู่ในฐานข้อมูลดู

ทิ้งท้ายก่อนจากกัน

  • ที่ผ่านมาผมเขียนถึงเรื่อง GIS Components ที่จะหานำมาพัฒนาโปรแกรม Desktop application ด้าน GIS สุดท้ายมาพบกับ Quantum GIS แต่มีปัญหาว่าโค๊ดเป็นภาษา C++ ซึ่งการแปลง header file ของ C++ ไม่ใช่เรื่องง่ายสำหรับผมซึ๋งไม่กระดิก C++ เลย และจำนวนไฟล์ header ก็มีมาก เลยลดเกรดลงมาดูจำพวก library ทีี่เกี่ยวข้องกับด้าน GIS ที่ดังๆ ก็คือ GDAL/OGR ชื่อเต็มๆก็คือ Geospatial Data Abstraction Library ที่จริงรู้จักตั้งนานแล้วแต่ไม่ใช่ในฐานะโปรแกรมมิ่ง GDAL/OGR ถูกนำไปใช้ด้วยโปรแกรมดังๆมากมาย เช่น ArcGIS, Google Earth, Minisota Mapserver, MapWindow, FWTools,QGIS และอื่นๆอีกหลายโปรแกรม เป็น library ที่นำมาจัดการกับพวก Raster file (GDAL) และ Vector file (OGR) ผมดูใน website ของ GDAL พบว่ามีคนเขียนด้วย VB6 ด้วยการ wrap ขึ้นเป็น class ผมนำมาแปลงเป็นโค๊ดของ Lazarus ช่วงนี้อยู่ระหว่างการทดสอบ แต่ก็ผ่านไปแล้วประมาณ 80% ว่างๆจะนำมาแนะนำกัน
  • คือในโลกของ Opensource เป็นโลกแห่งการ share กัน และร่วมพัฒนาต่อยอดขึ้นไปอีก GDAL/OGR ทีมพัฒนานำโดยคุณ Frank Warmerdam เป็นคนที่มีชื่อเสียงมาก ผมลองค้นคำว่า Frank Warmerdam GDAL บน Google พบจำนวน record ทั้งหมดประมาณ 75,000 ไม่ธรรมดาเลย ต่อไปลองไปดู plunins ของ Quantum GIS ที่ใช้ไลบรารีของ GDAL/OGR ที่ผมเปิดให้ดูเป็น OGR ใช้แปลง vector file ผมลองแปลงจาก Shape file ไปเป็น Sqlite แล้วเปิดด้วย QGIS ก็สามารถเปิดได้ ดูรูปด้านล่าง
การแปลง vector file ด้วยการใช้ plugins ของ GDAL/OGR บน QGIS
  • ตอนต่อๆไปผมจะำนำไลบรารี GDAL/OGR มารันด้วย Lazarus และจะลองเขียนโปรแกรมแปลงฟอร์แม็ตของ Raster/Vector ดู ด้วยการใช้สุดยอดไลบรารีตัวนี้ครับ

Leave a Reply

Your email address will not be published. Required fields are marked *