《-- Pro*CからPythonへの移行で学んだこと③:SQL・SQLAlchemy・トランザクション編 --》
Introduction / はじめに
One of the biggest differences between Pro*C and Python is how database access is written.
Pro*CとPythonで大きく異なる点の一つが、データベースへのアクセス方法です。
In Pro*C, SQL can be embedded directly in the source code using EXEC SQL.
Pro*Cでは、EXEC SQLを使ってプログラム内にSQLを直接記述できます。
In Python, one common approach is to use SQLAlchemy.
Pythonでは、SQLAlchemyなどを利用してデータベースへアクセスする方法があります。
SQL in ProC(ProCでSQLを実行する)
A Pro*C query may look like this:
Pro*Cでは、例えば次のようにSQLを記述します。
EXEC SQL
SELECT item_code
INTO :item_code
FROM ITEM_MST
WHERE id = :id;
The selected value is stored directly in a C variable.
取得した値をC側の変数へ直接格納できます。
SQL in Python with SQLAlchemy(Python+SQLAlchemyでSQLを実行する)
In Python, the same idea can be written like this:
Pythonでは、例えば次のように記述できます。
from sqlalchemy import text
sql = """
SELECT item_code
FROM ITEM_MST
WHERE id = :id
"""
result = conn.execute(
text(sql),
{"id": target_id}
)
The SQL statement and its parameters are passed separately.
SQLとSQLへ渡す値を分けて指定しています。
What Is :id?(:idとは?)
The :id in the SQL statement is a bind parameter.
SQL内の:idはバインドパラメータです。
WHERE id = :id
The actual value can be passed from Python like this:
実際の値はPython側から次のように渡せます。
{"id": target_id}
Using bind parameters makes the SQL easier to manage and also helps prevent SQL injection.
バインドパラメータを利用することでSQLを管理しやすくなり、SQLインジェクション対策としても重要です。
fetchone() and fetchall()
To get one row, we can use fetchone().
1件取得する場合はfetchone()を使用できます。
row = result.fetchone()
To get all remaining rows, we can use fetchall().
残りのレコードをまとめて取得する場合はfetchall()を使用できます。
rows = result.fetchall()
One important point is that the result is consumed as it is read.
ここで注意したいのは、取得結果は読み込むたびに消費されていくという点です。
For example:
例えば、
row = result.fetchone()
rows = result.fetchall()
The first row has already been consumed by fetchone(), so fetchall() returns only the remaining rows.
最初の1件はすでにfetchone()で取得されているため、その後のfetchall()では残りのレコードが取得されます。
This behavior is similar to reading records from a cursor.
カーソルから順番にレコードを読み込んでいくイメージに近いです。
INSERT and COMMIT(INSERTとCOMMIT)
A simple INSERT may look like this:
例えば、INSERTは次のように実行できます。
sql = """
INSERT INTO STOCK (
year_month,
item_code
)
VALUES (
:year_month,
:item_code
)
"""
conn.execute(
text(sql),
{
"year_month": "202608",
"item_code": "ABC001"
}
)
conn.commit()
The important part here is commit().
ここで重要なのがcommit()です。
Executing an INSERT, UPDATE, or DELETE does not necessarily mean that the change has been permanently saved.
INSERT、UPDATE、DELETEを実行しただけでは、変更がまだ確定していない場合があります。
commit() finalizes the transaction.
commit()を実行することでトランザクションを確定します。
ROLLBACK on Error(エラー時のROLLBACK)
A common pattern is to roll back the transaction when an error occurs.
エラーが発生した場合には、トランザクションをロールバックする方法があります。
try:
conn.execute(text(sql), params)
conn.commit()
except Exception:
conn.rollback()
raise
rollback() cancels the uncommitted changes.
rollback()によって、まだ確定していない変更を取り消せます。
Unique Constraint Errors(UNIQUE制約エラー)
During testing, we may encounter a unique constraint violation.
テスト中に、UNIQUE制約違反のエラーが発生することがあります。
For example, suppose the following combination must be unique:
例えば、次の組み合わせが一意でなければならないとします。
year_month = 202608
item_code = ABC001
If the same key already exists and we try to insert it again, the database may return:
同じキーを持つデータがすでに存在する状態でもう一度INSERTすると、
unique constraint violated
may be returned.
というエラーが発生する場合があります。
This usually means that a record with the same primary key or unique key already exists.
これは基本的に、同じ主キーまたは一意キーを持つレコードがすでに存在していることを意味します。
During batch testing, it may also happen when data inserted by a previous test remains in the database.
単体テストでは、前回のテストでINSERTしたデータがDBに残っていることで発生する場合もあります。
What I Learned / 学んだこと
At first, Pro*C database code and Python database code looked very different.
最初は、Pro*CとPythonではDB処理の書き方が大きく違うように感じました。
However, when I broke the process down, the basic flow was similar.
しかし処理を分解してみると、基本的な流れは共通していました。
Build SQL / SQLを作る
↓
Pass parameters / パラメータを渡す
↓
Execute / 実行する
↓
Read results / 結果を取得する
↓
Commit or Rollback / 確定または取消
Key Point / ポイント
Understanding when data is read, committed, or rolled back makes database processing much easier to understand.
**「いつ取得されるのか」「いつ確定するのか」「いつ取り消されるのか」**を意識すると、DB処理の流れを理解しやすくなります。