MQL5でデータベースを扱う、MT5とSQLiteの連携の基本操作

　MQL5ではSQLiteを使用したデータベースを操作するための関数が用意されてます。この記事ではMQL5での基本的なデータベース操作方法をまとめています。


　実際にMQL5プログラム上からデータベースの操作を操作するには「MT5で用意されている専用関数」と「SQLiteのコマンド」の両方知っておく必要があります。

#SQLiteの基礎知識

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1882169/fe5f81db-3caf-99f5-9c8f-c9ba8405fb8d.png)
　これは実際にMQL5プログラム上から作成したSQLiteの中身です。今回はローソク足の4本値をMQL5で取得してデータベースに追加しています。

__1.テーブル__
　Excelでいうブックのようなもの。データベースを作成してそこにデータを保管していきます。
__2.カラム__
　保管するデータを整理するための分類、属性を指します。または列を表すときにもカラムと呼ぶことがあります。通常はカラムごとにデータの型を指定することが多いです。
__3.レコード__
　実際に保管されているデータをレコードといいます。レコードは基本的にカラムの数だけ存在します。(空白NULLである場合もあります。)または行を表す「ロウ」と同じ意味で使われる場合もあります。
__4.フィールド__
　Excelでいうセルのようなもの。レコードを構成する要素のことです。


###__SQLiteとMQL5のデータ型の比較__

|SQLite|MQL5|説明|
|:--|:--|---|
|NULL||NULL値|
|INTEGER|int,longなど|符号付整数。1, 2, 3, 4, 6, 8(いずれか) バイト|
|REAL|double|浮動小数点数。8バイト|
|TEXT|string|文字列|
|BLOB||ビデオや音声、圧縮ファイル、実行ファイルなど|

 

#データベースにデータを保管
　データベースにデータを保管するには大まかに
1. __保管するSQLiteファイルを指定、または新規作成__
2. __テーブルがなければ新規作成__
3. __データを追加する__
　という手順が必要です。


###__保管するSQLiteファイルを指定、または新規作成__
　操作したいSQLiteファイルを指定するにはDatabaseOpen()関数を使用します。

```操作したいSQLiteファイルを開く.mq5
// 指定したファイル名のDBを開く、なければ新規作成
[データベースハンドル]=DatabaseOpen([SQLiteファイル名], DATABASE_OPEN_READWRITE | DATABASE_OPEN_CREATE |DATABASE_OPEN_COMMON);

// 指定したファイル名のDBを読み取り専用で開く
[データベースハンドル]=DatabaseOpen([SQLiteファイル名], DATABASE_OPEN_READONLY |DATABASE_OPEN_COMMON);
```
###__テーブルを新規作成__
　テーブルを新規作成する専用の関数は用意されていません。その代わりにSQLiteコマンドを実行するDatabaseExecute()関数を使います。

```指定したファイル名のDBでSQLiteコマンドを実行.mq5
DatabaseExecute([データベースハンドル],"[SQLiteコマンド]");
```

DatabaseExecute()関数内に[SQLiteコマンド]のところにテーブル作成するためのSQLiteコマンドをstring型で入れます。

```テーブル作成するためのSQLiteコマンド.sqlite
// テーブル作成
CREATE TABLE テーブル名(カラム名1, カラム名2, ...);

// カラムにデータ型を指定してテーブルを作成
CREATE TABLE テーブル名(カラム名1 データ型, カラム名2 データ型, ...)
```

###__データを追加する__
　データを追加する場合も専用の関数は用意されていませんのでDatabaseExecute()関数を使います。

```指定したファイル名のDBでSQLiteコマンドを実行.mq5
DatabaseExecute([データベースハンドル],"[SQLiteコマンド]");
```

```データを追加するためのSQLiteコマンド.sqlite
// 全てのカラムに値を指定してデータを追加する
INSERT INTO テーブル名 VALUES(値1, 値2, ...);

// 特定のカラムだけに値を指定してデータを追加する
INSERT INTO テーブル名(カラム1, カラム2, ...) VALUES(値1, 値2, ...);
```

　データの追加はレコード単位で行います。テーブルのカラムの数と追加するデータの数を一致させないとエラーになるため、空欄にしたいときはNULLをいれるか、カラムを指定してデータを追加する必要があります。

　DatabaseExecute()関数内の[SQLiteコマンド]はMQL5上ではすべてstring型で扱います。SQLite側ではINTEGER型など文字列でなくとも、MQL5から渡す[SQLiteコマンド]はstring型になるので注意が必要です。

##実際にデータベースにデータを保管してみる

```.cpp
void OnStart()
  {
   // SQLiteファイル名
   string FileName="Test.sqlite", TableName="TestTable";
   
   // 指定したファイル名のDBを開く、なければ新規作成
   int DBHandle=DatabaseOpen(FileName, DATABASE_OPEN_READWRITE | DATABASE_OPEN_CREATE |DATABASE_OPEN_COMMON);
   if(DBHandle==INVALID_HANDLE)
     {
      Print("DB: ", FileName, "新規作成 エラーコード: ", GetLastError());
      return;
     }
   
   // DB内のテーブルの存在を確認
   if(DatabaseTableExists(DBHandle, TableName))
     {
      // テーブルが既に存在する場合は、それを削除
      if(!DatabaseExecute(DBHandle, "DROP TABLE "+TableName))
        {
         Print("テーブルの削除に失敗しました。"+TableName+" エラーコード: ", GetLastError());
         DatabaseClose(DBHandle);
         return;
        }
     }
   
   // テーブルの新規作成
   if(!DatabaseExecute(DBHandle, "CREATE TABLE "+TableName+"("
                       "Open              REAL NOT NULL,"
                       "High              REAL NOT NULL,"
                       "Low               REAL NOT NULL,"
                       "Close             REAL NOT NULL );"))
     {
      Print("DB: ", FileName, "テーブル新規作成 エラーコード: ", GetLastError());
      DatabaseClose(DBHandle);
      return;
     }
   
   // DBに4本値のデータを10個追加する
   for(int i=0;i<10;i++)
     {
      if(!DatabaseExecute(DBHandle, "INSERT INTO "+TableName+" (Open,High,Low,Close) "
                              "VALUES ("
                              +DoubleToString( iOpen(_Symbol,PERIOD_CURRENT,i),_Digits)+","
                              +DoubleToString( iHigh(_Symbol,PERIOD_CURRENT,i),_Digits)+","
                              +DoubleToString( iLow(_Symbol,PERIOD_CURRENT,i),_Digits)+","
                              +DoubleToString( iClose(_Symbol,PERIOD_CURRENT,i),_Digits)
                              +");"))
        {
         Print("DB: ", FileName, "データの追加 エラーコード: ", GetLastError());
         DatabaseClose(DBHandle);
         return;
        }   
     }
   // DBを閉じる
   DatabaseClose(DBHandle);
  }
```

1. __指定したファイル名のDBを開く、なければ新規作成__
2. __DB内のテーブルの存在を確認し、あれば削除__
3. __テーブルの新規作成__
4. __テーブルにデータを追加する__
5. __DBを閉じる__

　という動作をするスクリプトです。プログラムの最後はDBを閉じるためのDatabaseClose()関数が必要なので記述漏れがないように注意します。

　MT5を開いて、実際にチャート上で動作されてみてください。ログにエラーメッセージが出なければ正常に動作していると思います。

###__確認方法__
　再度MetaEditorを開いてください。DB作成が成功していると \MetaQuotes\Terminal\Common\Files
に作成したDBファイル(今回の例ではTest.sqlite)があります。
1. 「ファイル」→「開く」
2. \MetaQuotes\Terminal\Common\Files 内のTest.sqliteを開く
3. 「ナビゲーター」→「データベース」を選ぶ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1882169/a11ce3ae-527a-d31e-1495-5ecf830331ca.png)
　先ほど作成したTest.sqliteの中にTestTable(テーブル名)があるのでTestTableをダブルクリック

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1882169/817f94c3-08ee-b9c1-ddbe-9cb3684d0e1f.png)
　このような画面が開いたら左上の実行を押す

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1882169/1c7817bd-da1b-d3f2-3645-ff567184d4ef.png)
　データベースに保管した4本値のデータが出てくれば成功です。

#データベースからデータを取得
　データベースからデータを取得するには大まかに
1. __取得するSQLiteファイルを指定__
2. __取得したいデータをリクエスト__
3. __ほしいデータを選んで取得__
　という手順が必要です。

###__取得するSQLiteファイルを指定__
　操作したいSQLiteファイルを指定するにはDatabaseOpen()関数を使用します。データベースにデータを保管するときと同じですが、データを取得するだけが目的なら読み取り専用でもOKです。

```操作したいSQLiteファイルを開く.mq5
// 指定したファイル名のDBを開く、なければ新規作成
[データベースハンドル]=DatabaseOpen([SQLiteファイル名], DATABASE_OPEN_READWRITE | DATABASE_OPEN_CREATE |DATABASE_OPEN_COMMON);

// 指定したファイル名のDBを読み取り専用で開く
[データベースハンドル]=DatabaseOpen([SQLiteファイル名], DATABASE_OPEN_READONLY |DATABASE_OPEN_COMMON);
```


###__取得したいデータをリクエスト__
　データを取得するためにデータ取得用SQLiteコマンドを明記してリクエストハンドルを作成する必要があります。

```取得したいデータをリクエスト.mq5
[リクエストハンドル]=DatabasePrepare([データベースハンドル],"[データ取得用SQLiteコマンド]");
```

```データ取得するためのSQLiteコマンド.sqlite
// 全てのカラムの値を取得する
SELECT * FROM テーブル名;

// 特定のカラムだけ値を取得する
SELECT カラム名1, カラム名2, ... FROM テーブル名;

// 特定の条件を満たしたレコードだけ値を取得する
SELECT カラム名 , ... FROM テーブル名 WHERE 条件式;
```


###__ほしいデータを選んで取得__
　リクエストが終わった段階では複数のカラム、レコードが選ばれている状態です。DatabaseRead()関数を使い、レコードをひとつずつ選択して、選択中のレコードの中から必要な箇所のデータを取得します。

```レコードを選択.mq5
// リクエストしたデータからレコードをひとつずつ選択
DatabaseRead([リクエストハンドル]);
```
DatabaseRead()関数は最後のレコードを選択し終えたらfalseを戻り値で返します。

　DatabaseRead()関数によってレコードが選ばれている状態でどのフィールドのデータを取得するかを記述します。

```データを取得(一部抜粋).mq5
// int型を取得
DatabaseColumnInteger([リクエストハンドル],[フィールド番号],[受け取る変数]);

// long型を取得
DatabaseColumnLong([リクエストハンドル],[フィールド番号],[受け取る変数]);

// double型を取得
DatabaseColumnDouble([リクエストハンドル],[フィールド番号],[受け取る変数]);

// string型を取得
DatabaseColumnText([リクエストハンドル],[フィールド番号],[受け取る変数]);
```
　これらデータを取得する関数は戻り値がbool型で引数に受け取る変数を指定しての参照渡しで受け取ることになります。

##実際にデータベースからデータを取得してみる

```.cpp
#property strict
#property script_show_confirm //実行前に確認ウィンドウ表示

void OnStart()
  {
   // SQLiteファイル名
   string FileName="Test.sqlite", TableName="TestTable";
   
   // 指定したファイル名のDBを開く、なければ新規作成
   int DBHandle=DatabaseOpen(FileName, DATABASE_OPEN_READWRITE | DATABASE_OPEN_CREATE |DATABASE_OPEN_COMMON);
   if(DBHandle==INVALID_HANDLE)
     {
      Print("DB: ", FileName, "新規作成 エラーコード: ", GetLastError());
      return;
     }
   
   // DB内のテーブルの存在を確認
   if(!DatabaseTableExists(DBHandle, TableName))
     {
      Print("DB: ", FileName, "テーブルがない テーブル名:", TableName);
      return;
        }

     
   // リクエストハンドルを作成
   int request=DatabasePrepare(DBHandle, "SELECT * FROM "+TableName);
   if(request==INVALID_HANDLE)
     {
      Print("DB: ", FileName, "リクエスト エラーコード: ", GetLastError());
      DatabaseClose(DBHandle);
      return;
     }
        
   // DB内のデータを取得してPrint()で出力
   double Open,High,Low,Close;
   
   for(int i=0; DatabaseRead(request); i++) // リクエストのレコードの数だけ繰り返す
     {
      if(   DatabaseColumnDouble(request,0,Open)
         && DatabaseColumnDouble(request,1,High)
         && DatabaseColumnDouble(request,2,Low)
         && DatabaseColumnDouble(request,3,Close))
        {
         Print(i," Open:",Open," High:",High," Low:",Low," Close:",Close);
        }
      else
        {
         Print(i, " データを取得  エラーコード: ", GetLastError());
         DatabaseFinalize(request);
         DatabaseClose(DBHandle);
         return;
        }
     }
   // 作成されたリクエストを削除
   DatabaseFinalize(request);
     
   // DBを閉じる
   DatabaseClose(DBHandle);
  }
```
1. __指定したファイル名のDBを読み取り専用で開く__
2. __DB内のテーブルの存在を確認__
3. __リクエストハンドルを作成__
4. __DB内のデータを取得してPrint()で出力__
5. __リクエストを削除してDBを閉じる__

　という動作をするスクリプトです。プログラムの最後はリクエストを削除するためのDatabaseFinalize()関数と、DBを閉じるためのDatabaseClose()関数が必要なので記述漏れがないように注意します。

　MT5を開いて、実際にチャート上で動作されてみてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1882169/59eb982b-829e-298f-e7b1-d4e4aa07f071.png)
先ほど作ったDBから値を取得してログにメッセージとして出力できていれば成功です。

　
最後にMQL5データベースの操作関数のページのリンクを載せておきます。

https://www.mql5.com/ja/docs/database