Database 手册 · Chapter 5

INSERT · UPDATE · DELETE

依次了解 Data.xms 中处理变更类 SQL 的 4 个函数。

函数作用模式
DB_InsertSample新增订单 1 条 INSERT参数绑定
DB_UpdateSelected标记选中行为完成事务 + 参数绑定
DB_DeleteSelected删除选中行参数绑定
DB_InsertInitialSamples在空表中批量 INSERT 5 条事务(多行)

先整理两个核心要点。

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 不断累积。
  • 失败时 LogError + ShowMessage 双输出 — 日志(追踪用)+ 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()
{
   // (相同的两行守卫)
 
   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 时调用一次。将 5 条用一个事务包起来,同时获得一致性与速度。

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