Self-join in Azure Cosmos DB per NoSQL

SI APPLICA A: NoSQL

In Azure Cosmos DB per NoSQL i dati sono privi di schema e in genere denormalizzati. Anziché unire dati tra entità e set, come in un database relazionale, i join vengono eseguiti all'interno di un singolo elemento. In particolare, i join hanno come ambito tale elemento e non possono verificarsi tra più elementi e contenitori.

Suggerimento

Se è necessario creare un join tra elementi e contenitori, è consigliabile rielaborare il modello di dati per evitare questo problema.

Self-join con un singolo elemento

Di seguito viene illustrato un esempio di self-join all'interno di un elemento. Si consideri un contenitore con un singolo elemento. Questo articolo rappresenta un prodotto con vari tag:

[
  {
    "id": "863e778d-21c9-4e2a-a984-d31f947c665c",
    "categoryId": "e592b992-d453-42ee-a74e-0de2cc97db42",
    "name": "Teapo Surfboard (6'10\") Grape",
    "sku": "teapo-surfboard-72109",
    "tags": [
      {
        "id": "556dc4f5-1dbd-41dc-9674-fda626e5d15c",
        "slug": "tail-shape-swallow",
        "name": "Tail Shape: Swallow"
      },
      {
        "id": "ac097b9a-8a30-4fd1-8cb6-69d3388ee8a2",
        "slug": "length-inches-82",
        "name": "Length: 82 inches"
      },
      {
        "id": "ce62b524-8e96-4999-b3e1-61ae7a672e2e",
        "slug": "color-group-purple",
        "name": "Color Group: Purple"
      }
    ]
  }
]

Cosa succede se è necessario trovare il gruppo di colori di questo prodotto? In genere, sarebbe necessario scrivere una query con un filtro che controlla ogni potenziale indice nella tags matrice per un valore con prefisso color-group-.

SELECT
  * 
FROM
  products p
WHERE
  STARTSWITH(p.tags[0].slug, "color-group-") OR
  STARTSWITH(p.tags[1].slug, "color-group-") OR
  STARTSWITH(p.tags[2].slug, "color-group-")

Questa tecnica può diventare insostenibile rapidamente. La complessità o la lunghezza della sintassi della query aumenta il numero di elementi potenziali nella matrice. Inoltre, questa query non è sufficientemente flessibile da gestire i prodotti futuri, che possono avere più di tre tag.

In un database relazionale tradizionale i tag vengono separati in una tabella separata e viene eseguito un join tra tabelle con un filtro applicato ai risultati. Nell'API per NoSQL è possibile eseguire un'operazione di self-join all'interno dell'elemento usando la JOIN parola chiave .

SELECT
  p.id,
  p.sku,
  t.slug
FROM
  products p
JOIN
  t IN p.tags

Questa query restituisce una matrice semplice con un elemento per ogni valore nella matrice di tag.

[
  {
    "id": "863e778d-21c9-4e2a-a984-d31f947c665c",
    "sku": "teapo-surfboard-72109",
    "slug": "tail-shape-swallow"
  },
  {
    "id": "863e778d-21c9-4e2a-a984-d31f947c665c",
    "sku": "teapo-surfboard-72109",
    "slug": "length-inches-82"
  },
  {
    "id": "863e778d-21c9-4e2a-a984-d31f947c665c",
    "sku": "teapo-surfboard-72109",
    "slug": "color-group-purple"
  }
]

Suddividere la query. La query include ora due alias: p per ogni elemento del prodotto nel set di risultati e t per la matrice self-join tags . La * parola chiave è valida solo per proiettare tutti i campi se può dedurre il set di input, ma ora sono disponibili due set di input (p e t). A causa di questo vincolo, è necessario definire in modo esplicito i campi restituiti come id e sku dal prodotto insieme slug ai tag . Per semplificare la lettura e la comprensione di questa query, è possibile eliminare il id campo e usare un alias per il campo del name tag per rinominarlo in tag.

SELECT
  p.sku,
  t.name AS tag
FROM
  products p
JOIN
  t IN p.tags
[
  {
    "sku": "teapo-surfboard-72109",
    "tag": "Tail Shape: Swallow"
  },
  {
    "sku": "teapo-surfboard-72109",
    "tag": "Length: 82 inches"
  },
  {
    "sku": "teapo-surfboard-72109",
    "tag": "Color Group: Purple"
  }
]

Infine, è possibile usare un filtro per trovare il tag color-group-purple. Poiché è stata usata la JOIN parola chiave , il filtro è sufficientemente flessibile per gestire qualsiasi numero variabile di tag.

SELECT
  p.sku,
  t.name AS tag
FROM
  products p
JOIN
  t IN p.tags
WHERE
  STARTSWITH(t.slug, "color-group-")
[
  {
    "sku": "teapo-surfboard-72109",
    "tag": "Color Group: Purple"
  }
]

Aggiunta automatica di più elementi

Si passerà a un esempio in cui è necessario trovare un valore all'interno di una matrice presente in più elementi. Per questo esempio, si consideri un contenitore con due elementi di prodotto. Ogni elemento contiene tag pertinenti per tale elemento.

[
  {
    "id": "80d62f31-9892-48e5-9b9b-5714d551b8b3",
    "categoryId": "19cd9b93-bdc5-4082-97fe-2c80c2fd77dd",
    "categoryName": "Sleeping Bags",
    "name": "Maresse Sleeping Bag (6') Ming",
    "sku": "maresse-sleeping-bag-65503",
    "tags": [
      {
        "id": "f50f3ee1-e150-4821-922b-ebe6ad82f313",
        "slug": "bag-shape-mummy",
        "name": "Bag Shape: Mummy"
      },
      {
        "id": "8564fb66-63ea-464a-872a-7598433b9479",
        "slug": "bag-insulation-down-fill",
        "name": "Bag Insulation: Down Fill"
      }
    ]
  },
  {
    "id": "6e9f51c1-6b45-440f-af5a-2abc96cd083d",
    "categoryId": "19cd9b93-bdc5-4082-97fe-2c80c2fd77dd",
    "categoryName": "Sleeping Bags",
    "name": "Vareno Sleeping Bag (6') Turmeric",
    "sku": "vareno-sleeping-bag-65508",
    "tags": [
      {
        "id": "e02502ce-367e-4fb4-940e-93d994fa6062",
        "slug": "bag-insulation-synthetic-fill",
        "name": "Bag Insulation: Synthetic Fill"
      },
      {
        "id": "c0844995-3db9-4dbb-8d9d-d2c2a6151b94",
        "slug": "color-group-yellow",
        "name": "Color Group: Yellow"
      },
      {
        "id": "f50f3ee1-e150-4821-922b-ebe6ad82f313",
        "slug": "bag-shape-mummy",
        "name": "Bag Shape: Mummy"
      }
    ]
  }
]

E se avessi bisogno di trovare ogni elemento con una forma borsa mamma ? È possibile cercare il tag bag-shape-mummy, ma sarebbe necessario scrivere una query complessa che conti per due caratteristiche di questi elementi:

  • Il tag con un bag-shape- prefisso si verifica in corrispondenza di indici diversi in ogni matrice. Per il sacco a pelo Vareno , il tag è il terzo elemento (indice: 2). Per il sacco a pelo Maresse , il tag è il primo elemento (indice: 0).

  • La tags matrice per ogni elemento è una lunghezza diversa. Il sacco a pelo Vareno ha due tag mentre il sacco a pelo Maresse ha tre.

In questo caso, la JOIN parola chiave è un ottimo strumento per creare un prodotto incrociato degli elementi e dei tag. I join creano un prodotto incrociato completo dei set che partecipano al join. Il risultato è un set di tuple con ogni permutazione dell'elemento e i valori all'interno della matrice di destinazione.

Un'operazione di join sui prodotti e i tag del sacco a pelo di esempio creano gli elementi seguenti:

Elemento Tag
Maresse Sleeping Bag (6') Ming Forma borsa: mummia
Maresse Sleeping Bag (6') Ming Isolamento sacchetto: riempimento giù
Vareno Sacco a pelo (6') Curcuma Isolamento sacchetto: riempimento sintetico
Vareno Sacco a pelo (6') Curcuma Gruppo di colori: giallo
Vareno Sacco a pelo (6') Curcuma Forma borsa: mummia

Ecco la query SQL e il set di risultati JSON per un join che include più elementi nel contenitore.

SELECT
  p.sku,
  t.name AS tag
FROM
  products p
JOIN
  t IN p.tags
WHERE
  p.categoryName = "Sleeping Bags"
[
  {
    "sku": "maresse-sleeping-bag-65503",
    "tag": "Bag Shape: Mummy"
  },
  {
    "sku": "maresse-sleeping-bag-65503",
    "tag": "Bag Insulation: Down Fill"
  },
  {
    "sku": "vareno-sleeping-bag-65508",
    "tag": "Bag Insulation: Synthetic Fill"
  },
  {
    "sku": "vareno-sleeping-bag-65508",
    "tag": "Color Group: Yellow"
  },
  {
    "sku": "vareno-sleeping-bag-65508",
    "tag": "Bag Shape: Mummy"
  }
]

Proprio come con il singolo elemento, è possibile applicare un filtro qui per trovare solo gli elementi che corrispondono a un tag specifico. Ad esempio, questa query trova tutti gli elementi con un tag denominato bag-shape-mummy per soddisfare il requisito iniziale indicato in precedenza in questa sezione.

SELECT
  p.sku,
  t.name AS tag
FROM
  products p
JOIN
  t IN p.tags
WHERE
  p.categoryName = "Sleeping Bags" AND
  t.slug = "bag-shape-mummy"
[
  {
    "sku": "maresse-sleeping-bag-65503",
    "tag": "Bag Shape: Mummy"
  },
  {
    "sku": "vareno-sleeping-bag-65508",
    "tag": "Bag Shape: Mummy"
  }
]

È anche possibile modificare il filtro per ottenere un set di risultati diverso. Ad esempio, questa query trova tutti gli elementi con un tag denominato bag-insulation-synthetic-fill.

SELECT
  p.sku,
  t.name AS tag
FROM
  products p
JOIN
  t IN p.tags
WHERE
  p.categoryName = "Sleeping Bags" AND
  t.slug = "bag-insulation-synthetic-fill"
[
  {
    "sku": "vareno-sleeping-bag-65508",
    "tag": "Bag Insulation: Synthetic Fill"
  }
]