sql >> Databáze >  >> NoSQL >> MongoDB

Hromadná aktualizace pole odpovídajících dílčích dokumentů v Mongodb

V nejkratší odpovědi je to „ano“ i „ne“.

Existuje skutečně způsob, jak porovnat jednotlivé prvky pole a aktualizovat je samostatnými hodnotami v jediném příkazu, protože ve skutečnosti můžete poskytnout "více" arrayFilters podmínky a použijte tyto identifikátory ve svém prohlášení o aktualizaci.

Problém s vaší konkrétní ukázkou je, že jedna z položek ve vaší "sadě změn" (poslední) neodpovídá žádnému aktuálně přítomnému členu pole. „Předpokládanou“ akcí by zde bylo $push tento nový neshodný člen do pole, kde nebyl nalezen. Tato konkrétní akce však nelze provést v "jediné operaci" , ale můžete použít bulkWrite() vydávat „více“ prohlášení k pokrytí tohoto případu.

Shoda různých podmínek pole

Když to vysvětlíte v bodech, zvažte první dvě položky ve vaší „sadě změn“. Můžete použít „single“ aktualizovat příkaz pomocí více arrayFilters takhle:

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

Pokud byste spustili, uvidíte, že upravený dokument se změní na:

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

Všimněte si, že v seznamu arrayFilters zadáváte každý "identfier". s více podmínkami, aby se prvek shodoval takto:

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

Každá "podmínka" se tedy efektivně mapuje jako:

  <identifier>.<property>

Takže ví, že se má dívat na "sazby" pole pomocí příkazu v bloku aktualizace pomocí $[] :

 "rates.$[one]"

A podívá se na každý prvek "sazby" aby odpovídala podmínkám. Tedy "jeden" identifikátor by odpovídal podmínkám s předponou "one" a podobně pro další sadu podmínek s předponou "dva" , proto se aktuální prohlášení o aktualizaci vztahuje pouze na ty, které odpovídají podmínkám přiřazeným k identifikátoru.

Pokud jste jen chtěli "sazby" vlastnost na rozdíl od celého objektu, pak stačí poznamenat jako:

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

Přidávání neshodných objektů

První část je tedy relativně jednoduchá na pochopení, ale jak bylo uvedeno, uděláte $push pro "prvek, který tam není" je jiná věc, protože v podstatě potřebujeme podmínku dotazu na úrovni "dokumentu", abychom určili, že prvek pole "chybí".

To v podstatě znamená, že musíte vydat aktualizaci pomocí $push hledá každý prvek pole, aby zjistil, zda existuje nebo ne. Pokud není přítomen, pak je dokument shodný a $push se provádí.

Zde je bulkWrite() vstoupí do hry a použijete ji přidáním další aktualizace k naší první operaci výše pro každý prvek v "sadě změn":

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

Všimněte si $elemMatch ve filtru dotazů, protože se jedná o požadavek na shodu prvku pole podle "více podmínek". Na arrayFilters jsme to nepotřebovali záznamy, protože pouze podívejte se na každou položku pole, na kterou se již vztahují, ale jako „dotaz“ podmínky vyžadují $elemMatch protože jednoduchý „tečkový zápis“ by vrátil nesprávné shody.

Podívejte se také na $not operátor se zde používá k "negaci" $elemMatch , protože našimi skutečnými podmínkami je shoda pouze s dokumentem, který "neobsahuje odpovídající prvek pole" k poskytnutým podmínkám, a to ospravedlňuje výběr pro připojení nového prvku.

A tento jediný příkaz vydaný serveru se v podstatě pokouší o čtyři operace aktualizace jako jedna pro pokus o aktualizaci odpovídajících prvků pole a další pro každý z tří "změnit sady" při pokusu o $push kde bylo zjištěno, že dokument neodpovídá podmínkám pro prvek pole v sadě změn.

Výsledek je tedy podle očekávání:

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

V závislosti na tom, kolik prvků ve skutečnosti neodpovídá bulkWrite() odpověď bude hlásit, kolik z těchto prohlášení se skutečně shodovalo a ovlivnilo dokument. V tomto případě je to 2 spárované a upravené, protože operace „první“ aktualizace odpovídá existujícím položkám pole a aktualizace „poslední“ změny odpovídá tomu, že dokument neobsahuje položku pole a provádí $push upravit.

Závěr

Takže tady máte kombinovaný přístup, kde:

  • První část "aktualizace" ve vaší otázce je velmi snadná a lze ji provést jediným příkazem , jak je ukázáno v první části.

  • Druhá část, kde je prvek pole, který "v současnosti neexistuje" v rámci aktuálního pole dokumentů to ve skutečnosti vyžaduje, abyste použili bulkWrite() za účelem vydání „více“ operací v jednom požadavku.

Proto aktualizujte , je "ANO" pro jednu operaci. Ale přidání rozdílu znamená více operací. Ale můžete oba přístupy kombinovat, jak je ukázáno zde.

Existuje mnoho "fantastických" způsobů, jak můžete tyto příkazy sestavit na základě obsahu pole "change set" s kódem, takže nemusíte "pevně kódovat" každý člen.

Jako základní případ pro JavaScript a kompatibilní s aktuální verzí mongo shellu (které poněkud nepříjemně nepodporuje operátory šíření objektů):

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

Tím se dynamicky vytvoří seznam „hromadných“ aktualizačních operací, který bude vypadat takto:

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

Stejně jako bylo popsáno v "dlouhé formě" obecné odpovědi, ale samozřejmě jednoduše používá vstupní "pole" obsah k sestavení všech těchto prohlášení.

Takovou dynamickou konstrukci objektů můžete provádět v jakémkoli jazyce a všechny ovladače MongoDB přijímají vstup nějakého typu struktury, kterou můžete „manipulovat“, která je poté transformována na BSON předtím, než je skutečně odeslána na server ke spuštění.




  1. Konfigurace redis tak, aby nejprve důsledně vyřazovala starší data

  2. Klíč ioredis s odpovídajícím vzorem

  3. 4 způsoby, jak uvést sbírky v databázi MongoDB

  4. Nodejs / Express – Spouštění mé aplikace:express.createServer() je zastaralé