Trabajo con el SGBD MySQL desde MQL5 (MQL4)

Eugeniy Lugovoy | 15 enero, 2015

Introducción

El problema de interacción de MQL con las bases de datos se ha tocado desde hace tiempo y todavía sigue siendo de actualidad. El uso de los SGBD amplia considerablemente las posibilidades de la plataforma MetaTrader: el almacenamiento y el análisis del historial de cotizaciones, el copiado de las operaciones de una plataforma comercial a la otra, la transmisión de las cotizaciones/operaciones en tiempo real, la ejecución de los cálculos analíticos voluminosos en el lado del servidor y/o según el horario, el monitoreo y control remoto con el uso de las tecnologías Web.

De una manera u otra, se hacían los intentos de amistar MQL y MySQL y las soluciones se publicaban regularmente en CodeBase.

Por ejemplo, "MySQL wrapper - biblioteca para MetaTrader 4" es el proyecto a partir del cual muchos programadores empezaban sus propios desarrollos con algunas adiciones. Según mi opinión, una de las desventajas de esta solución es la asignación de los arrays especiales para recibir los datos de las base de datos.

El proyecto "MySQL logger 1 - Asesor Experto para MetaTrader4" es de carácter estrechamente especializado, no utiliza el envoltorio (wrapper) para dirigirse a la biblioteca estándar libmysql.dll. Por eso ya no va a trabajar con MetaTrader4 Build 600+ porque los tipos de caracteres char han sido reemplazados por wchar_t. Además, el uso del tipo int en vez del puntero a la estructura TMYSQL causa en el proyecto así llamados memory leaks (resulta imposible controlar/liberar la memoria asignada).

Otro proyecto que llama la atención es "EAX_Mysql - MySQL library - biblioteca para MetaTrader 5". Tiene bastante buena implementación a un alto nivel. La lista de faltas que menciona el autor impone ciertas limitaciones en su uso.

Cualquier programador que necesita utilizar los SGBD en sus proyectos MQL siempre tiene dos opciones: desarrollar su propia solución y conocerla al detalle, o bien, usar/adoptar alguna solución ajena, aprender a utilizarla y detectar todos los defectos que afectan el trabajo de su proyecto.

Me he encontrado ante la misma necesidad teniendo estas dos opciones durante el desarrollo de un robot comercial bastante complejo. Después de buscar y estudiar una cantidad bastante importante de diferentes soluciones, he llegado a la conclusión que ninguna de las implementaciones encontradas me valen para llevar mi robot a un «nivel profesional».

Es más, había incluso algunas soluciones absurdas, por ejemplo: las operaciones DML/DDL (inserción/edición/eliminación de datos, creación/eliminación de los objetos en la base de datos) se realizaban usando la biblioteca estándar libmysql.dll, y la selección de datos (SELECT) se realizaba prácticamente mediante la consulta HTTP (usando inet.dll) al script PHP ubicado en el servidor Web en el lado del servidor MySQL. Las consultas SQL estaban escritas en el script PHP.

En otras palabras, para iniciar este proyecto, había que tener configurados e iniciados: el servidor MySQL, el servidor Web Apache/IIS, los scripts PHP/ASP en el lado del servidor... Como ven se trata de un número bastante grande de tecnologías en la misma combinación. Naturalmente, en algunas circunstancias eso puede ser aceptable, pero cuando la tarea consiste en la selección de datos de la base de datos- es un sin sentido. Además, el soporte de una embarazosa solución como ésta va a requerir mucho tiempo.

En la mayoría de las soluciones no surgía ningún problema con la inserción de datos, creación de objetos, etc. El problema consistía precisamente con la selección de datos puesto que era necesario devolver los datos en el entorno invocable.

El uso de los arrays para estos propósitos lo he considerado inconveniente e incómodo simplemente porque durante el desarrollo/depuración/soporte del proyecto principal las consultas para la selección a la base de datos pueden cambiarse, y si además habrá que controlar también la asignación correcta de la memoria para los arrays, pues... En fin, se puede y hay que evitarlo.

La interfaz MQL <-> MySql de la que se trata a continuación se basa en el enfoque típico que se utiliza en Oracle PL/SQL, MS SQL T-SQL, AdoDB, es decir, el trabajo con los cursors. Esta interfaz ha sido desarrollada desde el punto de vista de la comodidad de programación y de mantenimiento, más un mínimo de componentes. Está implementada como el envoltorio DLL para la biblioteca estándar libmysql.dll y un conjunto de funciones de interfaz en forma del archivo .mqh.


1. Interfaz MQL <-> MySQL

La interacción entre el terminal MetaTrader (mediante los programas MQL) se implementa usando los siguientes componentes:

Esquema de interacción de MQL con MySQL

1. Biblioteca de interfaz MQLMySQL.mqh. Se añade al proyecto usando la directiva #include y puede ser modificada a su gusto.

Contiene las directivas para importar las funciones de la biblioteca dinámica MQLMySQL.dll, así como las funciones de su invocación y procesamiento de errores.

2. Biblioteca dinámica MQLMySQL.dll. Representa un envoltorio para el acceso a las prestaciones funcionales de la biblioteca estándar libmysql.dll.

Además, la biblioteca MQLMySQL.dll procesa los resultados de ejecución de las operaciones y divide el acceso a las conexiones de la base de datos y a los cursors. Eso quiere decir que Usted puede crear y utilizar simultáneamente varias conexiones (desde uno o varios programas MQL), tener abiertas a la vez una serie de cursors con las consultas a uno o varios SGBD. La separación del acceso a los recursos compartidos está ejecutada con el uso de las exclusiones mutuas (mutexes).

3. La biblioteca dinámica estándar libmysql.dll es el controlador "nativo" (native) del acceso. Puede copiarla desde cualquier distribución del SGBD MySql a C:\Windows\Sytem32 o <Terminal>\MQL5\Libraries (para MetaTrader4 a <Terminal>\MQL4\Libraries).

En realidad, se encarga del envío de las consultas del SGBD y devolución de resultados.

Pues, vamos a analizar las siguientes cuestiones con más detalles: apertura/cierre de la conexión, ejecución de las consultas DML/DDL y selección de datos.

1.1. Abrir y cerrar la conexión

La función de interfaz MySqlConnect ha sido implementada para abrir la conexión con la base de datos MySQL:

Tipo

Nombre

Parámetros

Descripción

int

MySqlConnect

Esta función realiza la conexión con el SGBD y devuelve el identificador de la conexión. Este identificador va a ser necesario para realizar las consultas al SGBD.

En caso del fallo de la conexión, el valor devuelto será igual a "-1". Para obtener los detalles del error, compruebe las variables MySQLErrorNumber y MySqlErrorDescription.

Habitualmente esta función se invoca durante el procesamiento del evento OnInit() en el programa MQL.

string pHost

El nombre DNS o la dirección IP del servidor MySQL

string pUser

El usuario de la base de datos (por ejemplo, root)

string pPassword

La contraseña del usuario de la base de datos

string pDatabase

El nombre de la base de datos

int pPort

El puerto TCP/IP de la base de datos (normalmente, 3306)

string pSocket

El socket Unix (para los sistemas Unix-based)

int pClientFlag

La combinación de banderas especiales (normalmente, 0)

Para el cierre de la conexión ha sido implementada la función de interfaz MySqlDisconnect:

Tipo

Nombre Parámetros Descripción

void

MySqlDisconnect

Esta función cierra la conexión con el SGBD MySQL.

Habitualmente esta función se invoca durante el procesamiento del evento OnDeinit() en el programa MQL.

int pConnection

Identificador de la conexión

Hay que mencionar que el SGBD MySQL puede cerrar la conexión por sí mismo en caso del fallo de hardware, sobrecargas de la red o por el tiempo de inactividad (time-out) (cuando las consultas no llegan a la base de datos durante un tiempo prolongado).

A menudo para la escritura de datos en la base de datos el desarrollador utiliza el evento OnTick(). No obstante, cuando llega el fin de semana y el mercado está cerrado, la conexión se queda "colgada". En este caso el SGBD MySQL la cerrará por time-out (por defecto, 8 horas).

Y el lunes, cuando el mercado se abrirá, el desarrollador encontrará errores en el funcionamiento de su proyecto. Por eso les recomiendo insistentemente que comprueben la conexión y/o vuelvan a conectarse al SGBD dentro de un período de tiempo menor que el time-out establecido en los ajustes del servidor MySQL.

1.2. Ejecutar las consultas DML/DDL

Las operaciones DML son las operaciones que se utilizan para la manipulación de datos (Lenguaje de Manipulación de datos - Data Manipulation Language). La manipulación de datos incluye el siguiente conjunto de comandos: insertar (INSERT), actualizar (UPDATE) y eliminar (DELETE).

Las operaciones DDL son las operaciones que se utilizan para la descripción/definición de datos (Lenguaje de Definición de Datos - Data Definition Language). Aquí entra la creación (CREATE) de los objetos de la base de datos (tablas, visualizaciones, procedimientos guardados, disparadores, etc.), su alteración (ALTER) y eliminación (DROP).

Estos no son todos los comandos de los lenguajes DML/DDL. Además, hay otro lenguaje de gestión de las bases de datos DCL (Lenguaje de Control de Datos - Data Control Language) que se utiliza para separar el acceso a los datos, sin embargo, aquí no vamos a entrar en los detalles del lenguaje SQL. Cualquiera de estos comandos puede ser ejecutado mediante la función de interfaz MySqlExecute:


Tipo

Nombre

Parámetros

Descripción

bool

MySqlExecute

Se puede utilizar esta función para ejecutar los comandos no-SELECT del lenguaje SQL, después de que haya sido establecida la conexión con éxito con el SGBD (usando la función MySqlConnect).

Devuelve true en caso de la ejecución exitosa, de lo contrario devuelve false. Para obtener más detalles sobre el error, utilice las variables MySQLErrorNumber y MySqlErrorDescription.

int pConnection

Identificador de la conexión

string pQuery

Consulta SQL

Como la consulta SQL, también se puede utilizar el comando USE para la selección de la base de datos. Aparte me gustaría mencionar el uso de las consultas combinadas (multi-statement queries). Se trata de un conjunto de comandos SQL separados con un signo especial ";".

Para activar el modo multi-statements, es necesario abrir la conexión con el SGBD usando la bandera CLIENT_MULTI_STATEMENTS:

...
int ClientFlag = CLIENT_MULTI_STATEMENTS; // establecer la bandera del uso de las consultas combinadas
int DB; 

DB = MySqlConnect(Host, User, Password, Database, Port, Socket, ClientFlag); // conexión con el SGBD

if (DB == -1)
   {
    // procesamiento del error de conexión
   }
...

// preparación de la consulta SQL para insertar los datos (3 filas en una consulta)
string SQL;
SQL = "INSERT INTO EURUSD(Ask,Bid) VALUES (1.3601,1.3632);";
SQL = SQL + "INSERT INTO EURUSD(Ask,Bid) VALUES (1.3621,1.3643);";
SQL = SQL + "INSERT INTO EURUSD(Ask,Bid) VALUES (1.3605,1.3629);";
...

if (!MySqlExecute(DB,SQL)) 
   {
    // mostrar el mensaje del error
   }
...

En este fragmento, se insertarán 3 entradas en la tabla EURUSD con sólo una llamada al SGBD. Cada una de las consultas guardadas en la variable SQL está separada con el signo ";".

Este enfoque puede utilizarse en caso cuando surge la necesidad de una inserción/actualización/eliminación frecuente. El conjunto de comandos necesarios se combina en el mismo "pack", aliviando de esta manera el tráfico de la red y subiendo el rendimiento del SGBD.

La sintaxis del comando INSERT en MySQL está bastante bien desarrollada en cuanto al procesamiento de las excepciones.

Por ejemplo, si tenemos planteada la tarea de mover el historial de cotizaciones, para los pares de divisas es necesario crear las tablas con la clave primaria del tipo datetime porque precisamente la fecha y la hora de la barra son únicas. Es más, hay que comprobar si ya existen los datos sobre alguna barra determinada en la base de datos (con el fin de aumentar la estabilidad de migración de los datos). En caso de MySQL la necesidad de hacer esta comprobación desaparece puesto que el comando INSERT soporta ON DUPLICATE KEY.

Para ser más claro: si se realiza la inserción de datos y la entrada con esta fecha y la hora ya existe en la tabla, INSERT puede ser ignorado o reemplazado por UPDATE para esta fila (véase http://dev.mysql.com/doc/refman/5.0/en/insert-on-duplicate.html).

1.3. Seleccionar los datos

Para seleccionar los datos de la base de datos, se utiliza el comando SELECT del lenguaje SQL. Para su ejecución y obtención de resultados, está implementado el siguiente esquema:

  1. Preparación del comando SELECT.
  2. Apertura del cursor.
  3. Obtención del número de filas devueltas por la consulta.
  4. Organización del ciclo con la recuperación de cada fila de la consulta.
  5. Extracción de los datos en las variables MQL dentro del ciclo.
  6. Cierre del cursor.

Desde luego, es un esquema general, y no siempre todas las operaciones van a ser necesarias. Por ejemplo, si es necesario asegurarse de que la fila esté presente en la tabla (según algunos criterios), bastará con preparar la consulta, abrir el cursor, obtener el número de las filas y cerrar el cursor. En realidad, los puntos obligatorios son los siguientes: preparación del comando SELECT, apertura del cursor y su cierre.

¿Y qué es un cursor? Es una referencia a un área contextual de memoria; en sustancia, se trata del conjunto resultante de valores. Cuando se manda la consulta SELECT, el SGBD asigna la memoria para el resultado de la selección y crea un puntero a la fila el que se puede mover de una fila a la otra. De esta manera, se puede obtener el acceso a todas las filas en orden de sucesión (cláusula ORDER BY del comando SELECT).

Para la selección de datos se utilizan las siguientes funciones de interfaz:

Apertura del cursor:

Tipo

Nombre

Parámetros

Descripción

int

MySqlCursorOpen

Esta función abre el cursor para la consulta SELECT y devuelve el identificador del cursor en caso de la ejecución exitosa. De lo contrario, la función devolverá "-1", para obtener los detalles del error, utilice las variables MySQLErrorNumber y MySqlErrorDescription.

int pConnection

Identificador de la conexión con la base de datos

string pQuery

Consulta SQL (sentencia SELECT)

Obtención del número de filas devueltas por la consulta;

Tipo

Nombre

Parámetros

Descripción

int

MySqlCursorRows

Esta función devuelve el número de filas seleccionadas por la consulta.

int pCursorID

Identificador del cursor devuelto por la función MySqlCursorOpen

Recuperación de la fila de la consulta:

Tipo

Nombre

Parámetros

Descripción

bool

MySqlCursorFetchRow

Recupera una fila del conjunto de datos devueltos por la consulta. Tras la ejecución exitosa, se puede recuperar los datos en las variables MQL. La función devuelve true en caso del éxito, de lo contrario devuelve false.

int pCursorID

Identificador del cursor devuelto por la función MySqlCursorOpen

Recuperación de datos en las variables MQL tras la recuperación de la fila de la consulta:

Tipo

Nombre

Parámetros

Descripción

int

MySqlGetFieldAsInt

Esta función devuelve la representación del valor del campo de la tabla utilizando el tipo de datos int.

int pCursorID

Identificador del cursor devuelto por la función  MySqlCursorOpen

int pField

Número del campo en la lista SELECT (numeración desde 0)

double

MySqlGetFieldAsDouble

Esta función devuelve la representación del valor del campo de la tabla utilizando el tipo de datos  double.

int pCursorID

Identificador del cursor devuelto por la función  MySqlCursorOpen

int pField

Número del campo en la lista SELECT (numeración desde 0)

datetime

MySqlGetFieldAsDatetime

Esta función devuelve la representación del valor del campo de la tabla utilizando el tipo de datos datetime.

int pCursorID

Identificador del cursor devuelto por la función  MySqlCursorOpen

int pField

Número del campo en la lista SELECT (numeración desde 0)

string

MySqlGetFieldAsString

Esta función devuelve la representación del valor del campo de la tabla utilizando el tipo de datos string.

int pCursorID

Identificador del cursor devuelto por la función MySqlCursorOpen

int pField

Número del campo en la lista SELECT (numeración desde 0)


Todos los datos devueltos por MySQL están representados sin tipos en forma de las filas (native presentation).

Por eso utilizando estas funciones se puede convertir los datos seleccionados al tipo necesario. El único inconveniente es la especificación del número de la columna (numeración desde 0) en la lista SELECT en vez de su nombre. Sin embargo, cuando Usted desarrolla la aplicación, la preparación del comando SELECT y la obtención de resultados prácticamente siempre se encuentran en la zona de visibilidad de una página, es decir Usted ve la consulta SELECT cuando formula la lógica de la recuperación de los datos.

De esta manera, siempre sabe qué número tiene uno u otro campo en la lista SELECT (este enfoque se aplica también durante el acceso a los datos mediante AdoDB). En principio, no descarto la posibilidad de revisar este aspecto en el futuro en cuanto a su mejora, pero eso no tendrá mucho efecto en las prestaciones funcionales de la solución desarrollada.

Cierre del cursor:

Tipo

Nombre

Parámetros

Descripción

void

MySqlCursorClose

Esta función cierra el cursor especificado y libera la memoria.

int pCursorID

Identificador del cursor devuelto por la función MySqlCursorOpen

El cierre del cursor supone una operación sumamente importante. ¡Qué nunca se le olvide cerrar los cursores!

Imagínese que ha abierto un cursor al haber olvidado cerrarlo. Supongamos que la recuperación de los datos en el cursor se realiza con cada tick durante el procesamiento del evento OnTick() y cada vez nuevo cursor va a abrirse, se asignará la memoria para él (en el lado del servidor y en el lado del cliente). En algún momento el servidor le denegará el servicio porque el límite de los cursors abiertos estará agotado, y en el lado del cliente el búfer estará sobrecargado.

Naturalmente, aquí exagero un poco, es que este resultado es posible cuando se trabaja con libmysql.dll directamente. No obstante, la biblioteca dinámica MQLMySQL.DLL se encarga de distribuir la memoria para los cursors y precisamente ella da rechazo al intentar abrir el cursor que sale del límite permitido.

Durante la realización de las tareas reales, será suficiente tener abiertos a la vez 2-3 cursores. Cada cursor es capaz de procesar una medida cartesiana de datos, el uso de dos o tres cursors simultáneamente (por ejemplo, anidados, cuando un cursor depende del otro paramétricamente) cubrirá dos o tres dimensiones. Eso es suficiente para la mayoría de las tareas. Además, para la realización de las selecciones complejas de datos, siempre puede utilizar estos objetos para la representación de la base de datos (VIEW), crearlos en el lado del servidor y enviarles consultas desde el código MQL como a las tablas.

1.4. Información adicional

Lo que sigue a continuación puede ser mencionado como las prestaciones funcionales adicionales:

1.4.1. Lectura de datos desde el archivo .INI

Tipo

Nombre

Parámetros

Descripción

String

ReadIni

Devuelve el valor de la clave de la sección establecida del archivo INI.

string pFileName

Nombre del archivo INI

string pSection

Nombre de la sección

string pKey

Nombre de la clave


A menudo no es razonable guardar los datos sobre la conexión con el SGBD (dirección IP, servidor, puerto, nombre del usuario, contraseña, etc.) directamente en el código MQL (o en los parámetros del robot, indicador, script) porque el servidor puede ser trasladado, su dirección puede cambiarse dinamicamente, etc. Entonces, habrá que corregir el código MQL. De esta manera, es mejor guardar todos estos datos en el archivo INI estándar, mientras que en el programa MQL fijar sólo su nombre. Luego, usando la función ReadINI sólo hay que leer los parámetros de la conexión y usarlos.

Por ejemplo, el archivo INI contiene la siguiente información:

[MYSQL]
Server = 127.0.0.1
User = root
Password = Adm1n1str@t0r
Database = mysql
Port = 3306

Por ejemplo, para obtener la dirección IP, hay que ejecutar lo siguiente:

string vServer = ReadIni("C:\\MetaTrader5\\MQL5\\Experts\\MyConnection.ini", "MYSQL", "Server");

El archivo INI файл se encuentra en C:\MetaTrader5\MQL5\Experts y se llama "MyConnection.ini", Usted accede a la clave Server de la sección MYSQL. En un archivo INI Usted puede guardar los ajustes a diferentes servidores que utiliza en su proyecto.

1.4.2. Emplazamiento de las áreas problemáticas

En la biblioteca de interfaz está previsto el modo de emplazamiento (trace mode) que puede ser activado para la depuración de las consultas SQL en cualquier lugar del programa MQL.

Para eso hay que indicar en el área problemática lo siguiente:

SQLTrace = true;

y después de ella

SQLTrace = false;

Si activa el emplazamiento en el inicio del programa MQL y no la desactiva después, van a registrarse todas las consultas al SGBD. El registro se realiza en la consola del terminal (mediante el comando Print).


2. Ejemplos

Esta sección contiene algunos ejemplos de la conexión y el uso de las bibliotecas desarrolladas. Le servirán para que haga una idea sobre la comodidad del uso de esta solución de software.

El ejemplo MySQL-003.mq5 demuestra los siguiente: conexión con el SGBD (los parámetros de la conexión se almacenan en el archivo ini), creación de la tabla, inserción de datos (también con el uso de multi-statements) y desconexión del SGBD.

//+------------------------------------------------------------------+
//|                                                    MySQL-003.mq5 |
//|                                   Copyright 2014, Eugene Lugovoy |
//|                                              https://www.mql5.com |
//| Inserting data with multi-statement (DEMO)                       |
//+------------------------------------------------------------------+
#property copyright "Copyright 2014, Eugene Lugovoy."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property strict

#include <MQLMySQL.mqh>

string INI;
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
{
 string Host, User, Password, Database, Socket; // database credentials
 int Port,ClientFlag;
 int DB; // database identifier
 
 Print (MySqlVersion());

 INI = TerminalInfoString(TERMINAL_PATH)+"\\MQL5\\Scripts\\MyConnection.ini";
 
 // reading database credentials from INI file
 Host = ReadIni(INI, "MYSQL", "Host");
 User = ReadIni(INI, "MYSQL", "User");
 Password = ReadIni(INI, "MYSQL", "Password");
 Database = ReadIni(INI, "MYSQL", "Database");
 Port     = (int)StringToInteger(ReadIni(INI, "MYSQL", "Port"));
 Socket   = ReadIni(INI, "MYSQL", "Socket");
 ClientFlag = CLIENT_MULTI_STATEMENTS; //(int)StringToInteger(ReadIni(INI, "MYSQL", "ClientFlag"));  

 Print ("Host: ",Host, ", User: ", User, ", Database: ",Database);
 
 // open database connection
 Print ("Connecting...");
 
 DB = MySqlConnect(Host, User, Password, Database, Port, Socket, ClientFlag);
 
 if (DB == -1) { Print ("Connection failed! Error: "+MySqlErrorDescription); } else { Print ("Connected! DBID#",DB);}
 
 string Query;
 Query = "DROP TABLE IF EXISTS `test_table`";
 MySqlExecute(DB, Query);
 
 Query = "CREATE TABLE `test_table` (id int, code varchar(50), start_date datetime)";
 if (MySqlExecute(DB, Query))
    {
     Print ("Table `test_table` created.");
     
     // Inserting data 1 row
     Query = "INSERT INTO `test_table` (id, code, start_date) VALUES ("+(string)AccountInfoInteger(ACCOUNT_LOGIN)+",\'ACCOUNT\',\'"+TimeToString(TimeLocal(), TIME_DATE|TIME_SECONDS)+"\')";
     if (MySqlExecute(DB, Query))
        {
         Print ("Succeeded: ", Query);
        }
     else
        {
         Print ("Error: ", MySqlErrorDescription);
         Print ("Query: ", Query);
        }
     
     // multi-insert
     Query =         "INSERT INTO `test_table` (id, code, start_date) VALUES (1,\'EURUSD\',\'2014.01.01 00:00:01\');";
     Query = Query + "INSERT INTO `test_table` (id, code, start_date) VALUES (2,\'EURJPY\',\'2014.01.02 00:02:00\');";
     Query = Query + "INSERT INTO `test_table` (id, code, start_date) VALUES (3,\'USDJPY\',\'2014.01.03 03:00:00\');";
     if (MySqlExecute(DB, Query))
        {
         Print ("Succeeded! 3 rows has been inserted by one query.");
        }
     else
        {
         Print ("Error of multiple statements: ", MySqlErrorDescription);
        }
    }
 else
    {
     Print ("Table `test_table` cannot be created. Error: ", MySqlErrorDescription);
    }
 
 MySqlDisconnect(DB);
 Print ("Disconnected. Script done!");
}

El ejemplo MySQL-004.mq5 demuestra la selección de los datos desde la tabla creada por el script "MySQL-003.mq5" 

//+------------------------------------------------------------------+
//|                                                    MySQL-004.mq5 |
//|                                   Copyright 2014, Eugene Lugovoy |
//|                                              https://www.mql5.com |
//| Select data from table (DEMO)                                    |
//+------------------------------------------------------------------+
#property copyright "Copyright 2014, Eugene Lugovoy."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property strict

#include <MQLMySQL.mqh>

string INI;
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
{
 string Host, User, Password, Database, Socket; // database credentials
 int Port,ClientFlag;
 int DB; // database identifier
 
 Print (MySqlVersion());

 INI = TerminalInfoString(TERMINAL_PATH)+"\\MQL5\\Scripts\\MyConnection.ini";
 
 // reading database credentials from INI file
 Host = ReadIni(INI, "MYSQL", "Host");
 User = ReadIni(INI, "MYSQL", "User");
 Password = ReadIni(INI, "MYSQL", "Password");
 Database = ReadIni(INI, "MYSQL", "Database");
 Port     = (int)StringToInteger(ReadIni(INI, "MYSQL", "Port"));
 Socket   = ReadIni(INI, "MYSQL", "Socket");
 ClientFlag = (int)StringToInteger(ReadIni(INI, "MYSQL", "ClientFlag"));  

 Print ("Host: ",Host, ", User: ", User, ", Database: ",Database);
 
 // open database connection
 Print ("Connecting...");
 
 DB = MySqlConnect(Host, User, Password, Database, Port, Socket, ClientFlag);
 
 if (DB == -1) { Print ("Connection failed! Error: "+MySqlErrorDescription); return; } else { Print ("Connected! DBID#",DB);}
 
 // executing SELECT statement
 string Query;
 int    i,Cursor,Rows;
 
 int      vId;
 string   vCode;
 datetime vStartTime;
 
 Query = "SELECT id, code, start_date FROM `test_table`";
 Print ("SQL> ", Query);
 Cursor = MySqlCursorOpen(DB, Query);
 
 if (Cursor >= 0)
    {
     Rows = MySqlCursorRows(Cursor);
     Print (Rows, " row(s) selected.");
     for (i=0; i<Rows; i++)
         if (MySqlCursorFetchRow(Cursor))
            {
             vId = MySqlGetFieldAsInt(Cursor, 0); // id
             vCode = MySqlGetFieldAsString(Cursor, 1); // code
             vStartTime = MySqlGetFieldAsDatetime(Cursor, 2); // start_time
             Print ("ROW[",i,"]: id = ", vId, ", code = ", vCode, ", start_time = ", TimeToString(vStartTime, TIME_DATE|TIME_SECONDS));
            }
     MySqlCursorClose(Cursor); // NEVER FORGET TO CLOSE CURSOR !!!
    }
 else
    {
     Print ("Cursor opening failed. Error: ", MySqlErrorDescription);
    }
    
 MySqlDisconnect(DB);
 Print ("Disconnected. Script done!");
}

En estos ejemplos se utiliza el procesamiento típico de errores que se aplica en los proyectos reales.

Prácticamente, cada consulta utilizada en el programa MQL debe estar depurada en cualquier cliente MySQL (PHPMyAdmin, DB Ninja, consola MySQL). Yo personalmente utilizo y recomiendo el software profesional para el desarrollo de las bases de datos Quest TOAD for MySQL.


Conclusión

Este artículo no describe los detalles de la implementación de la biblioteca dinámica MQLMySQL.DLL desarrollada en el entorno Microsoft Visual Studio 2010 (C/C++). La solución ofrecida sirve para el uso práctico y cuenta con más de 100 integraciones exitosas en diferentes campos de desarrollo en el lenguaje MQL (desde la creación de los sistemas de trading multifuncionales hasta las publicaciones Web).