---
title: "Azure Cosmos DBのCRUD操作｜UPDATE文の代わりのPatch・一括更新・削除・トランザクションをPython SDKで解説"
url: "https://www.issoh.co.jp/tech/details/7511/"
published: 2025-07-01
updated: 2026-11-05
categories: ["Azure"]
publisher: "株式会社一創"
---

# Azure Cosmos DBのCRUD操作｜UPDATE文の代わりのPatch・一括更新・削除・トランザクションをPython SDKで解説

Azure Cosmos DB for NoSQLのクエリ言語はSELECT系の読み取り専用で、SQL ServerのようなUPDATE文やDELETE文はありません。更新と削除は、SDKのメソッド（`replace_item`・`patch_item`・`delete_item`）か、トランザクションバッチで行います。この記事では、Python SDK `azure-cosmos` 4.17.1（2026年9月16日リリース）を基準に、CRUDの4操作を実装順に説明します。SQLの`UPDATE … WHERE`や`DELETE … WHERE`に当たる、条件に合う複数件の書き換えも扱います。.NETとJavaScriptのメソッド名も対応表に載せています。

## まとめ：Cosmos DBのCRUD操作とSDKメソッドの選択

- 挿入は`create_item`（同じidがあれば409）か`upsert_item`（あれば丸ごと置換）。
- 1件の読み取りはidとパーティションキーを指定する`read_item`が基本です。1KBのアイテムは通常1RUですが、強い整合性または有界整合性制約では2RUになります。条件で探すときだけ`query_items`を使います。
- UPDATE文は無いので、更新は`replace_item`（全体置換）か`patch_item`（部分更新・1回10操作まで）で行います。同時更新の上書きはETagで防ぎ、不一致なら412が返ります。
- DELETE文も無いので、削除は`delete_item`で1件ずつ消します。パーティションキー単位の一括削除はパブリックプレビューです。期限付きデータにはTTLを使えますが、サーバーレスでは期限切れ削除も通常の削除と同じレートでRU課金されます。
- 条件に合う複数件の更新（`UPDATE … WHERE`の代わり）は、クエリでidとパーティションキー値を集め、同じキー値ごとに100件ずつトランザクションバッチで書きます。Python SDKにはバルク実行が無いので、キー値が散らばる大量削除は非同期クライアントで並行数を絞って実行します。
- 複数操作をまとめて成功・失敗させたいときはトランザクションバッチを使います。同一コンテナの同一パーティションキー値に限られ、100操作・2MB（Python SDKは1.2Mb）・5秒が上限です。

## CRUD操作とSDKメソッドの対応表（Python・.NET・JavaScript）

どのSDKも、ポイント操作（1件単位の読み書き）ではidとパーティションキーの両方を指定します。.NETは`Container`クラス、JavaScriptは`container.items`と`container.item(id, pk)`にメソッドがあります。

| 操作             | Python（azure-cosmos）                   | .NET（Microsoft.Azure.Cosmos）            | JavaScript（@azure/cosmos）     |
| -------------- | -------------------------------------- | --------------------------------------- | ----------------------------- |
| 挿入             | create\_item                           | CreateItemAsync                         | items.create                  |
| 挿入または置換        | upsert\_item                           | UpsertItemAsync                         | items.upsert                  |
| 1件読み取り         | read\_item                             | ReadItemAsync                           | item(id, pk).read             |
| 複数件読み取り        | read\_items                            | ReadManyItemsAsync                      | （クエリで代替）                      |
| クエリ            | query\_items                           | GetItemQueryIterator                    | items.query                   |
| 全体置換           | replace\_item                          | ReplaceItemAsync                        | item(id, pk).replace          |
| 部分更新           | patch\_item                            | PatchItemAsync                          | item(id, pk).patch            |
| 削除             | delete\_item                           | DeleteItemAsync                         | item(id, pk).delete           |
| パーティションキー単位の削除 | delete\_all\_items\_by\_partition\_key | DeleteAllItemsByPartitionKeyStreamAsync | deleteAllItemsForPartitionKey |
| トランザクションバッチ    | execute\_item\_batch                   | CreateTransactionalBatch                | items.batch                   |

2026年11月5日時点の安定版は、[PyPIのazure-cosmos](https://pypi.org/project/azure-cosmos/)が4.17.1、[NuGetのMicrosoft.Azure.Cosmos](https://www.nuget.org/packages/Microsoft.Azure.Cosmos)が3.63.3、[npmの@azure/cosmos](https://www.npmjs.com/package/@azure/cosmos)が4.10.1です。各SDKでは、操作に応じて表のメソッドを使い分けます。

## 接続とコンテナの準備

### クライアントの作成とEntra IDのデータプレーンロール

```
pip install azure-cosmos==4.17.1 azure-identity
```

```
import os
from azure.cosmos import CosmosClient, exceptions
from azure.core import MatchConditions
from azure.identity import DefaultAzureCredential

client = CosmosClient(os.environ["COSMOS_ENDPOINT"], credential=DefaultAzureCredential())
container = client.get_database_client("shop").get_container_client("orders")
```

Python SDKのREADMEは、キーではなく`DefaultAzureCredential`での認証を推奨しています。Entra IDで接続する場合、アプリのIDにはCosmos DB独自のデータプレーンロール（読み書きなら「Cosmos DB Built-in Data Contributor」）を`az cosmosdb sql role assignment create`で割り当てます。このロールで許可されるのはコンテナ内のデータ操作までです。データベースやコンテナの作成はコントロールプレーンの操作なので、ポータル・Azure CLI・Bicepなどで先に作っておきます。

### idとパーティションキーの制約

この記事の例は、パーティションキーを`/customerId`にした`orders`コンテナを前提にしています。idが一意になる範囲は論理パーティション（同じパーティションキー値）の中だけなので、ポイント操作では必ず両方を渡します。[サービスの上限](https://learn.microsoft.com/ja-jp/azure/cosmos-db/concepts-limits)は次のとおりです。

- アイテムの最大サイズは2MB（JSONをUTF-8にしたときの長さ）。超えると413が返ります。
- idの最大長は1,023バイト。`/`と`\`は使えません。Microsoftは、SDKやコネクタの互換性のために英数字のASCIIだけを使うよう強く推奨しています。
- オブジェクトや配列の入れ子は128階層まで。

ローカルで試す場合は[Azure Cosmos DB エミュレーターの使い方｜ローカル開発のDocker（vNext）起動とWindows版の選び方](/tech/details/6259/)で起動できます。パーティションキーの選び方やRU課金の考え方は[Azure Cosmos DBとは？料金の目安・RU課金・API選定と設計の注意点](/tech/details/4678/)にまとめています。

## 作成（Create）：create\_itemとupsert\_itemの使い分け

```
order = {
    "id": "order-1001",
    "customerId": "c-42",
    "status": "new",
    "total": 3980,
    "version": 1,
    "couponCode": "WELCOME",
    "items": [{"sku": "A-1", "qty": 2}],
}

try:
    container.create_item(body=order)
except exceptions.CosmosResourceExistsError:
    # 同じパーティションキー内に同じidがある（409 Conflict）
    print("order-1001 は登録済み")

# 以下はcreate_itemとは別の選択肢。既存データの全体置換を許可する場合だけ使う。
# container.upsert_item(body=order)
```

`create_item`は重複を409で知らせるので、二重登録を検出したい処理（注文の受付など）に向きます。`upsert_item`は既存のアイテムをボディで丸ごと置き換えます。ボディに含めなかったプロパティは消えるため、一部の項目だけを変えたい場合は後述の`patch_item`を使ってください。

書き込みのRUは、アイテムのサイズとインデックス対象のプロパティ数で増えます。Microsoft Learnの目安では、インデックス無しで1KBのアイテムを挿入すると約5.5RU、置換は同じアイテムの挿入の2倍です。

日本語の多いアイテムを書くなら、4.17.0（2026年9月9日）で追加された`enable_compact_utf8_item_writes=True`をクライアントの作成時に指定します。既定のSDKは非ASCII文字を`\uXXXX`形式にエスケープして送るため、UTF-8で3バイトの漢字が6バイトになり、要求サイズの上限（2MiB）に早く届きます。[Python SDKのREADME](https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/cosmos/azure-cosmos/README.md#compact-utf-8-item-writes)によると、対象はcreate・upsert・replace・patch・トランザクションバッチの本文で、保存される値と読み取り結果は変わりません。既定は無効です。

## 読み取り（Read）：ポイント読み取りとクエリ

### read\_itemによるポイント読み取りとRUの確認

```
item = container.read_item(item="order-1001", partition_key="c-42")
print(item["status"], item["_etag"])

# 直前の操作で消費したRU
print(container.client_connection.last_response_headers["x-ms-request-charge"])
```

idとパーティションキーで1件を取るポイント読み取りは最も安い読み取りで、1KBのアイテムなら1RU、100KBで10RUです。整合性レベルが「強い」または「有界整合性制約」の場合、読み取りのRUは2倍になります。存在しないidを読むと`CosmosResourceNotFoundError`（404）が発生します。

### query\_itemsのパラメーター化クエリとSELECT TOP

```
query = (
    "SELECT TOP 10 o.id, o.total, o.status FROM orders o "
    "WHERE o.customerId = @cid AND o.status = @status ORDER BY o._ts DESC"
)
rows = container.query_items(
    query=query,
    parameters=[
        {"name": "@cid", "value": "c-42"},
        {"name": "@status", "value": "new"},
    ],
    partition_key="c-42",
)
for row in rows:
    print(row)

# パーティションをまたぐクエリは明示的に許可する（同期クライアント）
paid = container.query_items(
    query="SELECT DISTINCT VALUE o.customerId FROM orders o WHERE o.status = 'paid'",
    enable_cross_partition_query=True,
)
```

値は文字列連結ではなく`@`付きのパラメーターで渡します。件数の制限は`TOP`、重複の除去は`DISTINCT`、ページングは`OFFSET n LIMIT m`で書けます。同期クライアントの`query_items`は、`partition_key`を指定するか`enable_cross_partition_query=True`を付けないとクエリを受け付けません。非同期クライアントにはこのフラグが無く、パーティションキーを省くと自動でパーティション横断になります。

Python SDKには、GROUP BYのクエリやDISTINCTを含むサブクエリのCOUNTなど、未対応の構文があります（READMEの「Limitations」）。集計が必要なら、対応している.NET SDKなどを使うか、クライアント側で集計します。

### read\_itemsによる複数件の一括読み取り

```
orders = container.read_items(
    items=[("order-1001", "c-42"), ("order-2001", "c-77")]
)
```

idとパーティションキーの組がわかっている複数件は、`WHERE o.id IN (...)`のクエリを書くより`read_items`でまとめて読みます。SDKのドキュメントは、これを個別のポイント読み取りを並べるより効率のよいバッチ読み取りと説明しています。.NETでは`ReadManyItemsAsync`が同じ役割です。

## 更新（Update）：UPDATE文が無いCosmos DBでの書き方

### replace\_itemによる全体置換とETagでの同時更新対策

```
item = container.read_item(item="order-1001", partition_key="c-42")
item["status"] = "paid"

try:
    container.replace_item(
        item=item["id"],
        body=item,
        etag=item["_etag"],
        match_condition=MatchConditions.IfNotModified,
    )
except exceptions.CosmosAccessConditionFailedError:
    # 競合を呼び出し元へ通知する。再試行時は読み直して更新条件を再判定する。
    raise
```

読み取り→書き換え→置換の流れは、間に別の処理が同じアイテムを更新すると、その変更を上書きして消してしまいます。Cosmos DBはアイテムを更新するたびに`_etag`を変えるので、読んだときの`_etag`を`if-match`として渡せば、変わっていた場合はサーバーが412で拒否します。Pythonでは`etag`と`match_condition=MatchConditions.IfNotModified`の組み合わせがこれに当たります。

### patch\_itemによる部分更新の6操作と10操作の上限

```
operations = [
    {"op": "set", "path": "/status", "value": "shipped"},
    {"op": "incr", "path": "/version", "value": 1},
    {"op": "add", "path": "/items/-", "value": {"sku": "B-9", "qty": 1}},
    {"op": "remove", "path": "/couponCode"},
]
container.patch_item(
    item="order-1001",
    partition_key="c-42",
    patch_operations=operations,
)
```

部分ドキュメント更新（Patch API）は、変える項目だけを送る更新です。読み取りとクライアント側での組み立てが要らず、転送量も減ります。[Microsoft Learnの部分ドキュメント更新の説明](https://learn.microsoft.com/ja-jp/azure/cosmos-db/partial-document-update)によると、操作は次の6種類で、1回の`patch_item`に含められるのは10操作までです。

| 操作（Pythonのop） | パスが無いとき         | パスがあるとき           |
| ------------- | --------------- | ----------------- |
| add           | 追加              | 値を置換（配列は指定位置に挿入）  |
| set           | 追加              | 値を置換（配列は指定位置を上書き） |
| replace       | エラー             | 値を置換              |
| remove        | エラー             | 削除                |
| incr          | 指定値で作成          | 加算（負の値で減算）        |
| move          | 移動先を作成（fromは必須） | 移動先を置換（fromは必須）   |

配列の末尾に足すときはインデックスの代わりに`-`を書きます（例の`/items/-`）。プロパティ名に`~`や`/`を含む場合は、JSON Patch（RFC 6902）の規則どおり`~0`・`~1`にエスケープします。マルチリージョン書き込みの構成では、同じアイテムでも別々のパスへのPatchは自動でマージされます。同じプロパティを複数リージョンで同時に更新した場合は、通常の競合解決ポリシーが適用されます。

### filter\_predicateによる条件付き更新

```
try:
    container.patch_item(
        item="order-1001",
        partition_key="c-42",
        patch_operations=[{"op": "set", "path": "/status", "value": "cancelled"}],
        filter_predicate="FROM c WHERE c.status = 'new'",
    )
except exceptions.CosmosHttpResponseError as e:
    # 条件を満たさないと412（Precondition Failed）で失敗し、更新されない
    print(e.status_code, e.message)
```

`filter_predicate`に`FROM c WHERE …`の形で条件を書くと、その条件を満たすときだけPatchが適用され、満たさなければ412が返ります。「statusがnewの注文だけキャンセルできる」という状態遷移のチェックをサーバー側で済ませられるため、ETagで再試行するより競合時の往復が減ります。Patchにも`etag`と`match_condition`を渡せます。

Python SDKのネイティブの書き込みリトライ（`retry_write`）は、クライアント単位で有効にしてもPatchには適用されません。`add`・`move`・`remove`などは同じリクエストを2回実行すると結果が変わるためで、Patchのリトライはリクエスト単位でだけ有効にできます。

## 削除（Delete）：DELETE文の代わりの3つの方法

### delete\_itemによる1件削除

```
try:
    container.delete_item(item="order-1001", partition_key="c-42")
except exceptions.CosmosResourceNotFoundError:
    pass  # 既に削除済み（404）
```

Python SDKのREADMEにも「SQL APIはSQLのDELETE文をサポートしない」と書かれています。条件に合うものを消したい場合は、`SELECT o.id, o.customerId`のようなクエリでidとパーティションキー値を集め、`delete_item`を1件ずつ呼ぶことになります。件数が多いと、その数だけ書き込みのRUがかかります。まとめて消すときの書き方は、後述の「条件に合う複数アイテムの更新・削除」で扱います。

### delete\_all\_items\_by\_partition\_keyによるパーティションキー単位の削除

```
# 事前にアカウントで DeleteAllItemsByPartitionKey 機能を有効化しておく
container.delete_all_items_by_partition_key(partition_key="c-42")
```

「顧客c-42のデータをすべて消す」のように、同じパーティションキー値のアイテムをまとめて消す機能です。2026年10月時点では[パブリックプレビュー](https://learn.microsoft.com/ja-jp/azure/cosmos-db/nosql/how-to-delete-by-partition-key)で、SLAの対象外です。アカウントのcapabilityに`DeleteAllItemsByPartitionKey`を追加してから使います（`az cosmosdb update --capabilities`。既に有効なcapabilityも含めて全部を列挙する必要があります）。

- 削除は非同期のバックグラウンド処理で、コンテナのRU/秒のうち最大10%をベストエフォートで使います。
- 階層型パーティションキーでは、全階層を指定した場合だけ削除できます。上位の階層だけを指定するとエラーになります。
- 処理中にポイントインタイム復元を行うと、削除予定のアイテムが復元先に残ることがあります。継続的バックアップが必須のコンテナでは使わないよう、Microsoftは案内しています。

Learnの記載では、対応SDKは.NET 3.45.0以降（SDKの安定版で利用可能ですが、サービス機能はプレビュー）、Python 4.4.0以降、Java 4.81.0以降です。

### TTLによる期限切れアイテムの自動削除

```
# sessions コンテナ（パーティションキー /userId・既定TTL -1）はポータルやCLIで作成済みとする
sessions = client.get_database_client("shop").get_container_client("sessions")
sessions.upsert_item(body={"id": "s-1", "userId": "u-7", "ttl": 86400})  # 最終更新から1日後に削除
```

ログやセッションのように「一定時間後に消す」データは、削除処理を書かずにTTL（Time to Live）に任せます。コンテナに`DefaultTimeToLive`を設定すると、アイテムは最終更新から指定秒数後に削除されます。`-1`なら既定では期限切れにならず、`ttl`を持つアイテムだけが期限切れになります。コンテナにTTLを設定していない場合、アイテムの`ttl`は無視されます。既定TTLはコンテナの設定なので、Entra IDのデータプレーンロールで動くアプリからではなく、コンテナの作成・変更と同じくポータルやAzure CLIで設定します。キー認証の管理スクリプトで作る場合は、`create_container_if_not_exists`に`default_ttl=-1`を渡せますが、既存のコンテナの設定はこの呼び出しでは変わりません。

期限切れのアイテムはすぐにクエリ結果から消えます。実際の削除はバックグラウンドで行われ、プロビジョニングスループットではユーザー要求で使われなかったRUが充てられます。RUが足りなければ削除は遅れます。一方、サーバーレスのアカウントでは、期限切れの削除にも通常の削除操作と同じレートでRUが課金されます。[Microsoft LearnのTTLの説明](https://learn.microsoft.com/ja-jp/azure/cosmos-db/time-to-live)に、この料金方式ごとの違いが書かれています。

## トランザクション：同一パーティションキー内のトランザクションバッチ

```
# ordersコンテナを明示的に選び直す
container = client.get_database_client("shop").get_container_client("orders")
# 前提: c-42内にorder-1002（数値のtotal）とorder-0999が存在し、
# order-1003は存在しない。前提を満たさない場合はロールバックされる。
batch = [
    ("create", ({"id": "order-1003", "customerId": "c-42", "status": "new", "total": 1200},)),
    ("patch", ("order-1002", [{"op": "incr", "path": "/total", "value": -200}])),
    ("delete", ("order-0999",)),
]

try:
    results = container.execute_item_batch(batch_operations=batch, partition_key="c-42")
except exceptions.CosmosBatchOperationError as e:
    # 失敗した操作の位置と応答。ほかの操作は 424 Failed Dependency になり、全体がロールバックされる
    print(e.error_index, e.operation_responses[e.error_index])
```

[トランザクションバッチ](https://learn.microsoft.com/ja-jp/azure/cosmos-db/transactional-batch)は、同じコンテナの同じパーティションキー値を持つポイント操作をまとめて実行し、全部成功すればコミット、1つでも失敗すれば全体をロールバックする仕組みです。Pythonでは`("操作名", (引数,), {オプション})`のタプルを並べ、操作名には`create`・`upsert`・`read`・`replace`・`delete`・`patch`を使えます。オプションに書けるのは`if_match_etag`・`if_none_match_etag`・`filter_predicate`（patchのみ）です。

サービス側の上限は1バッチ100操作、ペイロード2MB、実行時間5秒です。ただし、Python SDKのREADMEは、SDKに渡す操作の合計サイズの上限を「1.2Mb」と記載しています。Pythonでは2MBではなくこの値を目安にバッチを分けてください。パーティションキーが違うアイテムは同じバッチに入れられません。そのため、「注文」と「在庫」を同じトランザクションで更新したい場合は、両方を同一コンテナ内の同じパーティションキー値に置く設計が前提になります。

パーティションやコンテナをまたぐ処理には、[分散トランザクション](https://learn.microsoft.com/en-us/azure/cosmos-db/how-to-configure-and-use-distributed-transactions)がパブリックプレビューで用意されています。ただし対応SDKは.NET（3.64.0-preview.0以上）だけで、申請フォームでの登録が要ります。サーバーレス、複数リージョン書き込み、継続的バックアップ、階層型パーティションキーのアカウントでは使えず、上限は1トランザクション100操作・2MBです。Pythonで書くアプリは、引き続き同一パーティションキー値に寄せる設計で考えてください。

従来、トランザクションにはJavaScriptで書くストアドプロシージャを使っていました。Microsoft Learnは、トランザクションバッチならアプリと同じ言語で書けてCI/CDで版管理しやすく、同等の処理でレイテンシが最大30%短いと説明しています。新しく書くならバッチを先に検討してください。

## 条件に合う複数アイテムの更新・削除：UPDATE … WHEREの置き換え

「statusがnewの注文をすべてexpiredにする」「期限切れの注文を消す」のように、SQLなら`UPDATE … WHERE`や`DELETE … WHERE`の1文で済む処理は、Cosmos DBでは「クエリで対象のidとパーティションキー値を集める」「ポイント操作で書き込む」の2段に分けます。書き込みを同じパーティションキー値ごとに束ねられるかどうかで、書き方が変わります。

### パーティションキー単位の一括更新と100操作のバッチ分割

```
from collections import defaultdict
from itertools import islice

def chunked(seq, size):
    it = iter(seq)
    while chunk := list(islice(it, size)):
        yield chunk

# UPDATE orders SET status = 'expired' WHERE status = 'new' に相当
targets = container.query_items(
    query="SELECT c.id, c.customerId FROM c WHERE c.status = @from",
    parameters=[{"name": "@from", "value": "new"}],
    enable_cross_partition_query=True,
)
by_pk = defaultdict(list)
for t in targets:
    by_pk[t["customerId"]].append(t["id"])

ops = [{"op": "set", "path": "/status", "value": "expired"}]
guard = {"filter_predicate": "FROM c WHERE c.status = 'new'"}
for pk, ids in by_pk.items():
    for chunk in chunked(ids, 100):  # 1バッチ100操作が上限
        batch = [("patch", (item_id, ops), guard) for item_id in chunk]
        container.execute_item_batch(batch_operations=batch, partition_key=pk)
```

対象をパーティションキー値ごとに束ね、100件ずつ`execute_item_batch`に渡します。1回の往復で最大100件を書けるうえ、束ごとに全件成功か全件ロールバックかが決まるので、「束の半分だけ更新済み」の状態は残りません。101操作を渡すと、サービスは400（Bad Request）で拒否します。

原子性があるのは束の中だけで、ジョブ全体を1つのトランザクションにはできません。途中で止まっても再実行できるよう、条件を`guard`の`filter_predicate`にも書いておきます。クエリの後に別の処理がstatusを変えたアイテムは412で弾かれ、その束は`CosmosBatchOperationError`でロールバックされます。その束はクエリからやり直してください。削除の操作には`filter_predicate`を付けられないので、対象を集めるクエリで`_etag`も取り（`SELECT c.id, c.customerId, c._etag FROM c WHERE …`）、各操作を`("delete", (t["id"],), {"if_match_etag": t["_etag"]})`にします。クエリの後に更新されたアイテムがあれば、その束は412でロールバックされます。Patchの本文は小さいので100件でもPython SDKの1.2Mbに収まりますが、`upsert`や`replace`で大きなアイテムを束ねるなら、件数ではなく合計サイズで区切ります。

### 複数パーティションにまたがる大量削除と非同期実行の並行数制御

```
import asyncio
from itertools import islice
from azure.cosmos import exceptions
from azure.core import MatchConditions
from azure.cosmos.aio import CosmosClient as AsyncCosmosClient
from azure.identity.aio import DefaultAzureCredential as AsyncDefaultAzureCredential
# 非同期クライアントには aiohttp が要る（pip install aiohttp）

async def delete_targets(targets, limit=10):
    async with AsyncDefaultAzureCredential() as credential:
        async with AsyncCosmosClient(os.environ["COSMOS_ENDPOINT"], credential=credential) as aclient:
            orders = aclient.get_database_client("shop").get_container_client("orders")

            async def delete_one(t):
                try:
                    # クエリ時点から変更されていれば412で削除しない
                    await orders.delete_item(
                        item=t["id"],
                        partition_key=t["customerId"],
                        etag=t["_etag"],
                        match_condition=MatchConditions.IfNotModified,
                    )
                except exceptions.CosmosAccessConditionFailedError:
                    pass  # クエリ後に更新された。条件を再判定するまで残す
                except exceptions.CosmosResourceNotFoundError:
                    pass  # 既に削除済み

            it = iter(targets)
            while group := list(islice(it, limit)):  # 同時に投げる要求をlimit件に絞る
                await asyncio.gather(*(delete_one(t) for t in group))

targets = list(container.query_items(
    query="SELECT c.id, c.customerId, c._etag FROM c WHERE c.status = 'expired'",
    enable_cross_partition_query=True,
))
asyncio.run(delete_targets(targets))
```

対象のパーティションキー値が1件ずつ違うと（全顧客の古い注文を消す、など）、バッチに束ねられず、件数分のポイント操作を呼ぶことになります。.NET SDKには`AllowBulkExecution`によるバルク実行がありますが、[Python SDKのREADME](https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/cosmos/azure-cosmos/README.md#using-the-async-client-as-a-workaround-to-bulk)は「bulk requestsはまだ実装されていない」と書き、代わりに非同期クライアントでの並行実行を挙げています。

同時に投げる要求は`limit`件ずつ`asyncio.gather`に渡して絞り、削除にはクエリで取った`_etag`を条件に付けます。クエリの後に状態が変わったアイテムは412で残るので、別の処理が更新した注文を消してしまうことはありません。READMEは、この方法がRUを急速に消費するとして、先にエミュレーターで試すよう強く勧めています。プロビジョニングスループットのコンテナで429が続くなら並行数を下げ、オンラインの処理と同じ時間帯には走らせないでください。消したいのが「同じパーティションキー値の全部」なら`delete_all_items_by_partition_key`、期限で消すならTTLのほうが、この章の書き方より向いています。

## エラーコードとPython SDKの例外の対応

| ステータス                   | 主な原因                           | Pythonの例外                        |
| ----------------------- | ------------------------------ | -------------------------------- |
| 400 Bad Request         | Patchが10操作超・バッチが100操作超         | CosmosHttpResponseError          |
| 404 Not Found           | idまたはパーティションキーの誤り              | CosmosResourceNotFoundError      |
| 409 Conflict            | create\_itemで同じidが存在           | CosmosResourceExistsError        |
| 412 Precondition Failed | ETagの不一致・filter\_predicateの不成立 | CosmosAccessConditionFailedError |
| 413 Entity Too Large    | アイテムが2MB超                      | CosmosHttpResponseError          |
| 424 Failed Dependency   | バッチ内の別の操作が失敗                   | CosmosBatchOperationError        |
| 429 Too Many Requests   | プロビジョニングしたRU/秒を超過              | CosmosHttpResponseError          |

429はスループット不足のサインで、サーバーが指定する待ち時間の後に再試行します。Python SDKは既定で429を最大9回・合計30秒まで自動で再試行します（`RetryOptions`の既定値）。それでも頻発する場合は、RU/秒の引き上げかクエリの見直しが必要です。

## CRUD実装でRUと整合性を損なう書き方

CRUDのコードは動いても、書き方次第でRUを浪費したり、データの不整合を残したりします。レビューで止めるべきパターンは次の4つです。

- **idがわかっているのにクエリで読む**：`SELECT * FROM c WHERE c.id = @id`はクエリとして処理され、ポイント読み取り（1KBで1RU）より高くつきます。パーティションキーもわかるなら`read_item`に置き換えます。
- **同時に更新されうるアイテムをETag無しで置換する**：在庫数やカウンターを「読んで足して`replace_item`」すると、更新が消えます。加算なら`incr`のPatch、状態遷移なら`filter_predicate`、全体置換が避けられないならETagを付けます。
- **期限付きデータをバッチジョブで消す**：毎晩クエリで古いアイテムを探して`delete_item`を回すと、同時に動く処理とRUを奪い合います。プロビジョニングスループットのTTL削除は余剰RUを使いますが、サーバーレスでは通常の削除と同じレートでRU課金されます。
- **トランザクションが必要なのにパーティションキーを別々にする**：バッチは同一コンテナの同一パーティションキー値の中でしか使えません（パーティションをまたぐ分散トランザクションは.NET向けのプレビューのみ）。後から一括で更新したくなったアイテム同士は、設計段階で同じコンテナ・同じキー値にまとめておきます。

RDBからの移行でテーブルをどうコンテナとパーティションキーに割り当てるかは、弊社の[データベース設計・移行支援](/service/system/database/)でも扱っています。

Cosmos DBの書き込みを別サービスへ連携する場合は、変更フィードを検討できます。既定の最新バージョンモードは、読み取りの間に起きた中間の更新と削除を記録しません。削除も連携したい場合は、[2026年6月2日にGAになった全バージョンと削除モード](https://devblogs.microsoft.com/cosmosdb/azure-cosmos-db-all-versions-and-deletes-change-feed-mode-is-now-generally-available/)（継続的バックアップの有効化が前提）を使うか、削除の代わりに削除フラグを立ててTTLで消す設計にします。削除フラグの設計上の注意は[論理削除とは：物理削除との使い分けと一意制約・集計が壊れる問題の実装対策](/tech/details/18653/)で扱っています。実装は[チェンジフィードとは？Cosmos DB・DynamoDBでの変更フィード実装と採用判断](/tech/details/16113/)を参照してください。

## よくある質問

### Cosmos DBでUPDATEクエリは書けますか？

書けません。Cosmos DB for NoSQLのクエリ言語は読み取り用で、更新はSDKの`replace_item`（全体置換）か`patch_item`（部分更新）で行います。条件付きで更新したい場合は、Patchの`filter_predicate`に`FROM c WHERE ...`の形で条件を書きます。

### DELETE文で条件に合うアイテムをまとめて削除できますか？

できません。クエリでidとパーティションキー値を取得し、同じキー値ごとにトランザクションバッチの`delete`で100件ずつ消すか、同じパーティションキー値をすべて消すなら`delete_all_items_by_partition_key`（パブリックプレビュー）を使います。期限で消すデータならTTLが最も手間がかかりません。

### upsert\_itemで一部の項目だけを更新できますか？

できません。`upsert_item`は既存のアイテムを渡したボディで丸ごと置き換えるので、ボディに無いプロパティは消えます。一部の項目だけを変えるなら`patch_item`を使います。

### Cosmos DBのトランザクションは複数のパーティションにまたがれますか？

Python SDKでは、またがれません。トランザクションバッチもストアドプロシージャも、1つのコンテナの同じパーティションキー値（論理パーティション）の中だけで動作します。複数のパーティション・コンテナ・データベースをまたぐ分散トランザクションは、2026年11月時点では.NET SDK（3.64.0-preview.0以上）向けのパブリックプレビューで、申請制です。バッチの上限は100操作・2MB・5秒で、Python SDKはREADMEで合計サイズを1.2Mbまでとしています。

### SELECT TOPやOFFSET LIMITは使えますか？

使えます。件数の制限は`SELECT TOP 10 ...`、ページングは`OFFSET 20 LIMIT 10`のように書きます。重複の除去には`SELECT DISTINCT`を使います。

## 関連記事

- [Azure Cosmos DBとは？料金の目安・RU課金・API選定と設計の注意点](/tech/details/4678/)
- [Azure Cosmos DB エミュレーターの使い方｜ローカル開発のDocker（vNext）起動とWindows版の選び方](/tech/details/6259/)
- [チェンジフィードとは？Cosmos DB・DynamoDBでの変更フィード実装と採用判断](/tech/details/16113/)
- [論理削除とは：物理削除との使い分けと一意制約・集計が壊れる問題の実装対策](/tech/details/18653/)
- [結果整合性とは？強整合性との違いとレプリカ遅延の設計を実装者目線で解説【2026年版】](/tech/details/16287/)

---

出典: [Azure Cosmos DBのCRUD操作｜UPDATE文の代わりのPatch・一括更新・削除・トランザクションをPython SDKで解説](<https://www.issoh.co.jp/tech/details/7511/>)（株式会社一創）
