sql >> Databasteknik >  >> NoSQL >> MongoDB

Massuppdatera array av matchande deldokument i Mongodb

I det kortaste svaret är det både "ja" och "nej".

Det finns verkligen ett sätt att matcha individuella arrayelement och uppdatera dem med separata värden i en enda sats, eftersom du faktiskt kan tillhandahålla "flera" arrayFilters villkor och använd dessa identifierare i ditt uppdateringsutlåtande.

Problemet med ditt specifika exempel här är att en av posterna i din "ändringsuppsättning" (den sista) faktiskt inte matchar någon arraymedlem som för närvarande finns. Den "förmodade" åtgärden här skulle vara att $push den nya omatchade medlemmen i arrayen där den inte hittades. Men den specifika åtgärden kan inte göras i en "enkel operation" , men du kan använda bulkWrite() att utfärda "flera" uttalanden för att täcka det fallet.

Matchning av olika arrayvillkor

Förklara det i punkter, överväg de två första objekten i din "ändringsuppsättning". Du kan använda en "singel" uppdatera satsen med flera arrayFilters så här:

db.avail_rates_copy.updateOne(
  { "_id": 12345 },
  { 
    "$set": {
      "rates.$[one]": {
        "productId" : NumberInt(1234), 
        "rate" : 400.0, 
        "rateCardId": NumberInt(1),
        "month" : NumberInt(201801)
      },
      "rates.$[two]": {
        "productId" : NumberInt(1234), 
        "rate" : 500.0, 
        "rateCardId": NumberInt(1),
        "month" : NumberInt(201802)
      } 
    }
  },
  { 
    "arrayFilters": [
      {
        "one.productId": NumberInt(1234),
        "one.rateCardId": NumberInt(1),
        "one.month": NumberInt(201801)
      },
      {
        "two.productId": NumberInt(1234),
        "two.rateCardId": NumberInt(1),
        "two.month": NumberInt(201802)
      }
    ]
  }
)

Om du körde det skulle du se det ändrade dokumentet blir:

{
        "_id" : 12345,
        "_class" : "com.example.ProductRates",
        "rates" : [
                {                             // Matched and changed this by one
                        "productId" : 1234,
                        "rate" : 400,
                        "rateCardId" : 1,
                        "month" : 201801
                },
                {                            // And this as two
                        "productId" : 1234,
                        "rate" : 500,
                        "rateCardId" : 1,
                        "month" : 201802
                },
                {
                        "productId" : 1234,
                        "rate" : 400,
                        "rateCardId" : 2,
                        "month" : 201803
                },
                {
                        "productId" : 1235,
                        "rate" : 500,
                        "rateCardId" : 1,
                        "month" : 201801
                },
                {
                        "productId" : 1235,
                        "rate" : 234,
                        "rateCardId" : 2,
                        "month" : 201803
                }
        ]
}

Notera här att du anger varje "identifierare" i listan med arrayFilters med flera villkor för att matcha elementet så här:

  {
    "one.productId": NumberInt(1234),
    "one.rateCardId": NumberInt(1),
    "one.month": NumberInt(201801)
  },

Så varje "villkor" mappas effektivt som:

  <identifier>.<property>

Så det vet att den tittar på "priser" array efter uttalandet i uppdateringsblocket av $[] :

 "rates.$[one]"

Och tittar på varje element i "rates" att matcha förutsättningarna. Alltså "one" identifierare skulle matcha villkoren med prefixet "one" och likaså för den andra uppsättningen villkor med prefixet "två" , därför gäller den faktiska uppdateringssatsen endast de som matchar villkoren som tilldelats identifieraren.

Om du bara ville ha "priser" egenskap i motsats till hela objektet, då noterar du bara som:

{ "$set": { "rates.$[one].rate": 400, "rates.$[two].rate": 500 } }

Lägga till omatchade objekt

Så den första delen är relativt enkel att förstå, men som sagt gör en $push för "elementet som inte finns där" är en annan sak, eftersom vi i princip behöver ett frågevillkor på "dokument"-nivån för att avgöra att ett arrayelement "saknas".

Vad detta i huvudsak betyder är att du måste utfärda en uppdatering med $push letar efter varje arrayelement för att se om det finns eller inte. När det inte finns är dokumentet en matchning och $push utförs.

Det är här bulkWrite() kommer till spel, och du använder den genom att lägga till en extra uppdatering till vår första operation ovan för varje element i "ändringsuppsättningen":

db.avail_rates_copy.bulkWrite(
  [
    { "updateOne": {
      "filter": { "_id": 12345 },
      "update": {
        "$set": {
          "rates.$[one]": {
            "productId" : NumberInt(1234), 
            "rate" : 400.0, 
            "rateCardId": NumberInt(1),
            "month" : NumberInt(201801)
          },
          "rates.$[two]": {
            "productId" : NumberInt(1234), 
            "rate" : 500.0, 
            "rateCardId": NumberInt(1),
            "month" : NumberInt(201802)
          },
          "rates.$[three]": {
            "productId" : NumberInt(1235), 
            "rate" : 700.0, 
            "rateCardId": NumberInt(1),
            "month" : NumberInt(201802)
          }
        }
      },
      "arrayFilters": [
        {
          "one.productId": NumberInt(1234),
          "one.rateCardId": NumberInt(1),
          "one.month": NumberInt(201801)
        },
        {
          "two.productId": NumberInt(1234),
          "two.rateCardId": NumberInt(1),
          "two.month": NumberInt(201802)
        },
        {
          "three.productId": NumberInt(1235),
          "three.rateCardId": NumberInt(1),
          "three.month": NumberInt(201802)
        }
      ]    
    }},
    { "updateOne": {
      "filter": {
        "_id": 12345,
        "rates": {
          "$not": {
            "$elemMatch": {
              "productId" : NumberInt(1234), 
              "rateCardId": NumberInt(1),
              "month" : NumberInt(201801)
            }
          }
        }
      },
      "update": {
        "$push": {
          "rates": {
            "productId" : NumberInt(1234), 
            "rate" : 400.0, 
            "rateCardId": NumberInt(1),
            "month" : NumberInt(201801)
          }
        }
      }
    }},
    { "updateOne": {
      "filter": {
        "_id": 12345,
        "rates": {
          "$not": {
            "$elemMatch": {
              "productId" : NumberInt(1234), 
              "rateCardId": NumberInt(1),
              "month" : NumberInt(201802)
            }
          }
        }
      },
      "update": {
        "$push": {
          "rates": {
            "productId" : NumberInt(1234), 
            "rate" : 500.0, 
            "rateCardId": NumberInt(1),
            "month" : NumberInt(201802)
          }
        }
      }
    }},
    { "updateOne": {
      "filter": {
        "_id": 12345,
        "rates": {
          "$not": {
            "$elemMatch": {
              "productId" : NumberInt(1235),
              "rateCardId": NumberInt(1),
              "month" : NumberInt(201802)
            }
          }
        }
      },
      "update": {
        "$push": {
          "rates": {
            "productId" : NumberInt(1235),
            "rate" : 700.0, 
            "rateCardId": NumberInt(1),
            "month" : NumberInt(201802)
          }
        }
      }
    }}
  ],
  { "ordered": true }
)

Observera $elemMatch inom frågefiltret, eftersom detta är ett krav för att matcha ett arrayelement med "flera villkor". Vi behövde inte det på arrayFilters poster eftersom de endast titta på varje arrayobjekt de redan tillämpas på, men som en "fråga" kräver villkoren $elemMatch som enkel "punktnotation" skulle returnera felaktiga matchningar.

Se även $not operatorn används här för att "negera" $elemMatch , eftersom våra verkliga villkor är att endast matcha ett dokument som "inte har matchande arrayelement" till de angivna villkoren, och det är det som motiverar valet för att lägga till ett nytt element.

Och det enda uttalandet som skickas till servern försöker i huvudsak fyra uppdateringsoperationer som en för att försöka uppdatera matchade arrayelement och en annan för var och en av de tre "ändra set" försöker $push där dokumentet visade sig inte matcha villkoren för arrayelementet i "change set".

Resultatet är därför som förväntat:

{
        "_id" : 12345,
        "_class" : "com.example.ProductRates",
        "rates" : [
                {                               // matched and updated
                        "productId" : 1234,
                        "rate" : 400,
                        "rateCardId" : 1,
                        "month" : 201801
                },
                {                               // matched and updated
                        "productId" : 1234,
                        "rate" : 500,
                        "rateCardId" : 1,
                        "month" : 201802
                },
                {
                        "productId" : 1234,
                        "rate" : 400,
                        "rateCardId" : 2,
                        "month" : 201803
                },
                {
                        "productId" : 1235,
                        "rate" : 500,
                        "rateCardId" : 1,
                        "month" : 201801
                },
                {
                        "productId" : 1235,
                        "rate" : 234,
                        "rateCardId" : 2,
                        "month" : 201803
                },
                {                              // This was appended
                        "productId" : 1235,
                        "rate" : 700,
                        "rateCardId" : 1,
                        "month" : 201802
                }
        ]
}

Beroende på hur många element som faktiskt inte matchade bulkWrite() Svaret kommer att rapportera hur många av dessa uttalanden som faktiskt matchade och påverkade ett dokument. I det här fallet är det 2 matchas och modifieras, eftersom den "första" uppdateringsåtgärden matchar befintliga arrayposter, och den "senaste" ändringsuppdateringen matchar att dokumentet inte innehåller arrayposten och utför $push att ändra.

Slutsats

Så där har du det kombinerade tillvägagångssättet, där:

  • Den första delen av "uppdatera" i din fråga är mycket enkel och kan göras i ett enskilt uttalande , som visas i det första avsnittet.

  • Den andra delen där det finns ett array-element som "inte existerar för närvarande" inom den aktuella dokumentmatrisen kräver detta faktiskt att du använder bulkWrite() för att utföra "flera" operationer i en enda begäran.

Därför uppdatera , är "JA" till en enstaka operation. Men lägger till skillnad betyder flera operationer. Men du kan kombinera de två tillvägagångssätten precis som visas här.

Det finns många "fantastiska" sätt på vilka du kan konstruera dessa uttalanden baserat på "change set"-arrayinnehållet med kod, så du behöver inte "hårdkoda" varje medlem.

Som ett grundläggande fall för JavaScript och kompatibel med den nuvarande versionen av mongo-skalet (som lite irriterande inte stöder objektspridningsoperatorer):

db.getCollection('avail_rates_copy').drop();
db.getCollection('avail_rates_copy').insert(
  {
    "_id" : 12345,
    "_class" : "com.example.ProductRates",
    "rates" : [
      {
        "productId" : 1234,
        "rate" : 100,
        "rateCardId" : 1,
        "month" : 201801
      },
      {
        "productId" : 1234,
        "rate" : 200,
        "rateCardId" : 1,
        "month" : 201802
      },
      {
        "productId" : 1234,
        "rate" : 400,
        "rateCardId" : 2,
        "month" : 201803
      },
      {
        "productId" : 1235,
        "rate" : 500,
        "rateCardId" : 1,
        "month" : 201801
      },
      {
        "productId" : 1235,
        "rate" : 234,
        "rateCardId" : 2,
        "month" : 201803
      }
    ]
  }
);

var changeSet = [
  {
      "productId" : 1234, 
      "rate" : 400.0, 
      "rateCardId": 1,
      "month" : 201801
  }, 
  {
      "productId" : 1234, 
      "rate" : 500.0, 
      "rateCardId": 1,
      "month" : 201802
  }, 
  {

      "productId" : 1235, 
      "rate" : 700.0, 
      "rateCardId": 1,
      "month" : 201802
  }
];

var arrayFilters = changeSet.map((obj,i) => 
  Object.keys(obj).filter(k => k != 'rate' )
    .reduce((o,k) => Object.assign(o, { [`u${i}.${k}`]: obj[k] }) ,{})
);

var $set = changeSet.reduce((o,r,i) =>
  Object.assign(o, { [`rates.$[u${i}].rate`]: r.rate }), {});

var updates = [
  { "updateOne": {
    "filter": { "_id": 12345 },
    "update": { $set },
    arrayFilters
  }},
  ...changeSet.map(obj => (
    { "updateOne": {
      "filter": {
        "_id": 12345,
        "rates": {
          "$not": {
            "$elemMatch": Object.keys(obj).filter(k => k != 'rate')
              .reduce((o,k) => Object.assign(o, { [k]: obj[k] }),{})
          }
        }
      },
      "update": {
        "$push": {
          "rates": obj
        }
      }
    }}
  ))
];

db.getCollection('avail_rates_copy').bulkWrite(updates,{ ordered: true });

Detta kommer dynamiskt att konstruera en lista med "Mass"-uppdateringsoperationer som skulle se ut så här:

[
  {
    "updateOne": {
      "filter": {
        "_id": 12345
      },
      "update": {
        "$set": {
          "rates.$[u0].rate": 400,
          "rates.$[u1].rate": 500,
          "rates.$[u2].rate": 700
        }
      },
      "arrayFilters": [
        {
          "u0.productId": 1234,
          "u0.rateCardId": 1,
          "u0.month": 201801
        },
        {
          "u1.productId": 1234,
          "u1.rateCardId": 1,
          "u1.month": 201802
        },
        {
          "u2.productId": 1235,
          "u2.rateCardId": 1,
          "u2.month": 201802
        }
      ]
    }
  },
  {
    "updateOne": {
      "filter": {
        "_id": 12345,
        "rates": {
          "$not": {
            "$elemMatch": {
              "productId": 1234,
              "rateCardId": 1,
              "month": 201801
            }
          }
        }
      },
      "update": {
        "$push": {
          "rates": {
            "productId": 1234,
            "rate": 400,
            "rateCardId": 1,
            "month": 201801
          }
        }
      }
    }
  },
  {
    "updateOne": {
      "filter": {
        "_id": 12345,
        "rates": {
          "$not": {
            "$elemMatch": {
              "productId": 1234,
              "rateCardId": 1,
              "month": 201802
            }
          }
        }
      },
      "update": {
        "$push": {
          "rates": {
            "productId": 1234,
            "rate": 500,
            "rateCardId": 1,
            "month": 201802
          }
        }
      }
    }
  },
  {
    "updateOne": {
      "filter": {
        "_id": 12345,
        "rates": {
          "$not": {
            "$elemMatch": {
              "productId": 1235,
              "rateCardId": 1,
              "month": 201802
            }
          }
        }
      },
      "update": {
        "$push": {
          "rates": {
            "productId": 1235,
            "rate": 700,
            "rateCardId": 1,
            "month": 201802
          }
        }
      }
    }
  }
]

Precis som beskrevs i den "långa formen" av det allmänna svaret, men använder naturligtvis helt enkelt inmatningsinnehållet "array" för att konstruera alla dessa påståenden.

Du kan göra en sådan dynamisk objektkonstruktion på vilket språk som helst, och alla MongoDB-drivrutiner accepterar input av någon typ av struktur som du får "manipulera" som sedan omvandlas till BSON innan den faktiskt skickas till servern för exekvering.




  1. Vad är NameNode Automatic Failover i Hadoop HDFS?

  2. Ta bort ett objekt från en kapslad array med $pull och $[identifier] (mongoDB 3.6)

  3. MongoDB kapslad dokumentvalidering för underdokument

  4. Med Redis Cluster, är det möjligt att bara skicka hash-taggarna till eval?