- Знакомство с принципами работы с базой данных в MQL5
- Основы SQL
- Структура (схема) таблиц: типы данных и ограничения
- Интеграция ООП (MQL5) и SQL: концепция ORM
- Создание, открытие и закрытие базы данных
- Выполнение запросов без привязки к данным MQL5
- Проверка существования таблицы в базе данных
- Подготовка запросов с привязкой: DatabasePrepare
- Удаление и сброс подготовленных запросов
- Привязка данных к параметрам запроса:DatabaseBind/Array
- Выполнение подготовленных запросов: DatabaseRead/Bind
- Раздельное чтение полей: DatabaseColumn-функции
- Примеры CRUD-операций в SQLite через объекты ORM
- Транзакции
- Импорт и экспорт таблицы базы данных
- Печать таблиц и SQL-запросов в журнал
- Пример поиска торговой стратегии средствами SQLite
Знакомство с принципами работы с базой данных в MQL5
Базы данных хранят информацию в виде таблиц. Получение, модификация и добавление новых данных в них делается с помощью запросов на языке SQL. Про его специфику мы расскажем в следующих разделах. А пока продемонстрируем на примере скрипта DatabaseRead.mq5, никак не связанного с трейдингом, как создать простую базу данных и получить из неё информацию. Все упомянутые здесь функции будут подробно описаны позднее. Сейчас важно представить себе общие принципы.
Создание и закрытие базы с помощью встроенных функций DatabaseOpen/DatabaseClose аналогичны работе с файлами — так же создаем дескриптор для базы данных, проверяем его и закрываем в конце.
void OnStart()
|
После открытия базы убедимся в отсутствии в ней таблицы под нужным нам именем — если таблица уже существует, то при попытке вставить в неё такие же данные, как в нашем примере, произойдет ошибка, поэтому используем функцию DatabaseTableExists.
Удаление и создание таблицы совершается с помощью запросов, которые отправляются в базу двумя вызовами функции DatabaseExecute и сопровождаются контролем ошибок.
...
|
Поясним суть SQL-запросов. В таблице COMPANY у нас всего 5 полей: ID записи, имя, возраст, адрес и зарплата. Причем поле ID является ключом, то есть уникальным индексом. Индексы позволяют однозначно определять каждую запись и могут использоваться в разных таблицах для того чтобы связывать их между собой. Это аналогично тому, как идентификатор позиции связывает между собой все сделки и ордера, которые относятся к конкретной позиции.
Теперь необходимо заполнить таблицу данными, делается это с помощью запроса "INSERT":
// вставляем данные в таблицу
|
Здесь в таблицу COMPANY добавляются 4 записи, для каждой записи указывается список полей и значений, которые будут записаны в эти поля. Записи вставляются отдельными запросами "INSERT...", которые объединены в одну строку, через специальный символ-разделитель ';', но мы могли бы каждую запись вставлять в таблицу отдельным вызовом DatabaseExecute.
Так как по окончании работы скрипта база будет сохранена в файл "company.sqlite", то при последующем его запуске мы попытались бы записать те же самые данные в таблицу COMPANY с таким же ID. Это привело бы к ошибке — именно поэтому мы ранее удалили таблицу, чтобы при каждом запуске скрипта начинать работу с нуля.
Теперь получим все записи из таблицы COMPANY, где поле SALARY > 15000. Делается это с помощью функции DatabasePrepare, которая "компилирует" текст запроса и возвращает его дескриптор для последующего использования в функциях DatabaseRead или DatabaseReadBind.
// подготовим запрос с дескриптором
|
После того как запрос успешно создан, необходимо получить результаты его выполнения. Это можно сделать с помощью функции DatabaseRead, которая при первом вызове выполнит запрос и перейдет на первую запись в результатах. При каждом последующем вызове она будет считывать следующую запись, пока не дойдет до конца. В этом случае она вернет false, что означает "записей больше нет".
// распечатаем все записи с зарплатой больше 15000
|
Результатом выполнения будет:
Persons with salary > 15000:
|
Таким образом, функция DatabaseRead позволяет пройти по всем записям из результата запроса и далее получить полную информацию о каждом столбце в полученной таблице через DatabaseColumn-функции. Эти функции предназначены для универсальной работы с результатами любого запроса, но платой за это становится избыточный код.
Если структура результатов запроса заранее известна, то лучше воспользоваться функцией DatabaseReadBind, которая позволяет считать сразу всю запись целиком в структуру. Мы можем переделать предыдущий пример таким образом и представить под новым названием DatabaseReadBind.mq5. Сначала объявим структуру Person:
struct Person
|
Затем сделаем вычитку каждой записи из результатов запроса с помощью DatabaseReadBind(request, person) в цикле, пока функция возвращает значение true:
Person person;
|
Таким образом, мы сразу получаем из текущей записи значения всех полей и нам не требуется вычитывать их по отдельности.
Этот вводный пример был взят из статьи SQLite: нативная работа с базами данных на SQL в MQL5, где кроме него рассмотрено несколько вариантов прикладного применения базы данных для трейдеров. В частности, там можно найти восстановление истории позиций из сделок, анализ торгового отчета в разрезе стратегий, рабочих символов или наиболее предпочтительных торговых часов, а также приемы работы с результатами оптимизации.
Для освоения этого материала может потребоваться некоторое минимальное знание языка SQL, поэтому рассмотрим его вкратце в следующих разделах.