Работа с СУБД MySQL из MQL5 (MQL4)
Введение
Проблема взаимодействия 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-программ) реализуется с помощью:
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. Для ее выполнения и получения результата выборки реализована следующая схема:
- Подготовка команды SELECT.
- Открытие курсора.
- Получение количества строк, возвращенных запросом.
- Организация цикла с извлечением каждой строки запроса.
- Извлечение данных в переменные MQL внутри цикла.
- Закрытие курсора.
Разумеется, это общая схема, и не всегда будут необходимы все операции. Например, если нужно убедиться в том, что строка присутствует в таблице (по каким-либо критериям), достаточно будет подготовить запрос, открыть курсор, получить количество строк и закрыть курсор. По сути, обязательными пунктами являются - подготовка команды 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.
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Insert и Update query - только 16 кб лимит запроса ?
Если в запросе больше 16.000 символов то метатрейдер падает ( закрывается ). если меньше то норм.
Прикрепляю пример UPDATE за 32.000 символов
Поле для обновления в базе - LONGTEXT
Это больше похоже на лимит размера строки в MQL :-(
и можно компактнее написать :
REPLACE d1 ("t","o","h","l","c") VALUES (time1,open1,high1,low1,close1), (time2,open2,high2,low2,close2) ....
и/или разбить на два запроса, объединив в одну транзакцию
К примеру у меня данных 50 000 элементов и я их в таблице обновляю каждую 0,1 сек (числа). Сможет ли Мт5 их так же подхватывать из MySQL и обновлять каждую 0,1 сек? Лимитирование есть какое то функционала приведенного в этой статье по КБ за 1 запрос ?
Привет! Вопрос знатокам - как много данных и как часто я смогу читать из MySQL в МТ5 ?
К примеру у меня данных 50 000 элементов и я их в таблице обновляю каждую 0,1 сек (числа). Сможет ли Мт5 их так же подхватывать из MySQL и обновлять каждую 0,1 сек? Лимитирование есть какое то функционала приведенного в этой статье по КБ за 1 запрос ?
Ну, вопрос, конечно, интересный...
Надо сказать что по количеству возвращаемых SELECT строк запроса нет никаких лимитов.
Для самого же запроса лимит по размеру 64 Кб. Так что если Вы пытаетесь обновить 50к строк данных, то лучше их разбить на партии, скажем по 1000 строк и таким образом слать 50 запросов.
Что же касается скорости в 100 мс, если БД у вас на одном сервере, а ваш терминал в котором исполняется MQL с соединением к БД несколько отдален, то скорее всего Вы упретесь в network latency, в размере пинга...
Например, если у Вас между сервером БД и терминала пинг в размере 60 мс, то фактически ответ от сервера будет с задержкой = 60мс (запрос) + время обработки запроса на стороне БД + 60мс(ответ).
Этот проект всего лишь простенькая обертка для доступа к функционалу динамических библиотек mysql.
Набор функционала ограничен основными практически полезными функциями, можете расширять, добавлять то, что Вам необходимо, скажем добавить поддержку асинхронных запросов, и тогда можете слать все 50 запросов по 1000 строк не дожидаясь исполнения каждого по очереди.
P.S.: на Github можете посмотреть исходники библиотеки и предустановленные лимиты (https://github.com/elugovoy/MQLMySQL-Project/tree/master/MQLMySQL)
P.P.S.: также можете скачать, изменить на свое усмотрение, скомпилировать и тестить.
Можно както узнать количество Fields которые можно получить после MySqlCursorFetchRow ?
Может какаято функция скрытая есть типа RowFieldsSize ...
Как я понял, если Field нету, то MySqlGetFieldAsString возвращается пустота. Но и если String Field специально содержит пустоту, то тоже возвращается пустота. Т.е. не всегда можно самому отследить количество Fields перебором.
Как костыль можно узнать через sql команду сначала, но потом опять select делать, но это уже лишний Select.
Просим развивать библиотеку , очень полезная штука. Конечно давно пара mysql встроить в mt
Хм... а что за запросы такие хитрые у Вас что нужно определять количество полей, возвращенным им же?
Обычно в коменде SELECT перечисляют только то, что нужно в конкретной ситуации. Не используйте SELECT *, выбирайте только то, что нужно :) это нормальная практика.
Костылей не стоит делать, можете с Github исходники взять и добавить обертку для функции mysql_fetch_fields() MySQL API
Insert и Update query - только 16 кб лимит запроса ?
Если в запросе больше 16.000 символов то метатрейдер падает ( закрывается ). если меньше то норм.
Прикрепляю пример UPDATE за 32.000 символов
Поле для обновления в базе - LONGTEXT
В библиотеке определен размер для запросов в 64kb:
#define MAX_QUERY_SIZE 65535 // Max size of SQL query
Полагаю в Вашем случае (да наверное и не только в вашем, а дело в MQL string) идет 4-байтная кодировка utf, то есть 16*4 = 64 и предел таки достигается...
Тут либо запросы разбивать либо буфер увеличить для запроса и перекомпилировать.