Nguyên tắc hoạt động với cơ sở dữ liệu trong MQL5
Cơ sở dữ liệu lưu trữ thông tin dưới dạng bảng. Việc lấy, sửa đổi và thêm dữ liệu mới vào chúng được thực hiện bằng các truy vấn trong ngôn ngữ SQL. Chúng ta sẽ mô tả chi tiết đặc điểm của nó trong các phần sau. Trong lúc này, hãy sử dụng script DatabaseRead.mq5
, không liên quan gì đến giao dịch, và xem cách tạo một cơ sở dữ liệu đơn giản cũng như lấy thông tin từ nó. Tất cả các hàm được đề cập ở đây sẽ được mô tả chi tiết sau. Bây giờ điều quan trọng là hình dung các nguyên tắc chung.
Việc tạo và đóng cơ sở dữ liệu bằng các hàm tích hợp sẵn DatabaseOpen/DatabaseClose
tương tự như làm việc với tệp vì chúng ta cũng tạo một bộ mô tả cho cơ sở dữ liệu, kiểm tra nó và đóng nó vào cuối.
void OnStart()
{
string filename = "company.sqlite";
// tạo hoặc mở một cơ sở dữ liệu
int db = DatabaseOpen(filename, DATABASE_OPEN_READWRITE | DATABASE_OPEN_CREATE);
if(db == INVALID_HANDLE)
{
Print("DB: ", filename, " open failed with code ", _LastError);
return;
}
... // tiếp tục làm việc với cơ sở dữ liệu
// đóng cơ sở dữ liệu
DatabaseClose(db);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
Sau khi mở cơ sở dữ liệu, chúng ta sẽ đảm bảo rằng không có bảng nào trong đó có tên mà chúng ta cần. Nếu bảng đã tồn tại, khi cố gắng chèn cùng dữ liệu vào nó như trong ví dụ của chúng ta, sẽ xảy ra lỗi, vì vậy chúng ta sử dụng hàm DatabaseTableExists
.
Việc xóa và tạo bảng được thực hiện bằng các truy vấn được gửi đến cơ sở dữ liệu với hai lần gọi hàm DatabaseExecute
và đi kèm với việc kiểm tra lỗi.
...
// nếu bảng COMPANY tồn tại, thì xóa nó
if(DatabaseTableExists(db, "COMPANY"))
{
if(!DatabaseExecute(db, "DROP TABLE COMPANY"))
{
Print("Failed to drop table COMPANY with code ", _LastError);
DatabaseClose(db);
return;
}
}
// tạo bảng COMPANY
if(!DatabaseExecute(db, "CREATE TABLE COMPANY("
"ID INT PRIMARY KEY NOT NULL,"
"NAME TEXT NOT NULL,"
"AGE INT NOT NULL,"
"ADDRESS CHAR(50),"
"SALARY REAL );"))
{
Print("DB: ", filename, " create table failed with code ", _LastError);
DatabaseClose(db);
return;
}
...
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Hãy giải thích bản chất của các truy vấn SQL. Trong bảng COMPANY, chúng ta chỉ có 5 trường: ID bản ghi, tên, tuổi, địa chỉ và lương. Ở đây trường ID là khóa, tức là một chỉ mục duy nhất. Các chỉ mục cho phép mỗi bản ghi được định danh duy nhất và có thể được sử dụng trên các bảng để liên kết chúng với nhau. Điều này tương tự như cách ID vị thế liên kết tất cả các giao dịch và lệnh thuộc về một vị thế cụ thể.
Bây giờ cần điền dữ liệu vào bảng, điều này được thực hiện bằng truy vấn "INSERT":
// chèn dữ liệu vào bảng
if(!DatabaseExecute(db,
"INSERT INTO COMPANY (ID,NAME,AGE,ADDRESS,SALARY) VALUES (1,'Paul',32,'California',25000.00); "
"INSERT INTO COMPANY (ID,NAME,AGE,ADDRESS,SALARY) VALUES (2,'Allen',25,'Texas',15000.00); "
"INSERT INTO COMPANY (ID,NAME,AGE,ADDRESS,SALARY) VALUES (3,'Teddy',23,'Norway',20000.00);"
"INSERT INTO COMPANY (ID,NAME,AGE,ADDRESS,SALARY) VALUES (4,'Mark',25,'Rich-Mond',65000.00);"))
{
Print("DB: ", filename, " insert failed with code ", _LastError);
DatabaseClose(db);
return;
}
...
2
3
4
5
6
7
8
9
10
11
12
Ở đây, 4 bản ghi được thêm vào bảng COMPANY, mỗi bản ghi có danh sách các trường và các giá trị sẽ được ghi vào những trường này được chỉ định. Các bản ghi được chèn bằng các truy vấn "INSERT..." riêng biệt, được kết hợp thành một dòng, thông qua ký tự phân cách đặc biệt ';', nhưng chúng ta cũng có thể chèn từng bản ghi vào bảng bằng một lệnh gọi DatabaseExecute
riêng biệt.
Vì vào cuối script, cơ sở dữ liệu sẽ được lưu vào tệp "company.sqlite", lần chạy tiếp theo, chúng ta sẽ cố gắng ghi cùng dữ liệu vào bảng COMPANY với cùng ID. Điều này sẽ dẫn đến lỗi, đó là lý do tại sao chúng ta đã xóa bảng trước đó để bắt đầu lại từ đầu mỗi khi script được chạy.
Bây giờ chúng ta lấy tất cả các bản ghi từ bảng COMPANY với trường SALARY > 15000
. Điều này được thực hiện bằng hàm DatabasePrepare
, hàm này "biên dịch" văn bản yêu cầu và trả về bộ xử lý của nó để sử dụng sau này trong các hàm DatabaseRead
hoặc DatabaseReadBind
.
// chuẩn bị một yêu cầu với bộ mô tả
int request = DatabasePrepare(db, "SELECT * FROM COMPANY WHERE SALARY>15000");
if(request == INVALID_HANDLE)
{
Print("DB: ", filename, " request failed with code ", _LastError);
DatabaseClose(db);
return;
}
...
2
3
4
5
6
7
8
9
Sau khi yêu cầu được tạo thành công, chúng ta cần lấy kết quả của việc thực thi nó. Điều này có thể được thực hiện bằng hàm DatabaseRead
, hàm này trong lần gọi đầu tiên sẽ thực thi truy vấn và chuyển đến bản ghi đầu tiên trong kết quả. Ở mỗi lần gọi tiếp theo, nó sẽ đọc bản ghi tiếp theo cho đến khi đến cuối. Trong trường hợp này, nó sẽ trả về false
, nghĩa là "không còn bản ghi nào nữa".
// in tất cả các bản ghi với lương trên 15000
int id, age;
string name, address;
double salary;
Print("Persons with salary > 15000:");
for(int i = 0; DatabaseRead(request); i++)
{
// đọc giá trị của mỗi trường từ bản ghi nhận được bằng số thứ tự của nó
if(DatabaseColumnInteger(request, 0, id) && DatabaseColumnText(request, 1, name) &&
DatabaseColumnInteger(request, 2, age) && DatabaseColumnText(request, 3, address) &&
DatabaseColumnDouble(request, 4, salary))
Print(i, ": ", id, " ", name, " ", age, " ", address, " ", salary);
else
{
Print(i, ": DatabaseRead() failed with code ", _LastError);
DatabaseFinalize(request);
DatabaseClose(db);
return;
}
}
// xóa bộ xử lý sau khi sử dụng
DatabaseFinalize(request);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Kết quả thực thi sẽ là:
Persons with salary > 15000:
0: 1 Paul 32 California 25000.0
1: 3 Teddy 23 Norway 20000.0
2: 4 Mark 25 Rich-Mond 65000.0
2
3
4
Hàm DatabaseRead
cho phép bạn duyệt qua tất cả các bản ghi từ kết quả truy vấn và sau đó lấy thông tin đầy đủ về mỗi cột trong bảng kết quả thông qua các hàm DatabaseColumn
. Các hàm này được thiết kế để hoạt động phổ quát với kết quả của bất kỳ truy vấn nào nhưng cái giá phải trả là mã dư thừa.
Nếu cấu trúc của kết quả truy vấn được biết trước, tốt hơn nên sử dụng hàm DatabaseReadBind
, hàm này cho phép đọc toàn bộ bản ghi cùng một lúc vào một cấu trúc. Chúng ta có thể chỉnh sửa ví dụ trước theo cách này và trình bày nó dưới tên mới DatabaseReadBind.mq5
. Trước tiên, hãy khai báo cấu trúc Person
:
struct Person
{
int id;
string name;
int age;
string address;
double salary;
};
2
3
4
5
6
7
8
Sau đó, chúng ta sẽ trừ từng bản ghi từ kết quả truy vấn bằng DatabaseReadBind(request, person)
trong một vòng lặp miễn là hàm trả về true
:
Person person;
Print("Persons with salary > 15000:");
for(int i = 0; DatabaseReadBind(request, person); i++)
Print(i, ": ", person.id, " ", person.name, " ", person.age,
" ", person.address, " ", person.salary);
DatabaseFinalize(request);
2
3
4
5
6
Do đó, chúng ta ngay lập tức nhận được giá trị của tất cả các trường từ bản ghi hiện tại và không cần đọc chúng riêng lẻ.
Ví dụ giới thiệu này được lấy từ bài viết SQLite: làm việc tự nhiên với cơ sở dữ liệu SQL trong MQL5, nơi ngoài ví dụ này, còn xem xét một số tùy chọn ứng dụng cơ sở dữ liệu cho các nhà giao dịch. Cụ thể, bạn có thể tìm thấy ở đó việc khôi phục lịch sử vị thế từ các giao dịch, phân tích báo cáo giao dịch theo các chiến lược, biểu tượng hoạt động, hoặc giờ giao dịch ưa thích nhất, cũng như các kỹ thuật làm việc với kết quả tối ưu hóa.
Một số kiến thức cơ bản về SQL có thể cần thiết để nắm vững tài liệu này, vì vậy chúng ta sẽ đề cập ngắn gọn trong các phần sau.