Донецкий техникум промышленной автоматики

SQL і MQL5: Працюємо з базою даних SQLite

  1. вступ
  2. 1. Принципи SQLite
  3. 2. Робота з SQLite3 API
  4. 3. Складання 64-бітової версії sqlite3_64.dll
  5. Висновок

Small. Fast. Reliable.
Choose any of three.

вступ

Багато розробники замислювалися над необхідністю використовувати в своїх проектах бази даних для зберігання інформації, але кожен раз зупинялися, знаючи, якими додатковими витратами часу супроводжується установка SQL-сервера. Якщо для програмістів виконати це не так складно (або СУБД вже встановлена ​​для інших цілей), то для звичайного користувача дана операція викличе багато питань, або взагалі відіб'є бажання встановлювати софт.

В такому випадку програміст просто не зв'язується з СУБД, розуміючи, що крім нього цим рішенням скористаються одиниці. В кінцевому підсумку в хід йде робота з файлами (часто не одним при розрізненості даних): використання CSV, рідше XML або JSON, або запис бінарних даних з жорстким розміром структури і т.п.

Але, як виявляється, є відмінна альтернатива SQL-серверу! При цьому не потрібно встановлювати додатковий софт, вся робота йде локально у вашому проекті, але при цьому ви використовуєте всю міць мови SQL. Ім'я цієї альтернативи - SQLite.

Мета даної статті - забезпечити швидкий старт у використанні SQLite. Тому я не буду заглиблюватися в тонкощі і всілякі набори параметрів або прапорів функцій, а створю легку обгортку-коннектор, яка виконує SQL команди, і на прикладах покажу її застосування.

Необхідні умови для роботи зі статтею:

  • Гарний настрій ;)
  • Розпакуйте архів з додатку до статті в папку робочого терміналу MetaTrader 5
  • Встановіть будь-яку зручну для вас утиліту перегляду SQLite баз (наприклад, SQLiteStudio )
  • Додайте в закладки офіційну документацію по SQLite http://www.sqlite.org

зміст

1. Принципи SQLite
2. Робота з SQLite API
2.1. Відкриття і закриття бази даних
2.2. Виконання SQL запитів
2.3. Отримання даних з таблиць
2.4. Запис параметричних даних зі зв'язуванням
2.5. Транзакції / Multirow insert (приклад створення таблиці угод торгового рахунку)
3. Складання 64-бітної DLL версії SQLite3

1. Принципи SQLite

SQLite - це СУБД, головною особливістю якої є відсутність "слухача" SQL-сервера. Сервером вже є ваше додаток. Робота з базою даних в SQLite - це фактично робота з файлом (на диску або в пам'яті). Всі дані ви можете архівувати або ж переносити на інший комп'ютер без будь-якої потреби спеціально їх встановлювати.

Використання SQLite дає розробнику і користувачеві кілька незаперечних переваг:

  • не встановлювати додатковий софт;
  • дані зберігаються в локальному файлі, що створює прозорість їх контролю, тобто їх можна переглядати і редагувати незалежно від вашої програми;
  • можливість імпорту або експорту таблиць в інші СУБД;
  • в коді використовуються звичні SQL запити, що дозволяє в будь-який момент перевести додаток на роботу з іншими СУБД.

Працювати з SQLite можливо трьома способами:

  1. скористатися DLL файлом з повним набором API функцій;
  2. використовувати shell команди до EXE файлу;
  3. скомпілювати ваш проект, включивши в нього вихідні коди SQLite API.

У цій статті я розгляну застосування першого варіанту, як більш звичного в MQL5.

2. Робота з SQLite3 API

В роботі коннектора будуть використані наступні SQLite функції :

sqlite3_open sqlite3_prepare sqlite3_step sqlite3_finalize sqlite3_close sqlite3_exec sqlite3_errcode sqlite3_extended_errcode sqlite3_errmsg sqlite3_bind_null sqlite3_bind_int sqlite3_bind_int64 sqlite3_bind_double sqlite3_bind_text sqlite3_bind_blob sqlite3_column_count sqlite3_column_name sqlite3_column_type sqlite3_column_bytes sqlite3_column_int sqlite3_column_int64 sqlite3_column_double sqlite3_column_text sqlite3_column_blob

Також нам знадобляться низькорівневі функції msvcrt.dll для роботи з покажчиками:

strlen strcpy memcpy

Так як я створюю коннектор, який працює в 32 і 64-бітних терміналах, то необхідно подбати про розмір покажчика, що відправляється в функції API. Розділимо їх імена:

#define PTR32 int #define sqlite3_stmt_p32 PTR32 #define sqlite3_p32 PTR32 #define PTRPTR32 PTR32 #define PTR64 long #define sqlite3_stmt_p64 PTR64 #define sqlite3_p64 PTR64 #define PTRPTR64 PTR64

Все API функції перевантажимо для 32 і 64-бітових покажчиків при необхідності. Звертаю увагу, що всі покажчики в коннекторе будуть 64-бітного розміру. Їх переклад в 32-бітний розмір буде відбуватися безпосередньо в перевантажених API функції. Вихідний код імпорту функцій API знаходиться в файлі SQLite3Import.mqh

Типи даних SQLite

У третій версії SQLite передбачається п'ять типів даних

Тип
опис
NULL Відсутність даних. INTEGER Ціле розміром 1, 2, 3, 4, 6 або 8 байт в залежності від величини значення, що зберігається. REAL Речовий розміром 8 байт. TEXT Рядок з кінцевим символом \ 0 в кодуванні UTF-8 або UTF-16. BLOB Довільні бінарні дані

Для вказівки типів полів при створенні таблиці в SQL запиті можна скористатися також і іншими іменами типів, наприклад, BIGINT або INT, які прийняті в різних СУБД. В цьому випадку SQLite переведе їх в один зі своїх внутрішніх типів, в даному випадку - в INTEGER. Більш докладно про типи даних і їх зв'язку ви можете прочитати в документації http://www.sqlite.org/datatype3.html

2.1. Відкриття і закриття бази даних

Як ви вже знаєте, база даних в SQLite3 - це звичайний файл. Відкриття бази даних фактично є відкриття файлу і отримання його хендла.

Відкриття відбувається за допомогою функції sqlite3_open:

int sqlite3_open (const uchar & filename [], sqlite3_p64 & ppDb); filename [in] - шлях до файлу, або ім'я файлу, якщо файл відкривається в поточному місці розташування. ppDb [out] - змінна, куди буде записати адресу хендла файлу бази. Повертає SQLITE_OK в разі успіху або код помилки.

Закривається файл бази функцією sqlite3_close:

int sqlite3_close (sqlite3_p64 ppDb); ppDb [in] - хендл файлу Повертає SQLITE_OK в разі успіху або код помилки.

Створимо в коннекторе функції відкриття і закриття БД.

class CSQLite3Base {sqlite3_p64 m_db; bool m_bopened; string m_dbfile; public: CSQLite3Base (); virtual ~ CSQLite3Base (); public: bool IsConnected (); int Connect (string dbfile); void Disconnect (); int Reconnect (); }; CSQLite3Base :: CSQLite3Base () {m_db = NULL; m_bopened = false; } CSQLite3Base :: ~ CSQLite3Base () {Disconnect (); } Bool CSQLite3Base :: IsConnected () {return (m_bopened && m_db); } Int CSQLite3Base :: Connect (string dbfile) {if (IsConnected ()) return (SQLITE_OK); m_dbfile = dbfile; return (Reconnect ()); } Void CSQLite3Base :: Disconnect () {if (IsConnected ()) :: sqlite3_close (m_db); m_db = NULL; m_bopened = false; } Int CSQLite3Base :: Reconnect () {Disconnect (); uchar file []; StringToCharArray (m_dbfile, file); int res = :: sqlite3_open (file, m_db); m_bopened = (res == SQLITE_OK && m_db); return (res); }

Відкривати і закривати базу даних коннектор вже вміє. Давайте перевіримо роботу простим скриптом:

#include <MQH \ Lib \ SQLite3 \ SQLite3Base.mqh> CSQLite3Base sql3; void OnStart () {if (sql3.Connect ( "SQLite3Test.db3")! = SQLITE_OK) return; sql3.Disconnect (); }

Запустіть скрипт в дебаг-режимі, зробіть глибокий вдих і перевірте роботу кожного рядка. В результаті роботи з'явиться файл бази даних в папці установки терміналу MetaTrader 5. Радійте, що все пройшло вдало і переходите до наступного розділу.

2.2. Виконання SQL запитів

Будь-SQL запит в SQLite3 проходить мінімум три етапи:

  1. sqlite3_prepare - верифікація та отримання списку інструкцій (statement);
  2. sqlite3_step - виконання цих інструкцій;
  3. sqlite3_finalize - завершення і очищення пам'яті.

Ця схема, в основному, підходить для створення або видалення таблиць, або для запису небінарних даних, тобто для випадків, коли запит SQL не має на увазі повернення будь-яких даних, крім як статусу успішності його виконання.

Якщо ж запит на увазі отримання даних або запис бінарних даних, то на другому етапі нам треба буде скористатися функціями sqlite3_column_хх або sqlite3_bind_хх, відповідно. Ці функції докладно розглянемо в наступному розділі.

Напишемо метод CSQLite3Base :: Query для виконання простого SQL запиту:

int CSQLite3Base :: Query (string query) {if (! IsConnected ()) if (! Reconnect ()) return (SQLITE_ERROR); if (StringLen (query) <= 0) return (SQLITE_DONE); sqlite3_stmt_p64 stmt = 0; PTR64 pstmt = :: memcpy (stmt, stmt, 0); uchar str []; StringToCharArray (query, str); int res = :: sqlite3_prepare (m_db, str, - 1, pstmt, NULL); if (res! = SQLITE_OK) return (res); res = :: sqlite3_step (pstmt); :: sqlite3_finalize (pstmt); return (res); }

Як бачите функції sqlite3_prepare , sqlite3_step і sqlite3_finalize йдуть один за одним.

Розглянемо застосування CSQLite3Base :: Query при роботі з таблицями в SQLite:

sql3.Query ( "CREATE TABLE IF NOT EXISTS` TestQuery` ( `ticket` INTEGER,` open_price` DOUBLE, `comment` TEXT)");

Після виконання даної команди в базі даних з'явиться таблиця:

Після виконання даної команди в базі даних з'явиться таблиця:

sql3.Query ( "ALTER TABLE` TestQuery` RENAME TO `Trades`"); sql3.Query ( "ALTER TABLE` Trades` ADD COLUMN `profit`");

Після виконання цих команд маємо таблицю з новою назвою і доданим полем:

sql3.Query ( "INSERT INTO` Trades` VALUES (3, 1.212, 'info', 1) "); sql3.Query ( "UPDATE` Trades` SET `open_price` = 5.555,` comment` = 'New price' WHERE ( `ticket` = 3)")

В результаті додавання рядка і її зміни в таблиці з'явиться такий запис:

В результаті додавання рядка і її зміни в таблиці з'явиться такий запис:

І, нарешті, такі послідовно виконані команди очистять базу даних.

sql3.Query ( "DELETE FROM` Trades` ") sql3.Query (" DROP TABLE IF EXISTS `Trades`"); sql3.Query ( "VACUUM");

Перед розглядом наступного розділу нам знадобиться метод, який отримує опис помилки. З власного досвіду можу сказати, що код помилки скаже багато, але опис помилки покаже місце в тексті SQL-запиту, де помилка з'явилася, а це набагато прискорить її пошук і виправлення.

const PTR64 sqlite3_errmsg (sqlite3_p64 db); db [in] - хендл, отриманий функцією sqlite3_open Повертає покажчик на рядок з описом помилки.

У коннекторе додамо метод для отримання цього рядка з покажчика за допомогою strcpy і strlen.

string CSQLite3Base :: ErrorMsg () {PTR64 pstr = :: sqlite3_errmsg (m_db); int len ​​= :: strlen (pstr); uchar str []; ArrayResize (str, len + 1); :: strcpy (str, pstr); return (CharArrayToString (str)); }

2.3. Отримання даних з таблиць

Я вже згадував на початку розділу 2.2, читання даних виконується за допомогою функцій sqlite3_column_хх. Схематично це можна зобразити так:

  1. sqlite3_prepare
  2. sqlite3_column_count - дізнаємося число стовпців отриманої таблиці
  3. Поки результат кроку sqlite3_step == SQLITE_ROW
    1. sqlite3_column_хх - читаємо комірки цього рядка
  4. sqlite3_finalize

Так як ми підходимо до великого розділу з читання і запису даних, настав зручний момент описати три класи-контейнера, за допомогою яких буде відбуватися весь обмін. Необхідну модель даних нам диктує сам спосіб зберігання інформації в БД:
База даних
|
Таблиця - це масив рядів.
|
Ряд - це масив осередків.
|
Осередок - це байтовий буфер довільної довжини.

class CSQLite3Table {public: string m_colname []; CSQLite3Row m_data []; }; class CSQLite3Row {public: CSQLite3Cell m_data []; }; class CSQLite3Cell {public: enCellType type; CByteImg buf; };

Як бачите, зв'язку CSQLite3Row і CSQLite3Table примітивні - це звичайні масиви даних. Клас осередку CSQLite3Cell теж має масив даних uchar + поле Тип даних. Байтовий масив обслуговує клас CByteImage (за аналогією з відомим вам CFastFile ).

Для зручності роботи коннектора і контролю типів даних осередку я створив перерахування:

enum enCellType {CT_UNDEF, CT_NULL, CT_INT, CT_INT64, CT_DBL, CT_TEXT, CT_BLOB, CT_LAST};

Зверніть увагу, що до п'яти базових типів SQLite3 доданий тип CT_UNDEF для ідентифікації початкового стану осередку. Цілий тип INTEGER розділений на два CT_INT і CT_INT64 відповідно до аналогічно розділеними функціями sqlite3_bind_intXX, sqlite3_column_intXX.

Отримання даних

Для отримання даних з комірки створимо метод, узагальнюючий функції виду sqlite3_column_хх, який буде перевіряти тип даних, їх розмір і записувати їх в CSQLite3Cell.

bool CSQLite3Base :: ReadStatement (sqlite3_stmt_p64 stmt, int column, CSQLite3Cell & cell) {cell.Clear (); if (! stmt || column <0) return (false); int bytes = :: sqlite3_column_bytes (stmt, column); int type = :: sqlite3_column_type (stmt, column); if (type == SQLITE_NULL) cell.type = CT_NULL; else if (type == SQLITE_INTEGER) {if (bytes <5) cell.Set (:: sqlite3_column_int (stmt, column)); else cell.Set (:: sqlite3_column_int64 (stmt, column)); } Else if (type == SQLITE_FLOAT) cell.Set (:: sqlite3_column_double (stmt, column)); else if (type == SQLITE_TEXT || type == SQLITE_BLOB) {uchar dst []; ArrayResize (dst, bytes); PTR64 ptr = 0; if (type == SQLITE_TEXT) ptr = :: sqlite3_column_text (stmt, column); else ptr = :: sqlite3_column_blob (stmt, column); :: memcpy (dst, ptr, bytes); if (type == SQLITE_TEXT) cell.Set (CharArrayToString (dst)); else cell.Set (dst); } Return (true); }

Функція вийшла досить об'ємна, але, по суті, вона лише читає дані з поточної інструкції та зберігає їх в осередок.

Також перевантажимо функцію CSQLite3Base :: Query, додавши першим параметром контейнер-таблицю CSQLite3Table для отриманих даних.

int CSQLite3Base :: Query (CSQLite3Table & tbl, string query) {tbl.Clear (); if (! IsConnected ()) if (! Reconnect ()) return (SQLITE_ERROR); if (StringLen (query) <= 0) return (SQLITE_DONE); sqlite3_stmt_p64 stmt = NULL; PTR64 pstmt = :: memcpy (stmt, stmt, 0); uchar str []; StringToCharArray (query, str); int res = :: sqlite3_prepare (m_db, str, - 1, pstmt, NULL); if (res! = SQLITE_OK) return (res); int cols = :: sqlite3_column_count (pstmt); bool b = true; while (:: sqlite3_step (pstmt) == SQLITE_ROW) {CSQLite3Row row; for (int i = 0; i <cols; i ++) {CSQLite3Cell cell; if (ReadStatement (pstmt, i, cell)) row.Add (cell); else {b = false; break; }} Tbl.Add (row); if (! b) break; } For (int i = 0; i <cols; i ++) {PTR64 pstr = :: sqlite3_column_name (pstmt, i); if (! pstr) {tbl.ColumnName (i, ""); continue; } Int len ​​= :: strlen (pstr); ArrayResize (str, len + 1); :: strcpy (str, pstr); tbl.ColumnName (i, CharArrayToString (str)); } :: sqlite3_finalize (stmt); return (b? SQLITE_DONE: res); }

Всі необхідні функції для отримання даних є. Переходимо до їх прикладів:

CSQLite3Table tbl; sql3.Query (tbl, "SELECT * FROM` Trades` ")

Виведемо результат даного запиту на друк в термінал командою Print (TablePrint (tbl)). У журналі побачимо наступне (порядок від низу до верху):

sql3.Query (tbl, "SELECT COUNT (*) FROM` Trades` WHERE ( `profit`> 0)") sql3.Query (tbl, "SELECT MAX (` ticket`) FROM `Trades`") sql3.Query ( tbl, "SELECT SUM (` profit`) AS `sumprof`, AVG (` profit`) AS `avgprof` FROM` Trades` ") sql3.Query (tbl," SELECT `name` FROM` sqlite_master` WHERE `type` = 'table' ORDER BY `name`;");

Результат даного запиту виведемо аналогічно командою Print (TablePrint (tbl)). Побачимо наявну таблицю:

Побачимо наявну таблицю:

Як видно з прикладів, результати виконання запитів поміщаються в змінну tbl, з якої, потім нескладно отримати і обробити їх на ваш розсуд.

2.4. Запис параметричних даних зі зв'язуванням

Ще одна тема, яку варто обов'язково розглянути новачкові, це запис інформації в базу даних, яка має "незручний" формат. Звичайно ж, мова йде про бінарні дані, які "в лоб" не передати в звичайній текстової інструкції INSERT або UPDATE, тому що при першому ж нулі рядок зважить закінченою. Або, наприклад, бувають випадки, коли в самому рядку присутні спецсимволи одинарної лапки '.

У деяких ситуаціях пізніше зв'язування зручно, коли маємо широку таблицю, і записувати все поля в один рядок важко і ненадійно, так як є ймовірність щось прогледіти або втратити. Для даної операції зв'язування необхідні функції сімейства sqlite3_bind_хх .

Щоб застосувати зв'язування, необхідно замість переданих даних поставити шаблон. Я розгляну один з варіантів - це знак "?" . Тобто UPDATE-запит буде виглядати так:

UPDATE `Trades` SET` open_price` =?, `Comment` =? WHERE ( `ticket` = 3)


Потім, потрібно виконати поспіль дві функції: sqlite3_bind_ double і sqlite3_bind_ text, щоб помістити дані в open_price і comment. Загалом, схему роботи з bind- функціями можна представити у вигляді:

  1. sqlite3_prepare
  2. Один по одному викликаємо sqlite3_bind_хх і записуємо необхідні дані в інструкцію
  3. sqlite3_step
  4. sqlite3_finalize

За кількістю типів sqlite3_bind_xx повністю повторюють розглянуті функції читання, тому їх легко об'єднати в коннекторе в CSQLite3Base :: BindStatement:

bool CSQLite3Base :: BindStatement (sqlite3_stmt_p64 stmt, int column, CSQLite3Cell & cell) {if (! stmt || column <0) return (false); int bytes = cell.buf.Len (); enCellType type = cell.type; if (type == CT_INT) return (:: sqlite3_bind_int (stmt, column + 1, cell.buf.ViewInt ()) == SQLITE_OK); else if (type == CT_INT64) return (:: sqlite3_bind_int64 (stmt, column + 1, cell.buf.ViewInt64 ()) == SQLITE_OK); else if (type == CT_DBL) return (:: sqlite3_bind_double (stmt, column + 1, cell.buf.ViewDouble ()) == SQLITE_OK); else if (type == CT_TEXT) return (:: sqlite3_bind_text (stmt, column + 1, cell.buf.m_data, cell.buf.Len (), SQLITE_STATIC) == SQLITE_OK); else if (type == CT_BLOB) return (:: sqlite3_bind_blob (stmt, column + 1, cell.buf.m_data, cell.buf.Len (), SQLITE_STATIC) == SQLITE_OK); else if (type == CT_NULL) return (:: sqlite3_bind_null (stmt, column + 1) == SQLITE_OK); else return (:: sqlite3_bind_null (stmt, column + 1) == SQLITE_OK); }

Єдине завдання цього методу - записати буфер переданої осередки в інструкцію.

Аналогічним чином додамо метод CQLite3Table :: QueryBind, першим аргументом якого є рядок даних на запис:

int CSQLite3Base :: QueryBind (CSQLite3Row & row, string query) {if (! IsConnected ()) if (! Reconnect ()) return (SQLITE_ERROR); if (StringLen (query) <= 0 || ArraySize (row.m_data) <= 0) return (SQLITE_DONE); sqlite3_stmt_p64 stmt = NULL; PTR64 pstmt = :: memcpy (stmt, stmt, 0); uchar str []; StringToCharArray (query, str); int res = :: sqlite3_prepare (m_db, str, - 1, pstmt, NULL); if (res! = SQLITE_OK) return (res); bool b = true; for (int i = 0; i <ArraySize (row.m_data); i ++) {if (! BindStatement (pstmt, i, row.m_data [i])) {b = false; break; }} If (b) res = :: sqlite3_step (pstmt); :: sqlite3_finalize (pstmt); return (b? res: SQLITE_ERROR); }

Його завдання - записати рядок у відповідні параметри.

2.5. Транзакції / Multirow inserts

Перед розглядом цієї теми вам знадобиться знання ще однієї функції SQLite API. У минулому розділі я розглянув трьохетапну обробку запитів prepare + step + finalize. Альтернативним і в деяких випадках простим, а в деяких - навіть необхідним рішенням є функція sqlite3_exec :

int sqlite3_exec (sqlite3_p64 ppDb, const char & sql [], PTR64 callback, PTR64 pvoid, PTRPTR64 errmsg); ppDb [in] - хендл бази даних sql [in] - SQL запит Три інших параметра поки що не розглядаємо стосовно MQL5. Повертає SQLITE_OK при успішному виконанні або код помилки.

Її суть - виконати запит за один виклик без створення трьохетапну конструкцій.

Додамо її виклик в коннектор:

int CSQLite3Base :: Exec (string query) {if (! IsConnected ()) if (! Reconnect ()) return (SQLITE_ERROR); if (StringLen (query) <= 0) return (SQLITE_DONE); uchar str []; StringToCharArray (query, str); int res = :: sqlite3_exec (m_db, str, NULL, NULL, NULL); return (res); }

Використовувати отриманий метод просто. Наприклад, команду видалення таблиці (DROP TABLE) або стиснення бази (VACUUM) можна виконати так:

sql3.Exec ( "DROP TABLE` Trades` "); sql3.Exec ( "VACUUM");

транзакції

Тепер уявімо ситуацію, коли необхідно додати кілька тисяч рядків в таблицю. Якщо занести все це в цикл:

for (int i = 0; i <N; i ++) sql3.Query ( "INSERT INTO` Table` VALUES (1, 2, 'text') ");

то отримаємо про про ч ч е н ь м е е е д л е н н о в и виконання. На вставку тисячі рядків витратиться більше 10 (!) Секунд, бо так в SQLite робити не рекомендується. Виходом з даної ситуації є використання транзакцій : Все SQL-інструкції заносяться в один загальний список і потім проводяться одним запитом.

Для запису початку і закінчення транзакції використовуються SQL-оператори:

BEGIN ... COMMIT

Все що всередині - атомарному виконається на останньому операторі COMMIT. Для ситуації, коли треба перервати цикл, або з якоїсь причини не виконувати вже додані інструкції служить оператор ROLLBACK.

Як приклад зробимо додавання будь-яких угод рахунки в таблицю.

#include <MQH \ Lib \ SQLite3 \ SQLite3Base.mqh> void OnStart () {CSQLite3Base sql3; if (sql3.Connect ( "Deals.db3")! = SQLITE_OK) return; if (sql3.Query ( "CREATE TABLE IF NOT EXISTS` Deals` (` ticket` INTEGER PRIMARY KEY, `open_price` DOUBLE,` profit` DOUBLE, `comment` TEXT)")! = SQLITE_DONE) {Print (sql3.ErrorMsg ()); return; } If (sql3. Exec ( "BEGIN")! = SQLITE_OK) {Print (sql3.ErrorMsg ()); return; } HistorySelect (0, TimeCurrent ()); for (int i = 0; i <HistoryDealsTotal (); i ++) {CSQLite3Row row; long ticket = (long) HistoryDealGetTicket (i); row.Add (ticket); row.Add (HistoryDealGetDouble (ticket, DEAL_PRICE)); row.Add (HistoryDealGetDouble (ticket, DEAL_PROFIT)); row.Add (HistoryDealGetString (ticket, DEAL_COMMENT)); if (sql3.QueryBind (row, "REPLACE INTO` Deals` VALUES (" + row.BindStr () + ")")! = SQLITE_DONE) {sql3. Exec ( "ROLLBACK"); Print (sql3.ErrorMsg ()); return; }} If (sql3. Exec ( "COMMIT")! = SQLITE_OK) return; CSQLite3Table tbl; CSQLite3Cell cell; if (sql3.Query (tbl, "SELECT COUNT (*) FROM` Deals` WHERE (` profit`> 0) ")! = SQLITE_DONE) {Print (sql3.ErrorMsg ()); return; } Tbl.Cell (0, 0, cell); Print ( "Count (*) =", cell.GetInt64 ()); if (sql3.Query (tbl, "SELECT SUM (` profit`) AS `sumprof`, AVG (` profit`) AS `avgprof` FROM` Deals`")! = SQLITE_DONE) {Print (sql3.ErrorMsg ()) ; return; } Tbl.Cell (0, 0, cell); Print ( "SUM (` profit`) = ", cell.GetDouble ()); tbl.Cell (0, 1, cell); Print ( "AVG (` profit`) = ", cell.GetDouble ()); }

Виконавши даний скрипт на своєму рахунку, моментально записав угоди свого рахунку в таблицю.

Виконавши даний скрипт на своєму рахунку, моментально записав угоди свого рахунку в таблицю

У журналі терміналу вивелася статистична інформація

У журналі терміналу вивелася статистична інформація

Експериментуйте з даними скриптом: закоментуйте рядки з BEGIN, ROLLBACK і COMMIT, і якщо у вас на рахунку більше сотні угод, то ви відразу ж відчуєте різницю. До речі, по деяким тестів , Транзакції SQLite працюють швидше, ніж в MySQL або PostgreSQL.

3. Складання 64-бітової версії sqlite3_64.dll

  1. вікачуємо вихідний код SQLite (amalgamation) і витягуємо з нього файл sqlite3.c.
  2. викачуємо DLL sqlite-dll-win32 і розпаковуємо з неї файл sqlite3.dll.
  3. У папці, де розпакували dll файл, виконуємо в консолі команду LIB.EXE /DEF:sqlite3.def. Переконайтеся що шляхи до файлу lib.exe прописані в системній змінній PATH, або знайдіть його у вас в Visual Studio.
  4. Створюємо проект DLL, вибравши конфігурацію Release для 64 бітних платформ.
  5. Додаємо в проект скачаний файл sqlite3.c і отриманий файл sqlite3.def. Якщо на деякі функції з def файлу компілятор буде "лаятися", в цьому випадку просто закоментуйте їх.
  6. В налаштуваннях проекту встановимо наступні параметри:
    C / C ++ -> General -> Debug Information Format = Program Database (/ Zi)
    C / C ++ -> Precompiled Headers -> Create / Use Precompiled Header = Not Using Precompiled Headers (/ Yu)
  7. Компілюємо і отримуємо 64-бітну dll.

Висновок

Сподіваюся, що стаття стане вашою настільною інструкцією в справі освоєння SQLite. Цілком можливо, що ви будете використовувати її в своїх майбутніх проектах. Цей короткий огляд дав вам деяке уявлення про функціональність SQLite, як про ідеальний і надійному рішенні для додатків.

У статті ми розглянули всі приклади, з якими ви зіткнетеся при обробці торгових даних. В якості домашнього завдання - створіть простий збирач тиків, із записом тиків в таблицю для кожного символу. Вихідний код бібліотеки класів і тестові скрипти ви знайдете в додатку до статті.

Успіху і профітів!

B?
Comment` =?
B?