エンティティのアップサート

upsert 操作は、コレクション内のエンティティを挿入または更新するための便利な方法を提供します。

概要

upsert を使用すると、upsert リクエストで指定された主キーがコレクション内に存在するかどうかによって、新しいエンティティを挿入するか、既存のエンティティを更新するかを選択できます。主キーが見つからない場合は、挿入操作が行われます。それ以外の場合は、更新操作が行われます。autoID コレクションに対する部分更新は例外であり、以下で説明するように、既存のエンティティのみが更新されます。

Milvus におけるアップサートは、オーバーライドモードまたはマージモードのいずれかで動作します。

オーバーライドモードでのアップサート

オーバーライドモードで動作するアップサートリクエストは、INSERTとDELETEを組み合わせたものです。既存エンティティに対するupsert リクエストを受信すると、Milvusはリクエストペイロードに含まれるデータを挿入すると同時に、データ内で指定された元の主キーを持つ既存エンティティを削除します。

Upsert In Override Mode オーバーライドモードでのアップサート

ターゲットコレクションのプライマリフィールドでautoID が有効になっている場合でも、upsert リクエストにはターゲットエンティティのプライマリキーを含める必要があります。Milvusは、提供されたプライマリキーを使用して置換するエンティティを特定し、リクエストペイロードに含まれるデータに対して新しいプライマリキーを生成してから挿入を行います。

nullable が有効になっているフィールドについて、更新が必要ない場合は、upsert リクエストでそれらを省略できます。

マージモードでのアップサートCompatible with Milvus v2.6.2+

マージモードを使用すると、既存のエンティティの特定のフィールドを更新しつつ、他のフィールドは変更せずに保持できます。

Upsert In Merge Mode マージモードでのアップサート

partial_update=True を設定し、主キーと更新したいフィールドを指定します。

Milvus は、強一貫性クエリを使用して既存エンティティを取得し、変更内容を保存されたデータとマージして、マージされたエンティティを挿入し、古いエンティティを削除します。

プライマリキーが存在しない場合、結果はautoID が有効かどうかに依存します:

  • autoID が無効な場合、Milvusは指定された主キーを持つ新しいエンティティを挿入しようとします。通常の挿入要件を満たしていれば、リクエストは成功します。必須フィールドが欠落している場合、リクエストはフィールド欠落エラーで失敗します。NULL許容フィールドやデフォルト値を持つフィールドは、通常の挿入と同様に省略可能です。
  • autoID が有効な場合、リクエスト内のすべての主キーはすでに存在している必要があります。挿入に必要なすべてのフィールドを指定していたとしても、主キーのいずれかが欠けていると、Milvusはリクエストを拒否します。既存のエンティティの場合、マージモードでは主キーは変更されません。

部分更新が「フィールド欠落」エラーで失敗した場合は、対象エンティティが存在するかどうかを確認してください。既存のエンティティがない場合、Milvusは省略したフィールドの値を取得できません。

新規エンティティの場合は、insert を使用するか、オーバーライドモードでのupsertを使用してください。個々のフィールドに対するその後の更新には、マージモードを使用してください。

ARRAY フィールドの場合、Milvus v2.6.17以降では、マージモードで次の2つの演算子がサポートされています:ARRAY_APPEND およびARRAY_REMOVE 。これらの演算子を使用すると、エンティティをクエリして現在の値を取得することなく、既存のARRAY フィールドに要素を追加したり、一致する要素を削除したりできます。詳細については、「マージモードでのARRAYフィールドのアップサート」を参照してください。

Upsertの動作:特記事項

マージ機能を使用する前に、考慮すべきいくつかの特別な注意事項があります。以下のケースでは、title およびissue という 2 つのスカラーフィールド、主キーid 、およびvector というベクトルフィールドを持つコレクションが存在することを前提としています。

  • nullable が有効になっているフィールドのアップサート 。

    issue フィールドがNULL可能であると仮定します。これらのフィールドをアップサートする際は、以下の点に注意してください:

    • upsert リクエストでissue フィールドを省略し、partial_update を無効にした場合、issue フィールドは元の値を維持するのではなく、null に更新されます。

    • issue フィールドの元の値を保持するには、partial_update を有効にしてissue フィールドを省略するか、upsert リクエストに元の値を持つissue フィールドを含める必要があります。

  • 動的フィールドのキーをアップサートします。

    サンプルコレクションで動的キーを有効にしており、エンティティの動的フィールド内のキーと値のペアが{"author": "John", "year": 2020, "tags": ["fiction"]} のようなものであると仮定します。

    author 、year 、tags などのキーを使用してエンティティをアップサートする場合、または他のキーを追加する場合は、次の点に注意してください。

    • partial_update が無効な状態でアップサートを行う場合、デフォルトの挙動は上書きとなります。つまり、リクエストに含まれるスキーマで定義されていないすべてのフィールドとその値によって、動的フィールドの値が上書きされます。

      たとえば、リクエストに含まれるデータが `{"author": "Jane", "genre": "fantasy"}` である場合、対象エンティティの動的フィールド内のキーと値のペアは、その値に更新されます。

    • partial_update を有効にして upsert を行う場合、デフォルトの動作はマージです。つまり、動的フィールドの値は、リクエストに含まれるスキーマで定義されていないすべてのフィールドとその値とマージされます。

      たとえば、リクエストに含まれるデータが `{"author": "John", "year": 2020, "tags": ["fiction"]}` の場合、アップサート後にターゲットエンティティの動的フィールドのキーと値のペアは `{"author": "John", "year": 2020, "tags": ["fiction"], "genre": "fantasy"} ` になります。

  • JSONフィールドのアップサート。

    例として、コレクションにextras という名前のスキーマ定義済み JSON フィールドがあり、エンティティのこの JSON フィールド内のキーと値のペアが{"author": "John", "year": 2020, "tags": ["fiction"]} のようなものであるとします。

    変更された JSON データを使用してエンティティのextras フィールドをアップサートする場合、JSON フィールドは全体として扱われるため、個々のキーを選択的に更新することはできない点に注意してください。つまり、JSON フィールドはマージモードでのアップサートをサポートしていません。

  • ARRAY フィールドのアップサート 。

    デフォルトでは、マージモードのARRAY フィールドはREPLACEセマンティクスに従います。つまり、リクエストに含まれる値が既存の配列を上書きします。よりきめ細かな更新を行うために、Milvus v2.6.17以降では以下の2つの演算子もサポートされています。

    • ARRAY_APPEND リクエストペイロード内の要素を既存の配列の末尾に追加します。

    • ARRAY_REMOVE リクエストペイロード内の値と一致する要素を、既存の配列からすべて削除します。

    演算子の構文、サポートされる要素型、およびその他の制約については、「マージモードでの ARRAY フィールドのアップサート」を参照してください。

  • StructArray フィールドのアップサート。

    エンティティ内の StructArray フィールドに対してアップサートを行うと、フィールドの値が上書きされます。これを行うには、マージモードでアップサートを実行する場合であっても、構造体スキーマで定義されたすべてのサブフィールドを含む辞書のリストを指定する必要があります。

    詳細については、「マージモードでの StructArray フィールドのアップサート」を参照してください。

制限事項

上記の内容に基づき、以下の制限事項に従う必要があります。

  • 「autoID 」が有効になっている場合でも、upsert リクエストには常にターゲットエンティティのプライマリキーを含める必要があります。autoID コレクションの場合、プライマリキーの処理はアップサートモードによって異なります:

    • オーバーライドモードでは、主キーは置き換える既存エンティティを識別し、Milvus は置き換え対象のエンティティに対して新しい主キーを生成します。

    • マージモードでは、主キーは更新対象の既存エンティティを識別し、変更されません。主キーが存在しない場合、新しいエンティティを挿入する代わりにリクエストは失敗します。

  • ターゲットコレクションは読み込まれており、クエリに利用可能である必要があります。

  • リクエストで指定されたすべてのフィールドは、ターゲットコレクションのスキーマに存在している必要があります。

  • リクエストで指定されたすべてのフィールドの値は、スキーマで定義されたデータ型と一致している必要があります。

  • 関数を使用して別のフィールドから派生したフィールドについては、Milvus は再計算を可能にするため、アップサート中にその派生フィールドを削除します。

コレクションへのエンティティのアップサート

このセクションでは、my_collection という名前のコレクションにエンティティをアップサートします。このコレクションには、id 、vector 、title 、issue という 4 つのフィールドがあります。id フィールドはプライマリフィールドであり、title およびissue フィールドはスカラーフィールドです。

コレクション内にこれら3つのエンティティが存在する場合、upsertリクエストに含まれるものによって上書きされます。

from pymilvus import MilvusClient

client = MilvusClient(
    uri="http://localhost:19530",
    token="root:Milvus"
)

data=[
    {
        "id": 0, 
        "vector": [-0.619954382375778, 0.4479436794798608, -0.17493894838751745, -0.4248030059917294, -0.8648452746018911],
        "title": "Artificial Intelligence in Real Life", 
        "issue": "vol.12"
    }, {
        "id": 1, 
        "vector": [0.4762662251462588, -0.6942502138717026, -0.4490002642657902, -0.628696575798281, 0.9660395877041965], 
        "title": "Hollow Man", 
        "issue": "vol.19"
    }, {
        "id": 2, 
        "vector": [-0.8864122635045097, 0.9260170474445351, 0.801326976181461, 0.6383943392381306, 0.7563037341572827], 
        "title": "Treasure Hunt in Missouri", 
        "issue": "vol.12"
    }
]

res = client.upsert(
    collection_name='my_collection',
    data=data
)

print(res)

# Output
# {'upsert_count': 3}
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import io.milvus.v2.client.ConnectConfig;
import io.milvus.v2.client.MilvusClientV2;
import io.milvus.v2.service.vector.request.UpsertReq;
import io.milvus.v2.service.vector.response.UpsertResp;

import java.util.*;

MilvusClientV2 client = new MilvusClientV2(ConnectConfig.builder()
        .uri("http://localhost:19530")
        .token("root:Milvus")
        .build());

Gson gson = new Gson();
List<JsonObject> data = Arrays.asList(
        gson.fromJson("{\"id\": 0, \"vector\": [-0.619954382375778, 0.4479436794798608, -0.17493894838751745, -0.4248030059917294, -0.8648452746018911], \"title\": \"Artificial Intelligence in Real Life\", \"issue\": \"\vol.12\"}", JsonObject.class),
        gson.fromJson("{\"id\": 1, \"vector\": [0.4762662251462588, -0.6942502138717026, -0.4490002642657902, -0.628696575798281, 0.9660395877041965], \"title\": \"Hollow Man\", \"issue\": \"vol.19\"}", JsonObject.class),
        gson.fromJson("{\"id\": 2, \"vector\": [-0.8864122635045097, 0.9260170474445351, 0.801326976181461, 0.6383943392381306, 0.7563037341572827], \"title\": \"Treasure Hunt in Missouri\", \"issue\": \"vol.12\"}", JsonObject.class),
);

UpsertReq upsertReq = UpsertReq.builder()
        .collectionName("my_collection")
        .data(data)
        .build();

UpsertResp upsertResp = client.upsert(upsertReq);
System.out.println(upsertResp);

// Output:
//
// UpsertResp(upsertCnt=3)
const { MilvusClient, DataType } = require("@zilliz/milvus2-sdk-node")

const address = "http://localhost:19530";
const token = "root:Milvus";
const client = new MilvusClient({address, token});

data = [
    {id: 0, vector: [-0.619954382375778, 0.4479436794798608, -0.17493894838751745, -0.4248030059917294, -0.8648452746018911], title: "Artificial Intelligence in Real Life", issue: "vol.12"},
    {id: 1, vector: [0.4762662251462588, -0.6942502138717026, -0.4490002642657902, -0.628696575798281, 0.9660395877041965], title: "Hollow Man", issue: "vol.19"},
    {id: 2, vector: [-0.8864122635045097, 0.9260170474445351, 0.801326976181461, 0.6383943392381306, 0.7563037341572827], title: "Treasure Hunt in Missouri", issue: "vol.12"},
]

res = await client.upsert({
    collection_name: "my_collection",
    data: data,
})

console.log(res.upsert_cnt)

// Output
// 
// 3
// 
import (
    "context"
    "fmt"

    "github.com/milvus-io/milvus/client/v2/column"
    "github.com/milvus-io/milvus/client/v2/milvusclient"
)

ctx, cancel := context.WithCancel(context.Background())
defer cancel()

milvusAddr := "localhost:19530"
client, err := milvusclient.New(ctx, &milvusclient.ClientConfig{
    Address: milvusAddr,
})
if err != nil {
    fmt.Println(err.Error())
    // handle error
}
defer client.Close(ctx)

titleColumn := column.NewColumnString("title", []string{
    "Artificial Intelligence in Real Life", "Hollow Man", "Treasure Hunt in Missouri", 
})

issueColumn := column.NewColumnString("issue", []string{
    "vol.12", "vol.19", "vol.12"
})

_, err = client.Upsert(ctx, milvusclient.NewColumnBasedInsertOption("my_collection").
    WithInt64Column("id", []int64{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}).
    WithFloatVectorColumn("vector", 5, [][]float32{
        {0.3580376395471989, -0.6023495712049978, 0.18414012509913835, -0.26286205330961354, 0.9029438446296592},
        {0.19886812562848388, 0.06023560599112088, 0.6976963061752597, 0.2614474506242501, 0.838729485096104},
        {0.43742130801983836, -0.5597502546264526, 0.6457887650909682, 0.7894058910881185, 0.20785793220625592},
    }).
    WithColumns(titleColumn, issueColumn),
)
if err != nil {
    fmt.Println(err.Error())
    // handle err
}
export CLUSTER_ENDPOINT="http://localhost:19530"
export TOKEN="root:Milvus"

curl --request POST \
--url "${CLUSTER_ENDPOINT}/v2/vectordb/entities/upsert" \
--header "Authorization: Bearer ${TOKEN}" \
--header "Content-Type: application/json" \
--header "Request-Timeout: 10" \
-d '{
    "data": [
        {"id": 0, "vector": [0.3580376395471989, -0.6023495712049978, 0.18414012509913835, -0.26286205330961354, 0.9029438446296592], "title": "Artificial Intelligence in Real Life", "issue": "vol.12"},
        {"id": 1, "vector": [0.19886812562848388, 0.06023560599112088, 0.6976963061752597, 0.2614474506242501, 0.838729485096104], "title": "Hollow Man", "issue": "vol.19"},
        {"id": 2, "vector": [0.43742130801983836, -0.5597502546264526, 0.6457887650909682, 0.7894058910881185, 0.20785793220625592], "title": "Treasure Hunt in Missouri", "issue": "vol.12"},
],
    "collectionName": "my_collection"
}'

# {
#     "code": 0,
#     "data": {
#         "upsertCount": 3,
#         "upsertIds": [
#             0,
#             1,
#             2,
#         ]
#     }
# }

パーティションへのエンティティのアップサート

指定したパーティションにエンティティをアップサートすることもできます。以下のコードスニペットは、コレクション内に「PartitionA」という名前のパーティションが存在することを前提としています。

パーティション内にこれら3つのエンティティが存在する場合、リクエストに含まれるエンティティによって上書きされます。

data=[
    {
        "id": 10, 
        "vector": [0.06998888224297328, 0.8582816610326578, -0.9657938677934292, 0.6527905683627726, -0.8668460657158576], 
        "title": "Layour Design Reference", 
        "issue": "vol.34"
    },
    {
        "id": 11, 
        "vector": [0.6060703043917468, -0.3765080534566074, -0.7710758854987239, 0.36993888322346136, 0.5507513364206531], 
        "title": "Doraemon and His Friends", 
        "issue": "vol.2"
    },
    {
        "id": 12, 
        "vector": [-0.9041813104515337, -0.9610546012461163, 0.20033003106083358, 0.11842506351635174, 0.8327356724591011], 
        "title": "Pikkachu and Pokemon", 
        "issue": "vol.12"
    },
]

res = client.upsert(
    collection_name="my_collection",
    data=data,
    partition_name="partitionA"
)

print(res)

# Output
# {'upsert_count': 3}
import io.milvus.v2.service.vector.request.UpsertReq;
import io.milvus.v2.service.vector.response.UpsertResp;

Gson gson = new Gson();
List<JsonObject> data = Arrays.asList(
        gson.fromJson("{\"id\": 10, \"vector\": [0.06998888224297328, 0.8582816610326578, -0.9657938677934292, 0.6527905683627726, -0.8668460657158576], \"title\": \"Layour Design Reference\", \"issue\": \"vol.34\"}", JsonObject.class),
        gson.fromJson("{\"id\": 11, \"vector\": [0.6060703043917468, -0.3765080534566074, -0.7710758854987239, 0.36993888322346136, 0.5507513364206531], \"title\": \"Doraemon and His Friends\", \"issue\": \"vol.2\"}", JsonObject.class),
        gson.fromJson("{\"id\": 12, \"vector\": [-0.9041813104515337, -0.9610546012461163, 0.20033003106083358, 0.11842506351635174, 0.8327356724591011], \"title\": \"Pikkachu and Pokemon\", \"issue\": \"vol.12\"}", JsonObject.class),
);

UpsertReq upsertReq = UpsertReq.builder()
        .collectionName("my_collection")
        .partitionName("partitionA")
        .data(data)
        .build();

UpsertResp upsertResp = client.upsert(upsertReq);
System.out.println(upsertResp);

// Output:
//
// UpsertResp(upsertCnt=3)
const { MilvusClient, DataType } = require("@zilliz/milvus2-sdk-node")

// 6. Upsert data in partitions
data = [
    {id: 10, vector: [0.06998888224297328, 0.8582816610326578, -0.9657938677934292, 0.6527905683627726, -0.8668460657158576], title: "Layour Design Reference", issue: "vol.34"},
    {id: 11, vector: [0.6060703043917468, -0.3765080534566074, -0.7710758854987239, 0.36993888322346136, 0.5507513364206531], title: "Doraemon and His Friends", issue: "vol.2"},
    {id: 12, vector: [-0.9041813104515337, -0.9610546012461163, 0.20033003106083358, 0.11842506351635174, 0.8327356724591011], title: "Pikkachu and Pokemon", issue: "vol.12"},
]

res = await client.upsert({
    collection_name: "my_collection",
    data: data,
    partition_name: "partitionA"
})

console.log(res.upsert_cnt)

// Output
// 
// 3
// 
titleColumn = column.NewColumnString("title", []string{
    "Layour Design Reference", "Doraemon and His Friends", "Pikkachu and Pokemon", 
})
issueColumn = column.NewColumnString("issue", []string{
    "vol.34", "vol.2", "vol.12", 
})

_, err = client.Upsert(ctx, milvusclient.NewColumnBasedInsertOption("my_collection").
    WithPartition("partitionA").
    WithInt64Column("id", []int64{10, 11, 12, 13, 14, 15, 16, 17, 18, 19}).
    WithFloatVectorColumn("vector", 5, [][]float32{
        {0.3580376395471989, -0.6023495712049978, 0.18414012509913835, -0.26286205330961354, 0.9029438446296592},
        {0.19886812562848388, 0.06023560599112088, 0.6976963061752597, 0.2614474506242501, 0.838729485096104},
        {0.43742130801983836, -0.5597502546264526, 0.6457887650909682, 0.7894058910881185, 0.20785793220625592},
    }).
    WithColumns(titleColumn, issueColumn),
)
if err != nil {
    fmt.Println(err.Error())
    // handle err
}
export CLUSTER_ENDPOINT="http://localhost:19530"
export TOKEN="root:Milvus"

curl --request POST \
--url "${CLUSTER_ENDPOINT}/v2/vectordb/entities/upsert" \
--header "Authorization: Bearer ${TOKEN}" \
--header "Content-Type: application/json" \
--header "Request-Timeout: 10" \
-d '{
    "data": [
        {"id": 10, "vector": [0.06998888224297328, 0.8582816610326578, -0.9657938677934292, 0.6527905683627726, -0.8668460657158576], "title": "Layour Design Reference", "issue": "vol.34"},
        {"id": 11, "vector": [0.6060703043917468, -0.3765080534566074, -0.7710758854987239, 0.36993888322346136, 0.5507513364206531], "title": "Doraemon and His Friends", "issue": "vol.2"},
        {"id": 12, "vector": [-0.9041813104515337, -0.9610546012461163, 0.20033003106083358, 0.11842506351635174, 0.8327356724591011], "title": "Pikkachu and Pokemon", "issue": "vol.12"},
    ],
    "collectionName": "my_collection",
    "partitionName": "partitionA"
}'

# {
#     "code": 0,
#     "data": {
#         "upsertCount": 3,
#         "upsertIds": [
#             10,
#             11,
#             12,
#         ]
#     }
# }

マージモードでのエンティティのアップサートCompatible with Milvus v2.6.2+

次の例では、my_collection 内のプライマリキーが1 および2 であるエンティティについて、issue フィールドのみを更新します。実行する前に、両方のエンティティがすでに存在していることを確認してください。その他のフィールドは現在の値のまま保持されます。

マージモードでアップサートを実行する際は、リクエストの対象となるエンティティが同一のフィールドセットを持っていることを確認してください。以下のコードスニペットに示すように、アップサート対象となるエンティティが2つ以上ある場合、エラーを防止し、データの整合性を維持するために、それらに同一のフィールドが含まれていることが重要です。

data=[
    {
        "id": 1,
        "issue": "vol.14"
    },
    {
        "id": 2, 
        "issue": "vol.7"
    }
]

res = client.upsert(
    collection_name="my_collection",
    data=data,
    partial_update=True
)

print(res)

# Output
# {'upsert_count': 2}
JsonObject row1 = new JsonObject();
row1.addProperty("id", 1);
row1.addProperty("issue", "vol.14");

JsonObject row2 = new JsonObject();
row2.addProperty("id", 2);
row2.addProperty("issue", "vol.7");

UpsertReq upsertReq = UpsertReq.builder()
        .collectionName("my_collection")
        .data(Arrays.asList(row1, row2))
        .partialUpdate(true)
        .build();

UpsertResp upsertResp = client.upsert(upsertReq);
System.out.println(upsertResp);

// Output:
//
// UpsertResp(upsertCnt=2)
pkColumn := column.NewColumnInt64("id", []int64{1, 2})
issueColumn = column.NewColumnString("issue", []string{
    "vol.17", "vol.7",
})

_, err = client.Upsert(ctx, milvusclient.NewColumnBasedInsertOption("my_collection").
    WithColumns(pkColumn, issueColumn).
    WithPartialUpdate(true),
)
if err != nil {
    fmt.Println(err.Error())
    // handle err
}
const data=[
    {
        "id": 1,
        "issue": "vol.14"
    },
    {
        "id": 2, 
        "issue": "vol.7"
    }
];

const res = await client.upsert({
    collection_name: "my_collection",
    data,
    partial_update: true
});

console.log(res)

// Output
// 
// 2
// 
export CLUSTER_ENDPOINT="http://localhost:19530"
export TOKEN="root:Milvus"

export COLLECTION_NAME="my_collection"
export UPSERT_DATA='[
  {
    "id": 1,
    "issue": "vol.14"
  },
  {
    "id": 2,
    "issue": "vol.7"
  }
]'

curl -X POST "http://localhost:19530/v2/vectordb/entities/upsert" \
  -H "Content-Type: application/json" \
  -H "Request-Timeout: 10" \
  -H "Authorization: Bearer ${TOKEN}" \
  -d "{
    \"collectionName\": \"${COLLECTION_NAME}\",
    \"data\": ${UPSERT_DATA},
    \"partialUpdate\": true
  }"

# {
#     "code": 0,
#     "data": {
#         "upsertCount": 2,
#         "upsertIds": [
#              3,
#             12,
#         ]
#     }
# }

マージモードでの ARRAY フィールドのアップサートCompatible with Milvus 2.6.17+

Milvus v2.6.17 以前では、ARRAY フィールドの一部を更新するには、クライアント側での「読み取り→変更→書き込み」というフローが必要でした。つまり、既存の配列をクエリで取得し、アプリケーションコード内で変更を加え、置き換え用の完全な値をアップサートする必要がありました。 部分更新演算子(ARRAY_APPEND およびARRAY_REMOVE )を使用すると、追加または削除する要素のみを送信できるため、クライアント側のロジックを削減し、Upsert前の余分な読み取りを回避できます。

主キーが `1 ` のエンティティに、すでに `tags = ["new", "trial"]` が設定されていると仮定します。部分更新演算子が導入される前は、配列に要素 `"premium" ` を追加するには、置き換え用の配列全体をアップサートする必要がありました:

client.upsert(
    collection_name="users",
    data=[{"pk": 1, "tags": ["new", "trial", "premium"]}],
    partial_update=True,
)
List<JsonObject> replacementData = Collections.singletonList(
        gson.fromJson("{\"pk\": 1, \"tags\": [\"new\", \"trial\", \"premium\"]}", JsonObject.class)
);

client.upsert(UpsertReq.builder()
        .collectionName("users")
        .partialUpdate(true)
        .data(replacementData)
        .build());
// nodejs
// go
# restful

ARRAY_APPEND を使用すれば、追加する要素のみを送信すれば済みます:

client.upsert(
    collection_name="users",
    data=[{"pk": 1, "tags": ["premium"]}],
    field_ops={"tags": FieldOp.array_append()},
)
List<JsonObject> appendData = Collections.singletonList(
        gson.fromJson("{\"pk\": 1, \"tags\": [\"premium\"]}", JsonObject.class)
);

UpsertReq.FieldPartialUpdateOp appendTags = UpsertReq.FieldPartialUpdateOp.builder()
        .fieldName("tags")
        .opType(UpsertReq.FieldPartialUpdateOp.OpType.ARRAY_APPEND)
        .build();

client.upsert(UpsertReq.builder()
        .collectionName("users")
        .data(appendData)
        .fieldOps(Collections.singletonList(appendTags))
        .build());
// nodejs
// go
# restful

ARRAY_REMOVE を使用すると、削除対象の要素のみを送信します:

client.upsert(
    collection_name="users",
    data=[{"pk": 1, "tags": ["trial"]}],
    field_ops={"tags": FieldOp.array_remove()},
)
List<JsonObject> removeData = Collections.singletonList(
        gson.fromJson("{\"pk\": 1, \"tags\": [\"trial\"]}", JsonObject.class)
);

UpsertReq.FieldPartialUpdateOp removeTags = UpsertReq.FieldPartialUpdateOp.builder()
        .fieldName("tags")
        .opType(UpsertReq.FieldPartialUpdateOp.OpType.ARRAY_REMOVE)
        .build();

client.upsert(UpsertReq.builder()
        .collectionName("users")
        .data(removeData)
        .fieldOps(Collections.singletonList(removeTags))
        .build());
// nodejs
// go
# restful

field_ops を使用していずれかの演算子をフィールドに適用すると、部分更新のセマンティクスが暗黙的に有効になります。したがって、field_ops と一緒にpartial_update=True を渡す必要はありません。

制限事項

  • ペイロードの値は、対象のARRAY フィールドのelement_type と一致する必要があります。たとえば、対象フィールドがARRAY<VARCHAR> の場合、ペイロードには文字列値が含まれている必要があります。

  • Milvus v2.6.17 以降では、ARRAY_APPEND およびARRAY_REMOVE は、element_type がBOOL 、INT8 、INT16 、INT32 、INT64 、FLOAT 、DOUBLE 、またはVARCHAR のいずれかであるARRAY フィールドをサポートしています。

  • ARRAY_APPEND 操作の後、結果の配列の長さは、そのフィールドのmax_capacity を超えてはなりません。

  • 同一エンティティに対する並行したアップサートは、リクエスト間でアトミックではありません。2つのリクエストが同時に同じARRAY フィールドを更新した場合、後に行われた書き込みが先行する書き込みを上書きする可能性があります。すべての並行変更を保持する必要がある場合は、アプリケーションレベルでの調整を行ってください。

例

次の例では、主キーpk 、型がARRAY<VARCHAR> のtags フィールド、およびembedding ベクトルフィールドを持つ小さなusers コレクションを使用しています。まず、初期値tags を持つ2つのエンティティを挿入し、次にARRAY_APPEND およびARRAY_REMOVE を使用して、各演算子が格納された配列をどのように変更するかを示します。

from pymilvus import DataType, FieldOp, MilvusClient

client = MilvusClient(
    uri="http://localhost:19530",
    token="root:Milvus"
)

# 1. Create a collection with an ARRAY<VARCHAR> field
schema = client.create_schema(enable_dynamic_field=False)
schema.add_field("pk", DataType.INT64, is_primary=True)
schema.add_field("embedding", DataType.FLOAT_VECTOR, dim=5)
schema.add_field(
    "tags",
    DataType.ARRAY,
    element_type=DataType.VARCHAR,
    max_capacity=8,
    max_length=32,
)

index_params = client.prepare_index_params()
index_params.add_index(
    field_name="embedding",
    index_type="AUTOINDEX",
    metric_type="L2",
)

client.create_collection(
    collection_name="users",
    schema=schema,
    index_params=index_params
)

# 2. Seed two entities
client.insert(
    collection_name="users",
    data=[
        {"pk": 1, "embedding": [0.1, 0.2, 0.3, 0.4, 0.5], "tags": ["new"]},
        {"pk": 2, "embedding": [0.6, 0.7, 0.8, 0.9, 1.0], "tags": ["new", "trial"]},
    ],
)

# 3. Append tags without reading the existing ARRAY values
client.upsert(
    collection_name="users",
    data=[
        {"pk": 1, "tags": ["premium", "vip"]},
        {"pk": 2, "tags": ["premium"]},
    ],
    field_ops={"tags": FieldOp.array_append()},
)

res = client.query(
    collection_name="users",
    filter="pk in [1, 2]",
    output_fields=["pk", "tags"],
)
print(res)

# Example output:
# data: [
#   "{'pk': 1, 'tags': ['new', 'premium', 'vip']}",
#   "{'pk': 2, 'tags': ['new', 'trial', 'premium']}"
# ]

# 4. Remove matching tags without replacing the full ARRAY field
client.upsert(
    collection_name="users",
    data=[
        {"pk": 1, "tags": ["new"]},
        {"pk": 2, "tags": ["trial"]},
    ],
    field_ops={"tags": FieldOp.array_remove()},
)

res = client.query(
    collection_name="users",
    filter="pk in [1, 2]",
    output_fields=["pk", "tags"],
)
print(res)

# Example output:
# data: [
#   "{'pk': 1, 'tags': ['premium', 'vip']}",
#   "{'pk': 2, 'tags': ['new', 'premium']}"
# ]
// java
// nodejs
// go
# restful

マージモードでの StructArray フィールドのアップサート

エンティティ内の StructArray フィールドをアップサートすると、そのフィールドの値は上書きされます。つまり、StructArray フィールドをアップサートする際には、構造体スキーマで定義されているすべてのサブフィールドを含める必要があります。

以下の例は、6つのサブフィールドを持つStructArrayフィールド「chunks 」をマージモードでアップサートする方法を示しています。操作が完了すると、IDが1のエンティティのchunks フィールドは、リクエストで指定された2つの要素を持つ構造体を含む配列に設定されます。

client.upsert(
    collection_name="books",
    data=[{
        "id": 1,
        "chunks": [
            {
              "text": "Use HNSW efSearch to trade recall for latency.",
              "section": "index",
              "page": 1,
              "quality_score": 0.92,
              "has_code": True,
              "emb_list_vector": [0.11, 0.21, 0.31, 0.41]
            },
            {
              "text": "Range search returns vectors within a distance boundary.",
              "section": "search",
              "page": 2,
              "quality_score": 0.86,
              "has_code": False,
              "emb_list_vector": [0.18, 0.23, 0.29, 0.36]
            }
        ]
    }],
    partial_update=True
)
// java
// nodejs
// go
# restful