Panoramica dei campi JSON

Quando si realizzano applicazioni come cataloghi di prodotti, sistemi di gestione dei contenuti o motori di preferenze degli utenti, spesso è necessario memorizzare metadati flessibili accanto alle incorporazioni vettoriali. Gli attributi dei prodotti variano a seconda della categoria, le preferenze degli utenti evolvono nel tempo e le proprietà dei documenti hanno strutture complesse e annidate. I campi JSON di Milvus risolvono questa sfida, consentendo di memorizzare e interrogare dati strutturati flessibili senza sacrificare le prestazioni.

Che cos'è un campo JSON?

Un campo JSON è un tipo di dati definito dallo schema (DataType.JSON) in Milvus che memorizza dati strutturati di tipo chiave-valore. A differenza delle tradizionali colonne rigide dei database, i campi JSON possono ospitare oggetti annidati, array e tipi di dati misti, fornendo al contempo diverse opzioni di indicizzazione per query veloci.

Esempio di struttura di un campo JSON:

{
  "metadata": { 
    "category": "electronics",
    "brand": "BrandA",
    "in_stock": true,
    "price": 99.99,
    "string_price": "99.99",
    "tags": ["clearance", "summer_sale"],
    "supplier": {
      "name": "SupplierX",
      "country": "USA",
      "contact": {
        "email": "support@supplierx.com",
        "phone": "+1-800-555-0199"
      }
    }
  }
}

In questo esempio, metadata è un singolo campo JSON che contiene un mix di valori piatti (ad esempio category, in_stock), array (tags) e oggetti annidati (supplier).

Convenzione sui nomi: Usare solo lettere, numeri e trattini bassi nelle chiavi JSON. Evitare caratteri speciali, spazi o punti, perché potrebbero causare problemi di parsing nelle query.

Campo JSON vs. campo dinamico

Un punto di confusione comune è la differenza tra un campo JSON e un campo dinamico. Pur essendo entrambi legati a JSON, hanno scopi diversi.

La tabella seguente riassume le principali differenze tra un campo JSON e un campo dinamico:

Caratteristica

Campo JSON

Campo dinamico

Definizione dello schema

Un campo scalare che deve essere dichiarato esplicitamente nello schema della collezione con il tipo DataType.JSON.

Un campo JSON nascosto (chiamato $meta) che memorizza automaticamente i campi non dichiarati.

Caso d'uso

Memorizza dati strutturati in cui lo schema è noto e coerente.

Memorizza dati flessibili, in evoluzione o semi-strutturati che non si adattano a uno schema fisso.

Controllo

L'utente controlla il nome e la struttura del campo.

Gestiti dal sistema per i campi non definiti.

Interrogazione

Interrogare utilizzando il nome del campo o la chiave di destinazione all'interno del campo JSON: metadata["key"].

Interrogare direttamente utilizzando la chiave dinamica del campo: "dynamic_key" o tramite $meta: $meta["dynamic_key"]

Operazioni di base

Il flusso di lavoro fondamentale per l'utilizzo di un campo JSON prevede la sua definizione nello schema, l'inserimento dei dati e la successiva interrogazione dei dati mediante espressioni di filtro specifiche.

Definire un campo JSON

Per utilizzare un campo JSON, è necessario definirlo esplicitamente nello schema della collezione al momento della sua creazione. L'esempio seguente mostra come creare una raccolta con un campo metadata di tipo DataType.JSON:

from pymilvus import MilvusClient, DataType

client = MilvusClient(uri="http://localhost:19530") # Replace with your server address 

# Create schema
schema = client.create_schema(auto_id=False, enable_dynamic_field=True)

schema.add_field(field_name="product_id", datatype=DataType.INT64, is_primary=True) # Primary field
schema.add_field(field_name="vector", datatype=DataType.FLOAT_VECTOR, dim=5) # Vector field
# Define a JSON field that allows null values
schema.add_field(field_name="metadata", datatype=DataType.JSON, nullable=True)

client.create_collection(
    collection_name="product_catalog",
    schema=schema
)

In questo esempio, il campo JSON definito nello schema della raccolta consente valori nulli con nullable=True. Per maggiori dettagli, consultare Nullable e Default.

Inserire i dati

Una volta creata la raccolta, inserire le entità che contengono oggetti JSON strutturati nel campo JSON designato. I dati devono essere formattati come un elenco di dizionari.

entities = [
    {
        "product_id": 1,
        "vector": [0.1, 0.2, 0.3, 0.4, 0.5],
        "metadata": { # JSON field
            "category": "electronics",
            "brand": "BrandA",
            "in_stock": True,
            "price": 99.99,
            "string_price": "99.99",
            "tags": ["clearance", "summer_sale"],
            "supplier": {
                "name": "SupplierX",
                "country": "USA",
                "contact": {
                    "email": "support@supplierx.com",
                    "phone": "+1-800-555-0199"
                }
            }
        }
    }
]

client.insert(collection_name="product_catalog", data=entities)

Operazioni di filtraggio

Prima di poter eseguire operazioni di filtraggio sui campi JSON, accertarsi che:

  • Sia stato creato un indice su ogni campo vettore.

  • La collezione sia caricata in memoria.

Mostrare il codice

index_params = client.prepare_index_params()
index_params.add_index(
    field_name="vector",
    index_type="AUTOINDEX",
    index_name="vector_index",
    metric_type="COSINE"
)

client.create_index(collection_name="product_catalog", index_params=index_params)

client.load_collection(collection_name="product_catalog")

Una volta soddisfatti questi requisiti, è possibile utilizzare le espressioni seguenti per filtrare la raccolta in base ai valori del campo JSON. Queste espressioni di filtro sfruttano la sintassi specifica dei percorsi JSON e gli operatori dedicati.

Filtrare con la sintassi del percorso JSON

Per interrogare una chiave specifica, utilizzare la notazione a parentesi per accedere alle chiavi JSON: json_field_name["key"]. Per le chiavi annidate, concatenarle: json_field_name["key1"]["key2"].

Per filtrare le entità in cui category è "electronics":

# Define filter expression
filter = 'metadata["category"] == "electronics"'

client.search(
    collection_name="product_catalog",  # Collection name
    data=[[0.1, 0.2, 0.3, 0.4, 0.5]],               # Query vector (must match collection's vector dim)
    limit=5,                           # Max. number of results to return
    filter=filter,                    # Filter expression
    output_fields=["product_id", "metadata"]   # Fields to include in the search results
)

Per filtrare le entità in cui la chiave nidificata supplier["country"] è "USA":

# Define filter expression
filter = 'metadata["supplier"]["country"] == "USA"'

res = client.search(
    collection_name="product_catalog",  # Collection name
    data=[[0.1, 0.2, 0.3, 0.4, 0.5]],               # Query vector (must match collection's vector dim)
    limit=5,                           # Max. number of results to return
    filter=filter,                    # Filter expression
    output_fields=["product_id", "metadata"]   # Fields to include in the search results
)

print(res)

Filtrare con operatori specifici per JSON

Milvus fornisce anche operatori speciali per interrogare i valori degli array su chiavi di campo JSON specifiche. Ad esempio:

  • json_contains(identifier, expr): Verifica l'esistenza di un elemento o di un sotto-array specifico all'interno di un array JSON

  • json_contains_all(identifier, expr): Assicura che tutti gli elementi dell'espressione JSON specificata siano presenti nel campo

  • json_contains_any(identifier, expr): Filtra le entità in cui almeno un membro dell'espressione JSON è presente nel campo.

Per trovare un prodotto che abbia il valore "summer_sale" sotto la chiave tags:

# Define filter expression
filter = 'json_contains(metadata["tags"], "summer_sale")'

res = client.search(
    collection_name="product_catalog",  # Collection name
    data=[[0.1, 0.2, 0.3, 0.4, 0.5]],               # Query vector (must match collection's vector dim)
    limit=5,                           # Max. number of results to return
    filter=filter,                    # Filter expression
    output_fields=["product_id", "metadata"]   # Fields to include in the search results
)

print(res)

Per trovare un prodotto che abbia almeno uno dei valori "electronics", "new", o "clearance" sotto la chiave tags:

# Define filter expression
filter = 'json_contains_any(metadata["tags"], ["electronics", "new", "clearance"])'

res = client.search(
    collection_name="product_catalog",  # Collection name
    data=[[0.1, 0.2, 0.3, 0.4, 0.5]],               # Query vector (must match collection's vector dim)
    limit=5,                           # Max. number of results to return
    filter=filter,                    # Filter expression
    output_fields=["product_id", "metadata"]   # Fields to include in the search results
)

print(res)

Per ulteriori informazioni sugli operatori specifici di JSON, consultare la sezione Operatori JSON.

Avanti: Accelerare le query JSON

Per impostazione predefinita, le query sui campi JSON senza accelerazione eseguono una scansione completa di tutte le righe, che può essere lenta su grandi insiemi di dati. Per accelerare le query JSON, Milvus offre funzioni avanzate di indicizzazione e ottimizzazione dello storage.

La tabella seguente ne riassume le differenze e gli scenari di utilizzo migliori:

Tecnica

Migliore per

Array Accelerazione

Note

Indicizzazione JSON

Piccolo insieme di chiavi ad accesso frequente, array su una chiave di array specifica

Sì (sulla chiave dell'array indicizzato)

Deve preselezionare le chiavi, manutenzione necessaria se lo schema si evolve

Triturazione JSON

Accelerazione generale su molte chiavi, flessibile per query diverse

No (non accelera i valori all'interno degli array)

Configurazione extra dello storage, gli array necessitano ancora di un indice per chiave

Indice NGRAM

Ricerche con caratteri jolly, corrispondenza di sottostringhe nei campi di testo

N/D

Non per i filtri numerici/di intervallo

Suggerimento: è possibile combinare questi approcci, ad esempio utilizzare la triturazione JSON per accelerare le query, l'indicizzazione JSON per le chiavi degli array ad alta frequenza e l'indicizzazione NGRAM per la ricerca flessibile nel testo.

Per i dettagli sull'implementazione, consultare:

DOMANDE FREQUENTI

Ci sono limitazioni sulla dimensione di un campo JSON?

Sì. Ogni campo JSON è limitato a 65.536 byte.

Un campo JSON supporta l'impostazione di un valore predefinito?

No, i campi JSON non supportano valori predefiniti. Tuttavia, è possibile impostare nullable=True quando si definisce il campo per consentire l'inserimento di voci vuote.

Fare riferimento a Nullable e Default per i dettagli.

Esistono convenzioni di denominazione per le chiavi dei campi JSON?

Sì, per garantire la compatibilità con le query e l'indicizzazione:

  • Utilizzare solo lettere, numeri e trattini bassi nelle chiavi JSON.

  • Evitare l'uso di caratteri speciali, spazi o punti (., /, ecc.).

  • Le chiavi incompatibili possono causare problemi di parsing nelle espressioni dei filtri.

Come gestisce Milvus i valori stringa nei campi JSON?

Milvus memorizza i valori stringa esattamente come appaiono nell'input JSON, senza alcuna trasformazione semantica. Le stringhe non correttamente virgolettate possono causare errori durante l'analisi.

Esempi di stringhe valide:

"a\"b", "a'b", "a\\b"

Esempi di stringhe non valide:

'a"b', 'a\'b'