Kolom yang Dapat Bernilai NULL
Milvus mendukung bidang yang dapat bernilai NULL, yang memungkinkan nilai bidang tersebut tidak ada atau secara eksplisit ditetapkan ke NULL. Kemampuan untuk bernilai NULL ditentukan pada tingkat skema dan berlaku secara konsisten di seluruh operasi pengambilan data, pengindeksan, pencarian, dan kueri.
Gunakan kolom yang dapat bernilai NULL jika:
- Data diimpor dari sistem eksternal yang mengizinkan nilai yang kosong.
- Beberapa metadata bersifat opsional atau hanya tersedia untuk sebagian dari dataset.
- Embedding vektor dihasilkan secara asinkron dan disisipkan kemudian.
Batasan
Kolom vektor yang mengizinkan nilai NULL tidak mendukung ekspresi filter `
IS NULL` atau `IS NOT NULL`. Anda tidak dapat secara eksplisit menyaring entitas berdasarkan apakah nilai kolom vektor tersebut NULL.Mulai dari Milvus 3.0.0, bidang StructArray induk dapat dinyatakan sebagai nullable. Tetapkan "
nullable=True" pada bidang StructArray induk, bukan pada subbidang individual. Nilai NULL berlaku untuk seluruh bidang StructArray, bukan untuk elemen Struct individual, dan Milvus secara internal meneruskan sifat nullable dari induk ke subbidangnya. Bidang StructArray yang ditambahkan ke koleksi yang sudah ada harus bersifat nullable agar entitas yang sudah ada dapat mengembalikan nilai NULL untuk bidang baru tersebut. Untuk detailnya, lihat Batasan StructArray.Atribut nullable ditentukan saat bidang dibuat dan tidak dapat diubah setelahnya. Anda tidak dapat mengaktifkan atau menonaktifkan nullability untuk bidang yang sudah ada.
Bidang yang ditandai sebagai nullable tidak dapat digunakan sebagai kunci partisi. Bidang kunci partisi harus selalu berisi nilai yang valid dan tidak null. Untuk informasi lebih lanjut, lihat Gunakan Kunci Partisi.
Apa itu bidang yang dapat bernilai NULL?
Di Milvus, apakah suatu bidang diizinkan menyimpan nilai NULL dikendalikan oleh atribut bidang tingkat skema bernama ` nullable`.
Ketika sebuah bidang didefinisikan dengan ` nullable=True`, Milvus mengizinkan nilai bidang tersebut tidak ada selama proses pengambilan data. Dalam praktiknya, Milvus memperlakukan dua masukan berikut sebagai setara dan menyimpan nilai bidang sebagai `NULL`:
- Kolom tersebut dihilangkan dari entitas masukan.
- Kolom tersebut secara eksplisit ditetapkan ke NULL (misalnya, `
None` dalam Python).
Jika suatu bidang tidak didefinisikan sebagai nullable (perilaku default), setiap entitas harus menyediakan nilai yang valid untuk bidang tersebut. Mengabaikan bidang atau secara eksplisit menetapkan nilai NULL akan menyebabkan operasi penyisipan atau impor gagal.
Atribut nullable didukung untuk bidang skalar dan vektor dalam skema koleksi. Mulai dari Milvus 3.0.0, atribut ini juga didukung pada bidang induk StructArray. Jangan mengonfigurasi subbidang Struct sebagai nullable secara terpisah; tentukan nullability pada induk StructArray dan Milvus akan menyebarkan pengaturan tersebut ke subbidangnya secara internal.
Nullability menentukan apakah nilai bidang boleh tidak ada; hal ini tidak menentukan nilai apa yang digunakan ketika suatu bidang tidak ada.
- Jika bidang yang dapat bernilai null dikonfigurasi tanpa nilai default, mengabaikan bidang tersebut akan menghasilkan nilai NULL yang disimpan.
- Jika nilai default telah dikonfigurasi, Milvus mungkin akan menyimpan nilai default tersebut sebagai gantinya. Untuk detailnya, lihat Nilai Default.
Tentukan bidang yang dapat bernilai null dalam skema koleksi
Untuk menggunakan bidang nullable, Anda harus mengaktifkan atribut nullable saat mendefinisikan skema koleksi.
Dalam contoh ini, skema koleksi mendefinisikan bidang vektor bernama ` embedding ` dengan ` nullable=True`. Hal ini memungkinkan entitas dalam koleksi untuk mengosongkan nilai vektor atau secara eksplisit menetapkannya ke `NULL` selama proses pengambilan data.
from pymilvus import MilvusClient, DataType
client = MilvusClient(
uri="http://localhost:19530",
token="root:Milvus"
)
# Define schema fields
schema = client.create_schema()
schema.add_field("id", DataType.INT64, is_primary=True) # Primary field
schema.add_field(
field_name="embedding",
datatype=DataType.FLOAT_VECTOR,
dim=4,
nullable=True, # Enable the nullable attribute; defaults to False
)
client.create_collection(
collection_name="my_collection",
schema=schema,
)
import io.milvus.v2.client.ConnectConfig;
import io.milvus.v2.client.MilvusClientV2;
import io.milvus.v2.common.DataType;
import io.milvus.v2.service.collection.request.AddFieldReq;
import io.milvus.v2.service.collection.request.CreateCollectionReq;
MilvusClientV2 client = new MilvusClientV2(ConnectConfig.builder()
.uri("http://localhost:19530")
.token("root:Milvus")
.build());
CreateCollectionReq.CollectionSchema schema = CreateCollectionReq.CollectionSchema.builder()
.build();
schema.addField(AddFieldReq.builder()
.fieldName("id")
.dataType(DataType.Int64)
.isPrimaryKey(true)
.build());
schema.addField(AddFieldReq.builder()
.fieldName("embedding")
.dataType(DataType.FloatVector)
.dimension(4)
.isNullable(true)
.build());
client.createCollection(CreateCollectionReq.builder()
.collectionName("my_collection")
.collectionSchema(schema)
.build());
import { MilvusClient, DataType } from "@zilliz/milvus2-sdk-node";
const client = new MilvusClient({
address: "http://localhost:19530",
token: "root:Milvus",
});
await client.createCollection({
collection_name: "my_collection",
fields: [
{
name: "id",
data_type: DataType.Int64,
is_primary_key: true,
autoID: false,
},
{
name: "embedding",
data_type: DataType.FloatVector,
dim: 4,
nullable: true,
},
],
});
import (
"context"
"fmt"
"github.com/milvus-io/milvus/client/v2/entity"
"github.com/milvus-io/milvus/client/v2/milvusclient"
)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
client, err := milvusclient.New(ctx, &milvusclient.ClientConfig{
Address: "localhost:19530",
})
if err != nil {
fmt.Println(err.Error())
// handle error
}
defer client.Close(ctx)
schema := entity.NewSchema()
schema.WithField(entity.NewField().
WithName("id").
WithDataType(entity.FieldTypeInt64).
WithIsPrimaryKey(true),
).WithField(entity.NewField().
WithName("embedding").
WithDataType(entity.FieldTypeFloatVector).
WithDim(4).
WithNullable(true),
)
err = client.CreateCollection(ctx,
milvusclient.NewCreateCollectionOption("my_collection", schema))
if err != nil {
fmt.Println(err.Error())
// handle error
}
export TOKEN="root:Milvus"
export CLUSTER_ENDPOINT="http://localhost:19530"
export pkField='{
"fieldName": "id",
"dataType": "Int64",
"isPrimary": true
}'
export embeddingField='{
"fieldName": "embedding",
"dataType": "FloatVector",
"typeParams": {"dim": "4"},
"nullable": true
}'
curl --request POST \
--url "${CLUSTER_ENDPOINT}/v2/vectordb/collections/create" \
--header "Authorization: Bearer ${TOKEN}" \
--header "Content-Type: application/json" \
--header "Request-Timeout: 10" \
-d "{
\"collectionName\": \"my_collection\",
\"schema\": {
\"fields\": [
$pkField,
$embeddingField
]
}
}"
Dalam skema ini:
- Bidang `
embedding` secara eksplisit ditandai sebagai nullable. - Entitas dapat mengabaikan bidang `
embedding` atau menetapkan nilainya menjadi NULL selama proses penyisipan. - Keputusan untuk mengizinkan nilai NULL ditetapkan pada saat pembuatan koleksi.
Untuk kejelasan, contoh-contoh berikut berfokus pada bidang vektor yang dapat bernilai NULL (embedding). Mendefinisikan bidang skalar yang dapat bernilai NULL bersifat opsional dan tidak wajib untuk mengikuti sisa panduan ini.
Opsional: Menentukan bidang skalar yang dapat bernilai NULL
Bidang skalar juga dapat didefinisikan sebagai bidang yang dapat bernilai NULL menggunakan atribut ` nullable ` yang sama dan mengikuti aturan yang sama selama proses pengambilan data. Contohnya:
schema.add_field(
field_name="age",
datatype=DataType.INT64,
nullable=True,
)
schema.addField(AddFieldReq.builder()
.fieldName("age")
.dataType(DataType.Int64)
.isNullable(true)
.build());
// Add to the fields array when calling createCollection:
// { name: "age", data_type: DataType.Int64, nullable: true },
schema.WithField(entity.NewField().
WithName("age").
WithDataType(entity.FieldTypeInt64).
WithNullable(true),
)
# Add another field object to the schema "fields" array, for example:
# { "fieldName": "age", "dataType": "Int64", "nullable": true }
Perilaku penyisipan dengan nilai yang hilang atau NULL
Setelah suatu bidang didefinisikan sebagai nullable dalam skema koleksi, Milvus mengizinkan nilai bidang tersebut hilang atau secara eksplisit ditetapkan ke NULL selama proses ingestion data.
Contoh di bawah ini menyisipkan tiga entitas ke dalam koleksi yang dibuat dalam bagian " Mendefinisikan bidang yang dapat bernilai NULL" dalam skema koleksi, yang menunjukkan berbagai kasus ini.
data = [
{
"id": 1,
"embedding": [0.1, 0.2, 0.3, 0.4],
},
{
"id": 2,
"embedding": None, # Explicitly set to NULL
},
{
"id": 3, # Field omitted → stored as NULL
},
]
client.insert(
collection_name="my_collection",
data=data,
)
import com.google.gson.Gson;
import com.google.gson.JsonNull;
import com.google.gson.JsonObject;
import io.milvus.v2.service.vector.request.InsertReq;
import java.util.Arrays;
import java.util.List;
Gson gson = new Gson();
JsonObject row1 = new JsonObject();
row1.addProperty("id", 1);
row1.add("embedding", gson.toJsonTree(Arrays.asList(0.1f, 0.2f, 0.3f, 0.4f)));
JsonObject row2 = new JsonObject();
row2.addProperty("id", 2);
row2.add("embedding", JsonNull.INSTANCE); // Explicitly set to NULL
JsonObject row3 = new JsonObject();
row3.addProperty("id", 3); // Field omitted; stored as NULL
List<JsonObject> data = Arrays.asList(row1, row2, row3);
client.insert(InsertReq.builder()
.collectionName("my_collection")
.data(data)
.build());
const data = [
{ id: 1, embedding: [0.1, 0.2, 0.3, 0.4] },
{ id: 2, embedding: null },
{ id: 3 },
];
await client.insert({
collection_name: "my_collection",
data: data,
});
import (
"context"
"fmt"
"github.com/milvus-io/milvus/client/v2/milvusclient"
)
// Assumes `client` is the Milvus client from the Go schema example above.
ctx := context.Background()
rows := []any{
map[string]any{"id": int64(1), "embedding": []float32{0.1, 0.2, 0.3, 0.4}},
map[string]any{"id": int64(2), "embedding": nil},
map[string]any{"id": int64(3)},
}
_, err := client.Insert(ctx, milvusclient.NewRowBasedInsertOption("my_collection", rows...))
if err != nil {
fmt.Println(err.Error())
}
curl --request POST \
--url "${CLUSTER_ENDPOINT}/v2/vectordb/entities/insert" \
--header "Authorization: Bearer ${TOKEN}" \
--header "Content-Type: application/json" \
--header "Request-Timeout: 10" \
-d '{
"collectionName": "my_collection",
"data": [
{"id": 1, "embedding": [0.1, 0.2, 0.3, 0.4]},
{"id": 2, "embedding": null},
{"id": 3}
]
}'
Dalam contoh ini:
- Entitas id = 1 menyediakan nilai vektor yang valid.
- Entitas dengan ID = 2 secara eksplisit menetapkan nilai NULL ke bidang `
embedding`. - Entitas dengan ID = 3 mengabaikan bidang `
embedding` sepenuhnya; Milvus menyimpannya sebagai NULL.
Perilaku indeks pada bidang yang dapat bernilai NULL
Setelah menyisipkan data, Anda dapat membuat indeks pada bidang yang dapat bernilai NULL seperti biasa. Perbedaan utamanya adalah cara Milvus menangani nilai NULL selama pembuatan indeks:
- Hanya entitas dengan nilai non-NULL yang ditambahkan ke indeks.
- Entitas dengan nilai NULL dilewati dan tidak ikut serta dalam pembuatan indeks.
Untuk bidang vektor yang dapat bernilai NULL, ini berarti hanya entitas dengan vektor yang valid yang dapat dicari berdasarkan kesamaan vektor.
# Set index parameters
index_params = client.prepare_index_params()
index_params.add_index(
field_name="embedding",
index_type="AUTOINDEX",
metric_type="COSINE",
)
# Create index
client.create_index(
collection_name="my_collection",
index_params=index_params,
)
# Load collection for future search operations
client.load_collection(collection_name="my_collection")
import io.milvus.v2.common.IndexParam;
import io.milvus.v2.service.collection.request.LoadCollectionReq;
import io.milvus.v2.service.index.request.CreateIndexReq;
import java.util.Collections;
IndexParam indexParam = IndexParam.builder()
.fieldName("embedding")
.indexName("embedding_index")
.indexType(IndexParam.IndexType.AUTOINDEX)
.metricType(IndexParam.MetricType.COSINE)
.build();
client.createIndex(CreateIndexReq.builder()
.collectionName("my_collection")
.indexParams(Collections.singletonList(indexParam))
.build());
client.loadCollection(LoadCollectionReq.builder()
.collectionName("my_collection")
.build());
await client.createIndex({
collection_name: "my_collection",
field_name: "embedding",
index_name: "embedding_idx",
index_type: "AUTOINDEX",
metric_type: "COSINE",
});
await client.loadCollection({
collection_name: "my_collection",
});
import (
"context"
"fmt"
"github.com/milvus-io/milvus/client/v2/entity"
"github.com/milvus-io/milvus/client/v2/index"
"github.com/milvus-io/milvus/client/v2/milvusclient"
)
// Assumes `client` is the Milvus client from the Go schema example above.
ctx := context.Background()
indexOption := milvusclient.NewCreateIndexOption("my_collection", "embedding",
index.NewAutoIndex(entity.COSINE))
_, err := client.CreateIndex(ctx, indexOption)
if err != nil {
fmt.Println(err.Error())
}
_, err = client.LoadCollection(ctx, milvusclient.NewLoadCollectionOption("my_collection"))
if err != nil {
fmt.Println(err.Error())
}
curl --request POST \
--url "${CLUSTER_ENDPOINT}/v2/vectordb/indexes/create" \
--header "Authorization: Bearer ${TOKEN}" \
--header "Content-Type: application/json" \
--header "Request-Timeout: 10" \
-d '{
"collectionName": "my_collection",
"indexParams": [
{
"fieldName": "embedding",
"metricType": "COSINE",
"indexType": "AUTOINDEX"
}
]
}'
curl --request POST \
--url "${CLUSTER_ENDPOINT}/v2/vectordb/collections/load" \
--header "Authorization: Bearer ${TOKEN}" \
--header "Content-Type: application/json" \
--header "Request-Timeout: 10" \
-d '{"collectionName": "my_collection"}'
Pada tahap ini:
- Entitas dengan nilai embedding yang valid diindeks dan siap untuk pencarian.
- Entitas yang embedding-nya NULL tetap berada dalam koleksi, tetapi tidak dimasukkan ke dalam indeks vektor.
Perilaku pencarian dengan bidang yang dapat bernilai NULL
Saat Anda melakukan operasi pencarian pada bidang yang dapat bernilai null, Milvus hanya mengevaluasi entitas dengan nilai non-null untuk bidang yang digunakan dalam pencarian. Entitas yang bidang vektornya bernilai NULL akan dilewati secara otomatis.
Untuk bidang vektor yang dapat bernilai NULL seperti ` embedding ` dalam contoh ini:
- Hanya entitas dengan nilai vektor yang valid yang dievaluasi dan diberi peringkat.
- Entitas dengan vektor NULL tidak menyebabkan kesalahan.
- Jika jumlah vektor yang valid lebih kecil dari
topKyang diminta (limit), Milvus mungkin mengembalikan hasil yang lebih sedikit daripadalimit.
Contoh berikut melakukan pencarian vektor pada bidang vektor yang dapat bernilai NULL ( embedding):
res = client.search(
collection_name="my_collection",
data=[[0.1, 0.2, 0.3, 0.4]],
anns_field="embedding",
limit=3,
search_params={"metric_type": "COSINE"},
output_fields=["embedding"],
)
print(res)
import io.milvus.v2.service.vector.request.SearchReq;
import io.milvus.v2.service.vector.request.data.FloatVec;
import io.milvus.v2.service.vector.response.SearchResp;
import java.util.Arrays;
import java.util.Collections;
SearchResp res = client.search(SearchReq.builder()
.collectionName("my_collection")
.data(Collections.singletonList(new FloatVec(Arrays.asList(0.1f, 0.2f, 0.3f, 0.4f))))
.annsField("embedding")
.limit(3)
.outputFields(Collections.singletonList("embedding"))
.build());
System.out.println(res);
const res = await client.search({
collection_name: "my_collection",
data: [[0.1, 0.2, 0.3, 0.4]],
anns_field: "embedding",
limit: 3,
search_params: { metric_type: "COSINE" },
output_fields: ["embedding"],
});
console.log(res);
import (
"context"
"fmt"
"github.com/milvus-io/milvus/client/v2/entity"
"github.com/milvus-io/milvus/client/v2/milvusclient"
)
// Assumes `client` is the Milvus client from the Go schema example above.
ctx := context.Background()
query := []float32{0.1, 0.2, 0.3, 0.4}
resultSets, err := client.Search(ctx, milvusclient.NewSearchOption(
"my_collection",
3,
[]entity.Vector{entity.FloatVector(query)},
).WithANNSField("embedding").
WithOutputFields("embedding"))
if err != nil {
fmt.Println(err.Error())
}
fmt.Println(resultSets)
curl --request POST \
--url "${CLUSTER_ENDPOINT}/v2/vectordb/entities/search" \
--header "Authorization: Bearer ${TOKEN}" \
--header "Content-Type: application/json" \
--header "Request-Timeout: 10" \
-d '{
"collectionName": "my_collection",
"data": [[0.1, 0.2, 0.3, 0.4]],
"annsField": "embedding",
"limit": 3,
"searchParams": {"metricType": "COSINE"},
"outputFields": ["embedding"]
}'
Dalam pencarian ini:
- Hanya entitas dengan nilai
embeddingyang tidak null yang dipertimbangkan sebagai kandidat. - Entitas dengan nilai NULL untuk
embeddingtidak dimasukkan dalam evaluasi. - Jumlah hasil yang dikembalikan bergantung pada berapa banyak vektor valid yang ada dalam koleksi.
Implikasi kueri dan penyaringan
Contoh-contoh sebelumnya berfokus pada bidang vektor. Bagian ini menjelaskan bagaimana nilai NULL berperilaku dalam ekspresi penyaringan skalar.
Bidang skalar dapat didefinisikan dengan nullable=True dan mengikuti aturan pengambilan data yang sama seperti bidang vektor. Namun, nilai skalar NULL selalu dievaluasi sebagai false dalam ekspresi penyaringan.
Misalnya, dengan bidang skalar yang dapat bernilai NULL age, filter berikut memilih entitas yang usianya lebih dari 18:
expr = "age > 18"
String filter = "age > 18";
const expr = "age > 18";
filter := "age > 18"
# Use in query/search filter parameter, for example:
# "filter": "age > 18"
Entitas yang memiliki nilai ` age ` sebagai `NULL` dikecualikan dari hasil karena nilai `NULL` tidak memenuhi kondisi filter.
Demikian pula, pemeriksaan kesamaan tidak cocok dengan nilai NULL. Contohnya:
expr = 'status == "active"'
String filter = "status == \"active\"";
const expr = 'status == "active"';
filter := `status == "active"`
# "filter": "status == \"active\""
Entitas yang memiliki nilai ` status ` sebagai `NULL` dikecualikan dari hasil.
Kolom yang dapat bernilai NULL dan nilai default
Ketika baik ` nullable ` maupun ` default_value ` dikonfigurasi untuk suatu bidang, aturan berikut menentukan cara Milvus menangani input `NULL` atau nilai bidang yang hilang selama penyisipan.
| Nullable diaktifkan | Nilai default | Masukan pengguna (NULL atau diabaikan) | Hasil |
|---|---|---|---|
| Ya | Ya (bukan NULL) | NULL atau diabaikan | Menggunakan nilai default |
| Ya | Tidak | NULL atau diabaikan | Disimpan sebagai NULL |
| Tidak | Ya (bukan NULL) | NULL atau diabaikan | Menggunakan nilai default |
| Tidak | Tidak | NULL atau tidak ditentukan | Menimbulkan kesalahan |
| Tidak | Ya (NULL secara default) | NULL atau diabaikan | Menimbulkan kesalahan |
Poin-poin penting:
- Jika suatu kolom memiliki nilai default yang bukan NULL, nilai tersebut akan digunakan terlepas dari apakah opsi "
nullable" diaktifkan atau tidak. - Jika "
nullable=True" diaktifkan tetapi tidak ada nilai default yang ditetapkan, bidang tersebut akan menyimpan nilai NULL. - Jika opsi "
nullable=False" diaktifkan tetapi tidak ada nilai default yang ditetapkan, proses penyisipan akan gagal dan menimbulkan kesalahan. - Menetapkan nilai default NULL pada kolom yang tidak boleh NULL adalah tidak valid dan menyebabkan kesalahan.
Untuk contoh lengkap dan penggunaan API terkait nilai default, lihat Nilai Default.