yTakeです。度々、お世話になっています。
今回はデータベースに関してご教授頂ければと思います。
DELPHI XEでFirebirdをDBとして使用する事とします。
コンポーネントは、IBTable, IBQuery, IBTransaction, IBDatabase, DataSource に DBGrid, DBNavigaor を使用しています。
コンポーネントを配置し関係性を設定しただけで、Firebirdデータベースからその内容が表示されレコード間移動や追加編集がコンポーネント操作だけでSQL分を一切用いず実現され、便利さに驚いていますが、少し複雑な処理をさせる場合のプログラミング手法がよくわかりません。
具体的には、検索条件を設定してその結果をDBGrid上に表示させたいのですが、SQL文を作成しExecuteしてその結果まで得られているのですが、DBGridへの表示の仕方がわかりません。
ソース抜粋:
Form2.IBQuery2.SQL.Clear();
Form2.IBQuery2.SQL.Add( 'SELECT * FROM patient_tbl ' );
Form2.IBQuery2.SQL.Add( ' WHERE MU_CALC_DATE BETWEEN ''' + FormatDateTime( 'yyyy-mm-dd', begin_Date ) + ''' AND ''' + FormatDateTime( 'yyyy-mm-dd', end_Date ) + '''' );
Form2.IBQuery2.Open();
if Form2.IBQuery2.IsEmpty = True then
ShowMessage( 'Empty' )
else
begin
Form2.IBQuery2.RecordCount;
Form2.IBQuery2.FieldCount;
while Form2.IBQuery2.Eof = False do
begin
for i := 0 to Form2.IBQuery2.FieldCount - 1 do
begin
// Form2.DBGrid2.Fields[ i ] ;
Form2.IBTable2.FieldValues[ 'INFO5' ] := Form2.IBQuery2.FieldByName( 'INFO5' ).Value;
end;
Form2.IBQuery2.Next();
end;
end;
Form2.IBQuery2.Close();
ここまで、
試しに一つのフィールドのみ代入させてみましたが、EIBClientError:’編集中ではありません’が出ます。
DBGrid2.Fields[ i ]へ直接代入しようとも思いましたが、読み出し専用なので不可です。
なお、IBQuery2.RecordCountで検索に掛かったレコード数が反映されると思いましたが、違う様です。複数ヒットさせても、常に'1'でした。
どの様に、DBGridに結果を表示させるべきでしょうか?
参考になるサイトがあればそこをご紹介頂けるだけでも構いません。
これまでも色々と参照しましたが、よく分かりませんでした。
以上です。よろしくお願いします。
1.空のプロジェクトを開いて以下をコピーし、フォームデザイナに〔Ctrl〕+〔V〕で貼り付けて下さい。
-------------------- ここから --------------------
object DBGrid1: TDBGrid
Left = 8
Top = 8
Width = 661
Height = 317
DataSource = DataSource1
TabOrder = 0
TitleFont.Charset = DEFAULT_CHARSET
TitleFont.Color = clWindowText
TitleFont.Height = -11
TitleFont.Name = 'Tahoma'
TitleFont.Style = []
end
object IBDatabase1: TIBDatabase
Left = 20
Top = 340
end
object IBTransaction1: TIBTransaction
DefaultDatabase = IBDatabase1
Left = 80
Top = 340
end
object IBQuery1: TIBQuery
Database = IBDatabase1
Transaction = IBTransaction1
Left = 160
Top = 340
end
object DataSource1: TDataSource
DataSet = IBQuery1
Left = 240
Top = 340
end
object Button1: TButton
Left = 513
Top = 344
Width = 75
Height = 25
Caption = 'Connect'
TabOrder = 1
end
object Button2: TButton
Left = 594
Top = 344
Width = 75
Height = 25
Caption = 'DisConnect'
TabOrder = 2
end
-------------------- ここまで --------------------
2.Button1 (Connect) のイベントハンドラに以下を記述します。
procedure TForm1.Button1Click(Sender: TObject);
begin
with IBDatabase1 do
begin
LoginPrompt := False;
Params.Values['user_name'] := 'SYSDBA'; // User Name
Params.Values['password' ] := 'masterke'; // Password(IBならmasterkey)
Params.Values['lc_ctype' ] := 'UNICODE_FSS'; // CharSet
DatabaseName := 'localhost:C:\TEST\DATA.FDB';
end;
with IBQuery1 do
begin
SQL.Clear;
SQL.Add('Select * From TBL_EMPLOYEE');
Open;
end;
end;
3.Button2 (Disconnect) のイベントハンドラに以下を記述します。
procedure TForm1.Button2Click(Sender: TObject);
begin
IBQuery1.Close;
end;
4.実行してテストしてみてください。
> IBQuery2.RecordCountで検索に掛かったレコード数が反映されると思いましたが、違う様です。
違いますね。
IBQuery2.Last;
IBQuery2.First;
または、
IBQuery2.FetchAll;
した後だと RecordCount は総レコード数が返ります。
ただ、事前に Select Count(*) した SQL を投げて総レコード数を得たほうが
パフォーマンスがいい事が多いです。
詳しくは以下のページを参照して下さい。
[FAQ - InterBase & IBX ヘルプ (木村さんによる翻訳)]
http://www.geocities.jp/kimura804/rdb/InterBaseFAQ/IBandIBX_FAQandTutorials_j.htm#Top
以下は適宜書き換えてください。
・データベースの文字コードセット
・データベース名
・テーブル名を含む SQL 文
DEKOさん
早速のREPLYをありがとうございます。
早速試してみました。すると、見事に検索結果が反映されています。
みたところ、特にDBGridに対して代入などは行なわれていません。
ご案内して頂いたサイトを参照しながら、理解してゆきたいと思います。
少し分かって来ました。
IBTableは下位互換性の為で使用しない方が良く使う場合IBDataSetが代用可能とあります。
いずれも使わなくてもDEKOさんの例の様に実現できる事も分かりました。
ところが、実は現在設計中のDBソフトはデータベースファイルは一つですが、テーブルを3つ内包しています。
この内、二つは連携していて、一方の選択レコードに対応してもう一方のレコード内容を表示する様にしていました。
具体的には、
関連データの方の"IBTable"で、MasterSourceとして関連されるテーブル用の"DataSource"を指定し、"MasterFields"に関連されるテーブルから共通データが格納されているフィールドを、"IndexFieldName"に関連データのテーブルで共通データが格納されているフィールドを指定しています。
これで、曲がりなりにも、両者は想定通りに連携して内容を表示していました。
今回、IBTableに代えてIBDataSetを置いてみました。取り敢えず、親の方のテーブル(関連される方)はその内容を表示していますが、子のテーブル(関連データの方)は連携のさせ方が分かりません。
IBDataSet内には、IBTableの様な、MasterSource設定が見当たりません。連携させるためのフィールド設定の様な箇所も無い様に思います。
コンポーネントが別なので、全く同様とは思いませんが、設定項目が少なく
根本的にやり方が違うのでしょうか?
少々、分かり難い説明かもしれませんが、2つのテーブルを連携させてレコード内容を表示させたいと考えています。
以上です。よろしくお願いします。
自己レスです。
IBTableの方はいわば常時接続的な感じなので、コンポーネントレベルで参照先の設定が可能だったのではと解釈しました。
IBDataSetの場合、よりC/S的になるので、SQL文を発行して連携データを毎回引っ張ってくる必要があると考えました。
IBDataSetの"SelectSQL"へ親レコードの参照フィールド値と同じフィールド値を持つレコードをSELECTするSQL文を登録すると考えます。
しかし、別テーブルのフィールド値を条件とするWHERE句の記述が分かりません。
”SELECT * FROM Table1 WHERE Field1 FROM Table1 = Field1 FROM Table2”当然ではダメでした。
方向性としては、正しいのでしょうか?
SQL の Left Join でいいのではないでしょうか?
[Firebird Wiki]
http://firebirdwiki.jp/index.php?SELECT
このあたりは、SQL文の学習が必要になりますね。
SELECT T1.* FROM Table1 T1
Left Outer join Tabel2 T2 on (T1.Field1 = T2.Field1)
[T1,T2はAlias(別名)で無い場合はTable名.Field名で表す]
だと思います。
標準SQLだと、Table結合にはInner Join,Left outer join等があります。
調べればすぐに出てくると思います。
DEKOさん、HOtaさん
ありがとうございます。
確かにSQLの話になってきていてここで教えて頂く事ではないかもしれません。
ただ、今回"Left Join", "Inner Join"でもうまく表示されていません。エラーは特に無いようです。
ここで、私の状況説明に不正確性があった為かもしれません。
この様なコンポーネントの使い方で良いでしょうか?
データベースファイルは一つでテーブルが二つである事は始めにお伝えしてたと思います。実は、DBGridやIBQuery等のコンポーネントも2組使用しています。
DBGrid1 DBGrid2
DBNavigator1 DBNavigator2
IBDataSet1(IBTable1) IBDataSet2(IBTable2)
IBDataSource1 IBDataSource2
IBQuery1 IBQuery2
IBTrabsaction1 IBTransaction2
IBDatabase1 IBDatabase2
また、テーブルは例えばクラス名簿と試験結果表の様なものと考えて下さい。
試験結果も外部模試の様な個人によって受験数などが異なる様なテーブルを想定しています。
つまり、クラス名簿の方は重複がなく、行を選択する(個人を選ぶ)と対応する模試結果が表示され、これは個人によって連携して表示される行数が変わる事になります。
つまり、”JOIN”の様なテーブル結合では実現できない様な気がします。
JOINだと、結果は一つのDBGridにまとめて表示される気がします。
DBGrid1-IBDataSet1 と DBGrid2-IBDataSet2 の間で、
テーブル1のアクティブレコードのIDをキーにTable2で対応するIDを全てDBGrid2上に表示する方法があるでしょうか?
この様な場合でも、JOIN系を用いれば良いのでしょうか?
Table1のアクティブレコードの指定が分かりません。(Firebird入門を参照)
質問の意図は伝わったでしょうか?
質問の趣旨と変わっているような...まぁいいか。
以下のようなテーブルがあって CODE で連動する場合を考えてみます。
/* 親データ */
CREATE TABLE TBL_MASTER
(
CODE INTEGER NOT NULL,
NAME VARCHAR(100),
PRIMARY KEY (CODE)
);
INSERT INTO TBL_MASTER (CODE, NAME) VALUES (1, 'AAAAA');
INSERT INTO TBL_MASTER (CODE, NAME) VALUES (2, 'BBBBB');
INSERT INTO TBL_MASTER (CODE, NAME) VALUES (3, 'CCCCC');
INSERT INTO TBL_MASTER (CODE, NAME) VALUES (4, 'DDDDD');
INSERT INTO TBL_MASTER (CODE, NAME) VALUES (5, 'EEEEE');
commit;
/* 子データ */
CREATE TABLE TBL_DETAIL
(
CODE INTEGER NOT NULL,
SEQ INTEGER NOT NULL,
POINT DOUBLE PRECISION,
PRIMARY KEY (CODE, SEQ)
);
INSERT INTO TBL_DETAIL (CODE, SEQ, POINT) VALUES (1, 1, 10);
INSERT INTO TBL_DETAIL (CODE, SEQ, POINT) VALUES (1, 2, 20);
INSERT INTO TBL_DETAIL (CODE, SEQ, POINT) VALUES (2, 1, 30);
INSERT INTO TBL_DETAIL (CODE, SEQ, POINT) VALUES (2, 2, 40);
INSERT INTO TBL_DETAIL (CODE, SEQ, POINT) VALUES (3, 1, 50);
INSERT INTO TBL_DETAIL (CODE, SEQ, POINT) VALUES (3, 2, 60);
INSERT INTO TBL_DETAIL (CODE, SEQ, POINT) VALUES (4, 1, 70);
INSERT INTO TBL_DETAIL (CODE, SEQ, POINT) VALUES (4, 2, 80);
INSERT INTO TBL_DETAIL (CODE, SEQ, POINT) VALUES (5, 1, 90);
INSERT INTO TBL_DETAIL (CODE, SEQ, POINT) VALUES (5, 2, 100);
commit;
言わんとする事は、恐らく DBGrid1 に親データ (TBL_MASTER) を表示し、
DBGrid1 で選択されたレコードに関連する子データ (TBL_DETAIL) を
DBGrid2 に表示したいという事なのでしょう。
1.空のプロジェクトを開いて以下をコピーし、フォームデザイナに〔Ctrl〕+〔V〕で貼り付けて下さい。
-------------------- ここから --------------------
object IBDatabase1: TIBDatabase
Left = 24
Top = 216
end
object IBTransaction1: TIBTransaction
DefaultDatabase = IBDatabase1
Left = 84
Top = 216
end
object IBQuery1: TIBQuery
Database = IBDatabase1
Transaction = IBTransaction1
Left = 160
Top = 216
end
object DataSource1: TDataSource
DataSet = IBQuery1
Left = 240
Top = 216
end
object DBGrid1: TDBGrid
Left = 8
Top = 8
Width = 300
Height = 200
DataSource = DataSource1
TabOrder = 0
end
object IBQuery2: TIBQuery
Database = IBDatabase1
Transaction = IBTransaction1
Left = 160
Top = 276
end
object DataSource2: TDataSource
DataSet = IBQuery2
Left = 240
Top = 276
end
object DBGrid2: TDBGrid
Left = 316
Top = 8
Width = 300
Height = 200
DataSource = DataSource2
TabOrder = 1
end
object Button1: TButton
Left = 447
Top = 215
Width = 75
Height = 25
Caption = 'Connect'
TabOrder = 2
end
object Button2: TButton
Left = 538
Top = 215
Width = 75
Height = 25
Caption = 'DisConnect'
TabOrder = 3
end
-------------------- ここまで --------------------
2.Form1 の OnCreate イベントハンドラに以下を記述します。
procedure TForm1.FormCreate(Sender: TObject);
begin
with IBDatabase1 do
begin
LoginPrompt := False;
Params.Values['user_name'] := 'SYSDBA'; // User Name
Params.Values['password' ] := 'masterke'; // Password(IBならmasterkey)
Params.Values['lc_ctype' ] := 'UNICODE_FSS'; // CharSet
DatabaseName := 'localhost:C:\TEST\DATA.FDB';
end;
with IBQuery1 do
begin
SQL.Clear;
SQL.Add('Select * From TBL_MASTER');
end;
with IBQuery2 do
begin
SQL.Clear;
SQL.Add('Select * From TBL_DETAIL');
SQL.Add('Where ');
SQL.Add(' CODE = :_CODE ');
SQL.Add('ORDER BY ');
SQL.Add(' SEQ ');
end;
end;
3.Button1 (Connect) の OnClick イベントハンドラに以下を記述します。
procedure TForm1.Button1Click(Sender: TObject);
begin
IBQuery1.Open;
end;
4.Button2 (DIsconnect) の OnClick イベントハンドラに以下を記述します。
procedure TForm1.Button2Click(Sender: TObject);
begin
IBQuery2.Close;
IBQuery1.Close;
end;
5.DataSource1 の OnDataChange イベントハンドラに以下を記述します。
procedure TForm1.DataSource1DataChange(Sender: TObject; Field: TField);
begin
IBQuery2.Close;
IBQuery2.ParamByName('_CODE').AsInteger := IBQuery1.FieldByName('CODE').AsInteger;
IBQuery2.Open;
end;
6.実行してテストしてみてください。
# もっと効率的なやり方があったような気がしましたが、
# DBGrid は殆ど使った事がないので忘れているのかもしれません。
yTake さん
>また、テーブルは例えばクラス名簿と試験結果表の様なものと考えて下さい。
>試験結果も外部模試の様な個人によって受験数などが異なる様なテーブルを想定しています。
>つまり、クラス名簿の方は重複がなく、行を選択する(個人を選ぶ)と対応する模試結果が表示され、
>これは個人によって連携して表示される行数が変わる事になります。
やりたいことについての確認ですが、
------------------
|N_CODE |NAME |
------------------
|1 |A君 |
|2 |B君 |
|3 |C君 |
|4 |D君 |
|5 |E君 |
------------------
--------------------------
|N_CODE|KYOUKA|TESUU |
--------------------------
|1 |数学 |100 |
|2 |数学 |80 |
|2 |英語 |90 |
|3 |数学 |80 |
|3 |英語 |95 |
|3 |社会 |75 |
|4 |数学 |70 |
|4 |英語 |90 |
|5 |数学 |95 |
--------------------------
のようなクラス名簿と試験データの連動と理解すればよいのですね?
A君は一つの試験「数学」
B君は2つの試験「数学」「英語」
・・・・・のようなことを表示したい。ですか?
このような目的であれば、もっと簡単にアプローチが可能と思われますが・・・
多分、ノンコーディング(SQLを除く^^;)でそれらしいことができます。
フォームにTIBDatabase、TIBTransaction、TIBQueryを2つ、TDataSourceを2つ、
TDBGridを2つ載せます。
IBDatabaseとIBTransactionを関連付け、それぞれ必要なパラメータを設定し
データベースに接続できるようにします。
IBQuery1、DataSource1、DBGrid1ともIBQuery2、DataSource2、DBGrid2も
それぞれ関連付けます。
で、IBQuery1をマスター側、IBQuery2をディテール(詳細、子)側とするなら
IBQuery2のDataSourceプロパティにDataSource1を設定します。
それぞれのSQLですが、DEKOさんが例として提示されたテーブル定義を借りるなら
IBQuery1のSQLに
SELECT * FROM TBL_MASTER
と設定し
IBQuery2のSQLに
SELECT * FROM TBL_DETAIL WHERE CODE=:CODE
と設定します。
これで、IBQuery1、IBQuery2のActiveプロパティをTrueにすれば
お望みの表示になると思います。
とりあえずそのまま実行すれば、DBGrid1で選択される行を移動すると
それに対応したデータがDBGrid2に表示されます。
要は、ディテール側IBQueryのDataSourceプロパティにマスター側の
DataSourceを設定する事と、ディテール側のSQLのWHERE区で
<ディテール側テーブルのキーフィールド名>=:<マスター側テーブルのキーフィールド名>
を設定する事です。
ただ、この方法のパフォーマンスについては検証してないのであしからず ^^;
>> もっと効率的なやり方があったような気がしましたが、
> 多分、ノンコーディング(SQLを除く^^;)でそれらしいことができます。
それです!
私の二度目の投稿のコードをちょっと修正すると、
Quest さんの仰っている動作になります。
1.IBQuery2 の SQL を変更
with IBQuery2 do
begin
SQL.Clear;
SQL.Add('Select * From TBL_DETAIL');
SQL.Add('Where ');
SQL.Add(' CODE = :CODE '); // <- 修正
SQL.Add('ORDER BY ');
SQL.Add(' SEQ ');
end;
end;
2.Button1 (Connect) の OnClick イベントハンドラを修正
procedure TForm1.Button1Click(Sender: TObject);
begin
IBQuery1.Open;
IBQuery2.Open; // <- 追加
end;
3.DataSource1 の OnDataChange イベントハンドラを削除
4.IBQuery2 の DataSource プロパティに DataSource1 を指定
思い出しました。
[Delphi によるデータベースアプリケーションの作成方法 (Support KB)]
http://support.embarcadero.com/article/36434
[Delphi Tips - Query を使ったデータベースアプリケーション開発 (Support KB)]
http://support.embarcadero.com/article/35960
上が TTable、下が TQuery を使った例ですが、
ほぼそのまま TIBTable、TIBQuery で読み替える事が出来ます。
DEKOさん、maru03さん、Questさん
ありがとうございます
yTakeです
少し間が空いてしまった間に皆さまありがとうございました。
順次確認しています。
DEKOさんから指示頂けたやり方は確認できました。
IBTable, IBDataSet共に使わなくても良い事が分かります。
また、DBGridを使用されないとありますが、表に表示される場合はStringGrid等にFor文でまわしてCellへ入れて行くと言う様なやり方でしょうか?
その方が、小回りが利くと言う利点があるのでしょうか。
maru03さん
はいそうです。その様に考えています。また、クラス名簿に相当する方は例えば誕生日も記載され、”期間を区切ると該当する誕生日の生徒が検索される”と言うところが、そもそもの問い合わせの発端でした。
Questさん
ご指摘の点、これから確認してみます。
DEKOさん
何度もありがとうございます。
サイトのご紹介をありがとうございます。
参照した事はあったのですが、上記を踏まえて再読してみます。
取り敢えず、以上です。
テーブルの構造について
>はいそうです。その様に考えています。また、クラス名簿に相当する方は例えば誕生日も記載され、
>”期間を区切ると該当する誕生日の生徒が検索される”と言うところが、そもそもの問い合わせの発端でした。
上記の目的から、下記のようなことを考えてみました。
その前に、DBファイル「XXXX.FDB」(拡張子はFDBとしていますが・・)に
「名簿テーブル」「成績テーブル」ともに入っているとすると、
IBDatabase1 (IBDatabase2==>不要)
IBTrabsaction1 (IBTransaction2==>不要)
でいいと思います。
名簿データ、成績データの一部削除や追加を考慮すると、SQLの使用がいいと思います。
結果は、StringGrid等にFor文,While文でまわしてCellへ入れて行きます。
StringGridでダブルクリックすると「名前のCode番号」が取得できるので、
(StringGridのデータは、String型データですので注意が必要です)
名前のCode番号 := StrToInt(StringGrid1.Cells[0,StringGrid1.row]);
再度、SQL で必要な成績を取得すればいいのでは。
つまり
SQL_1 で 名簿テーブルから「条件」で抜き出す
StringGrid_1 に表示 (ここは ListBox などでも代用可能でしょう)
StringGrid_1 上でダブルクリックなどで、名前を確定
取得した「名前のCode番号」で SQL_2 にて必要な成績データを取得
DBGrid や StringGrid_2 に表示
DBGrid の場合は TIBQuery => TDataSource => DBGrid でOK
StringGrid の場合はFor文、while not(SQL.Eof) do でまわしてCellへ入れる
となるでしょう。
また、StringGrid なら、色々な表現ができます。
参考 Mr.XRAY さんのHP
http://mrxray.on.coocan.jp/Delphi/plSamples/050_TStringGrid_OnDrawCell.htm
皆さん、maru03さん
yTakeです。ありがとうございます。
おかげさまで希望の動作を実現できました。
maru03さんにご指摘頂いたIBDabase2, IBTransaction2が不要である点、分かってきました。
DBGridを始めに使ってしまて、あまりに簡単に表示できてしまったので、それを使うべきだと思い込んでしまった点が理解の妨げだったかもしれません。
StrinGrid等を使うとデザインの自由度があがりますが、その表示は自分でCellへ書き込む必要があるわけですね。
SQLの応答を受け取る為に、Openして、
抜粋:
j := 1;
while IBQuery2.Eof = False do
begin
for i := 0 to IBQuery2.FieldCount - 1 do
begin
if j = 0 then
StringGrid1.Cells[ i, j ] := IBQuery2.Fields[ i ].FieldName
else
StringGrid1.Cells[ i, j ] := IBQuery2.Fields[ i ].AsString;
end;
IBQuery2.Next();
inc( j );
end;
などとすると考えます。
StringGridの更なる使い方に関しても、XRrayさんのサイトを参考にさせて頂きます。
ありがとうございました。
ツイート | ![]() |