Campi nullabili
Milvus supporta i campi nullabili, che consentono a un valore di campo di essere assente o impostato esplicitamente su NULL. La nullabilità viene definita a livello di schema e si applica in modo coerente durante le operazioni di acquisizione dei dati, indicizzazione, ricerca e interrogazione.
Utilizzare i campi nullabili quando:
- I dati vengono acquisiti da sistemi esterni che consentono valori mancanti.
- Alcuni metadati sono facoltativi o disponibili solo per una parte del set di dati.
- Le rappresentazioni vettoriali vengono generate in modo asincrono e inserite in un secondo momento.
Limiti
I campi vettoriali che consentono valori NULL non supportano le espressioni di filtro del tipo "
IS NULL" o "IS NOT NULL". Non è possibile filtrare esplicitamente le entità in base al fatto che il valore di un campo vettoriale sia NULL.A partire da Milvus 3.0.0, il campo StructArray padre può essere nullabile. Impostare "
nullable=True" sul campo StructArray padre, non sui singoli sottocampi. Il valore NULL si applica all’intero campo StructArray, non a un singolo elemento Struct, e Milvus propaga internamente la nullabilità del campo padre ai suoi sottocampi. Un campo StructArray aggiunto a una raccolta esistente deve essere nullabile, in modo che le entità esistenti possano restituire NULL per il nuovo campo. Per ulteriori dettagli, consultare Limiti di StructArray.L’attributo di nullabilità viene definito al momento della creazione di un campo e non può essere modificato in seguito. Non è possibile abilitare o disabilitare la nullabilità per un campo esistente.
I campi contrassegnati come nullabili non possono essere utilizzati come chiavi di partizione. I campi delle chiavi di partizione devono sempre contenere valori validi e non nulli. Per ulteriori informazioni, consultare Utilizzo della chiave di partizione.
Che cos’è un campo nullabile?
In Milvus, la possibilità per un campo di memorizzare un valore NULL è controllata da un attributo di campo a livello di schema denominato ` nullable`.
Quando un campo è definito con l’attributo ` nullable=True`, Milvus consente che il valore del campo sia assente durante l’acquisizione dei dati. In pratica, Milvus considera equivalenti i due input seguenti e memorizza il valore del campo come NULL:
- Il campo è omesso dall’entità di input.
- Il campo è impostato esplicitamente su NULL (ad esempio,
Nonein Python).
Se un campo non è definito come nullabile (comportamento predefinito), ogni entità deve fornire un valore valido per quel campo. L’omissione del campo o l’assegnazione esplicita di un valore NULL causerà il fallimento dell’operazione di inserimento o importazione.
L'attributo "nullable" è supportato sia per i campi scalari che per quelli vettoriali in uno schema di raccolta. A partire da Milvus 3.0.0, è supportato anche sul campo padre StructArray. Non configurare i sottocampi Struct come nullabili in modo indipendente; definire la nullabilità sul campo padre StructArray e Milvus propagherà internamente tale impostazione ai suoi sottocampi.
La nullabilità determina se un valore di campo può essere assente; non definisce quale valore venga utilizzato quando un campo è assente.
- Se un campo nullabile viene configurato senza un valore predefinito, l’omissione del campo comporta la memorizzazione di un valore NULL.
- Se è stato configurato un valore predefinito, Milvus potrebbe memorizzare invece il valore predefinito. Per ulteriori dettagli, consultare la sezione Valori predefiniti.
Definizione di un campo nullable nello schema della collezione
Per utilizzare i campi nullabili, è necessario abilitare l'attributo `nullable` durante la definizione dello schema della collezione.
In questo esempio, lo schema della collezione definisce un campo vettoriale denominato ` embedding ` con ` nullable=True`. Ciò consente alle entità nella collezione di omettere il valore vettoriale o di impostarlo esplicitamente su `NULL` durante l'acquisizione dei dati.
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
]
}
}"
In questo schema:
- Il campo `
embedding` è contrassegnato esplicitamente come nullable. - Le entità possono omettere il campo `
embedding` oppure assegnargli un valore `NULL` durante l'inserimento. - La decisione di consentire valori NULL viene stabilita al momento della creazione della collezione.
Per chiarezza, gli esempi seguenti si concentrano su un campo vettoriale nullabile (embedding). La definizione di campi scalari nullabili è facoltativa e non è necessaria per seguire il resto di questa guida.
Opzionale: definire un campo scalare nullabile
Anche i campi scalari possono essere definiti come nullabili utilizzando lo stesso attributo ` nullable ` e seguono le stesse regole durante l’acquisizione. Ad esempio:
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 }
Comportamento di inserimento con valori mancanti o NULL
Una volta che un campo è stato definito come nullable nello schema della collezione, Milvus consente che il valore del campo sia mancante o esplicitamente impostato su NULL durante l’ingestione dei dati.
L’esempio riportato di seguito inserisce tre entità nella collezione creata in Definizione di un campo nullable nello schema della collezione, illustrando questi diversi casi.
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}
]
}'
In questo esempio:
- L'entità id = 1 fornisce un valore vettoriale valido.
- L'entità con id = 2 assegna esplicitamente un valore NULL al campo "
embedding". - L'entità con id = 3 omette completamente il campo "
embedding"; Milvus lo memorizza come NULL.
Comportamento dell'indice sui campi nullabili
Dopo aver inserito i dati, è possibile creare un indice su un campo nullabile come di consueto. La differenza fondamentale sta nel modo in cui Milvus gestisce i valori NULL durante la creazione dell’indice:
- All’indice vengono aggiunte solo le entità con valori non NULL.
- Le entità con valori NULL vengono ignorate e non partecipano alla creazione dell’indice.
Per un campo vettoriale nullabile, ciò significa che solo le entità con vettori validi diventano ricercabili in base alla somiglianza vettoriale.
# 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"}'
A questo punto:
- Le entità con valori di embedding validi vengono indicizzate e sono pronte per la ricerca.
- Le entità il cui embedding è NULL rimangono nella collezione, ma non vengono incluse nell'indice vettoriale.
Comportamento di ricerca con campi nullabili
Quando si eseguono operazioni di ricerca su un campo nullabile, Milvus valuta solo le entità con valori non nulli per il campo utilizzato nella ricerca. Le entità il cui campo vettoriale è NULL vengono automaticamente ignorate.
Per un campo vettoriale nullabile come embedding in questo esempio:
- Vengono valutate e classificate solo le entità con valori vettoriali validi.
- Le entità con vettori NULL non generano errori.
- Se il numero di vettori validi è inferiore al numero richiesto di
topK(limit), Milvus potrebbe restituire un numero di risultati inferiore alimit.
L'esempio seguente esegue una ricerca vettoriale sul campo vettoriale nullabile 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"]
}'
In questa ricerca:
- vengono considerate candidate solo le entità con valori non nulli per
embedding. - Le entità con valori NULL per
embeddingvengono escluse dalla valutazione. - Il numero di risultati restituiti dipende dal numero di vettori validi presenti nella raccolta.
Implicazioni relative alle query e al filtraggio
Gli esempi precedenti si concentrano sui campi vettoriali. Questa sezione descrive il comportamento dei valori NULL nelle espressioni di filtro scalari.
I campi scalari possono essere definiti con nullable=True e seguono le stesse regole di inserimento dei campi vettoriali. Tuttavia, i valori scalari NULL vengono sempre valutati come falsi nelle espressioni di filtro.
Ad esempio, dato un campo scalare nullabile age, il seguente filtro seleziona le entità la cui età è superiore a 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"
Le entità in cui ` age ` è `NULL` vengono escluse dai risultati poiché un valore `NULL` non soddisfa la condizione del filtro.
Allo stesso modo, i controlli di uguaglianza non corrispondono ai valori NULL. Ad esempio:
expr = 'status == "active"'
String filter = "status == \"active\"";
const expr = 'status == "active"';
filter := `status == "active"`
# "filter": "status == \"active\""
Le entità in cui status è NULL vengono escluse dai risultati.
Campi nullabili e valori predefiniti
Quando per un campo sono configurati sia ` nullable ` che ` default_value `, le seguenti regole determinano il modo in cui Milvus gestisce gli input NULL o i valori mancanti dei campi durante l’inserimento.
| Nullabile abilitato | Valore predefinito | Input dell’utente (NULL o omesso) | Risultato |
|---|---|---|---|
| Sì | Sì (non NULL) | NULL o omesso | Utilizza il valore predefinito |
| Sì | No | NULL o omesso | Memorizzato come NULL |
| No | Sì (non NULL) | NULL o omesso | Utilizza il valore predefinito |
| No | No | NULL o omesso | Genera un errore |
| No | Sì (valore predefinito NULL) | NULL o omesso | Genera un errore |
Punti chiave:
- Quando un campo ha un valore predefinito diverso da NULL, tale valore viene utilizzato indipendentemente dal fatto che l'opzione "
nullable" sia abilitata. - Quando è abilitata l'opzione "
nullable=True" ma non è impostato alcun valore predefinito, il campo memorizza il valore NULL. - Quando è impostato "
nullable=False" ma non è specificato alcun valore predefinito, l'inserimento fallisce generando un errore. - L'impostazione di un valore predefinito NULL su un campo non nullabile non è valida e causa un errore.
Per esempi completi e informazioni sull'utilizzo delle API relative ai valori predefiniti, consultare Valori predefiniti.