sql >> Database >  >> NoSQL >> MongoDB

Bulkupdate-array van overeenkomend subdocument in Mongodb

In het kortste antwoord is het zowel "ja" als "nee".

Er is inderdaad een manier om individuele array-elementen te matchen en bij te werken met afzonderlijke waarden in een enkele instructie, aangezien u in feite "meerdere" arrayFilters kunt opgeven. voorwaarden en gebruik die identifiers in uw updateverklaring.

Het probleem met uw specifieke voorbeeld hier is dat een van de items in uw "wijzigingsset" ( de laatste ) niet echt overeenkomt met een arraylid dat momenteel aanwezig is. De "veronderstelde" actie hier zou zijn om $push dat nieuwe niet-overeenkomende lid in de array waar het niet werd gevonden. Maar die specifieke actie kan niet worden gedaan in een "enkele bewerking" , maar u kunt bulkWrite() om "meerdere" verklaringen af ​​te geven om dat geval te dekken.

Overeenkomen met verschillende arraycondities

Als u dat in punten uitlegt, overweeg dan de eerste twee items in uw "wisselset". U kunt een "single" . toepassen update-instructie met meerdere arrayFilters zoals dit:

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

Als je dat zou uitvoeren, zou je zien dat het gewijzigde document wordt:

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

Merk op dat u elke "identfier" specificeert in de lijst met arrayFilters met meerdere voorwaarden om bij het element te passen, zoals:

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

Dus elke "conditie" wordt effectief toegewezen als:

  <identifier>.<property>

Dus hij weet dat hij kijkt naar de "tarieven" array door de instructie in het updateblok door de $[] :

 "rates.$[one]"

En kijkt naar elk element van "tarieven" om aan de voorwaarden te voldoen. Dus de "één" identifier zou overeenkomen met de voorwaarden voorafgegaan door "one" en ook voor de andere set voorwaarden voorafgegaan door "twee" , daarom is de eigenlijke update-instructie alleen van toepassing op die welke overeenkomen met de voorwaarden die aan de identifier zijn toegewezen.

Als je alleen de "tarieven" . wilt hebben eigenschap in tegenstelling tot het hele object, dan noteer je gewoon als:

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

Niet-overeenkomende objecten toevoegen

Dus het eerste deel is relatief eenvoudig te begrijpen, maar zoals gezegd het doen van een $push voor het "element dat er niet is" is een andere zaak, aangezien we in principe een vraagvoorwaarde op het "document"-niveau nodig hebben om te bepalen dat een array-element "ontbreekt".

Wat dit in wezen betekent, is dat je een update moet uitgeven met de $push op zoek naar elk array-element om te zien of het bestaat of niet. Als het niet aanwezig is, is het document een overeenkomst en de $push wordt uitgevoerd.

Dit is waar bulkWrite() komt in het spel en je gebruikt het door een extra update toe te voegen aan onze eerste bewerking hierboven voor elk element in de "veranderset":

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 }
)

Let op de $elemMatch binnen het queryfilter, omdat dit een vereiste is om een ​​array-element te matchen met "meerdere voorwaarden". Dat hadden we niet nodig in de arrayFilters inzendingen omdat ze alleen kijk naar elk array-item waarop ze al zijn toegepast, maar als een "query" vereisen de voorwaarden $elemMatch omdat een simpele "puntnotatie" onjuiste overeenkomsten zou opleveren.

Zie ook de $not operator wordt hier gebruikt om de $elemMatch , aangezien onze echte voorwaarden zijn om alleen overeen te komen met een document dat "niet overeenkomt met het array-element" aan de gestelde voorwaarden, en dat rechtvaardigt de selectie voor het toevoegen van een nieuw element.

En die ene verklaring die aan de server is afgegeven, probeert in wezen vier update-bewerkingen als een voor het proberen om overeenkomende array-elementen bij te werken, en een andere voor elk van de drie "sets wijzigen" probeert te $push waar het document niet overeenkwam met de voorwaarden voor het array-element in de "change set".

Het resultaat is dan ook zoals verwacht:

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

Afhankelijk van het aantal elementen dat niet overeenkomt met de bulkWrite() antwoord zal rapporteren over hoeveel van die verklaringen daadwerkelijk overeenkwamen met en van invloed waren op een document. In dit geval is het 2 overeenkomen en gewijzigd, aangezien de "eerste" update-bewerking overeenkomt met bestaande array-items, en de "laatste" wijzigingsupdate overeenkomt met het feit dat het document het array-item niet bevat en de $push wijzigen.

Conclusie

Dus daar heb je de gecombineerde aanpak, waarbij:

  • Het eerste deel van "bijwerken" in uw vraag is heel eenvoudig en kan in een enkele verklaring worden gedaan , zoals wordt aangetoond in de eerste sectie.

  • Het tweede deel met een array-element dat "momenteel niet bestaat" binnen de huidige documentarray vereist dit eigenlijk dat u gebruikt bulkWrite() om "meerdere" bewerkingen in één verzoek uit te voeren.

Daarom update , is "JA" voor een enkele bewerking. Maar verschil toevoegen betekent meerdere operaties. Maar je kunt de twee benaderingen combineren, zoals hier wordt aangetoond.

Er zijn veel "fantastische" manieren waarop u deze instructies kunt construeren op basis van de inhoud van de array "change set" met code, zodat u niet elk lid hoeft te "hardcoderen".

Als een basiscase voor JavaScript en compatibel met de huidige release van de mongo-shell (die enigszins hinderlijk geen objectspreidingsoperators ondersteunt):

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 });

Hiermee wordt dynamisch een lijst met "Bulk"-updatebewerkingen samengesteld die er als volgt uit zou zien:

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

Net zoals beschreven in de "lange vorm" van het algemene antwoord, maar gebruikt natuurlijk gewoon de invoer "array" -inhoud om al die uitspraken te construeren.

Je kunt zo'n dynamische objectconstructie in elke taal doen, en alle MongoDB-stuurprogramma's accepteren invoer van een soort structuur die je mag "manipuleren", die vervolgens wordt getransformeerd naar BSON voordat deze daadwerkelijk naar de server wordt gestuurd voor uitvoering.




  1. JSON gebruiken met MongoDB?

  2. MongoDB PHP Driver en MongoDB compatibiliteitscontrole

  3. ZRANGESTORE eerdere Redis 6.2.0

  4. ontbrekende lboost_thread-mt in mongodb cpp-stuurprogramma (ubuntu-server x64)