Självkopplingar – Frågespråk i Cosmos DB (i Azure och Fabric)

På frågespråket är data schemafria och vanligtvis avnormaliserade. I stället för att koppla data mellan entiteter och uppsättningar, som i en relationsdatabas, sker kopplingar inom ett enda objekt. Mer specifikt är kopplingar begränsade till det objektet och kan inte förekomma i flera objekt och containrar.

Tips/Råd

Om du behöver koppla mellan objekt och containrar bör du överväga att omarbeta datamodellen för att undvika det här antimönstret.

Självkoppling med ett enskilt objekt

Nu ska vi titta på ett exempel på en självkoppling i ett objekt. Överväg en container med ett enda objekt. Det här objektet representerar en produkt med olika storlekar:

[
  {
    "name": "Raiot Jacket",
    "sizes": [
      {
        "key": "s",
        "description": "Small"
      },
      {
        "key": "m",
        "description": "Medium"
      },
      {
        "key": "l",
        "description": "Large"
      },
      {
        "key": "xl",
        "description": "Extra Large"
      }
    ]
  }
]

Vad händer om du behöver hitta produkter med en viss storlek? Vanligtvis skulle du behöva skriva en fråga som har ett filter som kontrollerar varje potentiellt index i matrisen sizes för ett värde med ett prefix. I det här exemplet hittar frågan alla produkter med en storlek som slutar med Large:

SELECT
  *
FROM
  products p
WHERE
  p.sizes[0].description LIKE "%Large" OR
  p.sizes[1].description LIKE "%Large" OR
  p.sizes[2].description LIKE "%Large" OR
  p.sizes[3].description LIKE "%Large"

Den här tekniken kan snabbt bli ohållbar. Frågesyntaxens komplexitet eller längd ökar antalet potentiella objekt i matrisen. Dessutom är den här frågan inte tillräckligt flexibel för att hantera framtida produkter, som kan ha fler än tre storlekar.

I en traditionell relationsdatabas skulle storlekarna delas upp i en separat tabell och en koppling mellan tabeller utförs med ett filter som tillämpas på resultaten. På frågespråket kan vi utföra en självkopplingsåtgärd i objektet med hjälp av nyckelordet JOIN :

SELECT
  p.name,
  s.key,
  s.description
FROM
  products p
JOIN
  s in p.sizes

Den här frågan returnerar en enkel matris med ett objekt för varje värde i taggar-matrisen.

[
  {
    "name": "Raiot Jacket",
    "key": "s",
    "description": "Small"
  },
  {
    "name": "Raiot Jacket",
    "key": "m",
    "description": "Medium"
  },
  {
    "name": "Raiot Jacket",
    "key": "l",
    "description": "Large"
  },
  {
    "name": "Raiot Jacket",
    "key": "xl",
    "description": "Extra Large"
  }
]

Nu ska vi dela upp frågan. Frågan har nu två alias: p för varje produktartikel i resultatuppsättningen och s för den själv anslutna sizes matrisen. Nyckelordet * är endast giltigt för att projicera alla fält om det kan härleda indatauppsättningen, men nu finns det två indatauppsättningar (p och t). På grund av den här begränsningen måste vi uttryckligen definiera våra returnerade fält från name produkten tillsammans med keyoch description från storlekarna.

Slutligen kan vi använda ett filter för att hitta de storlekar som slutar med Large. Eftersom vi använde nyckelordet JOIN är vårt filter tillräckligt flexibelt för att hantera valfritt variabelt antal taggar:

SELECT
  p.name,
  s.key AS size
FROM
  products p
JOIN
  s in p.sizes
WHERE
  s.description LIKE "%Large"
[
  {
    "name": "Raiot Jacket",
    "size": "l"
  },
  {
    "name": "Raiot Jacket",
    "size": "xl"
  }
]

Ansluta flera objekt själv

Nu ska vi gå vidare till ett exempel där vi behöver hitta ett värde i en matris som finns i flera objekt. I det här exemplet bör du överväga en container med två produktobjekt. Varje objekt innehåller relevant colors för det objektet.

[
  {
    "name": "Gremon Fins",
    "colors": [
      "science-blue",
      "turbo"
    ]
  },
  {
    "name": "Elecy Jacket",
    "colors": [
      "indigo-shark",
      "jordy-blue-shark"
    ]
  },
  {
    "name": "Tresko Pack",
    "colors": [
      "golden-dream"
    ]
  }
]

Vad händer om du behöver hitta varje objekt med en färg som innehåller blue i namnet? Du kan söka efter strängen bluemanuellt, men du skulle behöva skriva en komplex fråga som står för två egenskaper för dessa objekt:

  • Färgerna med en blue delsträng förekommer vid olika index i varje matris. För produkten Elecy Jacket är färgen det andra objektet (index: 1). För produkten Gremon Fins är taggen det första objektet (index: 0). Produkten Tresko Pack har inte någon som innehåller den här delsträngen.

  • Matrisen colors för varje objekt är en annan längd. Produkterna Gremon Fins och Elecy Jacket har båda två färger medan Tresko Pack produkten bara har en.

Här är nyckelordet JOIN ett bra verktyg för att skapa en korsprodukt av objekt och färger. Kopplingar skapar en fullständig korsprodukt av de uppsättningar som deltar i kopplingen. Resultatet är en uppsättning tupplar med varje permutation av objektet och värdena i målmatrisen.

En kopplingsåtgärd för våra exempelprodukter och färger skapar följande objekt:

Produkt Färg
Gremon Fins science-blue
Gremon Fins turbo
Elecy Jacket indigo-shark
Elecy Jacket jordy-blue-shark
Tresko Pack golden-dream

I det här exemplet använder NoSQl-frågan nyckelordet JOIN för att skapa en korsprodukt och returnerar alla permutationer:

SELECT
  p.name,
  c AS color
FROM
  products p
JOIN
  c in p.colors
[
  {
    "name": "Elecy Jacket",
    "color": "indigo-shark"
  },
  {
    "name": "Elecy Jacket",
    "color": "jordy-blue-shark"
  },
  {
    "name": "Gremon Fins",
    "color": "science-blue"
  },
  {
    "name": "Gremon Fins",
    "color": "turbo"
  },
  {
    "name": "Tresko Pack",
    "color": "golden-dream"
  }
]

Precis som med det enskilda objektet kan du använda ett filter här för att bara hitta objekt som matchar en specifik tagg. Den här frågan hittar till exempel alla objekt med en delsträng som innehåller blue för att uppfylla det ursprungliga kravet som nämns tidigare i det här avsnittet.

SELECT
  p.name,
  c AS color
FROM
  products p
JOIN
  c in p.colors
WHERE
  c LIKE "%blue%"
[
  {
    "name": "Elecy Jacket",
    "color": "jordy-blue-shark"
  },
  {
    "name": "Gremon Fins",
    "color": "science-blue"
  }
]

Den här frågan kan förfinas ytterligare för att bara returnera namnen på de produkter som uppfyller filtret. Det här exemplet projicerar inte färgvärdena, men filtret fungerar fortfarande som förväntat:

SELECT VALUE
  p.name
FROM
  products p
JOIN
  c in p.colors
WHERE
  c LIKE "%blue%"
[
  "Elecy Jacket",
  "Gremon Fins"
]