Autojunção no Azure Cosmos DB for NoSQL

APLICA-SE A: NoSQL

No Azure Cosmos DB for NoSQL, os dados são livres de esquema e normalmente desnormalizados. Em vez de unir dados entre entidades e conjuntos, como você faria em um banco de dados relacional, as junções ocorrem em um só item. Especificamente, as junções têm como escopo esse item e não podem ocorrer em vários itens e contêineres.

Dica

Se você precisar ingressar em itens e contêineres, considere reformular o modelo de dados para evitar isso.

Autojunção com um só item

Vamos examinar um exemplo de uma autojunção dentro de um item. Considere um contêiner com um só item. Este item representa um produto com várias marcas:

[
  {
    "id": "aaaaaaaa-0000-1111-2222-bbbbbbbbbbbb",
    "categoryId": "bbbbbbbb-1111-2222-3333-cccccccccccc",
    "name": "Teapo Surfboard (6'10\") Grape",
    "sku": "teapo-surfboard-72109",
    "tags": [
      {
        "id": "cccccccc-2222-3333-4444-dddddddddddd",
        "slug": "tail-shape-swallow",
        "name": "Tail Shape: Swallow"
      },
      {
        "id": "dddddddd-3333-4444-5555-eeeeeeeeeeee",
        "slug": "length-inches-82",
        "name": "Length: 82 inches"
      },
      {
        "id": "eeeeeeee-4444-5555-6666-ffffffffffff",
        "slug": "color-group-purple",
        "name": "Color Group: Purple"
      }
    ]
  }
]

E se você precisar encontrar o grupo de cores desse produto? Normalmente, você precisaria escrever uma consulta com um filtro verificando cada índice possível na matriz tags para obter um valor com o prefixo 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-")

Essa técnica pode se tornar insustentável rapidamente. A complexidade ou comprimento da sintaxe da consulta aumenta o número de itens potenciais na matriz. Além disso, essa consulta não é flexível o suficiente para lidar com produtos futuros que possam ter mais de três marcas.

Em um banco de dados relacional tradicional, as marcas seriam separadas em uma tabela separada e uma junção entre tabelas seria executada com um filtro aplicado aos resultados. Na API para NoSQL, podemos executar uma operação de autojunção dentro do item usando a palavra-chave JOIN.

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

Essa consulta retorna uma matriz simples com um item para cada valor na matriz de marcas.

[
  {
    "id": "aaaaaaaa-0000-1111-2222-bbbbbbbbbbbb",
    "sku": "teapo-surfboard-72109",
    "slug": "tail-shape-swallow"
  },
  {
    "id": "aaaaaaaa-0000-1111-2222-bbbbbbbbbbbb",
    "sku": "teapo-surfboard-72109",
    "slug": "length-inches-82"
  },
  {
    "id": "aaaaaaaa-0000-1111-2222-bbbbbbbbbbbb",
    "sku": "teapo-surfboard-72109",
    "slug": "color-group-purple"
  }
]

Vamos dividir a consulta. A consulta agora tem dois aliases: p para cada item de produto no conjunto de resultados e t para a matriz com autojunção tags. A palavra-chave * só será válida para projetar todos os campos se puder inferir o conjunto de entrada, mas agora há dois conjuntos de entrada (p e t). Devido a essa restrição, precisamos definir explicitamente os campos retornados como id e sku do produto, juntamente com slug das marcas. Para facilitar a leitura e a compreensão dessa consulta, podemos remover o campo id e usar um alias para o campo da marca name a fim de renomeá-lo como 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"
  }
]

Por fim, podemos usar um filtro para localizar a marca color-group-purple. Como usamos a palavra-chave JOIN, o filtro é flexível o suficiente para lidar com qualquer número variável de marcas.

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"
  }
]

Autojunção de vários itens

Vamos passar para um exemplo em que precisamos encontrar um valor dentro de uma matriz que existe em vários itens. Para este exemplo, considere um contêiner com dois itens de produto. Cada item contém marcas relevantes para ele.

[
  {
    "id": "ffffffff-5555-6666-7777-aaaaaaaaaaaa",
    "categoryId": "cccccccc-8888-9999-0000-dddddddddddd",
    "categoryName": "Sleeping Bags",
    "name": "Maresse Sleeping Bag (6') Ming",
    "sku": "maresse-sleeping-bag-65503",
    "tags": [
      {
        "id": "b1b1b1b1-cccc-dddd-eeee-f2f2f2f2f2f2",
        "slug": "bag-shape-mummy",
        "name": "Bag Shape: Mummy"
      },
      {
        "id": "bbbbbbbb-7777-8888-9999-cccccccccccc",
        "slug": "bag-insulation-down-fill",
        "name": "Bag Insulation: Down Fill"
      }
    ]
  },
  {
    "id": "c2c2c2c2-dddd-eeee-ffff-a3a3a3a3a3a3",
    "categoryId": "cccccccc-8888-9999-0000-dddddddddddd",
    "categoryName": "Sleeping Bags",
    "name": "Vareno Sleeping Bag (6') Turmeric",
    "sku": "vareno-sleeping-bag-65508",
    "tags": [
      {
        "id": "dddddddd-9999-0000-1111-eeeeeeeeeeee",
        "slug": "bag-insulation-synthetic-fill",
        "name": "Bag Insulation: Synthetic Fill"
      },
      {
        "id": "a0a0a0a0-bbbb-cccc-dddd-e1e1e1e1e1e1",
        "slug": "color-group-yellow",
        "name": "Color Group: Yellow"
      },
      {
        "id": "b1b1b1b1-cccc-dddd-eeee-f2f2f2f2f2f2",
        "slug": "bag-shape-mummy",
        "name": "Bag Shape: Mummy"
      }
    ]
  }
]

E se você precisasse encontrar todos os itens que tivessem o formato de sacola (bag shape) mummy? Você poderia procurar a marca bag-shape-mummy, mas precisaria escrever uma consulta complexa considerando duas características desses itens:

  • A marca com um prefixo bag-shape- ocorre em diferentes índices em cada matriz. Para o saco de dormir (sleeping bag) Vareno, a marca é o terceiro item (índice: 2). Para o saco de dormir Maresse, a marca é o terceiro item (índice: 0).

  • A matriz tags de cada item tem um comprimento diferente. O saco de dormir Vareno tem duas marcas, enquanto o saco de dormir Maresse tem três.

Aqui, a palavra-chave JOIN é uma ótima ferramenta para criar um produto cruzado de itens e marcas. As junções criam um produto cruzado completo dos conjuntos que participam da junção. O resultado é um conjunto de tuplas com cada permutação do item e os valores dentro da matriz de destino.

Uma operação de junção nos produtos e nas marcas de saco de dormir de exemplo cria os seguintes itens:

Item Marca
Maresse Sleeping Bag (6') Ming Bag Shape: Mummy
Maresse Sleeping Bag (6') Ming Bag Insulation: Down Fill
Vareno Sleeping Bag (6') Turmeric Bag Insulation: Synthetic Fill
Vareno Sleeping Bag (6') Turmeric Color Group: Yellow
Vareno Sleeping Bag (6') Turmeric Bag Shape: Mummy

Veja a consulta SQL e o conjunto de resultados JSON para uma junção que inclui vários itens no contêiner.

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"
  }
]

Assim como acontece com o único item, você pode aplicar um filtro aqui para localizar apenas itens que correspondam a uma marca específica. Por exemplo, essa consulta localiza todos os itens com uma marca chamada bag-shape-mummy para atender ao requisito inicial já mencionado nesta seção.

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"
  }
]

Você também pode alterar o filtro para obter um conjunto de resultados diferente. Por exemplo, essa consulta localiza todos os itens que têm uma marca chamada 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"
  }
]