Рецепты MQL5 — База данных макроэкономических событий
Введение
В данной статье речь пойдёт о том, как группировать и управлять данными, которые описывают макроэкономические события календаря.
Волей-неволей в современном мире, где информационные потоки носят всепроникающий характер, при анализе событий приходится иметь дело с big data. И хотя, в большей степени в статье освещаются вопросы, связанные не с содержанием, а формой, тем не менее представляется, что правильная организация и структурирование данных немало способствует тому, что эти данные превратятся в информацию.
Поставленные задачи будем решать средствами SQLite. Отмечу, что разработчик добавил поддержку работы с базами данных SQLite прямо из MQL5 в билде 2265 (6 декабря 2019). До этого приходилось использовать различные коннекторы, например, как это описано в статье SQL и MQL5: Работаем с базой данных SQLite.
1. Документация и дополнительный материал
Весьма бегло пройдёмся по Документации в части работы с базами данных. Разработчик предоставляет 26 нативных функций:
- DatabaseOpen();
- DatabaseClose();
- DatabaseImport();
- DatabaseExport();
- DatabasePrint();
- DatabaseTableExists();
- DatabaseExecute();
- DatabasePrepare();
- DatabaseReset();
- DatabaseBind();
- DatabaseBindArray();
- DatabaseRead();
- DatabaseReadBind();
- DatabaseFinalize();
- DatabaseTransactionBegin();
- DatabaseTransactionCommit();
- DatabaseTransactionRollback();
- DatabaseColumnsCount();
- DatabaseColumnName();
- DatabaseColumnType();
- DatabaseColumnSize();
- DatabaseColumnText();
- DatabaseColumnInteger();
- DatabaseColumnLong();
- DatabaseColumnDouble();
- DatabaseColumnBlob().
Ещё есть блок статистических и блок математических функций, которые были добавлены совсем недавно. Ну и конечно отправной точкой для изучения этого функционала может послужить статья SQLite: нативная работа с базами данных на SQL в MQL5.
2. Класс базы данных CDatabase
Для удобства работы с базами данных создадим класс CDatabase. Сначала опишем состав класса. Затем на примерах проверим его работу.
В состав данных-членов класса CDatabase входят следующие:
- m_name - это имя файла базы данных (с расширением);
- m_handle - хэндл базы данных;
- m_flags - комбинация флагов;
- m_table_names – имена таблиц;
- m_curr_table_name – имя текущей таблицы;
- m_sql_request_ha – хэндл последнего SQL-запроса;
- m_sql_request – последний SQL-запрос.
Что касается методов, то их разбил бы на несколько групп:
- Методы, включающие в себя нативные функции для работы с базами данных (API MQL5 functions);
- Методы для работы с таблицами;
- Методы для работы с запросами;
- Методы для работы с представлениями;
- Методы получения значений членов-данных (get-методы).
Хотелось бы отметить следующий момент. В SQLite есть множество форм запросов – от простых до сложных. Не ставилось целью создать свой метод в классе CDatabase под каждую такую форму. Если в классе и отсутствует метод под тот или иной запрос, то запрос можно сформировать с помощью универсального метода CDatabase::Select().
Теперь рассмотрим на примерах, как можно использовать возможности класса CDatabase.
2.1 Создание базы данных
Создадим нашу первую календарную базу с помощью скрипта 1_create_calendar_db.mq5. В скрипте будет всего несколько строчек кода.
//--- include #include "..\CDatabase.mqh" //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { CDatabase db_obj; string file_name="Databases\\test_calendar_db.sqlite"; uint flags=DATABASE_OPEN_READWRITE | DATABASE_OPEN_CREATE; if(!db_obj.Open(file_name, flags)) ::PrintFormat("Failed to create a calendar database \"%s\"!", file_name); db_obj.Close(); } //+------------------------------------------------------------------+
После запуска скрипта, увидим, что в папке %MQL5\Files\Databases появился файл базы данных test_calendar_db.sqlite (Рис.1).
Рис.1 Файл базы данных test_calendar_db.sqlite
Если дальше откроем этот файл в редакторе кода, то увидим в свою очередь, что база данных пустая (Рис.2).
Рис.2 База данных test_calendar_db
2.2 Создание таблицы
Попробуем заполнить базу данных. С этой целью создадим таблицу COUNTRIES, в которую занесём список стран, календарные события которых впоследствии будут обрабатываться нашими запросами. С этой задачей справится скрипт 2_create_countries_table.mq5.
//--- include #include "..\CDatabase.mqh" //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- open a database CDatabase db_obj; string file_name="Databases\\test_calendar_db.sqlite"; uint flags=DATABASE_OPEN_READWRITE; if(!db_obj.Open(file_name, flags)) { ::PrintFormat("Failed to open a calendar database \"%s\"!", file_name); db_obj.Close(); return; } //--- create a table string table_name="COUNTRIES"; string params[]= { "COUNTRY_ID UNSIGNED BIG INT PRIMARY KEY NOT NULL,", // 1) country ID "NAME TEXT," // 2) country name "CODE TEXT," // 3) country code "CONTINENT TEXT," // 4) country continent "CURRENCY TEXT," // 5) currency code "CURRENCY_SYMBOL TEXT," // 6) currency symbol "URL_NAME TEXT" // 7) country URL }; if(!db_obj.CreateTable(table_name, params)) { ::PrintFormat("Failed to create a table \"%s\"!", table_name); db_obj.Close(); return; } db_obj.Close(); } //+------------------------------------------------------------------+
После запуска скрипта можно обнаружить, что в БД появилась таблица COUNTRIES (Рис.3).
Рис.3 Пустая таблица COUNTRIES
2.3 Заполнение таблицы
Заполним новую таблицу данными. Для этого воспользуемся возможностями класса CiCalendarInfo. Более подробно о классе написано в статье «Рецепты MQL5 – Экономический календарь». Непосредственно задачу выполнит скрипт 3_fill_in_countries_table.mq5.
//--- include #include "..\CalendarInfo.mqh" #include "..\CDatabase.mqh" //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- open a database CDatabase db_obj; string file_name="Databases\\test_calendar_db.sqlite"; uint flags=DATABASE_OPEN_READWRITE; if(!db_obj.Open(file_name, flags)) { db_obj.Close(); return; } //--- open a table string table_name="COUNTRIES"; if(db_obj.SelectTable(table_name)) if(db_obj.EmptyTable()) { db_obj.FinalizeSqlRequest(); string col_names[]= { "COUNTRY_ID", "NAME", "CODE", "CONTINENT", "CURRENCY", "CURRENCY_SYMBOL", "URL_NAME" }; //--- fill in the table CiCalendarInfo calendar_info; if(calendar_info.Init()) { MqlCalendarCountry calendar_countries[]; if(calendar_info.GetCountries(calendar_countries)) { if(db_obj.TransactionBegin()) for(int c_idx=0; c_idx<::ArraySize(calendar_countries); c_idx++) { MqlCalendarCountry curr_country=calendar_countries[c_idx]; string col_vals[]; ::ArrayResize(col_vals, 7); col_vals[0]=::StringFormat("%I64u", curr_country.id); col_vals[1]=::StringFormat("'%s'", curr_country.name); col_vals[2]=::StringFormat("'%s'", curr_country.code); col_vals[3]="NULL"; SCountryByContinent curr_country_continent_data; if(curr_country_continent_data.Init(curr_country.code)) col_vals[3]=::StringFormat("'%s'", curr_country_continent_data.ContinentDescription()); col_vals[4]=::StringFormat("'%s'", curr_country.currency); col_vals[5]=::StringFormat("'%s'", curr_country.currency_symbol); col_vals[6]=::StringFormat("'%s'", curr_country.url_name); if(!db_obj.InsertSingleRow(col_names, col_vals)) { db_obj.TransactionRollback(); db_obj.Close(); return; } db_obj.FinalizeSqlRequest(); } if(!db_obj.TransactionCommit()) ::PrintFormat("Failed to complete transaction execution, error %d", ::GetLastError()); } //--- print if(db_obj.PrintTable()<0) ::PrintFormat("Failed to print the table \"%s\", error %d", table_name, ::GetLastError()); } } db_obj.Close(); } //+------------------------------------------------------------------+
В журнале распечатаем данные таблицы COUNTRIES.
3_fill_in_countries_table (EURUSD,H1) #| COUNTRY_ID NAME CODE CONTINENT CURRENCY CURRENCY_SYMBOL URL_NAME 3_fill_in_countries_table (EURUSD,H1) --+----------------------------------------------------------------------------------------- 3_fill_in_countries_table (EURUSD,H1) 1| 554 New Zealand NZ Australia/Oceania NZD $ new-zealand 3_fill_in_countries_table (EURUSD,H1) 2| 999 European Union EU Europe EUR € european-union 3_fill_in_countries_table (EURUSD,H1) 3| 392 Japan JP Asia JPY ¥ japan 3_fill_in_countries_table (EURUSD,H1) 4| 124 Canada CA North America CAD $ canada 3_fill_in_countries_table (EURUSD,H1) 5| 36 Australia AU Australia/Oceania AUD $ australia 3_fill_in_countries_table (EURUSD,H1) 6| 156 China CN Asia CNY ¥ china 3_fill_in_countries_table (EURUSD,H1) 7| 380 Italy IT Europe EUR € italy 3_fill_in_countries_table (EURUSD,H1) 8| 702 Singapore SG Asia SGD R$ singapore 3_fill_in_countries_table (EURUSD,H1) 9| 276 Germany DE Europe EUR € germany 3_fill_in_countries_table (EURUSD,H1) 10| 250 France FR Europe EUR € france 3_fill_in_countries_table (EURUSD,H1) 11| 76 Brazil BR South America BRL R$ brazil 3_fill_in_countries_table (EURUSD,H1) 12| 484 Mexico MX North America MXN Mex$ mexico 3_fill_in_countries_table (EURUSD,H1) 13| 710 South Africa ZA Africa ZAR R south-africa 3_fill_in_countries_table (EURUSD,H1) 14| 344 Hong Kong HK Asia HKD HK$ hong-kong 3_fill_in_countries_table (EURUSD,H1) 15| 356 India IN Asia INR ₹ india 3_fill_in_countries_table (EURUSD,H1) 16| 578 Norway NO Europe NOK Kr norway 3_fill_in_countries_table (EURUSD,H1) 17| 840 United States US North America USD $ united-states 3_fill_in_countries_table (EURUSD,H1) 18| 826 United Kingdom GB Europe GBP £ united-kingdom 3_fill_in_countries_table (EURUSD,H1) 19| 756 Switzerland CH Europe CHF ₣ switzerland 3_fill_in_countries_table (EURUSD,H1) 20| 410 South Korea KR Asia KRW ₩ south-korea 3_fill_in_countries_table (EURUSD,H1) 21| 724 Spain ES Europe EUR € spain 3_fill_in_countries_table (EURUSD,H1) 22| 752 Sweden SE Europe SEK Kr sweden 3_fill_in_countries_table (EURUSD,H1) 23| 0 Worldwide WW World ALL worldwide
В MetaEditor таблица имеет следующий вид (Рис.4).
Рис.4 Заполненная таблица COUNTRIES
2.4 Выбор некоторых столбцов таблицы
Поработаем с данными таблицы COUNTRIES. Допустим, что нужно выбрать такие столбцы:
- "COUNTRY_ID";
- "COUNTRY_NAME";
- "COUNTRY_CODE";
- "COUNTRY_CONTINENT";
- "CURRENCY".
Создадим SQL-запрос с помощью скрипта 4_select_some_columns.mq5 следующим образом:
//--- include #include "..\CDatabase.mqh" //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- open a database CDatabase db_obj; string file_name="Databases\\test_calendar_db.sqlite"; uint flags=DATABASE_OPEN_READONLY; if(!db_obj.Open(file_name, flags)) { db_obj.Close(); return; } //--- check a table string table_name="COUNTRIES"; if(db_obj.SelectTable(table_name)) { string col_names_to_select[]= { "COUNTRY_ID", "NAME", "CODE", "CONTINENT", "CURRENCY" }; if(!db_obj.SelectFrom(col_names_to_select)) { db_obj.Close(); return; } //--- print the SQL request if(db_obj.PrintSqlRequest()<0) ::PrintFormat("Failed to print the SQL request, error %d", ::GetLastError()); db_obj.FinalizeSqlRequest(); } db_obj.Close(); } //+------------------------------------------------------------------+
При распечатывании запроса получим:
4_select_some_columns (EURUSD,H1) #| COUNTRY_ID NAME CODE CONTINENT CURRENCY 4_select_some_columns (EURUSD,H1) --+---------------------------------------------------------- 4_select_some_columns (EURUSD,H1) 1| 554 New Zealand NZ Australia/Oceania NZD 4_select_some_columns (EURUSD,H1) 2| 999 European Union EU Europe EUR 4_select_some_columns (EURUSD,H1) 3| 392 Japan JP Asia JPY 4_select_some_columns (EURUSD,H1) 4| 124 Canada CA North America CAD 4_select_some_columns (EURUSD,H1) 5| 36 Australia AU Australia/Oceania AUD 4_select_some_columns (EURUSD,H1) 6| 156 China CN Asia CNY 4_select_some_columns (EURUSD,H1) 7| 380 Italy IT Europe EUR 4_select_some_columns (EURUSD,H1) 8| 702 Singapore SG Asia SGD 4_select_some_columns (EURUSD,H1) 9| 276 Germany DE Europe EUR 4_select_some_columns (EURUSD,H1) 10| 250 France FR Europe EUR 4_select_some_columns (EURUSD,H1) 11| 76 Brazil BR South America BRL 4_select_some_columns (EURUSD,H1) 12| 484 Mexico MX North America MXN 4_select_some_columns (EURUSD,H1) 13| 710 South Africa ZA Africa ZAR 4_select_some_columns (EURUSD,H1) 14| 344 Hong Kong HK Asia HKD 4_select_some_columns (EURUSD,H1) 15| 356 India IN Asia INR 4_select_some_columns (EURUSD,H1) 16| 578 Norway NO Europe NOK 4_select_some_columns (EURUSD,H1) 17| 840 United States US North America USD 4_select_some_columns (EURUSD,H1) 18| 826 United Kingdom GB Europe GBP 4_select_some_columns (EURUSD,H1) 19| 756 Switzerland CH Europe CHF 4_select_some_columns (EURUSD,H1) 20| 410 South Korea KR Asia KRW 4_select_some_columns (EURUSD,H1) 21| 724 Spain ES Europe EUR 4_select_some_columns (EURUSD,H1) 22| 752 Sweden SE Europe SEK 4_select_some_columns (EURUSD,H1) 23| 0 Worldwide WW World ALL
Очевидно, что выборка была сделана без какой-либо сортировки.
2.5 Выбор некоторых отсортированных столбцов таблицы
Попробуем упорядочить данные в таблице по столбцу "COUNTRY_ID". Этот запрос имеет такую реализацию в скрипте 5_select_some_sorted_columns.mq5:
//--- include #include "..\CDatabase.mqh" //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- open a database CDatabase db_obj; string file_name="Databases\\test_calendar_db.sqlite"; uint flags=DATABASE_OPEN_READONLY; if(!db_obj.Open(file_name, flags)) { db_obj.Close(); return; } //--- check a table string table_name="COUNTRIES"; if(db_obj.SelectTable(table_name)) { string col_names_to_select[]= { "COUNTRY_ID", "NAME", "CODE", "CONTINENT", "CURRENCY" }; string ord_names[1]; ord_names[0]=col_names_to_select[0]; if(!db_obj.SelectFromOrderedBy(col_names_to_select, ord_names)) { db_obj.Close(); return; } //--- print the SQL request if(db_obj.PrintSqlRequest()<0) ::PrintFormat("Failed to print the SQL request, error %d", ::GetLastError()); db_obj.FinalizeSqlRequest(); } db_obj.Close(); } //+------------------------------------------------------------------+
В журнале появится результат выполнения запроса:
5_select_some_sorted_columns (EURUSD,H1) #| COUNTRY_ID NAME CODE CONTINENT CURRENCY 5_select_some_sorted_columns (EURUSD,H1) --+---------------------------------------------------------- 5_select_some_sorted_columns (EURUSD,H1) 1| 0 Worldwide WW World ALL 5_select_some_sorted_columns (EURUSD,H1) 2| 36 Australia AU Australia/Oceania AUD 5_select_some_sorted_columns (EURUSD,H1) 3| 76 Brazil BR South America BRL 5_select_some_sorted_columns (EURUSD,H1) 4| 124 Canada CA North America CAD 5_select_some_sorted_columns (EURUSD,H1) 5| 156 China CN Asia CNY 5_select_some_sorted_columns (EURUSD,H1) 6| 250 France FR Europe EUR 5_select_some_sorted_columns (EURUSD,H1) 7| 276 Germany DE Europe EUR 5_select_some_sorted_columns (EURUSD,H1) 8| 344 Hong Kong HK Asia HKD 5_select_some_sorted_columns (EURUSD,H1) 9| 356 India IN Asia INR 5_select_some_sorted_columns (EURUSD,H1) 10| 380 Italy IT Europe EUR 5_select_some_sorted_columns (EURUSD,H1) 11| 392 Japan JP Asia JPY 5_select_some_sorted_columns (EURUSD,H1) 12| 410 South Korea KR Asia KRW 5_select_some_sorted_columns (EURUSD,H1) 13| 484 Mexico MX North America MXN 5_select_some_sorted_columns (EURUSD,H1) 14| 554 New Zealand NZ Australia/Oceania NZD 5_select_some_sorted_columns (EURUSD,H1) 15| 578 Norway NO Europe NOK 5_select_some_sorted_columns (EURUSD,H1) 16| 702 Singapore SG Asia SGD 5_select_some_sorted_columns (EURUSD,H1) 17| 710 South Africa ZA Africa ZAR 5_select_some_sorted_columns (EURUSD,H1) 18| 724 Spain ES Europe EUR 5_select_some_sorted_columns (EURUSD,H1) 19| 752 Sweden SE Europe SEK 5_select_some_sorted_columns (EURUSD,H1) 20| 756 Switzerland CH Europe CHF 5_select_some_sorted_columns (EURUSD,H1) 21| 826 United Kingdom GB Europe GBP 5_select_some_sorted_columns (EURUSD,H1) 22| 840 United States US North America USD 5_select_some_sorted_columns (EURUSD,H1) 23| 999 European Union EU Europe EUR
Скрипт сработал правильно - столбец "COUNTRY_ID" начинается с 0, а заканчивается 999.
2.6 Выбор сгруппированных результатов заданного столбца таблицы
Теперь с помощью скрипта 6_select_some_grouped_columns.mq5 постараемся получить сгруппированные названия стран по континентам. Задача заключается в том, чтобы для каждой строки континента получить количество стран, в него входящих. Страны выбираются из столбца “NAME". После запуска скрипта в журнале появятся такие строчки:
6_select_some_grouped_columns (EURUSD,H1) #| CONTINENT COUNT(NAME) 6_select_some_grouped_columns (EURUSD,H1) -+------------------------------ 6_select_some_grouped_columns (EURUSD,H1) 1| Africa 1 6_select_some_grouped_columns (EURUSD,H1) 2| Asia 6 6_select_some_grouped_columns (EURUSD,H1) 3| Australia/Oceania 2 6_select_some_grouped_columns (EURUSD,H1) 4| Europe 9 6_select_some_grouped_columns (EURUSD,H1) 5| North America 3 6_select_some_grouped_columns (EURUSD,H1) 6| South America 1 6_select_some_grouped_columns (EURUSD,H1) 7| World 1
Континент “Europe” включает в себя больше всего стран – 9, а континенты “Africa” и “South America” - всего по 1. И к ним ещё присоединился весь мир – “World”.
2.7 Выбор уникальных значений заданного столбца таблицы
Теперь с помощью скрипта 7_select_distinct_columns.mq5 соберём уникальные значения в столбце "CURRENCY". Есть страны, где используется одна и та же валюта. Чтобы отсеять повторы, запустим этот скрипт и в журнале получим:
7_select_distinct_columns (EURUSD,H1) 1| NZD 7_select_distinct_columns (EURUSD,H1) 2| EUR 7_select_distinct_columns (EURUSD,H1) 3| JPY 7_select_distinct_columns (EURUSD,H1) 4| CAD 7_select_distinct_columns (EURUSD,H1) 5| AUD 7_select_distinct_columns (EURUSD,H1) 6| CNY 7_select_distinct_columns (EURUSD,H1) 7| SGD 7_select_distinct_columns (EURUSD,H1) 8| BRL 7_select_distinct_columns (EURUSD,H1) 9| MXN 7_select_distinct_columns (EURUSD,H1) 10| ZAR 7_select_distinct_columns (EURUSD,H1) 11| HKD 7_select_distinct_columns (EURUSD,H1) 12| INR 7_select_distinct_columns (EURUSD,H1) 13| NOK 7_select_distinct_columns (EURUSD,H1) 14| USD 7_select_distinct_columns (EURUSD,H1) 15| GBP 7_select_distinct_columns (EURUSD,H1) 16| CHF 7_select_distinct_columns (EURUSD,H1) 17| KRW 7_select_distinct_columns (EURUSD,H1) 18| SEK 7_select_distinct_columns (EURUSD,H1) 19| ALL
Таким образом в календаре есть события всего по 18 валютам и одна группа событий, которая относится ко всем валютам.
Несложно заметить, что методы выбора сгруппированных результатов и выбора уникальных значений имеют сходство. Продемонстрируем это на примере.
Скрипт 8_compare_ grouped_and_distinct_columns.mq5 распечатает в журнале такие результаты:
8_compare_ grouped_and_distinct_columns (EURUSD,H1) 8_compare_ grouped_and_distinct_columns (EURUSD,H1) Method CDatabase::SelectFromGroupBy() 8_compare_ grouped_and_distinct_columns (EURUSD,H1) #| CONTINENT 8_compare_ grouped_and_distinct_columns (EURUSD,H1) -+------------------ 8_compare_ grouped_and_distinct_columns (EURUSD,H1) 1| Africa 8_compare_ grouped_and_distinct_columns (EURUSD,H1) 2| Asia 8_compare_ grouped_and_distinct_columns (EURUSD,H1) 3| Australia/Oceania 8_compare_ grouped_and_distinct_columns (EURUSD,H1) 4| Europe 8_compare_ grouped_and_distinct_columns (EURUSD,H1) 5| North America 8_compare_ grouped_and_distinct_columns (EURUSD,H1) 6| South America 8_compare_ grouped_and_distinct_columns (EURUSD,H1) 7| World 8_compare_ grouped_and_distinct_columns (EURUSD,H1) 8_compare_ grouped_and_distinct_columns (EURUSD,H1) Method CDatabase::SelectDistinctFrom() 8_compare_ grouped_and_distinct_columns (EURUSD,H1) #| CONTINENT 8_compare_ grouped_and_distinct_columns (EURUSD,H1) -+------------------ 8_compare_ grouped_and_distinct_columns (EURUSD,H1) 1| Australia/Oceania 8_compare_ grouped_and_distinct_columns (EURUSD,H1) 2| Europe 8_compare_ grouped_and_distinct_columns (EURUSD,H1) 3| Asia 8_compare_ grouped_and_distinct_columns (EURUSD,H1) 4| North America 8_compare_ grouped_and_distinct_columns (EURUSD,H1) 5| South America 8_compare_ grouped_and_distinct_columns (EURUSD,H1) 6| Africa 8_compare_ grouped_and_distinct_columns (EURUSD,H1) 7| World
Методы вернули одинаковые по содержанию результаты потому, что для первого метода мы определили столбец "CONTINENT" как столбец (поле) группировки. Любопытно, что первый метод ещё и отсортировал нам выборку.
2.8 Выбор упорядоченных уникальных значений заданного столбца таблицы
Значения в столбце “CURRENCY” были отображены скриптом 7_select_distinct_columns.mq5 не в отсортированном виде. Давайте сделаем выборку уже с сортировкой (скрипт 9_select_sorted_distinct_columns.mq5). И пускай критерием сортировки станет столбец "COUNTRY_ID". В итоге манипуляций в журнале получим:
9_select_sorted_distinct_columns (EURUSD,H1) #| CURRENCY 9_select_sorted_distinct_columns (EURUSD,H1) --+--------- 9_select_sorted_distinct_columns (EURUSD,H1) 1| ALL 9_select_sorted_distinct_columns (EURUSD,H1) 2| AUD 9_select_sorted_distinct_columns (EURUSD,H1) 3| BRL 9_select_sorted_distinct_columns (EURUSD,H1) 4| CAD 9_select_sorted_distinct_columns (EURUSD,H1) 5| CNY 9_select_sorted_distinct_columns (EURUSD,H1) 6| EUR 9_select_sorted_distinct_columns (EURUSD,H1) 7| HKD 9_select_sorted_distinct_columns (EURUSD,H1) 8| INR 9_select_sorted_distinct_columns (EURUSD,H1) 9| JPY 9_select_sorted_distinct_columns (EURUSD,H1) 10| KRW 9_select_sorted_distinct_columns (EURUSD,H1) 11| MXN 9_select_sorted_distinct_columns (EURUSD,H1) 12| NZD 9_select_sorted_distinct_columns (EURUSD,H1) 13| NOK 9_select_sorted_distinct_columns (EURUSD,H1) 14| SGD 9_select_sorted_distinct_columns (EURUSD,H1) 15| ZAR 9_select_sorted_distinct_columns (EURUSD,H1) 16| SEK 9_select_sorted_distinct_columns (EURUSD,H1) 17| CHF 9_select_sorted_distinct_columns (EURUSD,H1) 18| GBP 9_select_sorted_distinct_columns (EURUSD,H1) 19| USD
Теперь все валюты отсортированы. Причём по умолчанию сортировка проходит по возрастанию.
2.9 Выбор некоторых столбцов таблицы по условию
Ранее мы уже создавали SQL-запрос для выборки столбцов таблицы. Теперь сделаем так, чтобы можно было получить столбцы при выполнения некоторого условия. Допустим, что нужно отобрать страны, ID которых равен или больше 392 и равен или меньше 840. Эту задачу решает скрипт 10_select_some_columns_where.mq5.
После запуска скрипта в журнале увидим:
10_select_some_columns_where (EURUSD,H1) #| COUNTRY_ID NAME CODE CONTINENT CURRENCY 10_select_some_columns_where (EURUSD,H1) --+---------------------------------------------------------- 10_select_some_columns_where (EURUSD,H1) 1| 392 Japan JP Asia JPY 10_select_some_columns_where (EURUSD,H1) 2| 410 South Korea KR Asia KRW 10_select_some_columns_where (EURUSD,H1) 3| 484 Mexico MX North America MXN 10_select_some_columns_where (EURUSD,H1) 4| 554 New Zealand NZ Australia/Oceania NZD 10_select_some_columns_where (EURUSD,H1) 5| 578 Norway NO Europe NOK 10_select_some_columns_where (EURUSD,H1) 6| 702 Singapore SG Asia SGD 10_select_some_columns_where (EURUSD,H1) 7| 710 South Africa ZA Africa ZAR 10_select_some_columns_where (EURUSD,H1) 8| 724 Spain ES Europe EUR 10_select_some_columns_where (EURUSD,H1) 9| 752 Sweden SE Europe SEK 10_select_some_columns_where (EURUSD,H1) 10| 756 Switzerland CH Europe CHF 10_select_some_columns_where (EURUSD,H1) 11| 826 United Kingdom GB Europe GBP 10_select_some_columns_where (EURUSD,H1) 12| 840 United States US North America USD
Т.е. выборка начинается с кода страны, который равен 392, а заканчивается кодом 840.
2.10 Выбор некоторых отсортированных столбцов таблицы по условию
Усложним предыдущую задачу. Добавим к выборке критерий сортировки – это принадлежность страны к континенту. Текущая задача решена в скрипте 11_select_some_sorted_columns_where.mq5. После его запуска в журнале увидим такие строки:
11_select_some_sorted_columns_where (EURUSD,H1) #| COUNTRY_ID NAME CODE CONTINENT CURRENCY 11_select_some_sorted_columns_where (EURUSD,H1) --+---------------------------------------------------------- 11_select_some_sorted_columns_where (EURUSD,H1) 1| 710 South Africa ZA Africa ZAR 11_select_some_sorted_columns_where (EURUSD,H1) 2| 392 Japan JP Asia JPY 11_select_some_sorted_columns_where (EURUSD,H1) 3| 410 South Korea KR Asia KRW 11_select_some_sorted_columns_where (EURUSD,H1) 4| 702 Singapore SG Asia SGD 11_select_some_sorted_columns_where (EURUSD,H1) 5| 554 New Zealand NZ Australia/Oceania NZD 11_select_some_sorted_columns_where (EURUSD,H1) 6| 578 Norway NO Europe NOK 11_select_some_sorted_columns_where (EURUSD,H1) 7| 724 Spain ES Europe EUR 11_select_some_sorted_columns_where (EURUSD,H1) 8| 752 Sweden SE Europe SEK 11_select_some_sorted_columns_where (EURUSD,H1) 9| 756 Switzerland CH Europe CHF 11_select_some_sorted_columns_where (EURUSD,H1) 10| 826 United Kingdom GB Europe GBP 11_select_some_sorted_columns_where (EURUSD,H1) 11| 484 Mexico MX North America MXN 11_select_some_sorted_columns_where (EURUSD,H1) 12| 840 United States US North America USD
В итоге на первое место в выборке попадает страна "South Africa", т.к. континент "Africa" идёт первым по списку континентов.
2.11 Обновление некоторых столбцов таблицы по условию
Допустим, что перед нами стоит задача обновить строки в выбранных столбцах. Причём нужно это сделать, выполнив предварительно какое-то условие.
Давайте возьмём азиатские страны и обнулим для них значения в столбцах "CURRENCY", "CURRENCY_SYMBOL". Эту задачу выполняет скрипт 12_update_some_columns.mq5.
По итогам его выполнения получаем такую таблицу:
12_update_some_columns (EURUSD,H1) #| COUNTRY_ID NAME CODE CONTINENT CURRENCY CURRENCY_SYMBOL URL_NAME 12_update_some_columns (EURUSD,H1) --+----------------------------------------------------------------------------------------- 12_update_some_columns (EURUSD,H1) 1| 554 New Zealand NZ Australia/Oceania NZD $ new-zealand 12_update_some_columns (EURUSD,H1) 2| 999 European Union EU Europe EUR € european-union 12_update_some_columns (EURUSD,H1) 3| 392 Japan JP Asia None None japan 12_update_some_columns (EURUSD,H1) 4| 124 Canada CA North America CAD $ canada 12_update_some_columns (EURUSD,H1) 5| 36 Australia AU Australia/Oceania AUD $ australia 12_update_some_columns (EURUSD,H1) 6| 156 China CN Asia None None china 12_update_some_columns (EURUSD,H1) 7| 380 Italy IT Europe EUR € italy 12_update_some_columns (EURUSD,H1) 8| 702 Singapore SG Asia None None singapore 12_update_some_columns (EURUSD,H1) 9| 276 Germany DE Europe EUR € germany 12_update_some_columns (EURUSD,H1) 10| 250 France FR Europe EUR € france 12_update_some_columns (EURUSD,H1) 11| 76 Brazil BR South America BRL R$ brazil 12_update_some_columns (EURUSD,H1) 12| 484 Mexico MX North America MXN Mex$ mexico 12_update_some_columns (EURUSD,H1) 13| 710 South Africa ZA Africa ZAR R south-africa 12_update_some_columns (EURUSD,H1) 14| 344 Hong Kong HK Asia None None hong-kong 12_update_some_columns (EURUSD,H1) 15| 356 India IN Asia None None india 12_update_some_columns (EURUSD,H1) 16| 578 Norway NO Europe NOK Kr norway 12_update_some_columns (EURUSD,H1) 17| 840 United States US North America USD $ united-states 12_update_some_columns (EURUSD,H1) 18| 826 United Kingdom GB Europe GBP £ united-kingdom 12_update_some_columns (EURUSD,H1) 19| 756 Switzerland CH Europe CHF ₣ switzerland 12_update_some_columns (EURUSD,H1) 20| 410 South Korea KR Asia None None south-korea 12_update_some_columns (EURUSD,H1) 21| 724 Spain ES Europe EUR € spain 12_update_some_columns (EURUSD,H1) 22| 752 Sweden SE Europe SEK Kr sweden 12_update_some_columns (EURUSD,H1) 23| 0 Worldwide WW World ALL worldwide
2.12 Замена и добавление некоторых строк таблицы
Продолжим работу с таблицами. Теперь попробуем заменить некоторые строки в выбранной таблице.
Пусть для страны “Mexico” в столбце "CURRENCY_SYMBOL" нужно заменить текущий символ "Mex$" на “Peso mexicano”. Выполнение этой задачи поручим скрипту 13_replace_some_rows.mq5.
В текущем варианте таблицы “COUNTRIES” Мексике соответствует такая запись:
COUNTRY_ID | NAME | CODE | CONTINENT | CURRENCY | CURRENCY_SYMBOL | URL_NAME |
---|---|---|---|---|---|---|
484 | Mexico | MX | North America | MXN | Mex$ |
|
Для того, чтобы заменить эту строку в таблице, нужно указать какое-то уникальное значение выбранной строки. Иначе SQLite не поймёт, что мы хотим заменить.
Допустим, что таким значением будет наименование страны (столбец “NAME”). Тогда в коде функция замены будет представлена так:
//--- the replaced row for "COUNTRY_NAME=Mexico" string col_names[]= { "NAME", "CURRENCY_SYMBOL" }; string col_vals[2]; col_vals[0]=::StringFormat("'%s'", "Mexico"); col_vals[1]=::StringFormat("'%s'", "Peso mexicano"); if(!db_obj.Replace(col_names, col_vals)) { db_obj.Close(); return; }
При выполнении скрипта получим такую ошибку:
11_replace_some_rows (EURUSD,H1) database error, NOT NULL constraint failed: COUNTRIES.COUNTRY_ID 11_replace_some_rows (EURUSD,H1) CDatabase::Replace: failed with code 5619
Очевидно, что нарушено NOT NULL ограничение. Всё дело в том, что изначально, при создании таблицы, было указано, что столбец COUNTRY_ID не может содержать нулевое значение. Значит, нужно обязательно добавить значение для этого столбца. И чтобы не получить полупустую строку, добавим значения для всех столбцов.
//--- the replaced row for "COUNTRY_NAME=Mexico" string col_names[]= { "COUNTRY_ID", "NAME", "CODE", "CONTINENT", "CURRENCY", "CURRENCY_SYMBOL", "URL_NAME" }; string col_vals[7]; col_vals[0]=::StringFormat("%I64u", 484); col_vals[1]=::StringFormat("'%s'", "Mexico"); col_vals[2]=::StringFormat("'%s'", "MX"); col_vals[3]=::StringFormat("'%s'", "North America"); col_vals[4]=::StringFormat("'%s'", "MXN"); col_vals[5]=::StringFormat("'%s'", "Peso mexicano"); col_vals[6]=::StringFormat("'%s'", "mexico"); if(!db_obj.Replace(col_names, col_vals)) { db_obj.Close(); return; }
Теперь скрипт отработает вполне нормально. В журнале получим такие записи:
13_replace_some_rows (EURUSD,H1) 'Mexico' row before replacement 13_replace_some_rows (EURUSD,H1) #| COUNTRY_ID NAME CODE CONTINENT CURRENCY CURRENCY_SYMBOL URL_NAME 13_replace_some_rows (EURUSD,H1) -+----------------------------------------------------------------------- 13_replace_some_rows (EURUSD,H1) 1| 484 Mexico MX North America MXN Mex$ mexico 13_replace_some_rows (EURUSD,H1) 13_replace_some_rows (EURUSD,H1) 'Mexico' row after replacement 13_replace_some_rows (EURUSD,H1) #| COUNTRY_ID NAME CODE CONTINENT CURRENCY CURRENCY_SYMBOL URL_NAME 13_replace_some_rows (EURUSD,H1) -+----------------------------------------------------------------------- 13_replace_some_rows (EURUSD,H1) 1| 484 Mexico MX North America MXN Peso mexicano mexico
Стоит отметить, что если бы не существовало строки, содержащей данные о Мексике, то такая строка была бы просто добавлена. Т.е. операция замены является ещё и операцией добавления строк таблицы.
2.13 Удаление некоторых строк таблицы
Теперь посмотрим, как можно не расширять состав строк таблицы, а сокращать. Для этого создадим скрипт 14_delete_some_rows.mq5, который станет по запросу удалять строки из выбранной таблицы, относящиеся к Азии.
После запуская скрипта распечатаем итоговую таблицу:
14_delete_some_rows (EURUSD,H1) #| COUNTRY_ID NAME CODE CONTINENT CURRENCY CURRENCY_SYMBOL URL_NAME 14_delete_some_rows (EURUSD,H1) --+----------------------------------------------------------------------------------------- 14_delete_some_rows (EURUSD,H1) 1| 554 New Zealand NZ Australia/Oceania NZD $ new-zealand 14_delete_some_rows (EURUSD,H1) 2| 999 European Union EU Europe EUR € european-union 14_delete_some_rows (EURUSD,H1) 3| 124 Canada CA North America CAD $ canada 14_delete_some_rows (EURUSD,H1) 4| 36 Australia AU Australia/Oceania AUD $ australia 14_delete_some_rows (EURUSD,H1) 5| 380 Italy IT Europe EUR € italy 14_delete_some_rows (EURUSD,H1) 6| 276 Germany DE Europe EUR € germany 14_delete_some_rows (EURUSD,H1) 7| 250 France FR Europe EUR € france 14_delete_some_rows (EURUSD,H1) 8| 76 Brazil BR South America BRL R$ brazil 14_delete_some_rows (EURUSD,H1) 9| 710 South Africa ZA Africa ZAR R south-africa 14_delete_some_rows (EURUSD,H1) 10| 578 Norway NO Europe NOK Kr norway 14_delete_some_rows (EURUSD,H1) 11| 840 United States US North America USD $ united-states 14_delete_some_rows (EURUSD,H1) 12| 826 United Kingdom GB Europe GBP £ united-kingdom 14_delete_some_rows (EURUSD,H1) 13| 756 Switzerland CH Europe CHF ₣ switzerland 14_delete_some_rows (EURUSD,H1) 14| 724 Spain ES Europe EUR € spain 14_delete_some_rows (EURUSD,H1) 15| 752 Sweden SE Europe SEK Kr sweden 14_delete_some_rows (EURUSD,H1) 16| 0 Worldwide WW World ALL worldwide 14_delete_some_rows (EURUSD,H1) 17| 484 Mexico MX North America MXN Peso mexicano mexico
Строк, относящихся к континенту Азия, не обнаружено.
2.14 Добавление столбцов в таблицу
К операциям с таблицами относится и вполне обычная задача – добавление новых столбцов.
Итак, пусть нам понадобилось расширить нашу таблицу “COUNTRIES” и добавить столбец, содержащий количество макроэкономических событий, которые попадают в календарь.
Выполнение задачи возложим на скрипт 15_add_new_column.mq5.
После выполнения скрипта проверим таблицу (Рис. 5). В ней появился новый столбец “EVENTS_NUM”.
Рис.5 Новый столбец “EVENTS_NUM” в таблице COUNTRIES
2.15 Переименование столбцов в таблице
Если нужно переименовать столбец в таблице, то обратимся к методу CDatabase::RenameColumn(const string _curr_name, const string _new_name). В качестве параметров нужно указать текущее имя столбца и новое имя столбца. Скрипт 16_rename_column.mq5 изменит имя столбца "EVENTS_NUM" на "EVENTS_NUMBER".
Рис.6 Переименованный столбец “EVENTS_NUMBER” в таблице COUNTRIES
Теперь таблица примет такой вид (Рис. 6).
2.16 Объединение строк некоторых столбцов
Допустим, что нужно объединить результаты выборок в одной таблице. Для этих целей подойдёт метод CDatabase::Union(). Выполнение задачи возложим на скрипт 17_union_some_columns.mq5.
Пусть у нас есть 2 таблицы “EUROPEAN_COUNTRIES” и “NORTH_AMERICAN_COUNTRIES”. В первой будут находится европейские страны, а во второй – северо-американские. Предварительно создадим таблицы, чтобы объединить их строки. Причём каждая из таблиц будет являться результирующей выборкой из первой таблицы “COUNTRIES”. В коде это выглядит так:
//--- create 2 tables string table1_name, table2_name, sql_request; table1_name="EUROPEAN_COUNTRIES"; table2_name="NORTH_AMERICAN_COUNTRIES"; sql_request="SELECT COUNTRY_ID AS id, NAME AS name, CURRENCY " "as currency FROM COUNTRIES " "WHERE CONTINENT='North America'"; if(!db_obj.CreateTableAs(table2_name, sql_request, true)) { db_obj.Close(); return; } db_obj.FinalizeSqlRequest(); sql_request="SELECT COUNTRY_ID AS id, NAME AS name, CURRENCY " "as currency FROM COUNTRIES " "WHERE CONTINENT='Europe'"; if(!db_obj.CreateTableAs(table1_name, sql_request, true)) { db_obj.Close(); return; } db_obj.FinalizeSqlRequest();
В ходе работы скрипта получим в журнале такие записи:
16_union_some_columns (EURUSD,H1) #| id name currency 16_union_some_columns (EURUSD,H1) --+---------------------------- 16_union_some_columns (EURUSD,H1) 1| 124 Canada CAD 16_union_some_columns (EURUSD,H1) 2| 250 France EUR 16_union_some_columns (EURUSD,H1) 3| 276 Germany EUR 16_union_some_columns (EURUSD,H1) 4| 380 Italy EUR 16_union_some_columns (EURUSD,H1) 5| 484 Mexico MXN 16_union_some_columns (EURUSD,H1) 6| 578 Norway NOK 16_union_some_columns (EURUSD,H1) 7| 724 Spain EUR 16_union_some_columns (EURUSD,H1) 8| 752 Sweden SEK 16_union_some_columns (EURUSD,H1) 9| 756 Switzerland CHF 16_union_some_columns (EURUSD,H1) 10| 826 United Kingdom GBP 16_union_some_columns (EURUSD,H1) 11| 840 United States USD 16_union_some_columns (EURUSD,H1) 12| 999 European Union EUR
Результирующая выборка включает страны Европы и Северной Америки.
2.17 Разность выборок
Допустим, что у нас есть 2 выборки. И нужно найти такие записи в первой выборке, которых нет во второй. На помощь тут приходит метод CDatabase::Except().
Возьмём для примера таблицы “COUNTRIES” и “EUROPEAN_COUNTRIES”. И посмотрим, какие страны останутся, если для первой таблицы применить оператор EXCEPT.
Решение задачи закодировано в скрипте 18_except_some_columns.mq5.
В результате выполнения программы в журнал будут выведены такие строки:
18_except_some_columns (EURUSD,H1) #| COUNTRY_ID NAME CURRENCY 18_except_some_columns (EURUSD,H1) -+---------------------------------- 18_except_some_columns (EURUSD,H1) 1| 0 Worldwide ALL 18_except_some_columns (EURUSD,H1) 2| 36 Australia AUD 18_except_some_columns (EURUSD,H1) 3| 76 Brazil BRL 18_except_some_columns (EURUSD,H1) 4| 124 Canada CAD 18_except_some_columns (EURUSD,H1) 5| 484 Mexico MXN 18_except_some_columns (EURUSD,H1) 6| 554 New Zealand NZD 18_except_some_columns (EURUSD,H1) 7| 710 South Africa ZAR 18_except_some_columns (EURUSD,H1) 8| 840 United States USD
Легко заметить, что в результате получилась выборка стран, в которую не попали европейские. Азиатских тоже нет. Они были удалены ранее.
2.18 Пересечение выборок
Теперь попробуем найти не то, чем выборки отличаются, а то, что их объединяет. Т.е. стоит задача найти общие строки выборок.
Предварительно обновим таблицу “COUNTRIES” и вернём её к первоначальному виду, куда входили азиатские страны.
Создадим две временных таблицы со столбцами “id”, ”name” и ”currency”. В первую попадут страны, у которых значение в столбце “COUNTRY_ID” не превышает 578, а во вторую – у которых в том же столбце не менее 392.
//--- create temporary tables string table1_name, table2_name, sql_request; table1_name="Table1"; table2_name="Table2"; sql_request="SELECT COUNTRY_ID AS id, NAME AS name, CURRENCY " "as currency FROM COUNTRIES " "WHERE COUNTRY_ID<=578"; if(!db_obj.CreateTableAs(table1_name, sql_request, true, true)) { db_obj.Close(); return; } db_obj.FinalizeSqlRequest(); //--- print the temporary table string temp_col_names[]= {"*"}; if(db_obj.SelectTable(table1_name, true)) if(db_obj.SelectFrom(temp_col_names)) { ::Print(" \nTable #1: "); db_obj.PrintSqlRequest(); db_obj.FinalizeSqlRequest(); } sql_request="SELECT COUNTRY_ID AS id, NAME AS name, CURRENCY " "as currency FROM COUNTRIES " "WHERE COUNTRY_ID>=392"; if(!db_obj.CreateTableAs(table2_name, sql_request, true, true)) { db_obj.Close(); return; } db_obj.FinalizeSqlRequest(); //--- print the temporary table if(db_obj.SelectTable(table2_name, true)) if(db_obj.SelectFrom(temp_col_names)) { ::Print(" \nTable #2: "); db_obj.PrintSqlRequest(); db_obj.FinalizeSqlRequest(); }
Воспользуемся возможностями метода CDatabase::Intersect() в скрипте 19_intersect_some_columns.mq5. В результате получим такие строки в журнале:
19_intersect_some_columns (EURUSD,H1) #| id name currency 19_intersect_some_columns (EURUSD,H1) -+------------------------- 19_intersect_some_columns (EURUSD,H1) 1| 392 Japan JPY 19_intersect_some_columns (EURUSD,H1) 2| 410 South Korea KRW 19_intersect_some_columns (EURUSD,H1) 3| 484 Mexico MXN 19_intersect_some_columns (EURUSD,H1) 4| 554 New Zealand NZD 19_intersect_some_columns (EURUSD,H1) 5| 578 Norway NOK
Скрипт отработал правильно – мы получили список стран, где у страны минимальное значение по id равно 392, а максимальное - 578.
2.19 Создание представлений
Представление (view) – это своего рода виртуальная таблица. Удобство состоит в том, что можно отобразить данные, выбранные из любой другой таблицы.
Создавать представления будем с помощью методов bool CDatabase::CreateView() и bool CDatabase::CreateViewWhere(). Первый создаёт какое-то безусловное представление, а второй – по указанному условию.
Рассмотрим такой пример. У нас есть таблица “COUNTRIES”. Пусть нужно отобрать в новую виртуальную таблицу все страны по столбцам “NAME”, “CONTINENT”, “CURRENCY”.
Решим это задание с помощью скрипта 20_create_view.mq5. На выходе получим такой результат - представление “All_countries” (Рис. 7).
Рис.7 Представление “All_countries”
Усложним пример и отберём теперь только европейские страны. За это дело возьмётся скрипт 21_create_view_where.mq5. В итоге у нас появилась виртуальная таблица, содержащая только европейские страны (Рис.8).
Рис.8 Представление “European”
С одной стороны представления не являются полноценными таблицами - в них нельзя добавлять, удалять или обновлять строки, а с другой - с их помощью можно в удобном виде агрегировать результаты сложных запросов, выбирать отдельные столбцы, меняя имена, не затрагивая связи между самими таблицами.
2.20 Удаление представлений
Удалить ранее созданное представление можно с помощью метода CDatabase::DropView().
Метод похож на свой аналог, который удаляет таблицы DropTable(). В предыдущих примерах, прежде чем создавать представление, вызывался именно метод удаления представления.
Скажем несколько слов о конструкции IF EXISTS. Если пробовать удалить несуществующее представление с этой конструкцией, то метод вернёт true, иначе – false.
Посмотрим, как работает скрипт 22_drop_view.mq5.
//--- drop a view string table_name="COUNTRIES"; if(db_obj.SelectTable(table_name)) for(int idx=0; idx<2; idx++) { string view_name=::StringFormat("European%d", idx+1); bool if_exists=idx; if(db_obj.DropView(view_name, if_exists)) ::PrintFormat("A view \"%s\" has been successfully dropped!", view_name); db_obj.FinalizeSqlRequest(); }
Сначала он пытается удалить несуществующее представление "European_countries1" без обращения к «IF EXISTS». В результате чего получаем ошибку 5601:
22_drop_view (EURUSD,H1) database error, no such view: European1 22_drop_view (EURUSD,H1) CDatabase::Select: failed with code 5601 22_drop_view (EURUSD,H1) A view "European2" has been successfully dropped!
После этого скрипт пытается удалить тоже несуществующее представление "European_countries2", но уже с использованием «IF EXISTS». Удаление второго представления будет успешным, хотя фактически никакого удаления и не было.
2.21 Переименование таблицы
Допустим, что перед нами стоит задача переименовать саму таблицу. Для этого обратимся к методу CDatabase::RenameTable(). Скрипт 23_rename_table.mq5 выполнит команду по переименованию.
Рис.9 Переименованная таблица COUNTRIES1
В результате текущая таблица станет называться “COUNTRIES1” (Рис.9).
3. База данных макроэкономических событий
В этом разделе предлагаю заняться созданием реляционной БД макроэкономических событий, которые освещаются в Календаре.
Итак, для начала создадим схему таблиц, из которых будет состоять будущая БД. Отмечу, что в статье «Рецепты MQL5 – Экономический календарь» уже была представлена связь между отношениями календарных структур. Поэтому в нашем случае для базы данных достаточно легко создать таблицы и построить для них связи.
3.1 Таблицы и реляционные связи
В базе данных всего будет 3 исходных таблицы:
- COUNTRIES;
- EVENTS;
- EVENT_VALUES.
Реляционные связи между таблицами представлены на Рис.10.
Рис.10 Схема связей между таблицами в базе данных Calendar_DB
Таблица COUNTRIES станет родительской для таблицы EVENTS. Последняя, в свою очередь, станет дочерней для первой.
Первичным ключом для таблицы COUNTRIES выступает столбец (поле) "COUNTRY_ID". На схеме ему предшествует знак "+". Для таблицы EVENTS таким ключом будет столбец "EVENT_ID", тогда как столбец "COUNTRY_ID" – это внешний ключ. На схеме ему предшествует символ "#".
Таблица EVENTS станет родительской для таблицы EVENT_VALUES, а вторая станет дочерней для первой.
В таблице EVENT_VALUES первичным ключом выступает столбец (поле) "VALUE_ID", а внешним - "EVENT_ID".
Ключи нужны именно для того, чтобы реализовать обозначенные выше связи между таблицами. А связи, в свою очередь, способствуют целостности данных в базе данных.
Связи между тремя таблицами имеют форму «один ко многим» (1..*). И, наверное, не составит особого труда их расшифровать. Первую связь между странами и событиями можно представить так: у одной страны есть много макроэкономических событий, а у одного события только одна страна. Вторую связь между событиями и событийными значениями можно проиллюстрировать так: у одного события есть много значений, а у любого значения – только одно событие.
Перейдём к коду. В скрипте sCreateAndFillCalendarDB.mq5 реализованы такие этапы:
- создание календарной БД;
- создание таблиц БД;
- заполнение таблиц.
Посмотрим, к примеру, как создаётся таблица EVENTS. Итоговый запрос по созданию этой таблицы будет иметь такой вид:
CREATE TABLE IF NOT EXISTS EVENTS ( EVENT_ID [UNSIGNED BIG INT] PRIMARY KEY NOT NULL, TYPE TEXT, SECTOR TEXT, FREQUENCY TEXT, TIME_MODE TEXT, COUNTRY_ID [UNSIGNED BIG INT] NOT NULL, UNIT TEXT, IMPORTANCE TEXT, MULTIPLIER TEXT, DIGITS [UNSIGNED INT], SOURCE TEXT, CODE TEXT, NAME TEXT, FOREIGN KEY ( COUNTRY_ID ) REFERENCES COUNTRIES (COUNTRY_ID) ON UPDATE CASCADE ON DELETE CASCADE )
Особый интерес вызывают строки, где создаётся внешний ключ. Строка FOREIGN KEY (COUNTRY_ID) означает, что в таблице есть внешний ключ по полю COUNTRY_ID. Конструкция REFERENCES COUNTRIES(COUNTRY_ID) используется для указания ссылки на родительскую таблицу COUNTRIES.
Выражения ON UPDATE CASCADE и ON DELETE CASCADE означают, что при удалении или изменении связанной строки из родительской таблицы в дочерней таблице строки также будут удалены или изменены.
Что касается заполнения таблиц, то ниже представлен блок кода, где идёт заполнение таблицы "COUNTRIES".
//--- Table 1 MqlCalendarCountry calendar_countries[]; table_name="COUNTRIES"; if(db_obj.SelectTable(table_name)) if(db_obj.EmptyTable()) { db_obj.FinalizeSqlRequest(); string col_names[]= { "COUNTRY_ID", // 1 "NAME", // 2 "CODE", // 3 "CONTINENT", // 4 "CURRENCY", // 5 "CURRENCY_SYMBOL",// 6 "URL_NAME" // 7 }; CiCalendarInfo calendar_info; if(calendar_info.Init()) { if(calendar_info.GetCountries(calendar_countries)) { if(db_obj.TransactionBegin()) for(int c_idx=0; c_idx<::ArraySize(calendar_countries); c_idx++) { MqlCalendarCountry curr_country=calendar_countries[c_idx]; string col_vals[]; ::ArrayResize(col_vals, 7); col_vals[0]=::StringFormat("%I64u", curr_country.id); col_vals[1]=::StringFormat("'%s'", curr_country.name); col_vals[2]=::StringFormat("'%s'", curr_country.code); col_vals[3]="NULL"; SCountryByContinent curr_country_continent_data; if(curr_country_continent_data.Init(curr_country.code)) col_vals[3]=::StringFormat("'%s'", curr_country_continent_data.ContinentDescription()); col_vals[4]=::StringFormat("'%s'", curr_country.currency); col_vals[5]=::StringFormat("'%s'", curr_country.currency_symbol); col_vals[6]=::StringFormat("'%s'", curr_country.url_name); if(!db_obj.InsertSingleRow(col_names, col_vals)) { db_obj.TransactionRollback(); db_obj.Close(); return; } db_obj.FinalizeSqlRequest(); } if(!db_obj.TransactionCommit()) ::PrintFormat("Failed to complete transaction execution, error %d", ::GetLastError()); } //--- print if(db_obj.PrintTable()<0) ::PrintFormat("Failed to print the table \"%s\", error %d", table_name, ::GetLastError()); } }
Сначала для дальнейшей работы с таблицей нужно её выбрать с помощью метода CDatabase::SelectTable(). Здесь можно провести аналогию с тем, как выбирается торговая позиция с помощью нативной функции ::PositionSelect() для её дальнейшей обработки.
Затем метод CDatabase::EmptyTable() предварительно очищает таблицу.
Далее в цикле перебираем страны и заполняем таблицу по столбцам
- "COUNTRY_ID",
- "COUNTRY_NAME",
- "COUNTRY_CODE",
- "CONTINENT",
- "CURRENCY",
- "CURRENCY_SYMBOL",
- "URL_NAME".
Вставка итоговой строки в таблицу осуществляется методом CDatabase::InsertSingleRow(). Отмечу, что тут, при наполнении таблицы, задействован транзакционный механизм. Более подробно об этом можно прочесть в разделе «Ускорение транзакций обертыванием в DatabaseTransactionBegin()/DatabaseTransactionCommit()».
По итогам заполнения трёх таблиц получились следующие результаты: таблица COUNTRIES содержит 23 записи, таблица EVENTS содержит 1500 записей, а таблица EVENT_VALUES - 158 696 (Рис.11).
Рис.11 Заполненная таблица EVENT_VALUES
Теперь данные у нас имеются, можно приступать к получению информации - формированию запросов.
3.2 Запросы к базе данных
По большому счёту все запросы к БД можно разделить на 2 группы:
1) запросы, получающие информацию из БД;
2) запросы, изменяющие данные в БД.
Сначала поработаем с примерами получения информации из календарной базы данных.
3.2.1 Выборка количества событий по странам
Давайте для начала спросим у базы данных, сколько макроэкономических событий приходится на каждую страну. Создадим следующий запрос, обратившись к таблице “EVENTS”:
SELECT COUNTRY_ID AS id, COUNT(EVENT_ID) AS events_num FROM EVENTS GROUP BY COUNTRY_ID
В MQL5-коде такой запрос реализован следующим образом:
//--- 1) group events number by country id string table_name="EVENTS"; if(db_obj.SelectTable(table_name)) { string col_names_to_select[]= { "COUNTRY_ID AS id", "COUNT(EVENT_ID) AS events_num" }; string gr_names[]= { "COUNTRY_ID" }; if(!db_obj.SelectFromGroupBy(col_names_to_select, gr_names)) { db_obj.Close(); return; } //--- print the SQL request if(db_obj.PrintSqlRequest()<0) ::PrintFormat("Failed to print the SQL request, error %d", ::GetLastError()); db_obj.FinalizeSqlRequest();
И в журнале терминала на выходе получим такую выборку по исходным столбцам “COUNTRY_ID” и “COUNT(EVENT_ID)”:
sRequest1 (EURUSD,H1) #| id events_num sRequest1 (EURUSD,H1) --+--------------- sRequest1 (EURUSD,H1) 1| 0 7 sRequest1 (EURUSD,H1) 2| 36 85 sRequest1 (EURUSD,H1) 3| 76 55 sRequest1 (EURUSD,H1) 4| 124 74 sRequest1 (EURUSD,H1) 5| 156 40 sRequest1 (EURUSD,H1) 6| 250 43 sRequest1 (EURUSD,H1) 7| 276 62 sRequest1 (EURUSD,H1) 8| 344 26 sRequest1 (EURUSD,H1) 9| 356 57 sRequest1 (EURUSD,H1) 10| 380 52 sRequest1 (EURUSD,H1) 11| 392 124 sRequest1 (EURUSD,H1) 12| 410 36 sRequest1 (EURUSD,H1) 13| 484 47 sRequest1 (EURUSD,H1) 14| 554 82 sRequest1 (EURUSD,H1) 15| 578 47 sRequest1 (EURUSD,H1) 16| 702 27 sRequest1 (EURUSD,H1) 17| 710 54 sRequest1 (EURUSD,H1) 18| 724 39 sRequest1 (EURUSD,H1) 19| 752 59 sRequest1 (EURUSD,H1) 20| 756 40 sRequest1 (EURUSD,H1) 21| 826 115 sRequest1 (EURUSD,H1) 22| 840 247 sRequest1 (EURUSD,H1) 23| 999 82
Согласитесь, не очень читабельно выглядит выборка, т.к. столбец “id” представляет собой идентификатор страны, а не её имя. Но имена стран находятся в другой таблице – “COUNTRIES”.
Чтобы получить имя страны и количество событий страны, нужно создать составной запрос (запрос в запросе).
Первый вариант такого составного запроса выглядит так:
SELECT c.NAME AS country, ev.events_num AS events_number FROM COUNTRIES c JOIN ( SELECT COUNTRY_ID AS id, COUNT(EVENT_ID) AS events_num FROM EVENTS GROUP BY COUNTRY_ID ) AS ev ON c.COUNTRY_ID = ev.id
В этом варианте используется тот запрос, который мы создали вначале. Но теперь он вошёл в состав другого запроса, тем самым изменив свою форму на форму подзапроса.
А второй вариант запроса можно реализовать в форме СТЕ:
WITH ev_cnt AS ( SELECT COUNTRY_ID AS id, COUNT(EVENT_ID) AS events_num FROM EVENTS GROUP BY COUNTRY_ID ) SELECT c.NAME AS country, ev.events_num AS events_number FROM COUNTRIES c INNER JOIN ev_cnt AS ev ON c.COUNTRY_ID = ev.id
В MQL5-коде составной запрос реализован так:
//--- 2) group events number by country name using a subquery ::Print("\nGroup events number by country name using a subquery:\n"); string subquery=db_obj.SqlRequest(); string new_sql_request=::StringFormat("SELECT c.NAME AS country," "ev.events_num AS events_number FROM COUNTRIES c " "JOIN(%s) AS ev " "ON c.COUNTRY_ID=ev.id", subquery); if(!db_obj.Select(new_sql_request)) { db_obj.Close(); return; } //--- print the SQL request if(db_obj.PrintSqlRequest()<0) ::PrintFormat("Failed to print the SQL request, error %d", ::GetLastError()); db_obj.FinalizeSqlRequest();
А табличное выражение (СТЕ) реализовано следующим образом:
//--- 3) group events number by country name using CTE ::Print("\nGroup events number by country name using CTE:\n"); new_sql_request=::StringFormat("WITH ev_cnt AS (%s)" "SELECT c.NAME AS country," "ev.events_num AS events_number FROM COUNTRIES c " "INNER JOIN ev_cnt AS ev ON c.COUNTRY_ID=ev.id", subquery); if(!db_obj.Select(new_sql_request)) { db_obj.Close(); return; } //--- print the SQL request if(db_obj.PrintSqlRequest()<0) ::PrintFormat("Failed to print the SQL request, error %d", ::GetLastError()); db_obj.FinalizeSqlRequest();
Оба варианта распечатают такие результаты запроса в журнале:
sRequest1 (EURUSD,H1) #| country events_number sRequest1 (EURUSD,H1) --+----------------------------- sRequest1 (EURUSD,H1) 1| Worldwide 7 sRequest1 (EURUSD,H1) 2| Australia 85 sRequest1 (EURUSD,H1) 3| Brazil 55 sRequest1 (EURUSD,H1) 4| Canada 74 sRequest1 (EURUSD,H1) 5| China 40 sRequest1 (EURUSD,H1) 6| France 43 sRequest1 (EURUSD,H1) 7| Germany 62 sRequest1 (EURUSD,H1) 8| Hong Kong 26 sRequest1 (EURUSD,H1) 9| India 57 sRequest1 (EURUSD,H1) 10| Italy 52 sRequest1 (EURUSD,H1) 11| Japan 124 sRequest1 (EURUSD,H1) 12| South Korea 36 sRequest1 (EURUSD,H1) 13| Mexico 47 sRequest1 (EURUSD,H1) 14| New Zealand 82 sRequest1 (EURUSD,H1) 15| Norway 47 sRequest1 (EURUSD,H1) 16| Singapore 27 sRequest1 (EURUSD,H1) 17| South Africa 54 sRequest1 (EURUSD,H1) 18| Spain 39 sRequest1 (EURUSD,H1) 19| Sweden 59 sRequest1 (EURUSD,H1) 20| Switzerland 40 sRequest1 (EURUSD,H1) 21| United Kingdom 115 sRequest1 (EURUSD,H1) 22| United States 247 sRequest1 (EURUSD,H1) 23| European Union 82
Легко заметить, что больше всего календарь уделяет внимание американским событиям - их в нём 247.
Давайте немного усложним задачу и добавим ещё в выборку столбец, в котором посчитаем, сколько важных событий приходится на ту или иную страну. Степень важности определена в столбце “IMPORTANCE”. Выбирать будем только те события, которые имеют значение “High”.
Сначала поработаем с таблицей “EVENTS”. Здесь нам нужно будет создать 2 выборки. Первая выборка – это подсчёт количества событий по странам. Эту задачу уже выполнили выше. Вторая выборка - подсчёт количества важных событий по странам. И в завершение нужно будет объединить 2 выборки.
SQL-код запроса представлен так:
SELECT evn.COUNTRY_ID AS id, COUNT(EVENT_ID) AS events_num, imp.high AS imp_events_num FROM EVENTS evn JOIN ( SELECT COUNTRY_ID AS id, COUNT(IMPORTANCE) AS high FROM EVENTS WHERE IMPORTANCE = 'High' GROUP BY COUNTRY_ID ) AS imp ON evn.COUNTRY_ID = imp.id GROUP BY COUNTRY_ID
Что касается реализации на MQL5, то код выглядит следующим образом:
//--- 5) important events - ids, events number and important events number ::Print("\nGroup events number and important events number by country id"); subquery=db_obj.SqlRequest(); string new_sql_request4=::StringFormat("SELECT ev.COUNTRY_ID AS id, COUNT(EVENT_ID) AS events_num," "imp.high AS imp_events_num " "FROM EVENTS ev JOIN (%s) AS imp " "ON ev.COUNTRY_ID=imp.id GROUP BY COUNTRY_ID", subquery); if(!db_obj.Select(new_sql_request4)) { db_obj.Close(); return; } //--- print the SQL request if(db_obj.PrintSqlRequest()<0) ::PrintFormat("Failed to print the SQL request, error %d", ::GetLastError()); db_obj.FinalizeSqlRequest();
В результате получим такие записи в журнале:
sRequest1 (EURUSD,H1) Group events number and important events number by country id: sRequest1 (EURUSD,H1) sRequest1 (EURUSD,H1) #| id events_num imp_events_num sRequest1 (EURUSD,H1) --+------------------------------ sRequest1 (EURUSD,H1) 1| 0 7 2 sRequest1 (EURUSD,H1) 2| 36 85 5 sRequest1 (EURUSD,H1) 3| 76 55 2 sRequest1 (EURUSD,H1) 4| 124 74 10 sRequest1 (EURUSD,H1) 5| 156 40 5 sRequest1 (EURUSD,H1) 6| 250 43 1 sRequest1 (EURUSD,H1) 7| 276 62 3 sRequest1 (EURUSD,H1) 8| 344 26 1 sRequest1 (EURUSD,H1) 9| 356 57 2 sRequest1 (EURUSD,H1) 10| 392 124 7 sRequest1 (EURUSD,H1) 11| 410 36 2 sRequest1 (EURUSD,H1) 12| 484 47 2 sRequest1 (EURUSD,H1) 13| 554 82 8 sRequest1 (EURUSD,H1) 14| 578 47 2 sRequest1 (EURUSD,H1) 15| 702 27 1 sRequest1 (EURUSD,H1) 16| 710 54 2 sRequest1 (EURUSD,H1) 17| 752 59 1 sRequest1 (EURUSD,H1) 18| 756 40 4 sRequest1 (EURUSD,H1) 19| 826 115 13 sRequest1 (EURUSD,H1) 20| 840 247 20 sRequest1 (EURUSD,H1) 21| 999 82 11
В итоговой выборке осталось только заменить столбец “id” на “country”.
Снова создадим составной запрос. Воспользуемся тем, что его части были написаны ранее. И в конце выборку упорядочим по убыванию значений в столбце ‘imp_events_number’. Составной запрос имеет такой вид:
SELECT c.NAME AS country, ev.events_num AS events_number, ev.imp_events_num AS imp_events_number FROM COUNTRIES c JOIN ( SELECT ev.COUNTRY_ID AS id, COUNT(EVENT_ID) AS events_num, imp.high AS imp_events_num FROM EVENTS ev JOIN ( SELECT COUNTRY_ID AS id, COUNT(IMPORTANCE) AS high FROM EVENTS WHERE IMPORTANCE = 'High' GROUP BY COUNTRY_ID ) AS imp ON ev.COUNTRY_ID = imp.id GROUP BY COUNTRY_ID ) AS ev ON c.COUNTRY_ID = ev.id ORDER BY imp_events_number DESC
В MQL5-коде запрос реализован так:
//--- 6) important events - countries, events number and important events number ::Print("\nGroup events number and important events number by country:\n"); subquery=db_obj.SqlRequest(); string new_sql_request5=::StringFormat("SELECT c.NAME AS country," "ev.events_num AS events_number," "ev.imp_events_num AS imp_events_number " "FROM COUNTRIES c " "JOIN(%s) AS ev " "ON c.COUNTRY_ID=ev.id " "ORDER BY imp_events_number DESC", subquery); if(!db_obj.Select(new_sql_request5)) { db_obj.Close(); return; } //--- print the SQL request if(db_obj.PrintSqlRequest()<0) ::PrintFormat("Failed to print the SQL request, error %d", ::GetLastError()); db_obj.FinalizeSqlRequest();
И теперь в журнале получим искомую выборку:
sRequest1 (EURUSD,H1) Group events number and important events number by country: sRequest1 (EURUSD,H1) sRequest1 (EURUSD,H1) #| country events_number imp_events_number sRequest1 (EURUSD,H1) --+----------------------------------------------- sRequest1 (EURUSD,H1) 1| United States 247 20 sRequest1 (EURUSD,H1) 2| United Kingdom 115 13 sRequest1 (EURUSD,H1) 3| European Union 82 11 sRequest1 (EURUSD,H1) 4| Canada 74 10 sRequest1 (EURUSD,H1) 5| New Zealand 82 8 sRequest1 (EURUSD,H1) 6| Japan 124 7 sRequest1 (EURUSD,H1) 7| Australia 85 5 sRequest1 (EURUSD,H1) 8| China 40 5 sRequest1 (EURUSD,H1) 9| Switzerland 40 4 sRequest1 (EURUSD,H1) 10| Germany 62 3 sRequest1 (EURUSD,H1) 11| Worldwide 7 2 sRequest1 (EURUSD,H1) 12| Brazil 55 2 sRequest1 (EURUSD,H1) 13| India 57 2 sRequest1 (EURUSD,H1) 14| South Korea 36 2 sRequest1 (EURUSD,H1) 15| Mexico 47 2 sRequest1 (EURUSD,H1) 16| Norway 47 2 sRequest1 (EURUSD,H1) 17| South Africa 54 2 sRequest1 (EURUSD,H1) 18| France 43 1 sRequest1 (EURUSD,H1) 19| Hong Kong 26 1 sRequest1 (EURUSD,H1) 20| Singapore 27 1 sRequest1 (EURUSD,H1) 21| Sweden 59 1
Как видно из выборки, больше всего важных событий у США - 20. На втором месте Великобритания - 13. Третье место занимает Евросоюз - 11. Япония занимает лишь 6-ое место - 7.
И давайте с помощью запроса найдём те страны, у которых вообще нет важных событий. Для этого нужно будет найти разность двух выборок. В первую выборку попадут все страны, которые возьмём из таблицы “COUNTRIES”, а во вторую - столбец со странами из предыдущего составного запроса.
Тогда SQL-код будет таким:
SELECT NAME FROM COUNTRIES EXCEPT SELECT country FROM ( SELECT c.NAME AS country, ev.events_num AS events_number, ev.imp_events_num AS imp_events_number FROM COUNTRIES c JOIN ( SELECT ev.COUNTRY_ID AS id, COUNT(EVENT_ID) AS events_num, imp.high AS imp_events_num FROM EVENTS ev JOIN ( SELECT COUNTRY_ID AS id, COUNT(IMPORTANCE) AS high FROM EVENTS WHERE IMPORTANCE = 'High' GROUP BY COUNTRY_ID ) AS imp ON ev.COUNTRY_ID = imp.id GROUP BY COUNTRY_ID ) AS ev ON c.COUNTRY_ID = ev.id )
MQL5-код будет выглядеть попроще, т.к. воспользуемся тем, что прошлый запрос станет нашим новым подзапросом.
//--- 7) countries having no important events ::Print("\nCountries having no important events:\n"); string last_request=db_obj.SqlRequest(); string new_sql_request6=::StringFormat("SELECT NAME FROM COUNTRIES " "EXCEPT SELECT country FROM (%s)", last_request); if(!db_obj.Select(new_sql_request6)) { db_obj.Close(); return; } //--- print the SQL request if(db_obj.PrintSqlRequest()<0) ::PrintFormat("Failed to print the SQL request, error %d", ::GetLastError()); db_obj.FinalizeSqlRequest();
По завершению выполнения кода получим в журнале такие записи:
sRequest1 (EURUSD,H1) Countries having no important events: sRequest1 (EURUSD,H1) sRequest1 (EURUSD,H1) #| NAME sRequest1 (EURUSD,H1) -+------ sRequest1 (EURUSD,H1) 1| Italy sRequest1 (EURUSD,H1) 2| Spain
Т.е. из всех стран только Италия и Испания не имеют важных событий. Запросы о событиях стран на MQL5 выполнялись в скрипте sRequest1.mq5.
3.2.2 Выборка значений ВВП по странам
В этом примере сделаем запрос к базе данных, в результате которого постараемся получить выборку значений ВВП различных стран. В качестве значения ВВП возьмём показатель «Валовой внутренний продукт (ВВП) кв/кв» (за 3-ий квартал).
Выборок будет несколько, поэтому запрос будет составным.
Сначала посмотрим, в экономике каких стран существует поквартальный показатель ВВП.
SQL-код представлен в таком виде:
SELECT COUNTRY_ID, EVENT_ID FROM EVENTS WHERE (NAME LIKE 'GDP q/q' AND SECTOR = 'Gross Domestic Product')
Реализация на MQL5 имеет такой вид (скрипт sRequest2.mq5):
//--- 1) countries by id where the indicator '%GDP q/q%' exists string col_names[]= {"COUNTRY_ID", "EVENT_ID"}; string where_condition="(NAME LIKE 'GDP q/q' AND SECTOR='Gross Domestic Product')"; if(!db_obj.SelectFromWhere(col_names, where_condition)) { db_obj.Close(); return; } ::Print("\nCountries by id where the indicator 'GDP q/q' exists:\n"); //--- print the SQL request if(db_obj.PrintSqlRequest()<0) ::PrintFormat("Failed to print the SQL request, error %d", ::GetLastError()); db_obj.FinalizeSqlRequest();
И распечатка из журнала после выполнения запроса:
sRequest2 (EURUSD,H1) Countries by id where the indicator 'GDP q/q' exists: sRequest2 (EURUSD,H1) sRequest2 (EURUSD,H1) #| COUNTRY_ID EVENT_ID sRequest2 (EURUSD,H1) --+--------------------- sRequest2 (EURUSD,H1) 1| 554 554010024 sRequest2 (EURUSD,H1) 2| 999 999030016 sRequest2 (EURUSD,H1) 3| 392 392010001 sRequest2 (EURUSD,H1) 4| 124 124010022 sRequest2 (EURUSD,H1) 5| 36 36010019 sRequest2 (EURUSD,H1) 6| 156 156010004 sRequest2 (EURUSD,H1) 7| 380 380010020 sRequest2 (EURUSD,H1) 8| 702 702010004 sRequest2 (EURUSD,H1) 9| 276 276010008 sRequest2 (EURUSD,H1) 10| 250 250010005 sRequest2 (EURUSD,H1) 11| 76 76010010 sRequest2 (EURUSD,H1) 12| 484 484020016 sRequest2 (EURUSD,H1) 13| 710 710060009 sRequest2 (EURUSD,H1) 14| 344 344020002 sRequest2 (EURUSD,H1) 15| 578 578020012 sRequest2 (EURUSD,H1) 16| 840 840010007 sRequest2 (EURUSD,H1) 17| 826 826010037 sRequest2 (EURUSD,H1) 18| 756 756040001 sRequest2 (EURUSD,H1) 19| 410 410010011 sRequest2 (EURUSD,H1) 20| 724 724010005 sRequest2 (EURUSD,H1) 21| 752 752010019
Увидим, что искомый показатель существует в 21 стране. Показатель не используется в Индии и как глобальный общемировой ("Worldwide").
Теперь нужно получить выборку значений показателя за 3-ий квартал и связать её с первой выборкой по id события.
SQL-запрос имеет такой вид:
SELECT evs.COUNTRY_ID AS country_id, evals.EVENT_ID AS event_id, evals.VALUE_ID AS value_id, evals.PERIOD AS period, evals.TIME AS time, evals.ACTUAL AS actual FROM EVENT_VALUES evals JOIN ( SELECT COUNTRY_ID, EVENT_ID FROM EVENTS WHERE (NAME LIKE 'GDP q/q' AND SECTOR = 'Gross Domestic Product') ) AS evs ON evals.event_id = evs.EVENT_ID WHERE (period = '2022.07.01 00:00' )
Что касается MQL5-кода, то составной запрос реализован так:
//--- 2) 'GDP y/y' event and last values string subquery=db_obj.SqlRequest(); string new_sql_request1=::StringFormat("SELECT evs.COUNTRY_ID AS country_id," "evals.EVENT_ID AS event_id," "evals.VALUE_ID AS value_id," "evals.PERIOD AS period," "evals.TIME AS time," "evals.ACTUAL AS actual " "FROM EVENT_VALUES evals " "JOIN(%s) AS evs ON evals.event_id = evs.event_id " " WHERE (period = \'2022.07.01 00:00\')", subquery); if(!db_obj.Select(new_sql_request1)) { db_obj.Close(); return; } ::Print("\n'GDP y/y' event and last values:\n"); //--- print the SQL request if(db_obj.PrintSqlRequest()<0) ::PrintFormat("Failed to print the SQL request, error %d", ::GetLastError()); db_obj.FinalizeSqlRequest();
И после выполнения в журнале появятся такие строки:
sRequest2 (EURUSD,H1) 'GDP q/q' event and last values: sRequest2 (EURUSD,H1) sRequest2 (EURUSD,H1) #| country_id event_id value_id period time actual sRequest2 (EURUSD,H1) --+----------------------------------------------------------------------- sRequest2 (EURUSD,H1) 1| 554 554010024 168293 2022.07.01 00:00 2022.12.14 23:45 2.0 sRequest2 (EURUSD,H1) 2| 999 999030016 158836 2022.07.01 00:00 2022.10.31 12:00 0.2 sRequest2 (EURUSD,H1) 3| 999 999030016 158837 2022.07.01 00:00 2022.11.15 12:00 0.2 sRequest2 (EURUSD,H1) 4| 999 999030016 158838 2022.07.01 00:00 2022.12.07 12:00 0.3 sRequest2 (EURUSD,H1) 5| 392 392010001 165181 2022.07.01 00:00 2022.11.15 01:50 -0.3 sRequest2 (EURUSD,H1) 6| 392 392010001 165182 2022.07.01 00:00 2022.12.08 01:50 -0.2 sRequest2 (EURUSD,H1) 7| 124 124010022 161963 2022.07.01 00:00 2022.11.29 15:30 0.7 sRequest2 (EURUSD,H1) 8| 36 36010019 173679 2022.07.01 00:00 2022.12.07 02:30 0.6 sRequest2 (EURUSD,H1) 9| 156 156010004 172459 2022.07.01 00:00 2022.10.24 04:00 3.9 sRequest2 (EURUSD,H1) 10| 380 380010020 162296 2022.07.01 00:00 2022.10.31 11:00 0.5 sRequest2 (EURUSD,H1) 11| 380 380010020 162297 2022.07.01 00:00 2022.11.30 11:00 0.5 sRequest2 (EURUSD,H1) 12| 702 702010004 167581 2022.07.01 00:00 2022.10.14 02:00 1.5 sRequest2 (EURUSD,H1) 13| 702 702010004 174527 2022.07.01 00:00 2022.11.23 02:00 1.1 sRequest2 (EURUSD,H1) 14| 276 276010008 172410 2022.07.01 00:00 2022.10.28 10:00 0.3 sRequest2 (EURUSD,H1) 15| 276 276010008 157759 2022.07.01 00:00 2022.11.25 09:00 0.4 sRequest2 (EURUSD,H1) 16| 250 250010005 169062 2022.07.01 00:00 2022.10.28 07:30 0.2 sRequest2 (EURUSD,H1) 17| 250 250010005 169389 2022.07.01 00:00 2022.11.30 09:45 0.2 sRequest2 (EURUSD,H1) 18| 76 76010010 173825 2022.07.01 00:00 2022.12.01 14:00 0.4 sRequest2 (EURUSD,H1) 19| 484 484020016 166108 2022.07.01 00:00 2022.10.31 14:00 1.0 sRequest2 (EURUSD,H1) 20| 484 484020016 166109 2022.07.01 00:00 2022.11.25 14:00 0.9 sRequest2 (EURUSD,H1) 21| 710 710060009 175234 2022.07.01 00:00 2022.12.06 11:30 1.6 sRequest2 (EURUSD,H1) 22| 344 344020002 155337 2022.07.01 00:00 2022.10.31 10:30 -2.6 sRequest2 (EURUSD,H1) 23| 344 344020002 155338 2022.07.01 00:00 2022.11.11 10:30 -2.6 sRequest2 (EURUSD,H1) 24| 578 578020012 172320 2022.07.01 00:00 2022.11.18 09:00 1.5 sRequest2 (EURUSD,H1) 25| 840 840010007 163417 2022.07.01 00:00 2022.10.27 14:30 2.6 sRequest2 (EURUSD,H1) 26| 840 840010007 163418 2022.07.01 00:00 2022.11.30 15:30 2.9 sRequest2 (EURUSD,H1) 27| 840 840010007 163419 2022.07.01 00:00 2022.12.22 15:30 3.2 sRequest2 (EURUSD,H1) 28| 826 826010037 157174 2022.07.01 00:00 2022.11.11 09:00 -0.2 sRequest2 (EURUSD,H1) 29| 826 826010037 157175 2022.07.01 00:00 2022.12.22 09:00 -0.3 sRequest2 (EURUSD,H1) 30| 756 756040001 159276 2022.07.01 00:00 2022.11.29 10:00 0.2 sRequest2 (EURUSD,H1) 31| 410 410010011 161626 2022.07.01 00:00 2022.10.27 01:00 0.3 sRequest2 (EURUSD,H1) 32| 410 410010011 161627 2022.07.01 00:00 2022.12.01 01:00 0.3 sRequest2 (EURUSD,H1) 33| 724 724010005 159814 2022.07.01 00:00 2022.10.28 09:00 0.2 sRequest2 (EURUSD,H1) 34| 724 724010005 159815 2022.07.01 00:00 2022.12.23 10:00 0.1 sRequest2 (EURUSD,H1) 35| 752 752010019 170359 2022.07.01 00:00 2022.10.28 08:00 0.7 sRequest2 (EURUSD,H1) 36| 752 752010019 171381 2022.07.01 00:00 2022.11.29 09:00 0.6
Нетрудно заметить, что в выборке по некоторым событиям с одним и тем же event_id есть несколько значений. Например, записи 2-4 относятся к показателю в Евросоюзе. Просто оценка ВВП производилась в нескольких чтениях, поэтому и значений показателя несколько. В итоге в результирующей выборке есть 36 записей, что явно больше количества стран, для которых рассчитывается данный показатель.
Если нужно сделать выборку, получив только последние значения для данного события, то в запрос необходимо внести возможность группировки и фильтр для результатов групп. Тогда получим такой составной SQL-запрос:
SELECT evs.COUNTRY_ID AS country_id, evals.EVENT_ID AS event_id, evals.VALUE_ID AS value_id, evals.PERIOD AS period, evals.TIME AS time, evals.ACTUAL AS actual FROM EVENT_VALUES evals JOIN ( SELECT COUNTRY_ID, EVENT_ID FROM EVENTS WHERE (NAME LIKE 'GDP q/q' AND SECTOR = 'Gross Domestic Product') ) AS evs ON evals.event_id = evs.EVENT_ID WHERE (period = '2022.07.01 00:00' ) GROUP BY evals.event_id HAVING MAX(value_id)
Группировать записи будем по столбцу (полю) "event_id". А в качестве основной записи, если будет несколько, возьмём ту, у которой максимальное значение по столбцу (полю) "value_id". Таким образом, например из трёх записей для Евросоюза будет выбрана только одна:
country_id | event_id | value_id | period | time | actual |
---|---|---|---|---|---|
999 | 999030016 | 158838 | 2022.07.01 00:00 | 2022.12.07 12:00 | 0.3 |
В итоге в журнале появятся такие записи:
sRequest2 (EURUSD,H1) 'GDP q/q' event and grouped last values: sRequest2 (EURUSD,H1) sRequest2 (EURUSD,H1) #| country_id event_id value_id period time actual sRequest2 (EURUSD,H1) --+----------------------------------------------------------------------- sRequest2 (EURUSD,H1) 1| 36 36010019 173679 2022.07.01 00:00 2022.12.07 02:30 0.6 sRequest2 (EURUSD,H1) 2| 76 76010010 173825 2022.07.01 00:00 2022.12.01 14:00 0.4 sRequest2 (EURUSD,H1) 3| 124 124010022 161963 2022.07.01 00:00 2022.11.29 15:30 0.7 sRequest2 (EURUSD,H1) 4| 156 156010004 172459 2022.07.01 00:00 2022.10.24 04:00 3.9 sRequest2 (EURUSD,H1) 5| 250 250010005 169389 2022.07.01 00:00 2022.11.30 09:45 0.2 sRequest2 (EURUSD,H1) 6| 276 276010008 172410 2022.07.01 00:00 2022.10.28 10:00 0.3 sRequest2 (EURUSD,H1) 7| 344 344020002 155338 2022.07.01 00:00 2022.11.11 10:30 -2.6 sRequest2 (EURUSD,H1) 8| 380 380010020 162297 2022.07.01 00:00 2022.11.30 11:00 0.5 sRequest2 (EURUSD,H1) 9| 392 392010001 165182 2022.07.01 00:00 2022.12.08 01:50 -0.2 sRequest2 (EURUSD,H1) 10| 410 410010011 161627 2022.07.01 00:00 2022.12.01 01:00 0.3 sRequest2 (EURUSD,H1) 11| 484 484020016 166109 2022.07.01 00:00 2022.11.25 14:00 0.9 sRequest2 (EURUSD,H1) 12| 554 554010024 168293 2022.07.01 00:00 2022.12.14 23:45 2.0 sRequest2 (EURUSD,H1) 13| 578 578020012 172320 2022.07.01 00:00 2022.11.18 09:00 1.5 sRequest2 (EURUSD,H1) 14| 702 702010004 174527 2022.07.01 00:00 2022.11.23 02:00 1.1 sRequest2 (EURUSD,H1) 15| 710 710060009 175234 2022.07.01 00:00 2022.12.06 11:30 1.6 sRequest2 (EURUSD,H1) 16| 724 724010005 159815 2022.07.01 00:00 2022.12.23 10:00 0.1 sRequest2 (EURUSD,H1) 17| 752 752010019 171381 2022.07.01 00:00 2022.11.29 09:00 0.6 sRequest2 (EURUSD,H1) 18| 756 756040001 159276 2022.07.01 00:00 2022.11.29 10:00 0.2 sRequest2 (EURUSD,H1) 19| 826 826010037 157175 2022.07.01 00:00 2022.12.22 09:00 -0.3 sRequest2 (EURUSD,H1) 20| 840 840010007 163419 2022.07.01 00:00 2022.12.22 15:30 3.2 sRequest2 (EURUSD,H1) 21| 999 999030016 158838 2022.07.01 00:00 2022.12.07 12:00 0.3
Теперь в выборке 21 запись. И последний штрих — это заменить код страны на её название. Изменим предыдущий SLQ-запрос на следующий:
SELECT c.NAME AS country, ev_evals.event_id AS event_id, ev_evals.value_id AS value_id, ev_evals.period AS period, ev_evals.TIME AS time, ev_evals.ACTUAL AS actual FROM COUNTRIES c JOIN ( SELECT evs.COUNTRY_ID AS country_id, evals.EVENT_ID AS event_id, evals.VALUE_ID AS value_id, evals.PERIOD AS period, evals.TIME AS time, evals.ACTUAL AS actual FROM EVENT_VALUES evals JOIN ( SELECT COUNTRY_ID, EVENT_ID FROM EVENTS WHERE (NAME LIKE 'GDP q/q' AND SECTOR = 'Gross Domestic Product') ) AS evs ON evals.event_id = evs.EVENT_ID WHERE (period = '2022.07.01 00:00') GROUP BY evals.event_id HAVING MAX(value_id) ) AS ev_evals ON c.COUNTRY_ID = ev_evals.country_id
Попутно реализуем этот составной запрос на MQL5:
//--- 4) 'GDP q/q' event and grouped last values with country names subquery=db_obj.SqlRequest(); string new_sql_request3=::StringFormat("SELECT c.NAME AS country," "ev_evals.event_id AS event_id," "ev_evals.value_id AS value_id," "ev_evals.period AS period," "ev_evals.TIME AS time," "ev_evals.ACTUAL AS actual " "FROM COUNTRIES c JOIN (%s) " "AS ev_evals ON c.COUNTRY_ID = ev_evals.country_id", subquery); if(!db_obj.Select(new_sql_request3)) { db_obj.Close(); return; } ::Print("\n'GDP q/q' event and grouped last values with country names:\n"); //--- print the SQL request if(db_obj.PrintSqlRequest()<0) ::PrintFormat("Failed to print the SQL request, error %d", ::GetLastError()); db_obj.FinalizeSqlRequest();
Искомая выборка будет распечатана в журнале:
sRequest2 (EURUSD,H1) 'GDP q/q' event and grouped last values with country names: sRequest2 (EURUSD,H1) sRequest2 (EURUSD,H1) #| country event_id value_id period time actual sRequest2 (EURUSD,H1) --+--------------------------------------------------------------------------- sRequest2 (EURUSD,H1) 1| Australia 36010019 173679 2022.07.01 00:00 2022.12.07 02:30 0.6 sRequest2 (EURUSD,H1) 2| Brazil 76010010 173825 2022.07.01 00:00 2022.12.01 14:00 0.4 sRequest2 (EURUSD,H1) 3| Canada 124010022 161963 2022.07.01 00:00 2022.11.29 15:30 0.7 sRequest2 (EURUSD,H1) 4| China 156010004 172459 2022.07.01 00:00 2022.10.24 04:00 3.9 sRequest2 (EURUSD,H1) 5| France 250010005 169389 2022.07.01 00:00 2022.11.30 09:45 0.2 sRequest2 (EURUSD,H1) 6| Germany 276010008 172410 2022.07.01 00:00 2022.10.28 10:00 0.3 sRequest2 (EURUSD,H1) 7| Hong Kong 344020002 155338 2022.07.01 00:00 2022.11.11 10:30 -2.6 sRequest2 (EURUSD,H1) 8| Italy 380010020 162297 2022.07.01 00:00 2022.11.30 11:00 0.5 sRequest2 (EURUSD,H1) 9| Japan 392010001 165182 2022.07.01 00:00 2022.12.08 01:50 -0.2 sRequest2 (EURUSD,H1) 10| South Korea 410010011 161627 2022.07.01 00:00 2022.12.01 01:00 0.3 sRequest2 (EURUSD,H1) 11| Mexico 484020016 166109 2022.07.01 00:00 2022.11.25 14:00 0.9 sRequest2 (EURUSD,H1) 12| New Zealand 554010024 168293 2022.07.01 00:00 2022.12.14 23:45 2.0 sRequest2 (EURUSD,H1) 13| Norway 578020012 172320 2022.07.01 00:00 2022.11.18 09:00 1.5 sRequest2 (EURUSD,H1) 14| Singapore 702010004 174527 2022.07.01 00:00 2022.11.23 02:00 1.1 sRequest2 (EURUSD,H1) 15| South Africa 710060009 175234 2022.07.01 00:00 2022.12.06 11:30 1.6 sRequest2 (EURUSD,H1) 16| Spain 724010005 159815 2022.07.01 00:00 2022.12.23 10:00 0.1 sRequest2 (EURUSD,H1) 17| Sweden 752010019 171381 2022.07.01 00:00 2022.11.29 09:00 0.6 sRequest2 (EURUSD,H1) 18| Switzerland 756040001 159276 2022.07.01 00:00 2022.11.29 10:00 0.2 sRequest2 (EURUSD,H1) 19| United Kingdom 826010037 157175 2022.07.01 00:00 2022.12.22 09:00 -0.3 sRequest2 (EURUSD,H1) 20| United States 840010007 163419 2022.07.01 00:00 2022.12.22 15:30 3.2 sRequest2 (EURUSD,H1) 21| European Union 999030016 158838 2022.07.01 00:00 2022.12.07 12:00 0.3
Хотелось бы отметить, что задача пусть и была решена за несколько подходов, но возможность включения одного запроса в состав другого существенно её облегчила.
Заключение
Надеюсь, что статья вызовет интерес у тех трейдеров и разработчиков, кто использует макроэкономические данные при создании своих стратегий. Ещё осмелюсь предположить, что скорее всего нет таких макропоказателей, по которым можно построить хорошую стратегию. Но, например, как добавление к исходным данным нейросетей эти показатели могут быть полезными.
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования