English Русский 中文 Español Deutsch 日本語 Português 한국어 Italiano Türkçe
SQL et MQL5 : Travailler avec la base de données SQLite

SQL et MQL5 : Travailler avec la base de données SQLite

MetaTrader 5Exemples | 13 janvier 2022, 13:49
345 0
---
---

Petit Rapide. Fiable.
Choisissez l'un des trois.

 

Introduction

De nombreux développeurs envisagent d'utiliser des bases de données dans leurs projets à des fins de stockage de données et pourtant ils sont hésitants à ce sujet, à cause du temps supplémentaire que l'installation du serveur SQL pourrait nécessiter. Et bien que cela ne soit pas si difficile pour les programmeurs (si un système de gestion de base de données (SGBD) a déjà été installé à d'autres fins), ce sera certainement un problème pour un utilisateur commun qui pourrait éventuellement être découragé d'installer le logiciel complètement.

Nombreux sont les développeurs qui choisissent de ne pas traiter avec les SGBD en réalisant que les solutions sur lesquelles ils travaillent actuellement seront utilisées par très peu de personnes. En conséquence, il travaillent avec des fichiers (ayant souvent à traiter plus d'un fichier, compte tenu de la variété des données utilisées) : CSV, moins souvent XML ou JSON, ou des fichiers de données binaires avec une taille de structure stricte, etc.

Cependant, il s'avère qu'il existe une excellente alternative au serveur SQL ! Et vous n'avez même pas besoin d'installer de logiciel supplémentaire car tout se fait localement dans votre projet, tout en vous permettant d'utiliser toute la puissance de SQL. Nous parlons de SQLite.

Le but de cet article est de vous familiariser rapidement avec SQLite. Je n'entrerai donc pas dans les subtilités et tous les ensembles de paramètres et indicateurs de fonction imaginables, mais créerai plutôt un papier de connexion léger pour exécuter des commandes SQL et démontrerai son utilisation.

Pour continuer avec l'article, il faut que vous :

  • Soyez de bonne humeur ;)
  • Extrayez les fichiers d'archive joints à l'article dans le dossier du terminal client MetaTrader 5
  • Installez n'importe quelle visionneuse SQLite pratique (par exemple SQLiteStudio)
  • Ajouter la documentation officielle sur SQLitehttp://www.sqlite.org aux Favoris

Contenu

1. Les principes SQLite
2. L’API SQLite3
    2.1. Ouverture et fermeture d'une base de données
    2.2. Exécution de requêtes SQL
    2.3. Obtenir des données à partir de tableaux
    2.4. Écriture de données de paramètres avec liaison
    2.5. Transactions / Inserts multilignes (Exemple de création de tableau de transactions d'un compte de trading)
3. Compilation de la version 64-Bit (sqlite3_64.dll)


1. Les principes SQLite

Le SQLite est un SGBDR dont la principale caractéristique est l'absence d'un serveur SQL installé localement. Votre application est vue ici comme un serveur. Travailler avec la base de données SQLite consiste essentiellement à travailler avec un fichier (sur un lecteur de disque ou dans la mémoire). Toutes les données peuvent être archivées ou déplacées vers un autre ordinateur sans qu'il soit nécessaire de les installer de manière spécifique.

Avec SQLite, les développeurs et utilisateurs peuvent bénéficier de plusieurs avantages indéniables :

  • pas besoin d'installer de logiciel supplémentaire ;
  • les données sont stockées dans un fichier local, offrant ainsi une transparence de gestion, c'est-à-dire que vous pouvez les visualiser et les éditer sans votre application ;
  • possibilité d'importer et d'exporter des tableaux vers d'autres SGBD ;
  • le code utilise des requêtes SQL familières, ce qui vous permet de forcer l'application à fonctionner avec d'autres SGBD à tout moment.

Il existe trois façons de travailler avec SQLite :

  1. vous pouvez utiliser le fichier DLL avec un ensemble complet de fonctions API ;
  2. vous pouvez utiliser des commandes shell dans un fichier EXE ;
  3. vous pouvez compiler votre projet en incluant les codes sources de l'API SQLite.

Dans cet article, je vais décrire la première option, la plus courante en MQL5.

 

2. L’API SQLite3

L'opération du connecteur nécessitera l'utilisation des fonctions SQLitesuivantes :

//--- 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

Vous aurez également besoin de fonctions msvcrt.dll de bas niveau pour travailler avec des pointeurs :

strlen
strcpy
memcpy

Étant donné que je crée un connecteur censé fonctionner dans des terminaux 32 et 64 bits, il est important de prendre en compte la taille du pointeur envoyé aux fonctions API. Séparons leurs noms :

// 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

Si nécessaire, toutes les fonctions API seront surchargées pour les pointeurs 32 et 64 bits. Veuillez noter que tous les pointeurs du connecteur seront 64 bits. Ils seront convertis en 32 bits directement dans les fonctions API surchargées. Le code source d'importation de la fonction API est fourni dans SQLite3Import.mqh


Types de données SQLite

Il existe cinq types de données dans SQLite Version 3

Type
Description
NULLE Valeur NULLE.
ENTIER Entier stocké sur 1, 2, 3, 4, 6 ou 8 octets selon l'amplitude de la valeur stockée.
RÉEL nombre réel de 8 octets.
TEXTE Chaîne de texte avec le caractère de fin \0 stocké à l'aide du codage UTF-8 ou UTF-16.
BLOB Données binaires arbitraires


Vous pouvez également utiliser d'autres noms, par exemple BIGINT ou INT acceptés dans divers SGBD pour spécifier le type de données d'un champ lors de la création d'une tableau à partir d'une requête SQL. Dans ce cas, SQLite les convertira dans l'un de ses types intrinsèques, dans ce cas en ENTIER. Pour plus d'informations sur les types de données et leurs relations, veuillez lire la documentationhttp://www.sqlite.org/datatype3.html


2.1. Ouverture et fermeture d'une base de données

Comme vous le savez déjà, une base de données dans SQLite3 est un fichier normal. Ainsi, ouvrir une base de données équivaut en fait à ouvrir un fichier et l’exécuter.

Cela se fait à l'aide de la fonction 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.

Un fichier de base de données est fermé à l'aide de la fonction 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.


Créons des fonctions d'ouverture et de fermeture de base de données dans le connecteur.

//+------------------------------------------------------------------+
//| 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);
  }

Le connecteur peut maintenant ouvrir et fermer une base de données. Vérifiez maintenant ses performances avec un script simple :

#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();
}

Exécutez le script en mode débogage, respirez profondément et vérifiez le fonctionnement de chaque chaîne. En conséquence, un fichier de base de données apparaîtra dans le dossier d'installation du terminal MetaTrader 5. Félicitez-vous pour ce succès et passez à la section suivante.


2.2. Exécution de requêtes SQL

Toute requête SQL dans SQLite3 doit passer par au moins trois étapes :

  1. sqlite3_prepare - vérification et réception de la liste des déclarations ;
  2. sqlite3_step - exécuter ces instructions ;
  3. sqlite3_finalize - finalisation et effacement de la mémoire.

Cette structure est principalement adaptée à la création ou à la suppression de tableaux, ainsi qu'à l'écriture de données non binaires, c'est-à-dire pour les cas où une requête SQL n'implique pas le retour de données à l'exception de son statut de réussite d'exécution.

Si la requête implique la réception de données ou l'écriture de données binaires, la fonction sqlite3_column_хх ousqlite3_bind_хх est utilisée à la deuxième étape, respectivement. Ces fonctions sont décrites en détail dans la section suivante.

Écrivons la méthode CSQLite3Base::Query pour exécuter une requête SQL simple :

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
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);
  }

Comme vous pouvez le voir, les fonctions sqlite3_prepare, sqlite3_step et sqlite3_finalize se succèdent les uns après les autres.

Envisagez l'exécution de CSQLite3Base::Query lorsque vous travaillez avec des tableaux dans SQLite :

// Create the table (CREATE TABLE)
sql3.Query("CREATE TABLE IF NOT EXISTS `TestQuery` (`ticket` INTEGER, `open_price` DOUBLE, `comment` TEXT)");

Après avoir exécuté cette commande, le tableau apparaît dans la base de données :

// 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`");

Après avoir exécuté ces commandes, nous recevons le tableau avec un nouveau nom et un champ supplémentaire :

// 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)")

L'entrée suivante apparaît dans le tableau après l'ajout et la modification de la nouvelle ligne :

Enfin, les commandes suivantes doivent être exécutées les unes après les autres pour nettoyer la base de données.

// 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");

Avant de passer à la section suivante, nous avons besoin de la méthode qui reçoit une description d'erreur. D'après ma propre expérience, je peux dire que le code d'erreur peut fournir de nombreuses informations, mais la description de l'erreur indique l'endroit dans le texte de la requête SQL où une erreur est apparue, simplifiant sa détection et sa correction.

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.

Dans le connecteur, nous devons ajouter la méthode pour recevoir cette chaîne du pointeur en utilisant strcpy et 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. Obtenir des données à partir de tableaux

Comme je l'ai déjà mentionné au début de la section 2.2, la lecture des données est effectuée à l'aide des fonctions sqlite3_column_хх Ceci peut être schématisé comme suit :

  1. sqlite3_prepare
  2. sqlite3_column_count - connaître le nombre de colonnes du tableau obtenu
  3. Alors que l'étape actuelle résulte sqlite3_step == SQLITE_ROW
    1. sqlite3_column_хх- lire les cellules de chaîne
  4. sqlite3_finalize

Puisque nous approchons d'une section étendue concernant la lecture et l'écriture de données, c'est le bon moment pour décrire trois classes de conteneurs utilisées dans l'ensemble de l'échange de données. Le modèle de données nécessaire dépend de la manière dont les données sont stockées dans la base de données :

Base de données
|
Le tableau est une matrice de lignes.
|
La ligne est un tableau de cellules.
|
La cellule est un tampon d'octets d'une longueur arbitraire.


//+------------------------------------------------------------------+
//| 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;
//...
  };

Comme vous pouvez le voir, les connexions CSQLite3Row et CSQLite3Table sont primitives - ce sont des tableaux de données conventionnels. La classe de cellules CSQLite3Cell a également un tableau de données uchar + un champ de type de données. Le tableau d'octets est implémenté dans la classe CByteImage (similaire au célèbre CFastFile).

J'ai créé l'énumération suivante pour faciliter le fonctionnement du connecteur et gérer les types de données de cellule :

enum enCellType
  {
   CT_UNDEF,
   CT_NULL,
   CT_INT,
   CT_INT64,
   CT_DBL,
   CT_TEXT,
   CT_BLOB,
   CT_LAST
  };

Notez que le type CT_UNDEF a été ajouté à cinq types SQLite3 de base pour identifier l'état initial de la cellule. Le type ENTIER divisé en CT_INT et CT_INT64selon les fonctions sqlite3_bind_intXX et sqlite3_column_intXX divisées de la même manière.

Obtenir des données

Afin d'obtenir des données de la cellule, nous devons créer la méthode généralisant les fonctions de type sqlite3_column_хх. Il vérifiera le type et la taille des données et les écrira dans 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);
  }

La fonction est assez volumineuse, mais elle ne lit que les données de l'instruction en cours et les stocke dans une cellule.

Nous devons également surcharger la fonction CSQLite3Base::Query en ajoutant le tableau de conteneur CSQLite3Table pour les données reçues comme premier paramètre.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
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
  }

Nous avons toutes les fonctions nécessaires pour recevoir des données. Passons aux exemples :

// Read data (SELECT)
CSQLite3Table tbl;
sql3.Query(tbl, "SELECT * FROM `Trades`")

Imprimez le résultat de la requête dans le terminal à l'aide de la commande suivante Print(TablePrint(tbl)). Nous verrons les entrées suivantes dans le journal (l'ordre est de bas en haut) :

// 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`;");

Le résultat de la requête est imprimé de la même manière en utilisant Print(TablePrint(tbl)). On peut voir le tableau existant :

Comme le montrent les exemples, les résultats de l'exécution des requêtes sont placés dans la variable tbl. Après cela, vous pouvez facilement les obtenir et les traiter à votre discrétion.


2.4. Écriture de données de paramètres avec liaison

Un autre sujet qui peut être important pour les nouveaux est l'écriture de données dans la base de données ayant un format «incommode». Bien sûr, nous parlons ici de données binaires. Il ne peut pas être transmis directement dans une instruction de texte INSÉRER ou METTRE À JOUR courante, car une chaîne est considérée comme complète lorsque le premier zéro est rencontré. Le même problème se produit lorsque la chaîne elle-même contient des guillemets simples '.

La reliure tardive peut être utile dans certains cas, surtout lorsque le tableau est large. Il serait difficile et peu fiable d'écrire tous les champs sur une seule ligne, car vous pouvez facilement manquer quelque chose. Les fonctions de la série sqlite3_bind_хх sont nécessaires pour l'opération de liaison.

Afin d'appliquer la liaison, un modèle doit être inséré à la place des données transmises. Je vais considérer l'un des cas - «?»signe. En d'autres termes, la requête METTRE À JOUR ressemblera à ceci :

MISE À JOUR `Trades` SET `open_price`=?, `comment`=? WHERE(`ticket`=3)


Ensuite, les fonctions sqlite3_bind_double et sqlite3_bind_textdoivent être exécutées l'une après l'autre pour placer les données dans open_price et commenter. En règle générale, l'utilisation des fonctions de liaison peut être représentée de la manière suivante :

  1. sqlite3_prepare
  2. Appelez sqlite3_bind_хх l'un après l'autre et écrivez les données requises dans la déclaration
  3. sqlite3_step
  4. sqlite3_finalize

Par le nombre de types sqlite3_bind_xx répète complètement les fonctions de lecture décrites ci-dessus. Ainsi, vous pouvez facilement les combiner dans le connecteur à 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);
  }

Le seul objectif de cette méthode est d'écrire le tampon de la cellule passée dans l'instruction.

Ajoutons la méthode CQLite3Table::QueryBind de la même manière. Son premier argument est une chaîne de données pour l'écriture :

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
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
  }

Son objectif est d'écrire la chaîne dans les paramètres appropriés.


2.5. Transactions / Insertions multilignes

Avant de continuer avec ce sujet, vous devez connaître une autre fonction de l'API SQLite. Dans la section précédente, j'ai décrit le traitement des requêtes en trois étapes : préparer+étape+finaliser. Cependant, il existe une solution alternative (dans certains cas, simple voire critique) – la fonctionsqlite3_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.

Son objectif principal est d'exécuter la requête en un seul appel sans créer de constructions en trois étapes.

Ajoutons son appel au connecteur :

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
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);
  }

La méthode qui en résulte est facile à utiliser. Par exemple, vous pouvez exécuter la commande de suppression de tableau (DROP TABLE) ou de compactage de base de données (VACUUM) de la manière suivante :

sql3.Exec("DROP TABLE `Trades`");

sql3.Exec("VACUUM");


Transactions

Maintenant, supposons que nous devions ajouter plusieurs milliers de lignes au tableau. Si on insère tout ça dans la boucle :

for (int i=0; i<N; i++)
   sql3.Query("INSERT INTO `Table` VALUES(1, 2, 'text')");

l'exécution sera très lente (plus de 10(!) secondes). Ainsi, une telle implémentation n'est pas recommandée dans SQLite. La solution la plus appropriée ici est d'utiliser transactions: toutes les instructions SQL sont saisies dans une liste commune puis passées en une seule requête.

Les instructions SQL suivantes sont utilisées pour écrire le début et la fin de la transaction :

BEGIN
...
COMMIT

Tout le contenu est exécuté à la dernière instruction COMMIT. L'instruction ROLLBACK est utilisée au cas où la boucle devrait être interrompue ou que les instructions déjà ajoutées ne devraient pas être exécutées.

À titre d'exemple, toutes les transactions de compte sont ajoutées au tableau.

#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());
  }

Une fois le script appliqué au compte, il insère instantanément les transactions du compte dans le tableau.

Les statistiques sont affichées dans le journal du terminal

Vous pouvez jouer avec le script : commentez les lignes contenant BEGIN, ROLLBACK et COMMIT. S'il y a plus de centaines d'offres sur votre compte, vous verrez immédiatement la différence. En fait, selon quelques tests, les transactions SQLite fonctionnent plus rapidement que dans MySQL ou PostgreSQL.


3. Compilation de la version 64 bits (sqlite3_64.dll)

  1. Téléchargez Code source SQLite (fusion) et recherchez le fichier sqlite3.c.
  2. Télécharger sqlite-dll-win32 et extraire le fichier sqlite3.dll de celui-ci.
  3. Exécutez la commande de console LIB.EXE /DEF:sqlite3.def dans le dossier où le fichier dll a été extrait. Assurez-vous que les chemins d'accès au fichier lib.exe sont définis dans la variable système PATH ou recherchez-le dans votre Visual Studio.
  4. Créer un projet DLL en sélectionnant la configuration de la version pour les plates-formes 64 bits.
  5. Ajoutez les fichiers sqlite3.c téléchargés et obtenus sqlite3.def au projet. Si le compilateur n'accepte pas certaines fonctions du fichier def, mettez-les simplement en commentaire.
  6. Les paramètres suivants doivent être définis dans les paramètres du projet :
    C/C++ Général Format des informations de débogage = Base de données du programme (/Zi)
    C/C++ En-têtes pré-compilés Créer/utiliser un en-tête pré-compilé = Ne pas utiliser d'en-têtes pré-compilés (/Yu)
  7. Compilez et obtenez une dll 64 bits.


Conclusion

J'espère que cet article deviendra votre guide indispensable dans la maîtrise de SQLite. Vous l’utiliserez peut-être dans vos futurs projets. Ceci fourni un bref aperçu des fonctionnalités de SQLite en tant que solution parfaite et fiable pour les applications.

Dans cet article, j'ai décrit tous les cas auxquels vous pouvez être confronté lors de la gestion des données de trading. Comme devoir, je vous recommande de développer un simple collecteur de ticks en les insérant dans le tableau pour chaque symbole. Vous pouvez trouver le code source de la bibliothèque de classes et les scripts de test dans la pièce jointe ci-dessous.

Je vous souhaite bonne chance et de gros bénéfices!

Traduit du russe par MetaQuotes Ltd.
Article original : https://www.mql5.com/ru/articles/862

Fichiers joints |
MQL5.zip (790.12 KB)
Indicateur de construction d'un graphique à trois sauts de ligne Indicateur de construction d'un graphique à trois sauts de ligne
Cet article est consacré au tableau des trois sauts de ligne, suggéré par Steve Nison dans son livre « Beyond Candlesticks ». Le plus grand avantage de ce graphique est qu’il permet de filtrer les fluctuations mineures d’un prix par rapport au mouvement précédent. Nous allons discuter du principe de la construction graphique, du code de l’indicateur et de quelques exemples de stratégies de trading basées sur celui-ci.
Réseau neuronal bon marché et joyeux - Lier NeuroPro avec MetaTrader 5 Réseau neuronal bon marché et joyeux - Lier NeuroPro avec MetaTrader 5
Si des programmes de réseau neuronal spécifiques au trading vous semblent coûteux et complexes ou, au contraire, trop simples, essayez NeuroPro. Il est gratuit et contient l'ensemble optimal de fonctionnalités pour les amateurs. Cet article vous expliquera comment l'utiliser en conjonction avec MetaTrader 5.
Récits de Robots de Trading : Est-ce moins plus que ca ? Récits de Robots de Trading : Est-ce moins plus que ca ?
Il y a deux ans, dans « La Dernière Croisade », nous avons passé en revue une méthode intéressante mais actuellement peu utilisée pour afficher des informations sur le marché - des graphiques de points et de figures. Maintenant, je vous suggère d’essayer d’écrire un robot de trading basé sur les modèles détectés sur le graphique de points et de figures.
Regardez la vidéo tutoriel Service de signaux MetaTrader Regardez la vidéo tutoriel Service de signaux MetaTrader
En seulement 15 minutes, ce didacticiel vidéo explique ce qu'est le service de signaux MetaTrader et montre en détail comment s'abonner aux signaux de trading et comment devenir un fournisseur de signaux dans notre service. En regardant ce tutoriel, vous pourrez vous abonner à n'importe quel signal de trading, ou publier et promouvoir vos propres signaux dans notre service.