SQLite3で配列を扱う:BLOB型 vs. JSON型

2024-04-02

C++で配列をSQLite3の1つの列に格納する方法

方法1:BLOB型として格納する

  1. 配列をバイナリデータに変換します。
  2. 変換したバイナリデータをSQLite3のBLOB型として格納します。
  3. データを取り出すときは、BLOB型データを配列に変換します。

例:

#include <sqlite3.h>

// 配列をバイナリデータに変換する関数
std::vector<uint8_t> to_blob(const int* array, int size) {
  std::vector<uint8_t> blob(size * sizeof(int));
  memcpy(blob.data(), array, size * sizeof(int));
  return blob;
}

// バイナリデータを配列に変換する関数
std::vector<int> from_blob(const uint8_t* blob, int size) {
  std::vector<int> array(size / sizeof(int));
  memcpy(array.data(), blob, size);
  return array;
}

int main() {
  // データベースを開く
  sqlite3* db;
  sqlite3_open("database.db", &db);

  // テーブルを作成する
  sqlite3_exec(db, "CREATE TABLE IF NOT EXISTS data (array BLOB)");

  // 配列を準備する
  int array[] = {1, 2, 3, 4, 5};

  // 配列をBLOB型として格納する
  sqlite3_stmt* stmt;
  sqlite3_prepare(db, "INSERT INTO data (array) VALUES (?)", -1, &stmt, nullptr);
  sqlite3_bind_blob(stmt, 1, to_blob(array, sizeof(array) / sizeof(int)).data(),
                    to_blob(array, sizeof(array) / sizeof(int)).size(),
                    SQLITE_TRANSIENT);
  sqlite3_step(stmt);
  sqlite3_finalize(stmt);

  // データを取り出す
  stmt = sqlite3_prepare(db, "SELECT array FROM data", -1, &stmt, nullptr);
  sqlite3_step(stmt);
  const uint8_t* blob = sqlite3_column_blob(stmt, 0);
  int size = sqlite3_column_bytes(stmt, 0);
  std::vector<int> array2 = from_blob(blob, size);

  // データベースを閉じる
  sqlite3_close(db);

  return 0;
}
  1. 配列をJSONに変換します。
#include <sqlite3.h>
#include <nlohmann/json.hpp>

int main() {
  // データベースを開く
  sqlite3* db;
  sqlite3_open("database.db", &db);

  // テーブルを作成する
  sqlite3_exec(db, "CREATE TABLE IF NOT EXISTS data (array TEXT)");

  // 配列を準備する
  int array[] = {1, 2, 3, 4, 5};

  // 配列をJSONに変換する
  nlohmann::json json = array;

  // JSONをTEXT型として格納する
  sqlite3_stmt* stmt;
  sqlite3_prepare(db, "INSERT INTO data (array) VALUES (?)", -1, &stmt, nullptr);
  sqlite3_bind_text(stmt, 1, json.dump().c_str(), -1, SQLITE_TRANSIENT);
  sqlite3_step(stmt);
  sqlite3_finalize(stmt);

  // データを取り出す
  stmt = sqlite3_prepare(db, "SELECT array FROM data", -1, &stmt, nullptr);
  sqlite3_step(stmt);
  const char* json_str = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 0));
  nlohmann::json json2 = nlohmann::json::parse(json_str);

  // データベースを閉じる
  sqlite3_close(db);

  return 0;
}

**どちら




方法1:BLOB型として格納する

#include <sqlite3.h>

// 配列をバイナリデータに変換する関数
std::vector<uint8_t> to_blob(const int* array, int size) {
  std::vector<uint8_t> blob(size * sizeof(int));
  memcpy(blob.data(), array, size * sizeof(int));
  return blob;
}

// バイナリデータを配列に変換する関数
std::vector<int> from_blob(const uint8_t* blob, int size) {
  std::vector<int> array(size / sizeof(int));
  memcpy(array.data(), blob, size);
  return array;
}

int main() {
  // データベースを開く
  sqlite3* db;
  sqlite3_open("database.db", &db);

  // テーブルを作成する
  sqlite3_exec(db, "CREATE TABLE IF NOT EXISTS data (array BLOB)");

  // 配列を準備する
  int array[] = {1, 2, 3, 4, 5};

  // 配列をBLOB型として格納する
  sqlite3_stmt* stmt;
  sqlite3_prepare(db, "INSERT INTO data (array) VALUES (?)", -1, &stmt, nullptr);
  sqlite3_bind_blob(stmt, 1, to_blob(array, sizeof(array) / sizeof(int)).data(),
                    to_blob(array, sizeof(array) / sizeof(int)).size(),
                    SQLITE_TRANSIENT);
  sqlite3_step(stmt);
  sqlite3_finalize(stmt);

  // データを取り出す
  stmt = sqlite3_prepare(db, "SELECT array FROM data", -1, &stmt, nullptr);
  sqlite3_step(stmt);
  const uint8_t* blob = sqlite3_column_blob(stmt, 0);
  int size = sqlite3_column_bytes(stmt, 0);
  std::vector<int> array2 = from_blob(blob, size);

  // データベースを閉じる
  sqlite3_close(db);

  return 0;
}

方法2:JSON型として格納する

#include <sqlite3.h>
#include <nlohmann/json.hpp>

int main() {
  // データベースを開く
  sqlite3* db;
  sqlite3_open("database.db", &db);

  // テーブルを作成する
  sqlite3_exec(db, "CREATE TABLE IF NOT EXISTS data (array TEXT)");

  // 配列を準備する
  int array[] = {1, 2, 3, 4, 5};

  // 配列をJSONに変換する
  nlohmann::json json = array;

  // JSONをTEXT型として格納する
  sqlite3_stmt* stmt;
  sqlite3_prepare(db, "INSERT INTO data (array) VALUES (?)", -1, &stmt, nullptr);
  sqlite3_bind_text(stmt, 1, json.dump().c_str(), -1, SQLITE_TRANSIENT);
  sqlite3_step(stmt);
  sqlite3_finalize(stmt);

  // データを取り出す
  stmt = sqlite3_prepare(db, "SELECT array FROM data", -1, &stmt, nullptr);
  sqlite3_step(stmt);
  const char* json_str = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 0));
  nlohmann::json json2 = nlohmann::json::parse(json_str);

  // データベースを閉じる
  sqlite3_close(db);

  return 0;
}

ポイント

  • 方法1は、バイナリデータとして直接格納するため、比較的処理速度が速い。
  • 方法2は、JSONとして格納するため、人間が読みやすく、編集しやすい。
  • どちらの方法を使うかは、データの性質や用途によって決める。
  • サンプルコードでは、int型の配列を格納していますが、他の型にも同様に適用できます。
  • 配列だけでなく、その他のデータ型も同様に格納できます。



C++で配列をSQLite3の1つの列に格納する方法

方法3:シリアライズライブラリを使う

Boost.Serializationやcerealなど、C++用のシリアライズライブラリを使って、配列をバイナリデータにシリアライズし、SQLite3に格納することができます。

#include <sqlite3.h>
#include <boost/serialization/vector.hpp>

int main() {
  // データベースを開く
  sqlite3* db;
  sqlite3_open("database.db", &db);

  // テーブルを作成する
  sqlite3_exec(db, "CREATE TABLE IF NOT EXISTS data (array BLOB)");

  // 配列を準備する
  std::vector<int> array = {1, 2, 3, 4, 5};

  // 配列をシリアライズする
  std::stringstream ss;
  boost::archive::binary_oarchive oa(ss);
  oa << array;

  // シリアライズ結果をBLOB型として格納する
  sqlite3_stmt* stmt;
  sqlite3_prepare(db, "INSERT INTO data (array) VALUES (?)", -1, &stmt, nullptr);
  sqlite3_bind_blob(stmt, 1, ss.str().data(), ss.str().size(), SQLITE_TRANSIENT);
  sqlite3_step(stmt);
  sqlite3_finalize(stmt);

  // データを取り出す
  stmt = sqlite3_prepare(db, "SELECT array FROM data", -1, &stmt, nullptr);
  sqlite3_step(stmt);
  const uint8_t* blob = sqlite3_column_blob(stmt, 0);
  int size = sqlite3_column_bytes(stmt, 0);

  // シリアライズ結果から配列をデシリアライズする
  std::stringstream ss2(std::string(reinterpret_cast<const char*>(blob), size));
  boost::archive::binary_iarchive ia(ss2);
  std::vector<int> array2;
  ia >> array2;

  // データベースを閉じる
  sqlite3_close(db);

  return 0;
}

方法4:カスタムデータ型を使う

SQLite3には、独自のデータ型を定義する機能があります。この機能を使って、配列を格納するためのカスタムデータ型を定義することができます。

#include <sqlite3.h>

// カスタムデータ型の登録
static int register_array_type(sqlite3* db) {
  sqlite3_stmt* stmt;
  sqlite3_prepare(db, "CREATE TABLE IF NOT EXISTS data (array ARRAY)", -1, &stmt, nullptr);
  sqlite3_step(stmt);
  sqlite3_finalize(stmt);

  sqlite3_create_function(db, "array_size", 1, SQLITE_INTEGER, nullptr, [](sqlite3_context* ctx, int argc, sqlite3_value** argv) {
    // 配列のサイズを取得する
    int size = sqlite3_array_size(argv[0]);
    sqlite3_result_int(ctx, size);
  }, nullptr);

  sqlite3_create_function(db, "array_get", 2, SQLITE_INTEGER, nullptr, [](sqlite3_context* ctx, int argc, sqlite3_value** argv) {
    // 配列の要素を取得する
    int index = sqlite3_value_int(argv[1]);
    int value = sqlite3_array_get_int(argv[0], index);
    sqlite3_result_int(ctx, value);
  }, nullptr);

  return 0;
}

int main() {
  // データベースを開く
  sqlite3* db;
  sqlite3_open("database.db", &db);

  // カスタムデータ型の登録
  register_array_type(db);

  // 配列を準備する
  int array[] = {1, 2, 3, 4, 5};

  // 配列を格納する
  sqlite3_stmt* stmt;
  sqlite3_prepare(db, "INSERT INTO data (array) VALUES (?)", -1, &stmt, nullptr);
  sqlite3_bind_array(stmt, 1, array, sizeof(array) / sizeof(int));
  sqlite3_step

c++ sqlite


SQLite3: アプリケーションコードでdatetime列にデフォルト値を設定する方法

例:この例では、usersテーブルにcreated_atという名前のdatetime列を作成し、デフォルト値を現在のタイムスタンプに設定しています。デフォルト値として使用できる値:文字列リテラル: '2024-03-29 09:03:00'...


1 行のクエリ結果をタブ区切りで出力

方法 1: .mode コマンドを使用するSQLite コマンドラインツールを開き、データベースファイルを指定します。以下のコマンドを実行して、出力モードを "line" に変更します。クエリを実行します。クエリ結果はタブ区切りで出力されます。...


プログラミング初心者でも安心!SQLiteのUPDATE文でLIMITとOFFSETを使ってレコードを更新する方法

LIMIT句は、更新するレコードの最大数を指定します。構文は以下の通りです。ここで、nは更新するレコードの最大数です。ここで、mは更新を開始するレコードのインデックスです。m番目のレコードは、LIMIT句で指定された数の最初のレコードとしてカウントされます。...


ASP.NET 5、Entity Framework Core 7、SQLite での「SQLite エラー 1: 'そのようなテーブルはありません: Blog'」に関するその他の情報

ASP. NET 5、Entity Framework 7、SQLite を使用しているアプリケーションで、「SQLite エラー 1: 'そのようなテーブルはありません: Blog'」というエラーが発生することがあります。これは、Blog テーブルが存在しない、またはアプリケーションがそのテーブルを見つけることができないことを示しています。...


RoomでNOT NULL制約を設定してデータベースの整合性を保つ

Android Room Persistence Libraryは、SQLiteデータベースを操作するためのライブラリです。Roomを使用すると、データベース操作をより簡単に記述することができます。このチュートリアルでは、Roomを使用して列をNOT NULLとして注釈する方法について説明します。...