0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Clione-SQL の 2way SQL を Python で使う

0
Last updated at Posted at 2026-02-07

Clione-SQL を知っていますか?

Java で DB アクセスのコードを書いたことがある人なら、検索条件の動的組み立てで苦しんだ経験があると思います。MyBatis の <if> タグ地獄、JPA の Criteria API の可読性崩壊、あるいは StringBuilder で SQL を組み立てる原始的な戦い。

そんな中で Clione-SQL というライブラリが提案したアプローチは、かなり異質でした。

SELECT * FROM employee
WHERE
    dept_id = /* $dept_id */1
    AND name = /* $name */'山田'
    AND status = /* $status */'active'

これがそのまま SQL テンプレートです。SQL コメントの中にパラメータ名を書いて、直後にデフォルト値を置く。DB ツールでそのまま実行すればデフォルト値で動くし、アプリケーション経由ならパラメータがバインドされる。いわゆる 2way SQL です。

かゆいところに手が届く行削除の仕様

Clione-SQL の何がすごいかというと、動的な条件組み立てのルールがシンプルなのに表現力が高いところです。

$ 付きパラメータに None を渡すと、その行が丸ごと消える。たったこれだけのルールで、動的検索が実現できます。

# name を指定しない → name の行が消える
db.query(User, "search.sql", {"dept_id": 10, "name": None, "status": "active"})
-- name の行が消えて、先頭に残った AND も自動除去される
SELECT * FROM employee
WHERE
    dept_id = ?
    AND status = ?

さらに、インデントで親子関係が定義されるので、括弧でグルーピングした条件も自然に扱えます。

WHERE
    id = /* $id */1
    AND (
        status = /* $status1 */'active'
        OR status = /* $status2 */'pending'
    )

status1status2 が両方 None なら、括弧ブロックごと消える。子が全部消えたら親も消える。WHERE の下の条件が全部消えたら WHERE 自体も消える。if 文を一つも書かずに、です。

IN 句もリストを渡せば自動展開されるし、空リストなら IN (NULL) になって 0 件。このへんの「あー、そうそう、そこ面倒なんだよ」というポイントを的確に潰してくれる設計が Clione-SQL の魅力だと思っています。

それを Python でも使いたかった

普段 Python でちょっとしたツールやバッチ処理を書くことが増えて、そのたびに「Clione-SQL があればなあ」と思っていました。Python の DB アクセスは ORM(SQLAlchemy 等)か生 SQL かの二択で、この「SQL はそのまま書きたいけど条件組み立ては楽したい」という中間地帯をカバーするものがない。

ということで、Clione-SQL と Doma2 の設計を Python にポーティングしたのが sqlym です。

pip install sqlym

使い方

dataclass を定義して、SQL ファイルを書いて、Sqlym 経由で実行するだけです。

from dataclasses import dataclass
from sqlym import Sqlym

@dataclass
class User:
    id: int | None = None
    name: str = ""
    email: str = ""

conn = sqlite3.connect("example.db")
conn.row_factory = sqlite3.Row
db = Sqlym(conn, sql_dir="sql")

SQL ファイル — DB ツールでそのまま実行できる

-- sql/select_by_name.sql
SELECT id, name, email
FROM users
WHERE 1 = 1
    AND name = /* $name */'John'
ORDER BY id

Python から呼ぶ

# 条件あり
users = db.query(User, "select_by_name.sql", {"name": "Alice"})

# 条件なし(name の行が消える)
users = db.query(User, "select_by_name.sql", {"name": None})

INSERT / UPDATE / DELETE も同じ要領です。

db.execute("insert.sql", {"name": "Alice", "email": "alice@example.com"})
user_id = db.insert("insert.sql", {"name": "Bob", "email": "bob@example.com"})  # lastrowid が返る
affected = db.execute("update.sql", {"id": 1, "name": "New Name", "email": "new@example.com"})
affected = db.execute("delete.sql", {"id": 3})

SQLite, PostgreSQL, MySQL, Oracle に対応していて、プレースホルダの形式や LIKE エスケープの違い、Oracle の IN 句 1000 件制限なども吸収します。仕様の詳細は SQL_SYNTAX.ja.md をご覧ください。

リポジトリの examples/ に基本的な CRUD、バッチ処理、トランザクション管理、Clean Architecture での構成例を置いてあるので、気になる方はそちらもどうぞ。

AI コーディングエージェントの時代に思うこと

この sqlym、コーディングの大部分を AI エージェントと一緒に進めました。

自分がやったのは「Clione-SQL のこの仕様を Python で再現したい」という設計意図を伝えることと、出来上がったものを触って「ここ違う」「こうじゃない」とフィードバックすること。いわゆるアーキテクチャとレビューの部分です。

ひと昔前なら、仕事の合間に別言語へのポーティングをやろうなんて思えなかった。Python の DB ドライバごとのプレースホルダの差異を調べて、IN 句の展開処理を書いて、テストを揃えて……正直、モチベーションが続かなかったと思います。

それが今は「こういうものが欲しい」をきちんと言語化できれば、形にできるようになった。AI エージェントは言われたとおりにコードを書くのは得意だけど、「何を作るか」「なぜこの設計なのか」は人間が決める必要がある。その意味で、プログラマの仕事が「コードを書くこと」から「設計と意思決定」にシフトしていくのを実感した経験でもありました。

「作りたいものはあるけど、手を動かす時間がない」という人にとって、今はかなりいい時代だと思います。

おわりに

繰り返しになりますが、sqlym がやっていることの本質は Clione-SQL と Doma2 が確立した設計の Python 移植です。2way SQL という発想、行単位の処理、インデントによる親子関係、パラメータコメント構文——これらはすべて先人の知恵をお借りしています。

Python で「SQL をそのまま書きたいけど動的条件の組み立てが面倒」と感じている方は、ぜひ試してみてください。

フィードバックや Issue お待ちしています。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?