![SQL 및 MQL5: SQLite 데이터베이스로 작업하기](https://c.mql5.com/i/articles/overlay.png)
SQL 및 MQL5: SQLite 데이터베이스로 작업하기
Small. Fast. Reliable.
세 개 중 하나를 선택하세요.
소개
많은 개발자들이 데이터 스토리지 목적으로 프로젝트에 데이터베이스를 사용하는 것을 고려하지만 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 Queries의 실행
2.3. 테이블에서 데이터 가져오기
2.4. 바인딩으로 매개 변수 데이터 작성하기
2.5. 트랜잭션 / 다중-행 삽입(거래 계정의 거래 테이블 생성 예시)
3. 컴파일링 64-Bit 버전 (sqlite3_64.dll)
1. SQLite 원리
SQLite는 로컬에 설치된 SQL 서버가 없는 것이 주요 기능인 RDBMS입니다. 응용프로그램이 서버로 표시됩니다. SQLite 데이터베이스 작업은 기본적으로 파일(디스크 드라이브 또는메모리)을 사용하는 것입니다. 특정 방법으로 설치할 필요 없이 모든 데이터를 보관하거나 다른 컴퓨터로 이동할 수 있습니다.
개발자와 사용자는 SQLite를 통해 다음과 같은 몇 가지 이점을 누릴 수 있습니다:
- 추가 소프트웨어를 설치할 필요가 없습니다;
- 데이터는 로컬 파일에 저장되므로 관리 투명성을 제공합니다. 즉, 애플리케이션과 관계없이 데이터를 보고 편집할 수 있습니다.
- 다른 DBMS로 테이블을 가져오고 내보낼 수 있습니다;
- 코드는 익숙한 SQL 쿼리를 사용하여 언제든지 응용프로그램이 다른 DBMS와 함께 작동하도록 할 수 있습니다.
SQLite를 사용하는 세 가지 방법은 다음과 같습니다:
- 전체 API 함수 집합과 함께 DLL 파일을 사용할 수 있습니다;
- EXE 파일에 셸 명령을 사용할 수 있습니다;
- SQLite API의 소스 코드를 포함하여 프로젝트를 컴파일할 수 있습니다.
이 문서에서는 MQL5에서 가장 일반적인 첫 번째 옵션에 대해 설명하겠습니다.
2. SQLite3 API
커넥터 작업을 수행하려면 다음 SQLite 함수를 사용해야 합니다:
//--- general functions sqlite3_open sqlite3_prepare sqlite3_step sqlite3_finalize sqlite3_close sqlite3_exec //--- functions for getting error descriptions sqlite3_errcode sqlite3_extended_errcode sqlite3_errmsg //--- functions for saving data sqlite3_bind_null sqlite3_bind_int sqlite3_bind_int64 sqlite3_bind_double sqlite3_bind_text sqlite3_bind_blob //--- functions for getting data 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 함수에 보내는 포인터의 크기를 고려하는 것이 중요합니다. 이름을 구분해 보겠습니다:
// for a 32 bit terminal #define PTR32 int #define sqlite3_stmt_p32 PTR32 #define sqlite3_p32 PTR32 #define PTRPTR32 PTR32 // for a 64 bit terminal #define PTR64 long #define sqlite3_stmt_p64 PTR64 #define sqlite3_p64 PTR64 #define PTRPTR64 PTR64-->
필요한 경우 32비트 및 64비트 포인터에 대해 모든 API 함수가 오버로드됩니다. 커넥터의 모든 포인터는 64비트입니다. 오버로드된 API 함수에서 직접 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 | 임의 이진 데이터입니다. |
또한 다양한 DBMS에서 승인된 BIGINT 또는 INT와 같은 다른 유형 이름을 사용하여 SQL 쿼리에서 테이블을 작성할 때 필드의 데이터 유형을 지정할 수 있습니다. 이 경우 SQLite는 고유한 유형 중 하나인 INTEGER(정수) 형식으로 변환합니다. 데이터 유형 및 데이터 관계에 대한 자세한 내용은 문서 http://www.sqlite.org/datatype3.html 을 참조하십시오.
2.1. 데이터베이스를 열고 닫기
이미 아시다시피 SQLite3의 데이터베이스는 일반 파일입니다. 따라서 데이터베이스를 여는 것은 사실상 파일을 열고 그 핸들을 얻는 것과 같습니다.
이 작업은 sqlite3_open 함수를 사용하여 수행됩니다:
int sqlite3_open(const uchar &filename[], sqlite3_p64 &ppDb); filename [in] - a pathname or file name if the file is being opened at the current location. ppDb [out] - variable that will store the file handle address. The function returns SQLITE_OK in case of success or else an error code.-->
다음 sqlite3_close 함수를 사용하여 데이터베이스 파일을 닫습니다:
int sqlite3_close(sqlite3_p64 ppDb); ppDb [in] - file handle The function returns SQLITE_OK in case of success or else an error code.-->
커넥터에 데이터베이스 열기 및 닫기 기능을 생성하겠습니다.
//+------------------------------------------------------------------+ //| 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 쿼리는 다음 세 단계를 거쳐야 합니다:
- sqlite3_prepare - 구문 목록을 확인하고 수신합니다;
- sqlite3_step - 다음 구문을 실행합니다;
- sqlite3_finalize - 완료하고 메모리를 지웁니다.
이 구조는 주로 테이블을 만들거나 삭제하는 데 적합하며, SQL 쿼리가 실행 성공 상태를 제외하고 데이터를 반환하지 않는 경우와 같이 이진 데이터가 아닌 데이터를 작성하는 데 적합합니다.
쿼리에 데이터 수신 또는 이진 데이터 쓰기가 포함된 경우, sqlite3_column_хх 또는 sqlite3_bind_хх 함수가 각각 두 번째 단계에서 사용됩니다. 이러한 기능은 다음 절에서 자세히 설명합니다.
CSQLite3Base::Query 방법을 작성하여 단순 SQL 쿼리 실행하기:
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ 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] - handle received by function sqlite3_open The pointer is returned to the string containing the error description.-->
커넥터에서 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 - 가져온 테이블의 열 수를 확인합니다.
- 현재 단계 결과 sqlite3_step == SQLITE_ROW
- sqlite3_column_хх - 문자열 셀을 읽습니다.
- sqlite3_finalize
데이터 읽기 및 쓰기에 관한 광범위한 섹션에 접근하고 있으므로, 전체 데이터 교환에 사용되는 세 가지 컨테이너 클래스를 설명할 수 있습니다. 필요한 데이터 모델은 데이터가 데이터베이스에 저장되는 방식에 따라 달라집니다:
데이터베이스
|
테이블은 행의 배열입니다.
|
행은 셀의 배열입니다.
|
셀은 임의 길이의 바이트 버퍼입니다.
//+------------------------------------------------------------------+ //| 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 유형에 추가되었습니다. 전체 정수(INTEGER) 유형을 CT_INT 및 CT_INT64로 나눕니다, 이는 비슷하게 나뉘어진 sqlite3_bind_intXX 및 sqlite3_column_intXX 함수에 따른 것입니다.
데이터 가져오기
셀에서 데이터를 가져오려면, 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. 바인딩으로 매개 변수 데이터를 작성하기
새로 시작하는 사람에게 중요한 또 다른 주제는 "불편한" 형식의 데이터베이스에 데이터를 쓰는 것입니다. 물론, 여기서 말하는 것은 이진법 데이터입니다. 첫 번째 0이 발견되면 문자열이 완료된 것으로 간주되기 때문에 일반적인 INSERT 또는 UPDATE 텍스트 구문에 직접 전달할 수 없습니다. 문자열 자체에 작은 따옴표 '가 포함된 경우에도 동일한 문제가 발생합니다.
특히 테이블이 넓은 경우 늦게 바인딩하는 것이 유용할 수 있습니다. 한 줄에 모든 필드를 쓰는 것은 어렵고 신뢰할 수 없습니다. 쉽게 놓칠 수 있기 때문입니다. 바인딩 작업에 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. 트랜잭션 / 다중 행 삽입
이 항목을 계속 진행하기 전에 SQLite API 함수를 하나 더 알아야 합니다. 이전 섹션에서 요청의 3단계 처리를 설명했습니다: 준비+스텝+마무리. 그러나 대안적인 솔루션(일부 경우 단순하거나 중요한)이 있습니다. – 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. It returns SQLITE_OK in case of success or else an error code.-->
주요 목표는 3단계 구성을 만들지 않고 단일 호출로 쿼리를 실행하는 것입니다.
커넥터에 호출을 추가하겠습니다:
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ 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");-->
트랜잭션
이제 표에 수천 개의 행을 추가해야 한다고 가정합니다. 이 모든 것을 루프에 삽입하면 됩니다:
for (int i=0; i<N; i++) sql3.Query("INSERT INTO `Table` VALUES(1, 2, 'text')");-->
실행 속도가 매우 느립니다 (10(!) 초 이상). 따라서 이러한 구현은 SQLite에서 권장되지 않습니다. 여기서 가장 적절한 솔루션은 트랜잭션을 사용하는 것입니다: 모든 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이 포함된 라인의 코멘트 아웃(comment out). 귀하의 계정에 수백 건 이상의 거래가 있는 경우 그 차이를 즉시 알 수 있습니다. 그런데, 일부 테스트에 따르면, MySQL 또는 PostgreSQL에서보다 SQLite 트랜잭션이 더 빨리 작동합니다.
3. 64-Bit Version (sqlite3_64.dll) 컴파일하기
- SQLite source code (통합) 다운로드 및 sqlite3.c 파일 찾기.
- sqlite-dll-win32 다운로드 및 sqlite3.dll 파일 추출.
- dll 파일이 추출된 폴더에서 LIB.EXE /DEF:sqlite3.def콘솔 명령을 실행합니다. lib.exe 파일의 경로가 PATH 시스템 변수에 설정되어 있는지 확인하거나 Visual Studio에서 찾아야 합니다.
- 64비트 플랫폼에 대한 릴리스 구성을 선택하여 DLL 프로젝트를 만듭니다.
- 다운로드한 sqlite3.c 및 획득한 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 bit dll 가져오기.
결론
저는 그 기사가 SQLite를 마스터하는 데 필수적인 가이드가 되기를 바랍니다. 아마도, 여러분은 그것을 앞으로의 프로젝트에 사용할 것입니다. 이 간략한 개요에서는 애플리케이션을 위한 완벽하고 안정적인 솔루션인 SQLite의 기능에 대한 통찰력을 제공합니다.
이 기사에서는 거래 데이터를 처리할 때 직면할 수 있는 모든 사례에 대해 설명했습니다. 각 기호에 대한 표에 틱을 삽입하는 간단한 틱 수집기를 개발하는 것이 좋습니다. 클래스 라이브러리 소스 코드 및 테스트 스크립트는 아래 첨부 파일에서 찾을 수 있습니다.
행운을 빌고 큰 이익을 많이 내시길 바랍니다!
MetaQuotes 소프트웨어 사를 통해 러시아어가 번역됨.
원본 기고글: https://www.mql5.com/ru/articles/862
![세줄 브레이크 차트(Three Line Break Chart)를 구성하기 위한 지시자](https://c.mql5.com/2/10/logo.png)
![신경망 저렴하고 쾌활합니다 - NeuroPro와 MetaTrader 5의 연결](https://c.mql5.com/2/12/NeuroPro_MetaTrader4_neural_net.png)
![거래 로봇 이야기: 더 적은 것이 더 많은 것인가요?](https://c.mql5.com/2/0/tales.png)
![비디오 튜토리얼: MetaTrader 신호 서비스](https://c.mql5.com/2/0/signal-video.png)