• О Милвусе
  • Начать
  • Концепции
  • Руководство пользователя
  • Импорт данных
  • Инструменты искусственного интеллекта
  • Руководство по администрированию
  • Инструменты
  • Интеграции
  • Учебники
  • Вопросы и ответы
  • API Reference

Вставка сущностей

Операция upsert предоставляет удобный способ вставки или обновления сущностей в коллекции.

Обзор

С помощью upsert можно вставить новую сущность или обновить существующую, в зависимости от того, существует ли в коллекции первичный ключ, указанный в запросе upsert. Если первичный ключ не найден, выполняется операция вставки. В противном случае будет выполнена операция обновления.

Апсерт в Milvus работает либо в режиме переопределения, либо в режиме слияния.

Апсерт в режиме переопределения

Запрос upsert, работающий в режиме переопределения, сочетает вставку и удаление. Когда поступает запрос upsert для существующей сущности, Milvus вставляет данные, содержащиеся в полезной нагрузке запроса, и одновременно удаляет существующую сущность с исходным первичным ключом, указанным в данных.

Upsert In Override Mode Upsert в режиме переопределения

Если для целевой коллекции включено autoid для ее первичного поля, Milvus сгенерирует новый первичный ключ для данных, содержащихся в полезной нагрузке запроса, прежде чем вставить их.

Для полей с включенным nullable можно опустить их в запросе upsert, если они не требуют обновлений.

Вставка в режиме слиянияCompatible with Milvus v2.6.2+

Вы также можете использовать флаг partial_update, чтобы заставить запрос upsert работать в режиме слияния. Это позволит вам включить в полезную нагрузку запроса только те поля, которые требуют обновления.

Upsert In Merge Mode Upsert в режиме слияния

Чтобы выполнить слияние, установите флаг partial_update на True в запросе upsert вместе с первичным ключом и полями, которые нужно обновить с их новыми значениями.

Получив такой запрос, Milvus выполняет запрос с сильной согласованностью для получения сущности, обновляет значения полей на основе данных в запросе, вставляет измененные данные, а затем удаляет существующую сущность с исходным первичным ключом, указанным в запросе.

Поведение при апсерте: особые указания

Перед использованием функции слияния следует учесть несколько особых замечаний. В следующих случаях предполагается, что у вас есть коллекция с двумя скалярными полями title и issue, с первичным ключом id и векторным полем vector.

  • Верхние поля с включенной функцией nullable .

    Предположим, что поле issue может быть нулевым. При апсерте этих полей обратите внимание на это:

    • Если вы опустите поле issue в запросе upsert и отключите partial_update, поле issue будет обновлено до null вместо того, чтобы сохранить свое первоначальное значение.

    • Чтобы сохранить исходное значение поля issue, нужно либо включить partial_update и опустить поле issue, либо включить поле issue с его исходным значением в запрос upsert.

  • Перенос ключей в динамическое поле.

    Предположим, что вы включили динамический ключ в коллекции примера, и пары ключ-значение в динамическом поле сущности похожи на {"author": "John", "year": 2020, "tags": ["fiction"]}.

    При апсерте сущности с ключами, такими как author, year, или tags, или при добавлении других ключей, обратите внимание на это:

    • Если вы выполняете апсерт с отключенным partial_update, поведение по умолчанию - переопределение. Это означает, что значение динамического поля будет переопределено всеми не определенными схемой полями, включенными в запрос, и их значениями.

      Например, если данные, включенные в запрос, имеют значение {"author": "Jane", "genre": "fantasy"}, то пары ключ-значение в динамическом поле целевой сущности будут обновлены до этого значения.

    • Если вы выполняете апсерт с включенным partial_update, то по умолчанию будет выполняться слияние. Это означает, что значение динамического поля будет объединено со всеми не определенными схемой полями, включенными в запрос, и их значениями.

      Например, если данные, включенные в запрос, имеют значение {"author": "John", "year": 2020, "tags": ["fiction"]}, то пары ключ-значение в динамическом поле целевой сущности после апсерт станут {"author": "John", "year": 2020, "tags": ["fiction"], "genre": "fantasy"}.

  • Апсертирование поля JSON.

    Предположим, что в коллекции примеров есть определенное схемой поле JSON с именем extras, а пары ключ-значение в этом поле JSON сущности похожи на {"author": "John", "year": 2020, "tags": ["fiction"]}.

    При апселлинге поля extras сущности с измененными JSON-данными обратите внимание, что JSON-поле рассматривается как единое целое, и вы не можете обновлять отдельные ключи выборочно. Другими словами, поле JSON НЕ поддерживает upsert в режиме слияния.

Пределы и ограничения

Исходя из вышеизложенного, существует несколько ограничений и запретов, которые необходимо соблюдать:

  • Запрос upsert всегда должен включать первичные ключи целевых сущностей.

  • Целевая коллекция должна быть загружена и доступна для запросов.

  • Все поля, указанные в запросе, должны существовать в схеме целевой коллекции.

  • Значения всех полей, указанных в запросе, должны соответствовать типам данных, определенным в схеме.

  • Для любого поля, полученного из другого с помощью функций, Milvus удалит производное поле во время апсерта, чтобы обеспечить возможность пересчета.

Апсертирование сущностей в коллекции

В этом разделе мы выполним апсерт сущностей в коллекцию с именем my_collection. Эта коллекция имеет только два поля, названные id, vector, title и issue. Поле id является первичным полем, а поля title и issue - скалярными полями.

Эти три сущности, если они существуют в коллекции, будут переопределены сущностями, включенными в запрос 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" \
-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.

Три сущности, если они существуют в разделе, будут заменены сущностями, включенными в запрос.

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" \
-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+

Следующий пример кода демонстрирует, как выполнить апсерт сущностей с частичным обновлением. Укажите только поля, нуждающиеся в обновлении, и их новые значения, а также явный флаг частичного обновления.

В следующем примере поле issue сущностей, указанных в запросе upsert, будет обновлено до значений, включенных в запрос.

При выполнении upsert в режиме слияния убедитесь, что сущности, участвующие в запросе, имеют одинаковый набор полей. Предположим, есть две или более сущностей, которые нужно вставить, как показано в следующем фрагменте кода, важно, чтобы они содержали одинаковые поля для предотвращения ошибок и поддержания целостности данных.

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 "Authorization: Bearer ${TOKEN}" \
  -d "{
    \"collectionName\": \"${COLLECTION_NAME}\",
    \"data\": ${UPSERT_DATA},
    \"partialUpdate\": true
  }"

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