- Principles of database operations in MQL5
- SQL Basics
- Structure of tables: data types and restrictions
- OOP (MQL5) and SQL integration: ORM concept
- Creating, opening, and closing databases
- Executing queries without MQL5 data binding
- Checking if a table exists in the database
- Preparing bound queries: DatabasePrepare
- Deleting and resetting prepared queries
- Binding data to query parameters: DatabaseBind/Array
- Executing prepared queries: DatabaseRead/Bind
- Reading fields separately: DatabaseColumn Functions
- Examples of CRUD operations in SQLite via ORM objects
- Transactions
- Import and export of database tables
- Printing tables and SQL queries to logs
- Example of searching for a trading strategy using SQLite
Principles of database operations in MQL5
Databases store information in the form of tables. Getting, modifying, and adding new data to them is done using queries in the SQL language. We will describe its specifics in the following sections. In the meantime, let's use the DatabaseRead.mq5 script, which has nothing to do with trading, and see how to create a simple database and get information from it. All functions mentioned here will be described in detail later. Now it is important to imagine the general principles.
Creating and closing a database using built-in DatabaseOpen/DatabaseClose functions are similar to working with files as we also create a descriptor for the database, check it, and close it at the end.
void OnStart()
|
After opening the database, we will make sure that there is no table in it under the name we need. If the table already exists, then when trying to insert the same data into it as in our example, an error will occur, so we use the DatabaseTableExists function.
Deleting and creating a table is done using queries that are sent to the database with two calls to the DatabaseExecute function and accompanied by error checking.
...
|
Let's explain the essence of SQL queries. In the COMPANY table, we have only 5 fields: record ID, name, age, address, and salary. Here the ID field is a key, that is, a unique index. Indexes allow each record to be uniquely identified and can be used across tables to link them together. This is similar to how the position ID links all trades and orders that belong to a particular position.
Now you need to fill the table with data, this is done using the "INSERT" query:
// insert data into table
|
Here, 4 records are added to the table COMPANY, for each record there is a list of fields, and values that will be written to these fields are indicated. Records are inserted by separate "INSERT..." queries, which are combined into one line, through a special delimiter character ';', but we could insert each record into the table with a separate DatabaseExecute call.
Since at the end of the script the database will be saved to the "company.sqlite" file, the next time it is run, we would try to write the same data to the COMPANY table with the same ID. This would lead to an error, which is why we previously deleted the table so that we would start from scratch every time the script was run.
Now we get all records from the COMPANY table with the field SALARY > 15000. This is done using the DatabasePrepare function, which "compiles" the request text and returns its handle for later use in the DatabaseRead or DatabaseReadBind functions.
// prepare a request with a descriptor
|
After the request has been successfully created, we need to get the results of its execution. This can be done using the DatabaseRead function, which on the first call will execute the query and jump to the first record in the results. On each subsequent call, it will read the next record until it reaches the end. In this case, it will return false, which means "there are no more records".
// printing all records with salary over 15000
|
The result of execution will be:
Persons with salary > 15000:
|
The DatabaseRead function allows you to go through all the records from the query result and then get complete information about each column in the resulting table via DatabaseColumn functions. These functions are designed to work universally with the results of any query but the cost is a redundant code.
If the structure of the query results is known in advance, it is better to use the DatabaseReadBind function, which allows you to read the entire record at once into a structure. We can remake the previous example in this way and present it under a new name DatabaseReadBind.mq5. Let's first declare the Person structure:
struct Person
|
Then we will subtract each record from the query results with DatabaseReadBind(request, person) in a loop as long as the function returns true:
Person person;
|
Thus, we immediately get the values of all fields from the current record and we do not need to read them separately.
This introductory example was taken from the article SQLite: native work with SQL databases in MQL5, where, in addition to it, several options for the application of the database for traders are considered. Specifically, you can find there restoring the history of positions from trades, analyzing a trading report in terms of strategies, working symbols, or the most preferred trading hours, as well as techniques for working with optimization results.
Some basic knowledge of SQL may be required to master this material, so we will cover it briefly in the following sections.