
SQL と MQL5:SQLite データベースとの連携
小型迅速信頼性
この3つのどれでも選んでください。
はじめに
開発者の多くがデータ保管のためにデータベースを利用することを考えますが、躊躇したままです。 SQL サーバーのインストールに余計な時間がどれほど取られるかわかっているからです。そして一方プログラマーにとってそれはさほど難しいことではなく(データベース管理システム(DBMS)がすでに別の目的でインストールされていれば)、それは明らかに、ソフトウェアを一緒にインストールすることに最終的に消極的な一般ユーザーにとっての問題なのです。
現在取り組んでいるソリューションがほとんど利用されないことがわかっているため、開発者の多くは DBMS に取り組もうとしません。結果、彼らはファイルの操作を来ないます(多様な使用データを与えられ、複数のファイルに対処する必要があることがよくあります。):CSV、頻度は低いものの XML や JSON、厳格なストラクチャサイズのバイナリファイルなど。
ただ、 SQL サーバーのすばらしい代替があることがわかりました。そしてすべてをみなさんのプロジェクト内で行われるため、追加のソフトウェアをインストールする必要はありません。それでも SQLのフル性能を利用できるのです。これから SQLiteについてお話していきたいと思います。
本稿のめざすところはみなさんに速くSQLite をスタートしてもらうことです。そのため、細かい部分や考えられるパラメータセットすべてや関数フラグについては述べませんが、その代り、SQL コマンドを実行するための軽い接続ラッパを作成し、使用を実地で見ていきます。
本稿を読み進むにあたりみなさんには以下が必要となります。
- 良い気分であること ;)
- 本稿添付のアーカイブファイルをMetaTrader 5 クライアントターミナルフォルダに抽出する。
- 都合のよい SQLite ビューアをインストールする。(たとえばSQLiteStudio)
- SQLite の公式ドキュメンテーション http://www.sqlite.org をお気に入りに追加する。
コンテンツ
1. SQLite 原理
2. SQLite3 API
2.1. データベースのオープンとクローズ
2.2. SQL クエリの実行
2.3. テーブルからのデータ取得
2.4. バインディングによるパラメータデータ書き込み
2.5. トランザクション/複数行挿入(トレード赤運とのディールテーブル作成例)
3. Compiling 64 バージョンのコンパイル (sqlite3_64.dll)
1. SQLite 原理
SQLite はキー機能がローカルでインストールされ SQL たサーバーにない RDBMS です。みなさんのアプリケーションはここではサーバーとして見られています。SQLite データベースとの連携は基本的にファイル(ディスクドライバー上またはメモリ内)との連携です。データはすべてアーカイブされるか特別な方法でインストールする必要はなく別のコンピュータに移動されます。
SQLite により開発者とユーザーがまぎれもないメリットのかずかずの恩恵を受けることができるのです。
- 追加のソフトウェアをインストールする必要がない
- データはローカルファイルに格納されるため、管理に透明性がある。すなわち、データ閲覧、編集をアプリケーションから離れて行える。
- 他の DBMS にテーブルをインポート、エクスポートする機能
- コードはよく知る SQL クエリを使用する。それによりアプリケーションをいつでも他の DBMS と連携させることができる。
SQLiteと連携する方法は3とおりあります。
- API 関数の完全セットを持つDLL ファイルを使用する。
- EXE ファイルに対するシェルコマンドを使用する。
- SQLite API のソースコードをインクルードしてプロジェクトをコンパイルする。
本稿では最初の選択肢について説明します。MQL5でもっとも通例なものです。
2. SQLite3 API
コネクタ操作のために以下の SQLite 関数を使用する必要があります。
//--- 一般的関数 sqlite3_open sqlite3_prepare sqlite3_step sqlite3_finalize sqlite3_close sqlite3_exec //--- エラー説明取得のための関数 sqlite3_errcode sqlite3_extended_errcode sqlite3_errmsg //--- データ保存のための関数 sqlite3_bind_null sqlite3_bind_int sqlite3_bind_int64 sqlite3_bind_double sqlite3_bind_text sqlite3_bind_blob //--- データ取得のための関数 sqlite3_column_count sqlite3_column_name sqlite3_column_type sqlite3_column_bytes sqlite3_column_int sqlite3_column_int64 sqlite3_column_double sqlite3_column_text sqlite3_column_blob
またポインターを処理するために低レベルの msvcrt.dll 関数も必要となります。
strlen strcpy memcpy
私は32ビットと64ビットターミナルで動作するコネクタを作成しているため、API 関数に送信されるポインタサイズに配慮することは重要です。名前を分けます。
// 32 ビットターミナル用 #define PTR32 int #define sqlite3_stmt_p32 PTR32 #define sqlite3_p32 PTR32 #define PTRPTR32 PTR32 // 64 ビットターミナル用 #define PTR64 long #define sqlite3_stmt_p64 PTR64 #define sqlite3_p64 PTR64 #define PTRPTR64 PTR64
必要に応じて、API 関数はすべて32 ビットまたは 64 ビットポインタにオーバーロードします。コネクタのポインターはすべて64ビットであることに注意してください。それらは32ビットから直接オーバーロードされたAPI 関数内で変換されます。関数インポートソースコードは SQLite3Import.mqhに提供されています。
SQLite データタイプ
SQLite バージョン 3 にはデータが5タイプあります。
タイプ | 説明 |
---|---|
NULL | NULL 値 |
INTEGER | 格納される値の大きさに応じて 1, 2, 3, 4, 6, または 8 バイトで格納される整数 |
REAL | 8 バイト実数 |
TEXT | UTF-8 または UTF-16 エンコーディングによって格納される終了文字 \0 を持つテキスト文字列 |
BLOB | 任意のバイナリデータ |
また他の名前タイプも使用することができます。たとえば SQL クエリからテーブルを作成する際、フィールドのデータタイプを指定するさまざまな DBMS を受け入れるBIGINTや INT です。この場合、SQLite はそれらをその組み込み型の一つに変換します。この場合は INTEGERです。データタイプとその関係についてのさらなる情報はドキュメンテーション http://www.sqlite.org/datatype3.html をご一読ください。
2.1. データベースのオープンとクローズ
すでにご存知のとおり、SQLite3 のデータベースは一般的なファイルです。データベースの最初は、実際ファイルを開きそのハンドルを取得することと同じです。
それはsqlite3_open 関数によって行われます。
int sqlite3_open(const uchar &filename[], sqlite3_p64 &ppDb); filename [in] -ファイルが現在の位置で開いていればパス名またはファイル名 pDb [out] -ファイルのハンドルアドレスを格納する変数 この関数は正常な場合 SQLITE_OK を返し、そうでねければエラーコードを返します。
データベースファイルは sqlite3_close 関数によって閉じられます。
int sqlite3_close(sqlite3_p64 ppDb); ppDb [in]-ファイルハンドル この関数は正常な場合 SQLITE_OK を返し、そうでねければエラーコードを返します。
データベースのオープンとクローズ関数をコネクタ内に作成します。
//+------------------------------------------------------------------+ //| CSQLite3Base class | //+------------------------------------------------------------------+ class CSQLite3Base { sqlite3_p64 m_db; // pointer to database file bool m_bopened; // flag "Is m_db handle valid" string m_dbfile; // path to database file public: CSQLite3Base(); // constructor virtual ~CSQLite3Base(); // destructor public: //--- connection to database bool IsConnected(); int Connect(string dbfile); void Disconnect(); int Reconnect(); }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CSQLite3Base::CSQLite3Base() { m_db=NULL; m_bopened=false; } //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CSQLite3Base::~CSQLite3Base() { Disconnect(); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CSQLite3Base::IsConnected() { return(m_bopened && m_db); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ int CSQLite3Base::Connect(string dbfile) { if(IsConnected()) return(SQLITE_OK); m_dbfile=dbfile; return(Reconnect()); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CSQLite3Base::Disconnect() { if(IsConnected()) ::sqlite3_close(m_db); m_db=NULL; m_bopened=false; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ int CSQLite3Base::Reconnect() { Disconnect(); uchar file[]; StringToCharArray(m_dbfile,file); int res=::sqlite3_open(file,m_db); m_bopened=(res==SQLITE_OK && m_db); return(res); }
これでコネクタがデータベースを開いたり閉じたりできます。ここでシンプルなスクリプトを使ってそのパフォーマンスを確認します。
#include <MQH\Lib\SQLite3\SQLite3Base.mqh> CSQLite3Base sql3; // database connector //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void OnStart() { //--- open database connection if(sql3.Connect("SQLite3Test.db3")!=SQLITE_OK) return; //--- close connection sql3.Disconnect(); }
スクリプトをデバッグモードで実行し、深呼吸をして書く文字列の処理を確認します。結果、データベースファイルはMetaTrader 5 ターミナルのインストールフォルダにあります。この成功をご自身で祝って次のセクションに進んでください。
2.2. SQL クエリの実行
SQLite3 内の SQL クエリはすべて最低3段階を経る必要があります。
- sqlite3_prepare-検証とステートメントリストの受け取り
- sqlite3_step-これらステートメントの実行
- sqlite3_finalize -完了とメモリクリア
このストラクチャは主にテーブルの作成と削除に適しています。また非バイナリデータを書くのにも適しています。すなわち、SQL クエリがそれ自体の実行が正常であるステートメント以外はデータを返さないことを示しています。
クエリがデータ受け取りやバイナリデータ書き込みをしたら、第二段階で関数 sqlite3_column_хх またはsqlite3_bind_хх がそれぞれ受け取りと書き込みに使用されます。これら関数は次のセクションで細かく説明します。
シンプルな SQLクエリを実行するための CSQLite3Base::Query メソッドを書きます。
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ int CSQLite3Base::Query(string query) { //--- check connection if(!IsConnected()) if(!Reconnect()) return(SQLITE_ERROR); //--- check query string if(StringLen(query)<=0) return(SQLITE_DONE); sqlite3_stmt_p64 stmt=0; // variable for pointer //--- get pointer PTR64 pstmt=::memcpy(stmt,stmt,0); uchar str[]; StringToCharArray(query,str); //--- prepare statement and check result int res=::sqlite3_prepare(m_db,str,-1,pstmt,NULL); if(res!=SQLITE_OK) return(res); //--- execute res=::sqlite3_step(pstmt); //--- clean ::sqlite3_finalize(pstmt); //--- return result return(res); }
ごらんのとおり、関数sqlite3_prepare、sqlite3_step 、 sqlite3_finalizeはそれぞれ以下です。
SQLiteでテーブルを処理する際には CSQLite3Base::Query の実行を考慮します。
// Create the table (CREATE TABLE) sql3.Query("CREATE TABLE IF NOT EXISTS `TestQuery` (`ticket` INTEGER, `open_price` DOUBLE, `comment` TEXT)");
このコマンドの実行後、テーブルはがデータベース内に入っています。
// Rename the table (ALTER TABLE RENAME) sql3.Query("ALTER TABLE `TestQuery` RENAME TO `Trades`"); // Add the column (ALTER TABLE ADD COLUMN) sql3.Query("ALTER TABLE `Trades` ADD COLUMN `profit`");
このコマンドの実行後、新しい名前と追加のフィールドを持つてーブルを取得します。
// Add the row (INSERT INTO) sql3.Query("INSERT INTO `Trades` VALUES(3, 1.212, 'info', 1)"); // Update the row (UPDATE) sql3.Query("UPDATE `Trades` SET `open_price`=5.555, `comment`='New price' WHERE(`ticket`=3)")
新しい行が追加され変更されると次のエントリーがテーブルに入ります。
最後に次のコマンドが次々と実行され、データベースがクリーンアップされます。
// Delete all rows from the table (DELETE FROM) sql3.Query("DELETE FROM `Trades`") // Delete the table (DROP TABLE) sql3.Query("DROP TABLE IF EXISTS `Trades`"); // Compact database (VACUUM) sql3.Query("VACUUM");
次のセクションに移るまでに、エラー解説を受け取るメソッドが必要です。私の経験では、エラーコードはたくさんの情報を提供しますが、エラー解説は SQL クエリテキスト内のどこにエラーが発生したか示し、エラー検出と修正を簡単にしています。
const PTR64 sqlite3_errmsg(sqlite3_p64 db); db [in] - sqlite3_open関数から受け取られるハンドル。ポインターはエラー解説を伴い文字列に返されます。
コネクタ内にはこの文字列をstrcpy および strlenによってポインターから受け取るためのメソッドを追加します。
//+------------------------------------------------------------------+ //| Error message | //+------------------------------------------------------------------+ string CSQLite3Base::ErrorMsg() { PTR64 pstr=::sqlite3_errmsg(m_db); // get message string int len=::strlen(pstr); // length of string uchar str[]; ArrayResize(str,len+1); // prepare buffer ::strcpy(str,pstr); // read string to buffer return(CharArrayToString(str)); // return string }
2.3. テーブルからのデータ取得
2.2セクションの最初ですでにお話したとおり、読み出しはsqlite3_column_хх関数によって行われます。これは以下に図として表示しています。
- sqlite3_prepare
- sqlite3_column_count-取得されたテーブルの列番号を見つけます。
- While current step result sqlite3_step == SQLITE_ROW
- sqlite3_column_хх-文字列セルを読みます。
- sqlite3_finalize
データの読み出しと書き込みに関する大きなセクションに近づきつつあるので、データ交換全体で使用される3つのコンテナクラスについて説明するにはよいタイミングです。必要なデータモデルはデータがデータベース内にどのように格納されるかによります。
データベース
|
Tableは列の配列です。
|
Row はセルの配列です。
|
Cell は任意長のバイトバッファです。
//+------------------------------------------------------------------+ //| CSQLite3Table class | //+------------------------------------------------------------------+ class CSQLite3Table { public: string m_colname[]; // column name CSQLite3Row m_data[]; // database rows //... };
//+------------------------------------------------------------------+ //| CSQLite3Row class | //+------------------------------------------------------------------+ class CSQLite3Row { public: CSQLite3Cell m_data[]; //... };
//+------------------------------------------------------------------+ //| CSQLite3Cell class | //+------------------------------------------------------------------+ class CSQLite3Cell { public: enCellType type; CByteImg buf; //... };
CSQLite3Row および CSQLite3Table の接続は原始的で、従来的データ配列です。CSQLite3Cell セルクラスも uchar タイプのデータ配列 + データタイプフィールドを持ちます。バイト配列は CByteImage クラスに実装されています(よく知られるCFastFile同様です)。
私は次の列挙を作成し、コネクタの処理とセルデータタイプの管理を容易にしました。
enum enCellType
{
CT_UNDEF,
CT_NULL,
CT_INT,
CT_INT64,
CT_DBL,
CT_TEXT,
CT_BLOB,
CT_LAST
};
CT_UNDEF タイプは5つの基本的な SQLite3 タイプに追加され、最初のセル状態を特定できるようにしていることに注意が必要です。同様に分割された関数sqlite3_bind_intXX と sqlite3_column_intXX とに従い INTEGER タイプ全体を CT_INTと CT_INT64 に分割します。
データ取得
セルからデータを得るには、sqlite3_column_ххタイプ関数を一般化するメソッドを作成します。それはデータタイプとサイズを確認しそれを CSQLite3Cellに書き込みます。
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CSQLite3Base::ReadStatement(sqlite3_stmt_p64 stmt,int column,CSQLite3Cell &cell) { cell.Clear(); if(!stmt || column<0) return(false); int bytes=::sqlite3_column_bytes(stmt,column); int type=::sqlite3_column_type(stmt,column); //--- if(type==SQLITE_NULL) cell.type=CT_NULL; else if(type==SQLITE_INTEGER) { if(bytes<5) cell.Set(::sqlite3_column_int(stmt,column)); else cell.Set(::sqlite3_column_int64(stmt,column)); } else if(type==SQLITE_FLOAT) cell.Set(::sqlite3_column_double(stmt,column)); else if(type==SQLITE_TEXT || type==SQLITE_BLOB) { uchar dst[]; ArrayResize(dst,bytes); PTR64 ptr=0; if(type==SQLITE_TEXT) ptr=::sqlite3_column_text(stmt,column); else ptr=::sqlite3_column_blob(stmt,column); ::memcpy(dst,ptr,bytes); if(type==SQLITE_TEXT) cell.Set(CharArrayToString(dst)); else cell.Set(dst); } return(true); }
この関数はひじょうに大型ですが、現在のステートメントからデータを読みそれをセルに格納するだけです。
また第一パラメータとして受け取ったデータに対しCSQLite3Table コンテナテーブルを追加することで CSQLite3Base::Query 関数をオーバーロードします。
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ int CSQLite3Base::Query(CSQLite3Table &tbl,string query) { tbl.Clear(); //--- check connection if(!IsConnected()) if(!Reconnect()) return(SQLITE_ERROR); //--- check query string if(StringLen(query)<=0) return(SQLITE_DONE); //--- sqlite3_stmt_p64 stmt=NULL; PTR64 pstmt=::memcpy(stmt,stmt,0); uchar str[]; StringToCharArray(query,str); int res=::sqlite3_prepare(m_db, str, -1, pstmt, NULL); if(res!=SQLITE_OK) return(res); int cols=::sqlite3_column_count(pstmt); // get column count bool b=true; while(::sqlite3_step(pstmt)==SQLITE_ROW) // in loop get row data { CSQLite3Row row; // row for table for(int i=0; i<cols; i++) // add cells to row { CSQLite3Cell cell; if(ReadStatement(pstmt,i,cell)) row.Add(cell); else { b=false; break; } } tbl.Add(row); // add row to table if(!b) break; // if error enabled } // get column name for(int i=0; i<cols; i++) { PTR64 pstr=::sqlite3_column_name(pstmt,i); if(!pstr) { tbl.ColumnName(i,""); continue; } int len=::strlen(pstr); ArrayResize(str,len+1); ::strcpy(str,pstr); tbl.ColumnName(i,CharArrayToString(str)); } ::sqlite3_finalize(stmt); // clean return(b?SQLITE_DONE:res); // return result code }
データ受信に必要な関数はすべてそろいました。それを例に渡します。
// Read data (SELECT) CSQLite3Table tbl; sql3.Query(tbl, "SELECT * FROM `Trades`")
次のコマンド Print(TablePrint(tbl))によってターミナル内のクエリ結果をプリントアウトします。以下のエントリーがジャーナルに表示されます(順序は下から上)。
// Sample calculation of stat. data from the tables (COUNT, MAX, AVG ...) sql3.Query(tbl, "SELECT COUNT(*) FROM `Trades` WHERE(`profit`>0)") sql3.Query(tbl, "SELECT MAX(`ticket`) FROM `Trades`") sql3.Query(tbl, "SELECT SUM(`profit`) AS `sumprof`, AVG(`profit`) AS `avgprof` FROM `Trades`")
// Get the names of all tables in the base sql3.Query(tbl, "SELECT `name` FROM `sqlite_master` WHERE `type`='table' ORDER BY `name`;");
クエリ結果は Print(TablePrint(tbl))によって同様にプリントアウトされます。既存テーブルを確認することができます。
例からわかるように、クエリ実行結果は tbl 変数に入れられます。その後、みなさんの記述で簡単にそれらを取得、処理することができます。
2.4. バインディングによるパラメータデータ書き込み
初心者の方に重要なもう一つ別の話題は『便利な』フォーマットによりデータベースにデータを書き込むことです。もちろんここではバイナリデータです。一般的な INSERT や UPDATE テキストステートメントでは直接渡すことができません。文字列は最初のゼロが検出されるとき簡潔しているとみなされるためです。文字列そのものが単一クオートを持つ場合も同じ問題が発生します '。
遅いバインディングが便利が場合もります。とくにテーブルの幅が広い場合です。すべてのフィールドを1行に書くことは難しく信頼性がありません。何かを見落としやすいからです。sqlite3_bind_хх シリーズの関数が処理をバインディングするのに必要です。
バインディングを取り入れるには、渡されたデータの代わりにテンプレートを挿入します。一例を考察します。 "?"記号です。別の言い方をすると、UPDATE クエリは次のように記述されます。
それから関数 sqlite3_bind_double および sqlite3_bind_text は open_price と commentにデータを入れるために順番に実行されます。通常 bind 関数での処理は次のように表現されます。
- sqlite3_prepare
- sqlite3_bind_хх をひとつずつ呼び、必要なデータをステートメントに書き込みます。
- sqlite3_step
- sqlite3_finalize
タイプ番号によりsqlite3_bind_xx は完全に上述の読み出し関数を繰り返します。そのため簡単にCSQLite3Base::BindStatementでそれらをコネクタ内でコンバインすることができます。
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CSQLite3Base::BindStatement(sqlite3_stmt_p64 stmt,int column,CSQLite3Cell &cell) { if(!stmt || column<0) return(false); int bytes=cell.buf.Len(); enCellType type=cell.type; //--- if(type==CT_INT) return(::sqlite3_bind_int(stmt, column+1, cell.buf.ViewInt())==SQLITE_OK); else if(type==CT_INT64) return(::sqlite3_bind_int64(stmt, column+1, cell.buf.ViewInt64())==SQLITE_OK); else if(type==CT_DBL) return(::sqlite3_bind_double(stmt, column+1, cell.buf.ViewDouble())==SQLITE_OK); else if(type==CT_TEXT) return(::sqlite3_bind_text(stmt, column+1, cell.buf.m_data, cell.buf.Len(), SQLITE_STATIC)==SQLITE_OK); else if(type==CT_BLOB) return(::sqlite3_bind_blob(stmt, column+1, cell.buf.m_data, cell.buf.Len(), SQLITE_STATIC)==SQLITE_OK); else if(type==CT_NULL) return(::sqlite3_bind_null(stmt, column+1)==SQLITE_OK); else return(::sqlite3_bind_null(stmt, column+1)==SQLITE_OK); }
このメソッドの唯一の課題は渡されたセルのバッファをステートメントに記入することです。
同様の方法でCQLite3Table::QueryBindメソッドを追加します。最初の引数は書き込みのためのデータ文字列です。
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ int CSQLite3Base::QueryBind(CSQLite3Row &row,string query) // UPDATE <table> SET <row>=?, <row2>=? WHERE (cond) { if(!IsConnected()) if(!Reconnect()) return(SQLITE_ERROR); //--- if(StringLen(query)<=0 || ArraySize(row.m_data)<=0) return(SQLITE_DONE); //--- sqlite3_stmt_p64 stmt=NULL; PTR64 pstmt=::memcpy(stmt,stmt,0); uchar str[]; StringToCharArray(query,str); int res=::sqlite3_prepare(m_db, str, -1, pstmt, NULL); if(res!=SQLITE_OK) return(res); //--- bool b=true; for(int i=0; i<ArraySize(row.m_data); i++) { if(!BindStatement(pstmt,i,row.m_data[i])) { b=false; break; } } if(b) res=::sqlite3_step(pstmt); // executed ::sqlite3_finalize(pstmt); // clean return(b?res:SQLITE_ERROR); // result }
その目的は文字列を適切なパラメータに書き込むことです。
2.5. Transactions/複数行挿入
この話題を進める前印、Multirow Insert関数をもう一つ知る必要があります。前のセクションで、リクエストの三段階処理を説明しました。:準備+ステップ+完成です。ただし、代替(ある場合、シンプルあるいは重要な)ソリューションがあります。sqlite3_exec 関数です。
int sqlite3_exec(sqlite3_p64 ppDb, const char &sql[], PTR64 callback, PTR64 pvoid, PTRPTR64 errmsg); ppDb [in] - database handle sql [in] - SQL query The remaining three parameters are not considered yet in relation to MQL5. 正常に処理された場合は SQLITE_OK を返し、それ以外はエラーコードを返します。
その主な目的は三段階の構築をせず、1度の呼び出しでクエリを実行することです。
コネクタにその呼び出しを追加します。
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ int CSQLite3Base::Exec(string query) { if(!IsConnected()) if(!Reconnect()) return(SQLITE_ERROR); if(StringLen(query)<=0) return(SQLITE_DONE); uchar str[]; StringToCharArray(query,str); int res=::sqlite3_exec(m_db,str,NULL,NULL,NULL); return(res); }
結果得られるメソッドは使いやすいものです。たとえば、テーブルの削除(DROP TABLE)を実行する、またはデータベース圧縮(VACUUM)のコマンドは以下のようなものです。
sql3.Exec("DROP TABLE `Trades`"); sql3.Exec("VACUUM");
Transactions
ここでテーブルに何千行と追加する必要があるとします。これをすべてループに挿入する場合、
for (int i=0; i<N; i++) sql3.Query("INSERT INTO `Table` VALUES(1, 2, 'text')");
実行速度はひじょうに遅いものとなります(10秒以上!)。そのような実装はSQLiteではお薦めしません 。ここでのもっとも適切な解決方法は transactionsを使用することです。:SQL ステートメントのすべてが共有リストに入っており、そのためシングルクエリとして渡されるのです。
トランザクションの開始と終了を書くのに以下の SQL ステートメントを利用します。
BEGIN ... COMMIT
内容はすべて最後の COMMIT ステートメントで実行されます。ROLLBACK ステートメントはループが中断されたり、すでに追加されたステートメントが実行されない場合に利用されます。
例としてすべてのアカウントディールがテーブルに追加されます。
#include <MQH\Lib\SQLite3\SQLite3Base.mqh> //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void OnStart() { CSQLite3Base sql3; //--- open database connection if(sql3.Connect("Deals.db3")!=SQLITE_OK) return; //--- if(sql3.Query("CREATE TABLE IF NOT EXISTS `Deals` (`ticket` INTEGER PRIMARY KEY, `open_price` DOUBLE, `profit` DOUBLE, `comment` TEXT)")!=SQLITE_DONE) { Print(sql3.ErrorMsg()); return; } //--- create transaction if(sql3.Exec("BEGIN")!=SQLITE_OK) { Print(sql3.ErrorMsg()); return; } HistorySelect(0,TimeCurrent()); //--- dump all deals from terminal to table for(int i=0; i<HistoryDealsTotal(); i++) { CSQLite3Row row; long ticket=(long)HistoryDealGetTicket(i); row.Add(ticket); row.Add(HistoryDealGetDouble(ticket, DEAL_PRICE)); row.Add(HistoryDealGetDouble(ticket, DEAL_PROFIT)); row.Add(HistoryDealGetString(ticket, DEAL_COMMENT)); if(sql3.QueryBind(row,"REPLACE INTO `Deals` VALUES("+row.BindStr()+")")!=SQLITE_DONE) { sql3.Exec("ROLLBACK"); Print(sql3.ErrorMsg()); return; } } //--- end transaction if(sql3.Exec("COMMIT")!=SQLITE_OK) return; //--- get statistical information from table CSQLite3Table tbl; CSQLite3Cell cell; if(sql3.Query(tbl,"SELECT COUNT(*) FROM `Deals` WHERE(`profit`>0)")!=SQLITE_DONE) { Print(sql3.ErrorMsg()); return; } tbl.Cell(0,0,cell); Print("Count(*)=",cell.GetInt64()); //--- if(sql3.Query(tbl,"SELECT SUM(`profit`) AS `sumprof`, AVG(`profit`) AS `avgprof` FROM `Deals`")!=SQLITE_DONE) { Print(sql3.ErrorMsg()); return; } tbl.Cell(0,0,cell); Print("SUM(`profit`)=",cell.GetDouble()); tbl.Cell(0,1,cell); Print("AVG(`profit`)=",cell.GetDouble()); }
スクリプトがアカウントに適用されたあと、それはすぐにアカウントディールをテーブルに挿入します。
ターミナルのジャーナルに統計が表示されます。
スクリプトでいろいろすることができます。行にはBEGIN、 ROLLBACK 、COMMITでコメントします。みなさんのアカウントに100以上のディールがあれば、すぐに違いがわかります。ところで、いくらかの テストによると、SQLite のトランザクションは MySQL や PostgreSQLより速いのです。
3. 64 ビットバージョンのコンパイル (sqlite3_64.dll)
- SQLite ソースコード(融合)をダウンロードし、sqlite3.cファイルを検索します。
- sqlite-dll-win32 をダウンロードしそこからsqlite3.dllファイルを抽出します。
- LIB.EXE /DEF:sqlite3.def コンソールコマンドを dll ファイルが抽出されたフォルダで実行します。lib.exe ファイルへのパスが PATH システム変数に設定されているかお手持ちの Visual Studioにあることを確認します。
- 64ビットプラットフォーム用コンフィギュレーションのリリースを選択し、DLL プロジェクトを作成します。
- ダウンロードされた DLL と、取得したsqlite3.def ファイルをプロジェクトに追加します。コンパイラが defファイルからの関数のなかのいくつかを受け付けなければ、それにコメントを付けます。
- 次のパラメータをプロジェクト設定に設定します。
C/C++ --> General --> Debug Information Format = Program Database (/Zi)
C/C++ --> Precompiled Headers --> Create/Use Precompiled Header = Not Using Precompiled Headers (/Yu) - コンパイルし64ビットの dll を取得します。
おわりに
本稿がSQLiteを習得するのに欠くことのできないガイドとなるよう願っています。おそらく将来のプロジェクトで利用することになるでしょう。この概要はアプリケーションにとって完璧で信頼できるソリューションとしてのSQLite の機能性についていくらかの洞察を提供しました。
本稿で私はトレードデータを処理するときに出会う可能性のある場合をすべて説明しました。宿題として、各シンボルのテーブルにティックを挿入してシンプルなティックコレクタを作成することをお薦めします。クラスライブラリのソースコードとテストスクリプトは下の添付にあります。
グッドラック!そしてビッグな利益を!
MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/862





- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索