INSERT · UPDATE · DELETE
Data.xms 에서 변경 계열 SQL 을 다루는 함수 4 개를 차례대로 살펴봅니다.
| 함수 | 역할 | 패턴 |
|---|---|---|
DB_InsertSample | 신규 주문 1 건 INSERT | 파라미터 바인딩 |
DB_UpdateSelected | 선택 행 작업 완료 처리 | 트랜잭션 + 파라미터 바인딩 |
DB_DeleteSelected | 선택 행 삭제 | 파라미터 바인딩 |
DB_InsertInitialSamples | 빈 테이블에 5 건 일괄 INSERT | 트랜잭션 (다건) |
핵심 두 가지를 먼저 정리합니다.
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 는 7 장에서 다루는 DataGrid 의 선택 행 인덱스입니다.
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) 쪽이 남았습니다 — RunSqlSelect 와 GetRowArray, 단일 값 RunSqlScalarInt.