前回までの記事。

履歴画面表示処理
履歴テーブルのデータを、履歴画面に表示させます。
まずは、LogMain.cppのOnInitDialogに、表示関数を追加。
BOOL CLogMain::OnInitDialog()
{
CDialogEx::OnInitDialog();
// 略
SetLogList(); // リスト表示
return TRUE; // return TRUE unless you set the focus to a control
// 例外 : OCX プロパティ ページは必ず FALSE を返します。
}SetLogLost関数を呼応します。
続いて、その関数の処理内容。
やり方は、メイン画面の商品一覧を出すときと同様です。
whileで何回もSQL実行させます。
/**
* @fn
* @brief リストコントロールにデータをセットする
* @param なし
* @return なし
*/
void CLogMain::SetLogList()
{
// 何もないけど、一応全削除処理
m_ctrlListCtrlLog.DeleteAllItems();
CMySQLManager Manager;
Manager.GetConnection();
LVITEM lvItem{};
lvItem.mask = LVIF_TEXT;
int iRoop = 0;
while (TRUE)
{
iRoop++; // 行番号インクリメント
CString strLogID = _T(""); // ログ番号
CString strOperation = _T(""); // 操作内容
CString strProductID = _T(""); // 商品ID
CString strCategory = _T(""); // カテゴリー列
CString strQuantity = _T(""); // 増減量
CString strCurrentStock = _T(""); // 現段階在庫量
CString strDateTime = _T(""); // ログ時間
if (Manager.bGetLogData(strLogID, strOperation, strProductID, strCategory, strQuantity, strCurrentStock, strDateTime, iRoop, m_strProductID, m_strCateory, m_strTime) == TRUE)
{
lvItem.iItem = iRoop - 1; // リストコントロールの行番号
// ログ番号列
lvItem.iSubItem = 0;
lvItem.pszText = strLogID.GetBuffer();
m_ctrlListCtrlLog.InsertItem(&lvItem);
// 操作内容列
lvItem.iSubItem = 1;
lvItem.pszText = strOperation.GetBuffer();
m_ctrlListCtrlLog.SetItem(&lvItem);
// 商品ID列
lvItem.iSubItem = 2;
lvItem.pszText = strProductID.GetBuffer();
m_ctrlListCtrlLog.SetItem(&lvItem);
// カテゴリー列
lvItem.iSubItem = 3;
lvItem.pszText = strCategory.GetBuffer();
m_ctrlListCtrlLog.SetItem(&lvItem);
// 増減量列
lvItem.iSubItem = 4;
lvItem.pszText = strQuantity.GetBuffer();
m_ctrlListCtrlLog.SetItem(&lvItem);
// 現段階在庫量列
lvItem.iSubItem = 5;
lvItem.pszText = strCurrentStock.GetBuffer();
m_ctrlListCtrlLog.SetItem(&lvItem);
// ログ時間列
lvItem.iSubItem = 6;
lvItem.pszText = strDateTime.GetBuffer();
m_ctrlListCtrlLog.SetItem(&lvItem);
}
else
{
// この場合は、これ以上データをリストに追加できない
// SQLExceptionも含める
break;
}
}
}31行目の関数だけはまだ作成してないので、
今のところエラーになります。
引数後ろ3つは、条件です。
今回もWITH句を使いますが、
新しいテーブルを作る際に条件を付けます。
空白だったら、それは全件表示とみなす予定です。
履歴テーブルデータ取得処理
先の31行目の関数定義です。
MySQLManager.hを開き、定義します。
public:
// 履歴テーブルデータ取得
BOOL bGetLogData(CString& strLogID,
CString& strOperation,
CString& strProductID,
CString& strCategory,
CString& strQuantity,
CString& strCurrentStock,
CString& strDateTime,
int iRoop,
CString strConditionProductID,
CString strConditionCategory,
CString strConditionDateTime);続いて、SQL処理作成。
MySQLManager.cppを開きます。
なお、自分史上、過去一複雑なSQLとなりました…。
仕事でもこんなの書かんぞ。
/**
* @fn
* @brief 履歴テーブル一覧ゲット
* @param CString& strLogID 履歴ID
* @param CString& strOperation 操作内容
* @param CString& strProductID 商品ID
* @param CString& strCategory カテゴリー
* @param CString& strQuantity 増減量
* @param CString& strCurrentStock 操作時在庫量
* @param CString& strDateTime 操作日時
* @param int iRowNum 検索行
* @param CString strConditionProductID 商品ID条件
* @param CString strConditionCategory カテゴリー条件
* @param CString strConditionDateTime 操作日時条件
* @return BOOL TRUE 検索成功
* FALSE 検索失敗
*/
BOOL CMySQLManager::bGetLogData(CString& strLogID,
CString& strOperation,
CString& strProductID,
CString& strCategory,
CString& strQuantity,
CString& strCurrentStock,
CString& strDateTime,
int iRowNum,
CString strConditionProductID,
CString strConditionCategory,
CString strConditionDateTime)
{
try
{
// スキーマセット
m_con->setSchema(SCHEMA);
CString strRowNum = _T("");
strRowNum.Format(_T("%d"), iRowNum); // CString化
sql::SQLString strSqlRowNum = strConverter(strRowNum);
sql::SQLString sqlStrWithSelect =
"WITH NumberedRows AS ( SELECT " + TABLE_LOGS + ".*," + TABLE_PRODUCT + "." + COL_PRODUCT_CATEGORY + ", ROW_NUMBER() OVER ( ORDER BY " + COL_LOGS_ID + " ) AS row_num";
sql::SQLString sqlStrWithFrom = " FROM " + TABLE_LOGS;
sql::SQLString sqlStrWithJoin = " JOIN " + TABLE_PRODUCT + " ON " + TABLE_LOGS + "." + COL_LOGS_PRODUCT_ID + " = " + TABLE_PRODUCT + "." + COL_PRODUCT_ID;
sql::SQLString sqlStrWithWhere = ""; // WHERE句
// なにか一つでも検索条件が必要か?
if (strConditionProductID != _T("") || strConditionCategory != _T("") || strConditionDateTime != _T(""))
{
sqlStrWithWhere += " WHERE ";
if (strConditionProductID != _T(""))
{
// 商品IDに条件アリ
sqlStrWithWhere += (TABLE_LOGS + "." + COL_LOGS_PRODUCT_ID + " = '" + strConverter(strConditionProductID) + "'");
if (strConditionCategory != _T("") || strConditionDateTime != _T(""))
{
// まだ条件があるので、ANDが必要
sqlStrWithWhere += " AND ";
}
}
if (strConditionCategory != _T(""))
{
// カテゴリーに条件アリ
sqlStrWithWhere += (TABLE_PRODUCT + "." + COL_PRODUCT_CATEGORY + " = '" + strConverter(strConditionCategory) + "'");
if (strConditionDateTime != _T(""))
{
// まだ条件があるので、ANDが必要
sqlStrWithWhere += " AND ";
}
}
if (strConditionDateTime != _T(""))
{
//ログ時間に条件アリ
sqlStrWithWhere += TABLE_LOGS + "." + COL_LOGS_TIME_STAMP + " >= '" + strConverter(strConditionDateTime) + " 00:00:00' AND " + TABLE_LOGS + "." + COL_LOGS_TIME_STAMP + " <= '" + strConverter(strConditionDateTime) + " 23:59:59'";
}
}
sql::SQLString sqlStrWithPhase = sqlStrWithSelect + sqlStrWithFrom + sqlStrWithJoin + sqlStrWithWhere;
sql::SQLString sqlStrSelectPhase = " ) SELECT * FROM NumberedRows WHERE row_num = " + strSqlRowNum;
sql::SQLString strSqlQuery = sqlStrWithPhase + sqlStrSelectPhase;
//クエリ実行
std::unique_ptr<sql::PreparedStatement> pstmt(m_con->prepareStatement(strSqlQuery));
std::unique_ptr<sql::ResultSet> res(pstmt->executeQuery());
if (!res->next())
{
// ここに入れば、もう検索するものはない...
return FALSE;
}
else
{
// 該当行発見
// ログ番号
std::string strCol = res->getString(COL_LOGS_ID);
strLogID = strConvertFromUTF8ToUTF16(strCol);
// 操作内容
strCol = res->getString(COL_LOGS_OPERATION);
strOperation = strConvertFromUTF8ToUTF16(strCol);
// 商品ID
strCol = res->getString(COL_LOGS_PRODUCT_ID);
strProductID = strConvertFromUTF8ToUTF16(strCol);
// カテゴリー
strCol = res->getString(COL_PRODUCT_CATEGORY);
strCategory = strConvertFromUTF8ToUTF16(strCol);
// 増減量
strCol = res->getString(COL_LOGS_QUANTITY);
strQuantity = strConvertFromUTF8ToUTF16(strCol);
// 現段階在庫量
strCol = res->getString(COL_LOGS_CURRENT_STOCK);
strCurrentStock = strConvertFromUTF8ToUTF16(strCol);
// ログ時間
strCol = res->getString(COL_LOGS_TIME_STAMP);
strDateTime = strConvertFromUTF8ToUTF16(strCol);
}
}
catch (sql::SQLException& e)
{
ErrSQLException(e);
return FALSE;
}
return TRUE;
}88行目以降はいいでしょう。
いつも通りです。
問題(?)は、SQL。
変数を多用しているため、かなり複雑に見えてしまいます。
例えば、引数のstrConditionProductIDが22222、
strConditionCategoryがFood、
strConditionDateTimeが2025-01-01がわたってきたとします。
その場合、SQLはこのようになります。
WITH NumberedRows AS
( SELECT logs.*, product.category, ROW_NUMBER() OVER ( ORDER BY log_id ) AS row_num
FROM logs
JOIN product ON logs.product_id = product.product_id
WHERE logs.product_id = '22222'
AND product.category = 'Food'
AND logs.time_stamp >= '2025-01-01 00:00:00' AND logs.time_stamp <= '2025-01-01 23:59:59'
)
SELECT * FROM NumberedRows WHERE row_num = i -- iは引数の検索行
NumberedRowsの作りは、
logsテーブルの列全部、productテーブルのカテゴリー、logsテーブルの行番号です。
4行目のJOIN句はlogsとproductを引っ付けてるだけ。
5~7行目は、条件です。
作ったNumberedRowsテーブルから、順に取り出します。
正直、これ以上ここで解説する余力はありません…。
これを何も見ずにもう一回書けと言われても、
自分自身書けるのか…?
ビルドして動作確認です。
動作確認
ひとまず、元々のlogsテーブルはこのような感じ。

まずは履歴条件画面で、
なにも条件を入れず検索ボタンを押します。


ちゃんと取れてそうです。
条件を付けてみます。
まずは商品IDが22222。


ちゃんと22222だけ表示されました。
続いて、カテゴリーがFood。
何も条件を付けてないとログIDが7, 8, 10, 13, 14, 16ですけど果たして。


最後に時間指定。
ログ番号1だけ日付が違うので、2024/12/21で検索しましょう。


良き!良き!
あんまり重要視してませんでしたが、
入力チェックも働いてくれてるみたいです。


終わりに
以上を持って、在庫管理システムの作成を終了します。
簡易的な処理が多いシステムでした。
ここら辺になると、
ほとんど何も参考にせず、一人でシステムを作ることができるなぁって自画自賛です。
(今回のSQLは除く…)
今回のソースはGitに上げてみたのですが…。
上げ方あってるかわからんなぁ。
誰か面と向かって教えてほしい…
デスクトップアプリはオワコンと聞く時代なので、
このスキルが役に立つのか、正直わかりませんね。
少しずつですが、
別のスキルも身に着けてる最中です。
今回はここまで。
次は何作りましょうかね。
コメント