Database マニュアル · Chapter 5

INSERT · UPDATE · DELETE

Data.xms の更新系 SQL を扱う 4 関数を順に見ていきます。

関数役割パターン
DB_InsertSample新規オーダーを 1 件 INSERTパラメータバインディング
DB_UpdateSelected選択行を完了処理トランザクション + パラメータバインディング
DB_DeleteSelected選択行を削除パラメータバインディング
DB_InsertInitialSamples空テーブルへ 5 件まとめて INSERTトランザクション(複数行)

まず核となる 2 つを整理します。

1) パラメータバインディング — RunSqlQueryParam(sql, paramArray)

文字列連結で SQL を組み立てる代わりに、? プレースホルダに値を配列で渡します。SQL インジェクションを防ぎ、引用符のエスケープといった細かい問題も無くなります。

array p[] = {""};
p.Clear();
p.Add(value1);
p.Add(value2);
// ... ? の数と p の要素数は厳密に一致
 
string sql = "INSERT INTO ... VALUES(?, ?, ?, ?)";
DB["local"].RunSqlQueryParam(sql, p);

型は自動変換されます(int / double / string / bool)。

2) トランザクション — BeginTransaction / Commit / Rollback

複数の SQL をひとまとまりで実行する場合や、失敗時に変更をすべて巻き戻したい場合に使います。途中で失敗したら必ず Rollback — でないと次のトランザクションに影響します。

if( DB["local"].BeginTransaction() == false ) return false;
 
if( DB["local"].RunSqlQueryParam(sql, p) == false )
{
   DB["local"].Rollback();
   return false;
}
 
if( DB["local"].Commit() == false ) return false;

DB_InsertSample — 単一 INSERT

Add ボタンが呼ぶ関数。メニューは 7 項目をローテーションし、オーダー番号は OrderSeq カウンタで自動加算されます。

FUNCTION DB_InsertSample()
{
   if( DB["local"].IsOpen == false )
   {
      ShowMessage(EB_Ok, "DB is not open. Press [Open] first.");
      return false;
   }
 
   array menuPool[] = {"Americano", "Latte", "Cappuccino", "Espresso",
                      "Mocha", "Green Tea", "Lemonade"};
   OrderSeq = OrderSeq + 1;
   int menuIdx = OrderSeq % 7;
 
   array p[] = {""};
   p.Clear();
   p.Add($"O{OrderSeq}");          // order_no
   p.Add(menuPool[menuIdx]);       // menu_name
   p.Add(SYS.DateTimeString);      // start_time
   p.Add("");                      // end_time(作業中なので空)
   p.Add(0);                       // weight_g
   p.Add("Pending");               // result
   p.Add(0);                       // is_error
 
   string sql = "INSERT INTO order_history(order_no, menu_name, start_time, end_time, weight_g, result, is_error) VALUES(?,?,?,?,?,?,?)";
   if( DB["local"].RunSqlQueryParam(sql, p) == false )
   {
      LogError($"DB_InsertSample failed : {DB["local"].LastError}");
      ShowMessage(EB_Ok, $"DB Insert failed : {DB["local"].LastError}");
      return false;
   }
 
   Log($"DB_InsertSample : O{OrderSeq} {menuPool[menuIdx]} inserted");
 
   return DB_Refresh();
}

ポイント:

  • 必ず IsOpen でガード — 閉じた接続にコマンドを送ると LastError が積み上がるだけ。
  • 失敗時は LogErrorShowMessage 両方に出力 — ログ(追跡用)と UI(現場通知用)。
  • 最後に DB_Refresh() を呼んで DataGrid を再ロード(第 6 章)。

DB_UpdateSelected — トランザクション UPDATE

Update ボタンが呼ぶ。DataGrid の選択行の PK を読み、完了(Done)に変更します。

FUNCTION DB_UpdateSelected()
{
   if( DB["local"].IsOpen == false )
   {
      ShowMessage(EB_Ok, "DB is not open. Press [Open] first.");
      return false;
   }
 
   if( SelectIndex < 0 || SelectIndex >= DB["local"].RowCount )
   {
      ShowMessage(EB_Ok, "Select a row first.");
      return false;
   }
 
   // DataGrid 選択行から PK(id) を取得
   int targetId = DB["local"].GetValueInt(/*row*/SelectIndex, /*colName*/"id");
 
   if( DB["local"].BeginTransaction() == false )
   {
      LogError($"BeginTransaction failed : {DB["local"].LastError}");
      return false;
   }
 
   array p[] = {""};
   p.Clear();
   p.Add(SYS.DateTimeString);   // end_time
   p.Add(250);                  // weight_g(デモ用固定値)
   p.Add("Done");               // result
   p.Add(targetId);             // WHERE id = ?
 
   string sql = "UPDATE order_history SET end_time=?, weight_g=?, result=? WHERE id=?";
   if( DB["local"].RunSqlQueryParam(sql, p) == false )
   {
      DB["local"].Rollback();
      LogError($"DB_UpdateSelected failed : {DB["local"].LastError}");
      return false;
   }
 
   if( DB["local"].Commit() == false )
   {
      LogError($"Commit failed : {DB["local"].LastError}");
      return false;
   }
 
   Log($"DB_UpdateSelected : id={targetId} updated");
 
   return DB_Refresh();
}

SelectIndex は DataGrid の選択行インデックス(第 7 章)。GetValueInt(SelectIndex, "id") のパターンが核 — 行番号とカラム名でセルの値を読みます。

DB_DeleteSelected — シンプルな DELETE

FUNCTION DB_DeleteSelected()
{
   // (同じ 2 行のガード)
 
   int targetId = DB["local"].GetValueInt(/*row*/SelectIndex, /*colName*/"id");
 
   array p[] = {""};
   p.Clear();
   p.Add(targetId);
 
   string sql = "DELETE FROM order_history WHERE id=?";
   if( DB["local"].RunSqlQueryParam(sql, p) == false )
   {
      LogError($"DB_DeleteSelected failed : {DB["local"].LastError}");
      return false;
   }
 
   Log($"DB_DeleteSelected : id={targetId} deleted");
   SelectIndex = -1;
 
   return DB_Refresh();
}

SelectIndex = -1 に戻す理由 — 削除直後に同じインデックスを再利用すると別の行を指してしまうため。

DB_InsertInitialSamples — 複数行をトランザクションで

空テーブルの初回 Open 時に 1 度だけ呼ばれる。5 件を 1 つのトランザクションでくくる ことで、一貫性と速度を同時に確保するパターン。

if( DB["local"].BeginTransaction() == false ) return false;
 
string sql = "INSERT INTO order_history(...) VALUES(?,?,?,?,?,?,?)";
 
for( i, 0, 4 )
{
   array p[] = {""};
   p.Clear();
   p.Add(/* ... */);
 
   if( DB["local"].RunSqlQueryParam(sql, p) == false )
   {
      DB["local"].Rollback();
      return false;
   }
}
 
if( DB["local"].Commit() == false ) return false;

5 件だと差は小さく見えますが、数百〜数千件 の履歴データを一括投入する量産環境では、トランザクション vs 非トランザクションの速度差は非常に大きくなります。

次の章へ

読み取り(SELECT)が残っています — RunSqlSelectGetRowArray、単一値の RunSqlScalarInt