English 中文 Español Deutsch 日本語 Português 한국어 Français Italiano Türkçe
Работа с СУБД MySQL из MQL5 (MQL4)

Работа с СУБД MySQL из MQL5 (MQL4)

MetaTrader 5Интеграция | 15 октября 2014, 09:03
9 642 97
Eugeniy Lugovoy
Eugeniy Lugovoy

Введение

Проблема взаимодействия MQL с базами данных уже давно поднималась и актуальна по сей день. С использованием СУБД возможности платформы MetaTrader значительно расширяются: хранение и анализ истории котировок, копирование сделок с одной торговой платформы на другую, трансляция котировок/сделок в режиме реального времени, выполнение громоздких аналитических вычислений на стороне сервера и/или по расписанию, мониторинг и удаленное управление счетом с использованием Web-технологий.

Так или иначе, попытки подружить MQL и MySQL предпринимались и решения регулярно выкладывались в CodeBase.

К примеру "MySQL оболочка - библиотека для MetaTrader 4" - это тот проект, с которого многие начинали вести свою разработку с какими-либо дополнениями. На мой взгляд, из неудобств данного решения - это выделение специальных массивов для получения данных из БД.

Проект "MySQL logger 1 - эксперт для MetaTrader 4" является узкоспециализированным, он не использует обертку для обращения к стандартной библиотеке libmysql.dll. Поэтому с MetaTrader4 Build 600+ он уже работать не будет, так как произошла замена символьных типов char на wchar_t, да и использование типа int вместо указателя на структуру TMYSQL вносит так называемые memory leaks в проект (выделенную память невозможно контролировать/освободить).

Еще один проект, который обращает на себя внимание, это "EAX_Mysql - MySQL library - библиотека для MetaTrader 5". Выполнен достаточно грамотно и на хорошем уровне. Перечень недоработок, о которых говорит сам автор, накладывает некоторые ограничения в его использовании.

Для тех, кто сталкивался с необходимостью использования СУБД в своих MQL-проектах всегда стоит выбор - либо написать "что-то свое" и знать каждый винтик, либо использовать/адаптировать какое-либо стороннее решение, а также научиться им пользоваться и "выловить" все "грабли", мешающие работе своего проекта.

С такой необходимостью и перед таким выбором стал и я в ходе разработки достаточно сложного торгового робота. После поиска и изучения весьма большого количества решений я понял, что для поднятия торгового робота до "профессионального уровня" ни одна из найденных реализаций не подходит.

Более того, встречались и абсурдные решения, например: DML/DDL операции (вставка/изменение/удаление данных, создание/удаление объектов БД) производились с использованием стандартной libmysql.dll, а выборка данных (SELECT) производилась фактически HTTP запросом (с использованием inet.dll) к PHP скрипту, расположенному на Web-сервере на стороне MySQL сервера. Именно в PHP скрипте были прописаны SQL запросы.

Иными словами, чтобы запустить данный проект, необходимо было иметь в наличии настроенными и запущенными: MySQL-сервер, Apache/IIS Web-сервер, PHP/ASP-скрипты на стороне сервера... Достаточно большое количество технологий в связке. Разумеется, в каких-то условиях это может быть приемлемо, но когда стоит задача всего лишь выборки данных из БД - это нонсенс. К тому же сопровождение такого громоздкого решения будет отнимать много времени.

Со вставкой данных, созданием объектов и т.п., во многих решениях проблем не возникало. Проблемой была именно выборка данных, так как данные необходимо было вернуть в вызывающую среду.

Использование массивов для этих целей я счел нецелесообразным и неудобным просто потому, что в ходе разработки/отладки/сопровождения основного проекта запросы на выборку к БД могут изменяться, а контролировать еще и правильное выделение памяти под массивы... В общем, от этого можно и нужно избавляться.

В основу интерфейса MQL <-> MySql, о котором далее пойдет речь, был положен типичный подход, который используется в Oracle PL/SQL, MS SQL T-SQL, AdoDB - работа с курсорами. Данный интерфейс был разработан с точки зрения удобства программирования и сопровождения, плюс минимум компонентов. Реализован он виде DLL-обертки к стандартной библиотеке libmysql.dll и набора интерфейсных функций в виде .mqh файла.


1. Интерфейс MQL <-> MySQL

Взаимодействие между терминалом MetaTrader (посредством MQL-программ) реализуется с помощью:

Схема взаимодействия MQL с MySQL

1. Интерфейсной библиотеки MQLMySQL.mqh. Она добавляется в проект директивой #include и может быть модифицирована по вашему вкусу.

Именно в ней находятся директивы импорта функций динамической библиотеки MQLMySQL.dll, а так же функции их вызова и обработка ошибок.

2. Динамическая библиотека MQLMySQL.dll. Она представляет собой обертку для доступа к функционалу стандартной библиотеки libmysql.dll.

Также, библиотека MQLMySQL.dll обрабатывает результаты выполнения операций и разделяет доступ к соединениям БД и курсорам. То есть, вы можете создавать и использовать одновременно несколько соединений (из одной или нескольких MQL-программ), одновременно держать открытыми ряд курсоров с запросами к одной или нескольким СУБД. Разделение доступа к общим ресурсам выполнено с использованием мьютексов.

3. Стандартная динамическая библиотека libmysql.dll является "родным" (native) драйвером доступа. Ее вы можете скопировать из любого дистрибутива СУБД MySql в C:\Windows\Sytem32 или <Terminal>\MQL5\Libraries (для MetaTrader4 в <Terminal>\MQL4\Libraries).

Собственно говоря, она и отвечает за отправку запросов СУБД и возврат результатов.

Остановимся подробнее на основных моментах, таких как: открытие/закрытие соединения, выполнение DML/DDL запросов и выборка данных.

1.1. Открытие и закрытие соединения

Для открытия соединения с базой данных MySQL реализована интерфейсная функция MySqlConnect:

Тип

Имя

Параметры

Описание

int

MySqlConnect

Данная функция выполняет соединение с СУБД и возвращает идентификатор соединения. Этот идентификатор будет необходим для выполнения запросов к СУБД.

В случае ошибки соединения, возвращаемое значение равно "-1". Для уточнения ошибки проверьте переменные MySQLErrorNumber и MySqlErrorDescription.

Обычно эта функция вызывается при обработке события OnInit() в программе MQL.

string pHost

DNS имя или IP-адрес сервера MySQL

string pUser

Пользователь БД (например, root)

string pPassword

Пароль пользователя БД

string pDatabase

Имя базы данных

int pPort

TCP/IP порт базы данных (обычно 3306)

string pSocket

Unix сокет (для Unix-based систем)

int pClientFlag

Комбинация специальных флагов (обычно 0)

Для закрытия соединения - реализована интерфейсная функция MySqlDisconnect:

Тип

Имя Параметры Описание

void

MySqlDisconnect

Данная функция выполняет закрытие соединения с СУБД MySQL.

Обычно эта функция вызывается при обработке события OnDeinit() в программе MQL.

int pConnection

Идентификатор соединения

Надо отметить, что СУБД MySQL может закрыть соединение самостоятельно в случае аппаратного сбоя, сетевых перегрузок или при наступлении таймаута (когда в течение длительного времени к базе данных не поступают запросы).

Нередко разработчик для записи данных в БД использует событие OnTick(). Однако, когда наступают выходные и рынок закрыт, соединение остается "висеть". В таком случае СУБД MySQL закроет его по таймауту (по умолчанию 8 часов).

А в понедельник, когда рынок будет открыт, разработчик обнаружит ошибки в работе своего проекта. Поэтому настоятельно рекомендуется выполнять проверку соединения и/или переподключаться к СУБД через интервал времени, меньший таймаута, установленного в настройках MySQL сервера.

1.2. Выполнение DML/DDL запросов

Операции DML - это операции, используемые для манипулирования данными (Data Manipulation Language). Манипуляции с данными включают следующий набор команд: вставка (INSERT), обновление (UPDATE) и удаление (DELETE).

Операции DDL - это операции, используемые для описания/определения данных (Data Definition Language). Сюда входит создание (CREATE) объектов базы данных (таблиц, представлений, хранимых процедур, триггеров и т.п.), их изменение (ALTER) и удаление (DROP).

Это далеко не все команды языков DML/DDL, кроме того, существует еще язык управления данными DCL (Data Control Language), используемый для разделения доступа к данным, однако здесь мы не будем углубляться в особенности языка SQL. Любая из этих команд может быть выполнена посредством интерфейсной функции MySqlExecute:


Тип

Имя

Параметры

Описание

bool

MySqlExecute

Данная функция может быть использована для выполнения не-SELECT команд языка SQL, после того как было успешно установлено соединение с СУБД (с помощью функции MySqlConnect).

В случае успешного выполнения команды функция возвращает true, в противном случае - false. Чтобы уточнить детали об ошибке - используйте переменные MySQLErrorNumber и MySqlErrorDescription.

int pConnection

Идентификатор соединения

string pQuery

SQL-запрос

В качестве SQL-запроса вы можете также использовать команду USE для выбора базы данных. Отдельно хочется отметить использование комбинированных запросов (multi-statement queries). Это набор SQL команд, разделенных специальным символом ";".

Для включения режима multi-statements, необходимо выполнить открытие соединения с СУБД с флагом CLIENT_MULTI_STATEMENTS:

...
int ClientFlag = CLIENT_MULTI_STATEMENTS; // установка флага использования комбинированных запросов
int DB; 

DB = MySqlConnect(Host, User, Password, Database, Port, Socket, ClientFlag); // соединение с СУБД

if (DB == -1)
   {
    // обработка ошибки соединения
   }
...

// подготовка SQL запроса на вставку данных (3 строки в одном запросе)
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)) 
   {
    // вывод сообщения об ошибке
   }
...

В данном фрагменте будет произведена вставка 3-х записей в таблицу EURUSD за одно обращение к СУБД. Каждый из запросов, хранящихся в переменной SQL, разделен символом ";".

Такой подход может быть использован в случае, когда есть необходимость частой вставки/обновления/удаления; набор необходимых команд комбинируется в одну "посылку", тем самым разгружается сетевой трафик и повышается производительность СУБД.

Хочется добавить, что синтаксис команды INSERT в MySQL достаточно хорошо проработан в плане обработки исключений.

Например, если стоит задача переноса истории котировок, то для этого необходимо создавать таблицы по валютным парам с первичным ключом типа datetime, так как именно дата и время бара являются уникальными. Более того, необходимо проверять - присутствуют ли данные о каком-либо конкретном баре уже в базе данных (дабы повысить стабильность миграции данных). В случае MySQL необходимость в подобной проверке отпадает, поскольку команда INSERT поддерживает предложение ON DUPLICATE KEY.

Простыми словами - если производится попытка вставки данных и в таблице уже есть запись с такой датой и временем, команда INSERT может быть проигнорирована или заменена на UPDATE для данной строки (см. http://dev.mysql.com/doc/refman/5.0/en/insert-on-duplicate.html).

1.3. Выборка данных

Для выборки данных из базы данных используется команда SELECT языка SQL. Для ее выполнения и получения результата выборки реализована следующая схема:

  1. Подготовка команды SELECT.
  2. Открытие курсора.
  3. Получение количества строк, возвращенных запросом.
  4. Организация цикла с извлечением каждой строки запроса.
  5. Извлечение данных в переменные MQL внутри цикла.
  6. Закрытие курсора.

Разумеется, это общая схема, и не всегда будут необходимы все операции. Например, если нужно убедиться в том, что строка присутствует в таблице (по каким-либо критериям), достаточно будет подготовить запрос, открыть курсор, получить количество строк и закрыть курсор. По сути, обязательными пунктами являются - подготовка команды SELECT, открытие курсора и его закрытие.

Что же такое курсор? Это ссылка на контекстную область памяти, по сути - результирующий набор значений. Когда вы отправляете запрос SELECT, СУБД выделяет память под результат выборки и создает указатель на запись, который можно перемещать от одной записи к другой. Тем самым можно получить доступ ко всем записям в порядке очереди, определенной запросом (предложение ORDER BY команды SELECT).

Для выборки данных разработаны следующие интерфейсные функции:

Открытие курсора:

Тип

Имя

Параметры

Описание

int

MySqlCursorOpen

Данная функция открывает курсор для SELECT-запроса и возвращает идентификатор курсора в случае успешного выполнения. В противном случае, функция вернет "-1", для уточнения причины ошибки используйте переменные MySQLErrorNumber и MySqlErrorDescription.

int pConnection

Идентификатор соединения с БД

string pQuery

SQL-запрос (команда SELECT)

Получение количества строк, возвращенных запросом:

Тип

Имя

Параметры

Описание

int

MySqlCursorRows

Данная функция возвращает количество строк, выбранных запросом.

int pCursorID

Идентификатор курсора, возвращенный функцией MySqlCursorOpen

Извлечение строки запроса:

Тип

Имя

Параметры

Описание

bool

MySqlCursorFetchRow

Извлекает одну строку из набора данных, возвращенных запросом. После успешного выполнения вы можете извлечь данные в переменные MQL. Функция возвращает true в случае успеха, в противном случае - false.

int pCursorID

Идентификатор курсора, возвращенный функцией MySqlCursorOpen

Извлечение данных в переменные MQL после извлечения строки запроса:

Тип

Имя

Параметры

Описание

int

MySqlGetFieldAsInt

Данная функция возвращает представление значения поля таблицы, используя тип данных int.

int pCursorID

Идентификатор курсора, возвращенный функцией  MySqlCursorOpen

int pField

Номер поля в списке SELECT (нумерация с 0)

double

MySqlGetFieldAsDouble

Данная функция возвращает представление значения поля таблицы, используя тип данных double.

int pCursorID

Идентификатор курсора, возвращенный функцией  MySqlCursorOpen

int pField

Номер поля в списке SELECT (нумерация с 0)

datetime

MySqlGetFieldAsDatetime

Данная функция возвращает представление значения поля таблицы, используя тип данных datetime.

int pCursorID

Идентификатор курсора, возвращенный функцией  MySqlCursorOpen

int pField

Номер поля в списке SELECT (нумерация с 0)

string

MySqlGetFieldAsString

Данная функция возвращает представление значения поля таблицы, используя тип данных string.

int pCursorID

Идентификатор курсора, возвращенный функцией MySqlCursorOpen

int pField

Номер поля в списке SELECT (нумерация с 0)


Все данные, возвращаемые MySQL, представлены без типов в виде строк (native presentation).

Поэтому, используя данные функции, вы можете привести выбранные данные к нужному типу. Единственным неудобством является указание номера столбца (нумерация с 0) в списке SELECT вместо указания его имени. Однако, при разработке, подготовка команды SELECT и получение результатов практически всегда находятся в области видимости одной страницы, то есть вы видите сам запрос SELECT, когда прописываете логику извлечения данных.

Таким образом, всегда знаете, под каким номером какое поле из списка SELECT находится (данный подход также применяется при доступе к данным средствами AdoDB). В принципе, я не исключаю возможности доработки данного момента в будущем, однако на функционале разработанного решения это мало отразится.

Закрытие курсора:

Тип

Имя

Параметры

Описание

void

MySqlCursorClose

Данная функция закрывает указанный курсор и освобождает память.

int pCursorID

Идентификатор курсора, возвращенный функцией MySqlCursorOpen

Закрытие курсора весьма ответственная операция. Никогда не забывайте закрывать курсоры.

Представьте себе, вы открыли курсор, забыв его закрыть. Предположим, извлечение данных в курсор выполняется с каждым тиком при обработке события OnTick() и каждый раз новый курсор будет открываться, под него будет производиться выделение памяти (как на стороне клиента, так и на стороне сервера). В какой-то момент сервер даст вам отказ в обслуживании т.к. лимит открытых курсоров будет исчерпан, а на клиенте может быть переполнение буфера.

Разумеется, здесь я преувеличил, такой исход возможен при работе с libmysql.dll напрямую. Однако динамическая библиотека MQLMySQL.DLL занимается распределением памяти для курсоров и именно она даст отказ при попытке открытия курсора, выходящего за допустимый лимит.

При реализации реальных задач достаточно держать одновременно открытыми 2-3 курсора. Каждый курсор может обрабатывать одно декартово измерение данных, использование же двух-трех курсоров одновременно (вложенных, например, один параметрически зависит от другого) покроет два-три измерения. Этого вполне достаточно для множества задач. Кроме того, для реализации сложных выборок данных вы всегда можете использовать такие объекты для представления базы данных (VIEW), создавать их на стороне сервера и выполнять запросы к ним из кода MQL как к таблицам.

1.4. Дополнительно

В качестве дополнительного функционала можно отметить:

1.4.1. Чтение данных из .INI файла

Тип

Имя

Параметры

Описание

String

ReadIni

Возвращает значение ключа заданной секции INI-файла.

string pFileName

Имя INI-файла

string pSection

Название секции

string pKey

Название ключа


Зачастую хранить сведения о соединении с СУБД (IP адрес сервера, порт, имя пользователя, пароль и т.п.) прямо в коде MQL (или параметрах робота, индикатора, скрипта) не рационально, т.к. сервер может быть перенесен, его адрес может динамически изменяться и т.п. При этом будет необходимо корректировать MQL-код. Таким образом, все эти данные выгоднее хранить в стандартном .INI файле, а в MQL программе зафиксировать лишь его имя. Затем, функцией ReadINI считывать параметры соединения и использовать их.

К примеру, INI файл содержит следующую информацию:

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

Для получения, например IP адреса сервера необходимо выполнить:

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

Сам INI файл находится по пути C:\MetaTrader5\MQL5\Experts и называется "MyConnection.ini", обращение идет к ключу Server секции MYSQL. В одном INI-файле вы можете хранить настройки к различным серверам, используемым в вашем проекте.

1.4.2. Трассировка проблемных участков

В интерфейсной библиотеке предусмотрен режим трассировки, который вы можете включить для отладки SQL-запросов в любом месте MQL программы.

Для этого достаточно в проблемном месте указать:

SQLTrace = true;

а после него

SQLTrace = false;

Если же включить трассировку в самом начале MQL программы и не выключать, то будет производиться лог всех обращений к СУБД. Сам лог ведется в консоли терминала (командой Print).


2. Примеры

В данном разделе приведены несколько примеров подключения и использования разработанных библиотек. По ним вы можете судить об удобстве использования данного программного решения.

Пример MySQL-003.mq5 демонстрирует: соединение с СУБД (параметры соединения хранятся в .ini файле), создание таблицы, вставка данных (также с использованием multi-statements) и отключение от СУБД.

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

Пример MySQL-004.mq5 демонстрирует выборку данных из таблицы, созданной скриптом "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!");
}

В приведенных примерах используется типовая обработка ошибок, применяемая в реальных проектах.

Фактически, каждый запрос, используемый в MQL-программе, должен быть отлажен в любом клиенте MySQL (PHPMyAdmin, DB Ninja, консоль MySQL). Лично я использую и рекомендую профессиональное ПО для разработки баз данных Quest TOAD for MySQL.


Заключение

Данная статья не описывает деталей реализации динамической библиотеки MQLMySQL.DLL, разработанной в среде Microsoft Visual Studio 2010 (C/C++). Предлагаемое программное решение предназначено для практического использования и насчитывает более 100 успешных внедрений в различных областях разработки на языке MQL (от создания комплексных торговых систем до Web-публикаций).

  • К статье прикреплены версии библиотек для MQL4 и MQL5, также прикреплен архив с исходным кодом MQLMySQL.DLL;
  • Документация включена в архивы;
  • Для использования примеров не забудьте указать параметры соединения к вашей СУБД в файле \Scripts\MyConnection.ini.
Прикрепленные файлы |
MQLMySQL_for_MQL4.zip (1124.85 KB)
MQLMySQL_for_MQL5.zip (1125.19 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (97)
Eugeniy Lugovoy
Eugeniy Lugovoy | 7 мар. 2023 в 01:15
Viktor Vasilyuk #:

Мне возвращает знаки вопроса, вместо кириллицы

part DDL table:

тип column -> text (не varchar)

MT4 dll:

UTF8 идет двухбайтный, классический, так сказать, у вас используется четырехбайтный при определении таблицы. Скорее всего в этом проблема. Тип Text на уровне драйвера MySQL не отличается от varchar.
Eugeniy Lugovoy
Eugeniy Lugovoy | 7 мар. 2023 в 01:16
Viktor Vasilyuk #:

Мне возвращает знаки вопроса, вместо кириллицы

part DDL table:

тип column -> text (не varchar)

MT4 dll:

Но я проверю как появится возможность
Viktor Vasilyuk
Viktor Vasilyuk | 8 мар. 2023 в 11:30
Eugeniy Lugovoy #:
UTF8 идет двухбайтный, классический, так сказать, у вас используется четырехбайтный при определении таблицы. Скорее всего в этом проблема. Тип Text на уровне драйвера MySQL не отличается от varchar.

спасибо за уточнение. Абсолютно все знать не могу, поетому и спрашиваю.

Решение для себя нашел тут.

P.S: Проверил для SELECT - работает

Eugeniy Lugovoy
Eugeniy Lugovoy | 8 мар. 2023 в 23:54
Viktor Vasilyuk #:

спасибо за уточнение. Абсолютно все знать не могу, поетому и спрашиваю.

Решение для себя нашел тут.

P.S: Проверил для SELECT - работает

Ну конверт результата запроса допустимо, однако не забывайте что у вас определение utf8mb4 на уровне таблицы, то есть для всех полей varchar по умолчанию будет использоваться, и если у вас в запросе будет условие по текстовому полю, то MySQL попытается неявно преобразование выполнить. А если принять во внимание что у вас по текстовому столбцу может быть построен индекс, то оптимизатор может его не подхватить при таком преобразовании… отсюда вытекут проблемы с производительностью запросов. 
Так что желательно контролировать такие вещи как на уровне таблицы, так и на уровне определения столбцов.
Viktor Vasilyuk
Viktor Vasilyuk | 9 мар. 2023 в 09:41
Eugeniy Lugovoy #:
Ну конверт результата запроса допустимо, однако не забывайте что у вас определение utf8mb4 на уровне таблицы, то есть для всех полей varchar по умолчанию будет использоваться, и если у вас в запросе будет условие по текстовому полю, то MySQL попытается неявно преобразование выполнить. А если принять во внимание что у вас по текстовому столбцу может быть построен индекс, то оптимизатор может его не подхватить при таком преобразовании… отсюда вытекут проблемы с производительностью запросов. 
Так что желательно контролировать такие вещи как на уровне таблицы, так и на уровне определения столбцов.

там поле description. Что-то на подобие "комментарии", поле не обязательно, просто текстовое обьяснение. Индекса не будет.

MySQL utf8mb4 делает по умолчанию. Скажите, как лучше указать в DDL в следующий раз? что менять и на какую кодировку

Рецепты MQL5 - обработка  события BookEvent Рецепты MQL5 - обработка события BookEvent
В статье рассматриваются событие стакана BookEvent и принцип его обработки. В качестве примера создается MQL5-программа, обрабатывающая состояния стакана. Используется объектно-ориентированный подход. Результаты обработки выводятся на экран в виде панели и уровней стакана.
Рецепты MQL5 - обработка пользовательских событий графика Рецепты MQL5 - обработка пользовательских событий графика
В данной статье рассматриваются аспекты проектирования и разработки системы пользовательских событий графика в среде MQL5. Предлагается пример подхода для классификации событий. Приводится программный код событийного класса и класса-обработчика пользовательских событий.
Основы  программирования на MQL5 - Глобальные переменные терминала Основы программирования на MQL5 - Глобальные переменные терминала
В данной статье демонстрируются объектно-ориентированные возможности языка MQL5 по созданию объектов, отвечающих за работу с глобальными переменными терминала. В качестве практического примера рассматривается ситуация, когда глобальные переменные могут использоваться как контрольные точки выполнения этапов программы.
Подготовка торгового счета к миграции на виртуальный хостинг Подготовка торгового счета к миграции на виртуальный хостинг
Клиентский терминал MetaTrader идеально подходит для автоматизации торговых стратегий. Для разработчиков торговых роботов в нем есть всё ‒ мощный язык программирования MQL4/MQL5 на основе C++, удобная среда разработки MetaEditor, многопоточный тестер стратегий с поддержкой распределенных вычислений в MQL5 Cloud Network. В этой статье вы узнаете, как перенести свой клиентский терминал со всеми разработками в виртуальную среду.