Auto-associações no Azure Cosmos DB para NoSQL

APLICA-SE A: NoSQL

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

Gorjeta

Se você precisar unir itens e contêineres, considere retrabalhar seu modelo de dados para evitar isso.

Auto-associação com um único item

Vejamos um exemplo de uma auto-junção dentro de um item. Considere um recipiente com um único item. Este item representa um produto com várias tags:

[
  {
    "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 deste produto? Normalmente, você precisaria escrever uma consulta que tenha um filtro verificando cada índice potencial na matriz em busca de um valor com um prefixo tags de 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-")

Esta técnica pode tornar-se insustentável rapidamente. A complexidade ou o comprimento da sintaxe de 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 podem ter mais de três tags.

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

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 tags.

[
  {
    "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 detalhar a consulta. A consulta agora tem dois aliases: p para cada item de produto no conjunto de resultados e t para a matriz autoassociada tags . A * palavra-chave só é válida para projetar todos os campos se puder inferir o conjunto de entradas, mas agora há dois conjuntos de entrada (p e t). Devido a essa restrição, devemos definir explicitamente nossos campos retornados como id e sku do produto junto com slug as tags . Para tornar essa consulta mais fácil de ler e entender, podemos soltar o id campo e usar um alias para o campo da name tag para renomeá-lo para 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"
  }
]

Finalmente, podemos usar um filtro para encontrar a tag color-group-purple. Como usamos a JOIN palavra-chave, nosso filtro é flexível o suficiente para lidar com qualquer número variável de tags.

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

Auto-junção de vários itens

Vamos passar para um exemplo onde 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 tags relevantes para esse item.

[
  {
    "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 com um formato de saco de múmia ? Você poderia pesquisar a tag bag-shape-mummy, mas precisaria escrever uma consulta complexa que levasse em conta duas características desses itens:

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

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

Aqui, a JOIN palavra-chave é uma ótima ferramenta para criar um produto cruzado dos itens e tags. As junções criam um produto cruzado completo dos conjuntos que participam na 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 em nossos produtos e tags de saco de dormir de amostra cria os seguintes itens:

Item Etiqueta
Saco de dormir Maresse (6') Ming Forma do saco: Múmia
Saco de dormir Maresse (6') Ming Isolamento do saco: Down Fill
Vareno Saco de dormir (6') Cúrcuma Isolamento do saco: Enchimento sintético
Vareno Saco de dormir (6') Cúrcuma Grupo de cores: Amarelo
Vareno Saco de dormir (6') Cúrcuma Forma do saco: Múmia

Aqui está 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 com o item único, você pode aplicar um filtro aqui para encontrar apenas itens que correspondam a uma tag específica. Por exemplo, essa consulta localiza todos os itens com uma tag nomeada bag-shape-mummy para atender ao requisito inicial mencionado anteriormente 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 tag 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"
  }
]