C++Builder Programming Forum
C++Builder  |  Delphi  |  FireMonkey  |  C/C++  |  Free Pascal  |  Firebird
볼랜드포럼 BorlandForum
 경고! 게시물 작성자의 사전 허락없는 메일주소 추출행위 절대 금지
C++빌더 포럼
Q & A
FAQ
팁&트릭
강좌/문서
자료실
컴포넌트/라이브러리
메신저 프로젝트
볼랜드포럼 홈
헤드라인 뉴스
IT 뉴스
공지사항
자유게시판
해피 브레이크
공동 프로젝트
구인/구직
회원 장터
건의사항
운영진 게시판
회원 메뉴
북마크
볼랜드포럼 광고 모집

C++빌더 팁&트릭
C++Builder Programming Tip&Tricks
[132] [팁] Database Programming 입문
박지훈.임프 [cbuilder] 10885 읽음    2001-07-30 16:23
이 팁은 담비님(천리안 FREKBS)님이 1999년 04월 06일에 천리안 프로그래머포럼에 올리신 것입니다.
담비님으로부터는 전제하여 올리는 데 대해 허락을 받았습니다.
좋은 정보를 공유하도록 허락해주신 담비님께 감사드립니다.

───────────────────────────────────────

안녕하세요! 담비입니다.

출처 : http://www.inprise.co.jp/tips/cbuilder/cb002/index.html

Borland C++ Builder를 이용한 초보적인 DB 프로그래밍에 관하여 설명하였다.
처음 DB를 사용해 프로그래밍을 하는분은 참고하기 바란다. 더불어 C++ Builder
사용자 가이드와 개발자 가이드도 함께 읽어주길 바란다.

1. TSession

Borland Database Engine(BDE)에는 Session이라는 Database의 접속을 관리하는
기구가 있다. 이 기구를 클래스화 시킨것이 TSession 클래스이다. 통상 TSession
클래스를 사용하는것은 없지만, Paradox의 네트워크 콘트롤 파일의 제어와 thread를
사용한 프로그램에는 TSession을 사용한 Database의 접속을 관리한다.
TSession에는 Paradox의 네트워크 콘트롤 파일의 디렉토리를 나타내는 NetFileDir,
일시적인 파일의 저장 디렉토리를 표시하는 PrivateDir, Session의 개시, 종료를
제어하는 Active, 보존하고 있는 DataSet이 전부 닫힌 경우에도 Database 접속을
유지할것인가를 결정하는 KeepConnections, 또 Session을 식별하기 위해 SessinName
프로퍼티가 있다.
TSession을 사용한 구체적인 예로 개별적인 thread를 사용해 SQL을 실행하는 예를
소개한다.

VCL로 therad를 사용하는것에는 TTherad 클래스로부터 파생한 클래스를 작성하여
Execute 메소드를 Overwrite한다. TTherad 클래스는, instance가 생성되면
Execute 메소드가 호출된다.(TTherad 생성자의 인수로 true를 건네주었을 때는
Execute 메소드가 호출되지 않는다. Execute 메소드를 생성자의 실행후에 실행시키는
경우에는 TThread로부터 파생한 클래스의 생성자의 최후로 Resume 메소드를
호출하도록 한다.)

Database를 사용하는 thread 클래스로서 TThread로부터 TDBThread 클래스를
파생시킨다.  또, 생성자와 Execute 메소드를 정의해 일어난다.
   class TDBThread : public TThread {
     protected:
       void __fastcall Execute();
     public:
        __fastcall TDBThread();
   };

생성자에는 파생측의 클래스인 생성자를 Execute 메소드가 실행되도록 false를
인수로하여 호출해야 한다.
   __fastcall TDBThread::TDBThread() : TThread( false )
   {
   }

Execute 메소드는 thread가 개시되고서 호출되어지기 때문에 thread로 실행하기
원하는 내용은 TThread로부터 파생시킨 클래스가 Override한 Execute 메소드중에
기술한다. thread중에서는 새로운 Session을 작성해야만 한다.
Application에는 Sessions 변수가 있고, 모든 Session을 관리한다.(디폴트로는
Default라는 SessenName의 Session이 동작한다.)
Sessins 변수는 TSessionList 클래스의 인스탄스로 OpenSession 메소드가 준비되어
있다. OpenSession 메소드는 세션명을 건네주어 세션이 작성된 상태라면 Active
상태로 만들고, 세션이 작성되지 않은 상태라면 세션을 작성하여 Active상태가
되게한다. 또 Database를 편집하기 위해 TQuery를 작성하여 새로 작성된 Session을
set한다. TQuery의 각각의 인스탄스를 보존하기 위한 변수를 TDBThread의
private부에 정의한다.
   void __fastcall TDBThread::Execute()
   {
    thSession = Sessions->OpenSession("THSESSION");
    thQuery = new TQuery( NULL );
    thQuery->SessionName = "THSESSION";
    thQuery->DatabaseName = "BCDEMOS";
   }

위의 예에서도 TQuery::SessionName에는 TSessionList::OpenSesion에 건내준 Session
명을 건네준다. 또 실제로 Database를 작성한 알리아스로 TQuery의 DatabaseName에
BCDEMOS를 설정한다. 실제로 테이블을 작성하여 데이타를 등록하는 함수를
TDBThread의 메소드로 작성한다.
   void __fastcall TDBThread::MyExecSQL()
   {
    TStrings *s = thQuery->SQL;
    s->Add( "CRETE TABLE \"THTEST.DB\" (" );
    s->Add( "  Field1 SMALLINT," );
    s->Add( "  Field2 INTEGER," );
    s->Add( "  Field3 CHAR(10)," );
    s->Add( "  Field4 NUMERIC(3,2)," );
    s->Add( "  PRIMARY KEY(Field1) )" );
    thQuery->ExecSQL();
    s->Clear();
    s->Add( "INSERT INTO \"THTEST.DB\" (Field1, Field2, Field3, Field4)" );
    s->Add( " VALUES( :Data1, :Data2, :Data3, :Data4)" );
    for( int i=1; i<500; i++ )
    {
     thQuery->Params->ParamValues[ "Data1" ] = (short)i;
     thQuery->Params->ParamValues[ "Data2" ] = i*2;
     thQuery->Params->ParamValues[ "Data3" ] = "Test" + IntToStr( i );
     thQuery->Params->ParamValues[ "Data4" ] = (float)i/3;
     thQuery->ExecSQL();
     if( Terminated )
        return;
    }
   }

코드를 분석해보면 시간이 걸리는 데이터의 등록을 하고있지만, thread중의 함수이기
위해 메세지를 처리하는 함수를 호출하고 있지 않다. Execute를 Override한 함수로는
이 함수를 호출하도록 해 thread를 종료하는 메소드인 Terminate 메소드를 호출하고
있다.

또, therad가 종료될 때, 클래스 자신을 파기시키는 것이 가능하다. TDBThread
클래스의 생성자에서 FreeOnTerminate 프로퍼티를 true로 함으로서 thread의 종료시
thread의 인스탄스가 파기된다.(TThread의 생성자를 true를 인수로 하여 호출하여
TDBThread의 생성자에서 Resume 메소드를 호출하도록 변경한다.) 다시 생성한
TQuery의 인스탄스를 TDBThread의 파괴자에서 파기한다.
      class TDBThread : public TThread {
      private:
        TSession *thSession;
        TQuery   *thQuery;
      protected:
        void __fastcall Execute();
        virtual void __fastcall MyExecSQL();
      public:
        __fastcall TDBThread();
        __fastcall ~TDBThread();
        __property TQuery *Q = {read=thQuery};
        __property Terminated;
      };

    __fastcall TDBThread::TDBThread() :
               thSession(0), thQuery(0), TThread( true )
    {
      FreeOnTerminate = true;
      Resume();
    }

    __fastcall TDBThread::TDBThread()
    {
      if( thQuery )
        delete thQuery;
    }

    void __fastcall TDBThread::Execute()
    {
      thSession = Sessions->OpenSession( "THSESSION" );
      thQuery = new TQuery( NULL );
      thQuery->SessionName = "THSESSION";
      thQuery->DatabaseName = "BCDEMOS";
      MyExecSQL();
      Terminate();
    }

막상, 이 클래스를 사용하는 프로그램에서는, TDBThread의 인스탄스를 생성한것만
으로 thread가 생성된 테이블이 작성된 코드가 추가된다.  새로운 인스탄스로
실행되고 있는 것으로 프로그램측의 제어가 빼앗기는것 없이 처리를 진행한다.

2. TDatabase

TDabase는 C/S 어플리케이션에서 중요한 요소를 제어하고 관리한다. Database
컴포넌트를 명시적으로 작성하고 있지 않은 경우에 데이타베이스 내의 테이블을
열면 C++ Builder는 일싲거인 TDatabase 컴퍼넌트를 작성한다.
통상 TDatabase를 사용하는것은 없지만, DataSet 컴퍼넌트를 이용할 수 있는
데이타베이스 접속명을 지정하는 DatabaseName, BDE 환경 설정 유틸리티로 정의된
기존의 BDE 알리아스 이름을 지정하는 AliasName, TDatabase 내의 액티브한 Data Set
에 대한 참조의 배열인 DataSets, 엑티브한 데이터셋의 수를 지정하는 DatasetCount,
데이타베이스를 열거나 닫는 Connected, 데이타베이스를 열때마다 서버에 Login하지
않도록 하는 KeepConnecton, 트랜잭션을 개시하는 StartTransaction, 변경을
제어하는 RollBack과 Commit, 트랜잭션 고립레벨을 지정하는 TransIsolatin등이
있다.

알리아스는 데이타베이스 서버의 데이타베이스 테이블과 접속 파라미터의 위치를
지정한다. 통상 C++ Builder의 외부에서 알리아스를 작성하기에는 BDE 환경 설정
유틸리티인 BDECFG32.EXE를 사용한다. 알리아스는 BDE 환경 설정 파일 IDAPI32.CFG
에 저장된다. 자세한 내용은 BDE 환경 설정 유틸리티의 헬프를 참조한다.
TDatabase를 사용하면, 개발중의 어플리케이션에서만 사용할 수 있는 알리아스를
새로이 작성할 수 있다. TDatabase에서 작성한 알리아스는 BDE환경 설정 파일인
IDAPI32.CFG에는 추가되지 않는다.

여기서는, TDatabase와 TTable을 사용한 간단한 예를 소개한다.

  TDatabase *Database1 = new TDatabase( this );

TDatabase 알리아스를 작성하는것에는, 값을 그 DatabaseName 프로퍼티에 할당한다.
그렇게 하면, 어느 DataSet 컴포넌트로부터도 DatabaseName을 지정하는것만으로
Local alias를 사용할 수 있다.

  Database1->DatabaseName = "DatabaseA";

DriverName은 STANDARD (dBase와 Paradox용), ORACLE, SYBASE, INFORMIX, INTERBASE
등의 BDE 드라이버의 이름이다. 드라이버의 종류를 지정하는 AliasName이 설정되어
있으면, 이 프로퍼티는 clear된다.

  Database1->DriverName = "STANDARD";

데이타베이스를 열 때마다 서버에 Login하지 않도록 하기 위해서 true를 설정한다.

  Database1->KeepConnection = true;

데이타베이스 서버에 Login할 때 사용자명과 암호가 필요하기 때문에, LoginPrompt를
true로 설정한다.

  Database1->LoginPrompt = true;

SessionName은 데이타베이스에서 사용하는 세션 컴포넌트를 식별하는 문자열이다.

   Database1->SessionName = "Default";

   Database1->Tag = 0;

TransIsolation 프로퍼티는 SQL 서로에서 사용하는 트랜잭션 고립 레벨을 지정한다.
tiDirtyRead는 다른 동시적인 트랜잭션에 의해 일어난 데이타베이스에의 수행되지
못한 수정을 읽게 한다. 수행되지 못한 수정은 상구적이지 않고 언제든지 롤백 될
수 있다. 이 레벨에서는 다른 트랜잭션에 의한 수정으로 부터 최소한 구분된다.
tiReadCommitted는 다른 동시적인 트랜잭션이 일으킨 수정 중 수행된(항구적인)
수정만을 읽을 수 있게 한다. 이것이 기본 구분 레벨이다. tiRepeatableRead는
하나의, 한번의 데이타베이스 읽기만을 허용한다. 트랜잭션은 다른 동시적인
트랜잭션에 의해 이루어진 순차적인 수정을 볼 수 없다. 이 구분 레벨은 일단
어플리케이션, 이 레코드를 읽으면 그 레코드의 뷰가 바뀌지 않음을 보장한다.
이 레벨에서 트랜잭션은 다른 동시적인 트랜잭션으로 부터 가장 구분된다.
Paradox 또는 dBase에 대한 로컬 트랜잭션의 경우, TransIsolatin을 tiDrityRead에
설정할 필요가 있다. tiDrityRead에 설정하지 않으면 예외가 발생한다.

  Database1->TransIsolation = tiDirtyRead;

Params 프로퍼티에는 SQL 서버의 데이타베이스를 열기 위혀서 필요한 파라메타가
들어있다. 디폴트 설정으로 이것들의 파라메타는 BDE 환경 설정 유틸리티에서
지정한다.파라메타를 어플리케이션 전용의 알리아스용에 커스터마이즈하기에는
데이타베이스 파라메타 에디터를 사용한다.
  Database1->Params->Values["PATH"] = "c:\\CBuilder\\Examples\\Data";
  Database1->Params->Values["ENABLE BCD"] = "FALSE";
  Database1->Params->Values["DEFAULT DRIVER"] = "PARADOX";

접속을 가능하게 한다.
  Database1->Connected = true;

TTable의 설정을 한다.
  TTable *Table1 = new TTable( this );

Table1의 DatabaseName에 조금 전에 작성한 DatabaseA를 지정한다. 그러면, Table1의
TableName에는 BCDEMOS와 같은 TableName을 지정할 수 있다.
   Table1->DatabaseName = "DatabaseA";
   Table1->TableName = "BIOLIFE.DB";

3. TTable

TTable 컴포넌트는 엑세스의 대상이 되는 데이타베이스 Table을 지정하는 것으로
DataSet을 작성한다.
디폴트로, TTable 컴포넌트는 Activ하게 되면 table의 모든 열에 Access한다.
TDBEdit등의 데이타 콘트롤 컴포넌트는 TTable 오브젝트에 대응하게 되며,
table 내의 임의의 항목을 표시할 수 있다. TDBGrid 등의 복수열의 비주얼
컴포넌트는 table의 TField List를 사용해 table 내의 모든 열에 Access해 표시한다.
form상의 TTable 컴포넌트를 더블 클릭하면, 항목 에디터가 기동된다. 항목 에디터는
데이타 콘트롤 컴포넌트가 데이타를 표시하는 방법을 지정한다.
여기에서는 Paradox table을 사용할 때의 master link를 사용한 TTable의 간단한
사용예를 소개해, TTAble의 프로퍼티의 설정을 설명한다.
이 예는, 제품을 구분하고 있는 구분 정보 table과, 제품을 관리하고 있는 제품
정보 table을 이용해 개발자 table을 등록한다.

먼저 Database를 설정한다.
   Database1->Close();
   Database1->DatabaseName = "MasterlinkDtb";
   Database1->DriverName = "STANDARD";
   Database1->TransIsolation = tiDirtyRead;

Path의 설정에는 상대 패스명도 지정할 수 있다. 여기에서는 실행하고 있는
어플리케이션 밑의 masterlink디렉토리를 가리킨다.
   Database1->Params->Values["PATH"] = "MasterLink";
   Database1->Params->Values["ENABLE BCD"] = "FALSE";
   Database1->Params->Values["DEFAULT DRIVER"] = "PARADOX";
   Database1->Open();

다음에 table을 3개 작성한다.
제품을 구분하는 구분 정보의 Division.db table과 제품 정보의 Product.db table,
개발자와 사용하고 있는 제품을 관리하는 Developer.db를 작성한다.

Division (제품 구분) table의 작성
─────────────────
table의 작성중에는 DataSet을 닫는다.
DatabaseName에 조금 전에 작성한 MasterlinkDtb를 설정한다.
  DivisionTbl->Close();
  DivisionTbl->DatabaseName = "MasterlinkDtb";
  DivisionTbl->TableName = "Division.db";
  DivisionTbl->TableType = ttDefault;
  DivisionTbl->FieldDefs->Clear();

항목을 작성할 때는 FieldDefs 프로퍼티를 사용한다.
FieldDefs 프로퍼티 메소드의 Add를 사용해 항목명을 추가해 간다.
Division(제품구분) table은, 문자형의 항목을 2개 가진다. 구분 번호의
Division Number, 구분명의 Division Name이다.
  DivisionTbl->FieldDefs->Add( "Division Number", ftString, 5, true );
  DivisionTbl->FieldDefs->Add( "Division Name", ftString, 30, true);
  DivisionTbl->IndexDefs->Clear();

인덱스의 작성은 IndexDefs 프로퍼티를 사용한다.
FieldDefs 프로퍼티의 메소드의 Add를 사용해 인덱스명을 지정한다.
인덱스를 DivisionNumberIdx라는 이름으로 작성한다.
  DivisionTbl->IndexDefs->Add( "DivisionNumberIdx", "Division Number",
                               TIndexOptions() << ixPrimary << ixUnique );

DataSet을 작성해 Active하게 한다.
  DivisionTbl->CreateTable();
  DivisionTbl->Open();

Product(제품정보) table의 작성
───────────────
  ProductTbl->Close();
  ProductTbl->DatabaseName = "MasterlinkDtb";
  ProductTbl->TableName = "Product.db";
  ProductTbl->TableType = ttDefault;
  ProductTbl->FieldDefs->Clear();

Product(제품 정보) table은, 문자형의 항목 3개를 가진다.
제품 번호의 Product Number, 제품명의 Product Name, 구분번호의 Division Number
이다.
  ProductTbl->FieldDefs->Add( "Product Number", ftString, 5, true );
  ProductTbl->FieldDefs->Add( "Product Name", ftString, 30, true);
  ProductTbl->FieldDefs->Add( "Division Number", ftString, 5, true );
  ProductTbl->IndexDefs->Clear();

인덱스를 ProductNumberIdx라는 이름으로 작성한다.
  ProductTbl->IndexDefs->Add( "ProductNumberIdx", "Product Number",
                                TIndexOptions() << ixPrimary << ixUnique );

2차 인덱스를 DivisionNumberIdx라는 이름으로 작성한다.
  ProductTbl->IndexDefs->Add( "DivisionNumberIdx", "Division Number",
                                TIndexOptions() );

DataSet을 작성하고 Active하게 한다.
  ProductTbl->CreateTable();
  ProductTbl->Open();

Developer(개발자) table의 작성
───────────────
  DeveloperTbl->Close();
  DeveloperTbl->DatabaseName = "MasterlinkDtb";
  DeveloperTbl->TableName = "Product.db";
  DeveloperTbl->TableType = ttDefault;
  DeveloperTbl->FieldDefs->Clear();

Developer(개발자) table은, 문자형의 항목을 3개 가진다.
개발자명의 Developer Name, 구분명의 Division Name, 제품명의 Product Name이다.
  DeveloperTbl->FieldDefs->Add( "Product Number", ftString, 5, true );
  DeveloperTbl->FieldDefs->Add( "Product Name", ftString, 30, true);
  DeveloperTbl->FieldDefs->Add( "Division Number", ftString, 5, true );

DataSet을 작성한다.
  DeveloperTbl->CreateTable();
  DeveloperTbl->Open();

레코드의 추가는 FieldByName 메소드를 사용한다.
FieldByName에 항목명을 설정해 데이타를 입력한다.
대입후에는 Post를 호출하여 데이타의 갱신을 확정한다.
Division(제품구분) table에 3개의 레코드를 추가한다.
첫번째 레코드는
  DivisionTbl->Append();
  DivisionTbl->FieldByName( "Division Number" )->AsString = "D0001";
  DivisionTbl->FieldByName( "Division Name" )->AsString = "개발 언어";
  DivisionTbl->Post();

두번째 레코드는
  DivisionTbl->Append();
  DivisionTbl->FieldByName( "Division Number" )->AsString = "D0002";
  DivisionTbl->FieldByName( "Division Name" )->AsString = "데이타베이스";
  DivisionTbl->Post();

세번째 레코드는
  DivisionTbl->Append();
  DivisionTbl->FieldByName( "Division Number" )->AsString = "D0003";
  DivisionTbl->FieldByName( "Division Name" )->AsString = "스프레드쉬트";
  DivisionTbl->Post();

Product(제품정보) table에 5개의 레코드를 추가한다.
  ProductTbl->Append();
  ProductTbl->FieldByName( "Product Number" )->AsString = "P0001";
  ProductTbl->FieldByName( "Product Name" )->AsString = "Delphi";
  ProductTbl->FieldByName( "Division Number" )->AsString = "D0001";
  ProductTbl->Post();

  ProductTbl->Append();
  ProductTbl->FieldByName( "Product Number" )->AsString = "P0002";
  ProductTbl->FieldByName( "Product Name" )->AsString = "Borland C++";
  ProductTbl->FieldByName( "Division Number" )->AsString = "D0001";
  ProductTbl->Post();

  ProductTbl->Append();
  ProductTbl->FieldByName( "Product Number" )->AsString = "P0003";
  ProductTbl->FieldByName( "Product Name" )->AsString = "Paradox";
  ProductTbl->FieldByName( "Division Number" )->AsString = "D0002";
  ProductTbl->Post();

  ProductTbl->Append();
  ProductTbl->FieldByName( "Product Number" )->AsString = "P0004";
  ProductTbl->FieldByName( "Product Name" )->AsString = "dBase";
  ProductTbl->FieldByName( "Division Number" )->AsString = "D0002";
  ProductTbl->Post();

  ProductTbl->Append();
  ProductTbl->FieldByName( "Product Number" )->AsString = "P0005";
  ProductTbl->FieldByName( "Product Name" )->AsString = "Quattro Pro";
  ProductTbl->FieldByName( "Division Number" )->AsString = "D0003";
  ProductTbl->Post();

DataSource의 작성
─────────
각각의 table에 대응하는 DataSource를 3개 작성한다.
  DivisionDts->DataSet = DivisionTbl;     //구분 정보용
  ProductDts->DataSet = ProductTbl;       //제품 정보용
  DeveloperDts->DataSet = DeveloperTbl;   //개발자용

MasterLink의 설정
────────
Procuct(제품 정보) table에 마스터 링크를 설정한다.
이 table의 항목 Division Number(구분번호)는 Division(구분정보) table의
Division Number(구분 번호)와 일치한다. Division(구분정보) table의 Division
Number(구분 번호)에 일치하지 않는 제품 정보를 Developer(개발자) table에
넣지 않도록 하기 위해서 마스터 링크를 설정한다.
MasterSource에 구분 정보의 DataSource의 DivisionDts를 설정한다.
MasterFields에는 Division Number(구분 번호)를 설정한다.
IndexName은, ProductTbl(제품정보) table의 2차 인텍스의 DivisionNumberIdx를
설정한다.
  ProductTbl->MasterSource = DivisionDts;
  ProductTbl->MasterFields = "Division Number";
  ProductTbl->IndexName = "DivisionNumberIdx";

Developer(개발자) table에 데이타를 입력할 때에 DBLookupComboBox를 2개 사용한다.
Division Name(구분명)의 입력에 DivisionDlcb와 Product Name(제품명)의 입력에
ProductDlcb를 준비한다.
  DivisionDlcb->DataSource =  DeveloperDts;
  DivisionDlcb->DataField  = "Division Name";
  ProductDlcb->DataSource =  DeveloperDts;
  ProductDlcb->DataField  = "Product Name";

각각의 DBLookupComboBox의 ListSource와 ListField의 설정을 하는것으로
Pull-down에 표시되는 데이터를 지정한다.
  DivisionDlcb->ListSource =  DivisionDts;
  DivisionDlcb->ListField  = "Division Name";
  DivisionDlcb->KeyField   = "Division Name";
  ProductDlcb->ListSource =  ProductDts;
  ProductDlcb->ListField  = "Product Name";
  ProductDlcb->KeyField   = "Product Name";

다음은 Developer(개발자) table의 Developer Name(개발자명)에 사용하는 DBEdit와,
Developer(개발자) table의 데이터를 확인하기 위해서 DBGrid와, 데이터의 편집을
하기 위해서 DBNavigator를 준비시킬수 있으며 마스터링크를 이용한 데이터 편집이
가능하게 된다.
각각의 DBLookupComboBox에서의 선택값이 Developer table에 반영된다.

4. TQuery

TTable은 데이터를 클라이언트에서 참조하는데 반해서, TQuery는 서버상에서 데이터
로의 Access를 하고, 결과를 Client에게 돌려주는 구조로 되어있다. 결국, C/S 형의
데이타베이스에는 TQuery를 사용하는 편이 보다 효율적이다.
(C/S형의 데이타베이스에 TTable을 사용할 수도 있지만 데이타를 클라이언트에
로드하기때문에 Access가 상당히 늦게 된다. 예로 해당하는 레코드의 건수를
조사하는 것은, 모든 데이타가 네트워크에 전해져 클라이언트까지 흐르는 것을
의미한다. TQuery에서는 서버상에서 데이타가 로드되고, 결과만이 클라이언트로
전해진다. 로컬 데이타베이스에 대해 TQuery를 사용할 수도 있다. 이것은 Local SQL
이라 불리운다.)

TQuery는 TTable과 다름이 없는 TDBDataSet으로부터 파생된 클래스이다.(TDBDataSet
클래스는 TDataSet으로부터 파생되고 있다.) TQuery는 SQL문을 실행하는 것으로
데이타베이스에 Access하는 클래스로, SQL문을 지정하기 위한 프로퍼티로 TStrings
형의 SQL 프로퍼티와 TParams 형의 Params 프로퍼티가 있다.

SQL 프로퍼티에는 순수하게 SQL문을 기술한다. 이 프로퍼티는, __published 섹션에서
정의도어 있으므로 디자인시에도 편집이 가능하다. 오브젝트인스펙터상의 SQL
프로퍼티의 값(..)을 클릭하는 것으로 "문자열 리스트의 설정" 다잉얼로그 박스가
표시된다. 이 다이얼로그 상에서 SQL문을 기술할 수 있다. 또, 실행시에는 SQL
프로퍼티에 TStrings를 사용할 수 있다. 이 변수는 Params 프로퍼티에 등록하고,
프로그램의 실행시에 Params 프로퍼티의 각 변수로 값을 설정하는 것으로 SQL문에
반영시킬 수 있다. 다음의 예는 BIOLIFE.DB의 Category 필드에 Params 프로퍼티에
등록된 CategoryDate와 일치하는 레코드를 취득한다.
  SELECT * FROM "BIOLIFE.DB" WHERE Category = :CategoryData

실행시에 Params 프로퍼티에 등록한 데이타를 변경하려면 ParamByName을 사용한다.
  Query1->ParamByName( "CategoryData" )->AsString = "하타";
  Query1->Open();

위의 예에서 사용한 Open 함수는 SQL 프로퍼티에 등록된 SQL문을 실행하고 DataSet을
Active하게 한다.

Table의 작성
──────
TTable은 CreateTable 메소드를 사용하지만, TQuery는 Table의 작성도 SQL문으로
해야한다. Table의 작성은 CREATE TABLE을 사용한다.
CREATE TABLE은 Table의 이름, 다음에 Table의 구조를 ()안에 지정한다. 예로
S,I,A10,N형의 항목을 가지는 Paradox table TEST.DB를 작성하려면 다음과 같이
SQL 프로퍼티에 set한다.
  CREATE TABLE "TEST.DB"
         (
          FIELD1 SMALLINT,
          FIELD2 INTEGER,
          FIELD3 CHAR(10),
          FIELD4 NUMERIC(4,2)
         )

Paradox Table이므로 1차 키를 Set하려면 PRIMARY KEY를 추가한다.
(Paradox table의 경우, 1차 키는 CREATE TABLE에서만 작성할 수 있다.)

  PRIMARY KEY(FIELD1)

또 지금부터의 SQL문을 실행하려면 Query 컴포넌트를 Active하게하는
(Query1->Active = true;)가, Open에는 (Query->Open();)이 SQL 프로퍼티에 set된
SQL문을 실행하는 ExecSQL 메소드를 호출한다.
실행시에 SQL 프로퍼티에 set하고, 그 SQL문을 실행하는 위의 예는 다음과 같이
하면된다.
  TStrings *s = Query1->SQL;
  s->Clear();
  s->Add( " CREATE TABLE \"TEST.DB\" ( " );
  s->Add( "   FIELD1 SMALLINT, " );
  s->Add( "   FIELD2 INTEGER, " );
  s->Add( "   FIELD3 CHAR(10), " );
  s->Add( "   FIELD4 NUMERIC(4,2), " );
  s->Add( "   PRIMARY KEY(FIELD1) ) " );

  Query1->ExecSQL();

단독으로 인덱스를 작성하는것도 가능하다. 인덱스를 작성하기 위한 SQL문은
CREATE INDEX를 사용한다. CREATE INDEX는 인덱스의 이름, 인덱스를 작성하는 대상이
되는 TABLE명 및 필드명을 지정한다. 위의 예에서 작성한 table에 인덱스를 붙이면
다음과 같은 SQL문이 된다.
  CREATE INDEX FIELD2 ON "TEST.DB" ( FIELD2 )

복수의 필드를 지정하여 인덱스를 작성하려면 ()안에 복수의 필드명을 지정한다.
  CREATE INDEX FIELD2_3 ON "TEST.DB" ( FIELD2, FIELD3 )

역시, 필드 타입 등에 대한 상세한 SQL의 헬프를 참조한다.

Table의 표시(참조)
─────────
TQuery를 사용해 table의 내용을 데이타 콘트롤 컴포넌트에서 참조하려면 TTable과
같이, TDataSource 컴포넌트에 중개시키고, 데이타 콘트롤 컴포넌트에 링크시킨다.
예로 사용한 table의 내용을 TDBGrid 콤퍼넌트에 표시시키기 위해서는, TDBGrid의
DataSource 프포퍼티에 TDataSource 컴포넌트를 set하고 TDataSource의 DataSet
프로퍼티에서 table을 참조하는 SQL문을 기술한다. 위의 예에서 작성한 table을
TDBGrid 컴포넌트에 표시시키려면, TDataSource 및 TDBGrid 콤포넌트를 폼에
위치시키고(DataSource1, DBGrid1) DBGrid1->DataSource에 DataSource1을,
DataSource1->DataSet에 Query1->SQL에 다음과 같은 SQL문을 기술한다.
  SELECT * FROM "TEXT.DB"

SELECT문은 table을 특정하게 사용하기때문에 자주 사용된다. 표시하는 필드를
지정하고 싶은 경우에는 다음과 같이 * 의 대신에 필드명을 지정한다.
  SELECT * "FIELD1", "FIELD2" FROM "TEST.DB"

다음과 같이 각 필드의 table을 명시적으로 지정하는것도 가능하다.
  SELECT TEST."FIELD1", TEST."FIELD2" FROM "TEST.DB"

실제로 표시하기 위해서는 TQuery 컴포넌트를 Active하게 해야먄 한다. Active
프로퍼티를 ture로 할여 Open 메소드를 호출한다.
  TStrings *s = Query1->SQL;
  s->Clear();
  s->Add( " SELECT * FROM \"TEST.DB\" " );
  Query1->Open();

같은 형태의 레코드의 추가에도 SQL문으로 한다. 예로 다음의 예는 INSERT INTO를
사용하고, 상기의 Table의 각 필드에 1,2,"stirng1", 3.14를 가진 레코드를 추가
한다.
  TStrings *s = Query1->SQL;
  s->Clear();
  s->Add( " INSERT INTO \"TEST.DB\" (FIELD1, FIELD2, FIELD3, FIELD4) " );
  s->Add( " VALUES( 1, 2, \"string1\", 3.14 ) " );
  Query1->ExecSQL();

VALIES의 각 데이터에 변수를 사용하는 것은 일반적이다. TQuery 컴포넌트는 Params
프로퍼티를 가지고 있는 것으로 Params 프로퍼티의 각 변수를 사용해 SQL문을
실행한다. SQL 프로퍼티에 다음 형태의 SQL문이 설정되고 있어 Params 프로퍼티가
지정되고 있으면, 동적으로 등록하는 데이터를 변경할 수 있다.

  SQL 프로퍼티
     INSERT INTO "TEST.DB"
                 (FIELD1, FIELD2, FIELD3, FIELD4 )
           VALUES( :DATA1, :DATA2, :DATA3, :DATA4 )

     for( int i = 0; i < 10; i++ )      {
        Query1->ParamByName( "DATA1" )->AsInteger = i;
        Query1->ParamByName( "DATA2" )->AsInteger = i*2;
        Query1->ParamByName( "DATA3" )->AsString  = "Test" + IntToStr( i );
        Query1->ParamByName( "DATA4" )->AsFloat   = i*100/3;
        Query1->ExecSQL();      }

기타, 자세한 내용은 SQL의 헬프를 참조한다.

+ -

관련 글 리스트
132 [팁] Database Programming 입문 박지훈.임프 10885 2001/07/30
Google
Copyright © 1999-2015, borlandforum.com. All right reserved.