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

Najděte počet všech překrývajících se intervalů

Jak správně uvádíte, existují různé přístupy s různou složitostí související s jejich prováděním. To v podstatě popisuje, jak se dělají a který z nich implementujete, závisí na tom, pro která data a případ použití se nejlépe hodí.

Aktuální shoda rozsahu

MongoDB 3.6 $lookup

Nejjednodušší přístup lze použít pomocí nové syntaxe $lookup operátor s MongoDB 3.6, který umožňuje potrubí být dán jako výraz „samopřipojit“ ke stejné sbírce. To může v podstatě znovu dotazovat kolekci na všechny položky, kde je počáteční čas "nebo" čas ukončení aktuálního dokumentu spadá mezi stejné hodnoty jakéhokoli jiného dokumentu, samozřejmě kromě původního:

db.getCollection('collection').aggregate([
  { "$lookup": {
    "from": "collection",
    "let": {
      "_id": "$_id",
      "starttime": "$starttime",
      "endtime": "$endtime"
    },
    "pipeline": [
      { "$match": {
        "$expr": {
          "$and": [
            { "$ne": [ "$$_id", "$_id" },
            { "$or": [
              { "$and": [
                { "$gte": [ "$$starttime", "$starttime" ] },
                { "$lte": [ "$$starttime", "$endtime" ] }
              ]},
              { "$and": [
                { "$gte": [ "$$endtime", "$starttime" ] },
                { "$lte": [ "$$endtime", "$endtime" ] }
              ]}
            ]},
          ]
        },
        "as": "overlaps"
      }},
      { "$count": "count" },
    ]
  }},
  { "$match": { "overlaps.0": { "$exists": true }  } }
])

Jediný $lookup provede "připojení" ve stejné kolekci, což vám umožní zachovat hodnoty "aktuálního dokumentu" pro "_id" , "počáteční čas" a "čas ukončení" hodnoty pomocí "let" možnost fáze potrubí. Ty budou dostupné jako "místní proměnné" pomocí $$ prefix v následujícím "pipeline" výrazu.

V rámci tohoto „sub-pipeline“ používáte $match fáze potrubí a $expr dotazovací operátor, který umožňuje vyhodnotit logické výrazy agregačního rámce jako součást podmínky dotazu. To umožňuje porovnání mezi hodnotami při výběru nových dokumentů vyhovujících podmínkám.

Podmínky jednoduše hledají "zpracované dokumenty", kde je "_id" pole se nerovná "aktuálnímu dokumentu", $and kde buď "počáteční čas" $or "čas ukončení" hodnoty "aktuálního dokumentu" spadají mezi stejné vlastnosti "zpracovaného dokumentu". Upozorňujeme, že tyto a také příslušné $gte a $lte operátory jsou "operátory porovnání agregací" a nikoli "operátor dotazu" formulář, jako vrácený výsledek vyhodnocený $expr musí být boolean v souvislosti s. To je to, co operátory agregačního porovnávání skutečně dělají, a je to také jediný způsob, jak předávat hodnoty pro srovnání.

Protože chceme pouze „počet“ shod, $count k tomu slouží potrubní stupeň. Výsledek celkového $lookup bude pole „jednoho prvku“, kde byl počet, nebo „prázdné pole“, kde nebyla žádná shoda s podmínkami.

Alternativním případem by bylo „vynechat“ $count fázi a jednoduše umožnit návrat odpovídajících dokumentů. To umožňuje snadnou identifikaci, ale jako „pole vložené do dokumentu“ musíte mít na paměti počet „přesahů“, které budou vráceny jako celé dokumenty, a že to nezpůsobí porušení limitu BSON 16 MB. Ve většině případů by to mělo být v pořádku, ale v případech, kdy u daného dokumentu očekáváte velký počet překryvů, to může být skutečný případ. Takže je to opravdu něco, co je třeba si uvědomit.

$lookup fáze potrubí v tomto kontextu „vždy“ vrátí pole ve výsledku, i když je prázdné. Název výstupní vlastnosti "sloučení" do existujícího dokumentu bude "overlaps" jak je uvedeno v "jako" vlastnost na $lookup fázi.

Podle $lookup , můžeme pak provést jednoduchý $match s regulárním výrazem dotazu využívajícím $exists test na 0 hodnota indexu výstupního pole. Tam, kde je v poli skutečně nějaký obsah, a proto se "překrývá", podmínka bude pravdivá a dokument se vrátí, přičemž bude zobrazen buď počet, nebo dokumenty "překrývající se" podle vašeho výběru.

Ostatní verze – Dotazy k „připojení“

Alternativním případem, kdy váš MongoDB tuto podporu postrádá, je „připojit se“ ručně zadáním stejných podmínek dotazu uvedených výše pro každý zkoumaný dokument:

db.getCollection('collection').find().map( d => {
  var overlaps = db.getCollection('collection').find({
    "_id": { "$ne": d._id },
    "$or": [
      { "starttime": { "$gte": d.starttime, "$lte": d.endtime } },
      { "endtime": { "$gte": d.starttime, "$lte": d.endtime } }
    ]
  }).toArray();

  return ( overlaps.length !== 0 ) 
    ? Object.assign(
        d,
        {
          "overlaps": {
            "count": overlaps.length,
            "documents": overlaps
          }
        }
      )
    : null;
}).filter(e => e != null);

Toto je v podstatě stejná logika, až na to, že se ve skutečnosti musíme vrátit „zpět do databáze“, abychom mohli zadat dotaz tak, aby odpovídal překrývajícím se dokumentům. Tentokrát jsou to "dotazovací operátory", které se používají k nalezení toho, kde spadají hodnoty aktuálního dokumentu mezi hodnoty zpracovávaného dokumentu.

Vzhledem k tomu, že výsledky jsou již vráceny ze serveru, neexistuje žádné omezení omezení BSON na přidávání obsahu do výstupu. Možná máte omezení paměti, ale to je další problém. Jednoduše řečeno vracíme pole místo kurzoru pomocí .toArray() takže máme odpovídající dokumenty a můžeme jednoduše přistupovat k délce pole, abychom získali počet. Pokud dokumenty ve skutečnosti nepotřebujete, použijte .count() místo .find() je mnohem efektivnější, protože odpadá režie načítání dokumentů.

Výstup je pak jednoduše sloučen s existujícím dokumentem, kde dalším důležitým rozdílem je, že jelikož jsou teze „vícenásobné dotazy“, neexistuje způsob, jak zajistit podmínku, že musí něco „odpovídat“. To nás nechává uvažovat, že budou výsledky, kde počet (nebo délka pole) je 0 a vše, co v tuto chvíli můžeme udělat, je vrátit null hodnotu, kterou můžeme později .filter() z výsledného pole. Jiné metody iterace kurzoru využívají stejný základní princip „zahazování“ výsledků tam, kde je nechceme. Spuštění dotazu na serveru však nic nebrání a toto filtrování je v té či oné formě „po zpracování“.

Snížení složitosti

Výše uvedené přístupy tedy pracují s popsanou strukturou, ale celková složitost samozřejmě vyžaduje, že pro každý dokument musíte v podstatě prozkoumat každý jiný dokument ve sbírce, abyste hledali přesahy. Proto při používání $lookup umožňuje určitou "efektivitu" ve snížení režie přenosu a odezvy stále trpí stejným problémem, že stále v podstatě porovnáváte každý dokument se vším.

Lepší řešení "tam, kde se to hodí" je místo toho uložit "pevnou hodnotu"* reprezentující interval na každém dokumentu. Mohli bychom například „předpokládat“, že existují pevné „rezervační“ lhůty jedné hodiny za den, celkem tedy 24 období rezervace. Toto "mohlo" být reprezentováno něco jako:

{ "_id": "A", "booking": [ 10, 11, 12 ] }
{ "_id": "B", "booking": [ 12, 13, 14 ] }
{ "_id": "C", "booking": [ 7, 8 ] }
{ "_id": "D", "booking": [ 9, 10, 11 ] }

S daty organizovanými takto, kde byl nastaven indikátor pro interval, je složitost výrazně snížena, protože je to opravdu jen otázka "seskupení" na hodnotu intervalu z pole v rámci "rezervace" vlastnost:

db.booking.aggregate([
  { "$unwind": "$booking" },
  { "$group": { "_id": "$booking", "docs": { "$push": "$_id" } } },
  { "$match": { "docs.1": { "$exists": true } } }
])

A výstup:

{ "_id" : 10, "docs" : [ "A", "D" ] }
{ "_id" : 11, "docs" : [ "A", "D" ] }
{ "_id" : 12, "docs" : [ "A", "B" ] }

To správně identifikuje to pro 10 a 11 intervaly oba "A" a "D" obsahovat překrytí, zatímco "B" a "A" překrývají na 12 . Jiné intervaly a shodu dokumentů jsou vyloučeny prostřednictvím stejného $exists test kromě této doby na 1 index (nebo je přítomen druhý prvek pole), abyste viděli, že v seskupení bylo "více než jeden" dokument, což naznačuje překrytí.

To jednoduše využívá $unwind fázi agregačního potrubí k „dekonstruaci/denormalizaci“ obsahu pole, abychom měli přístup k vnitřním hodnotám pro seskupování. Přesně to se děje ve $group fáze, kde je poskytnutým „klíčem“ id intervalu rezervace a $push operátor se používá ke "sbírání" dat o aktuálním dokumentu, který byl nalezen v této skupině. $match je vysvětleno dříve.

To lze dokonce rozšířit pro alternativní prezentaci:

db.booking.aggregate([
  { "$unwind": "$booking" },
  { "$group": { "_id": "$booking", "docs": { "$push": "$_id" } } },
  { "$match": { "docs.1": { "$exists": true } } },
  { "$unwind": "$docs" },
  { "$group": {
    "_id": "$docs",
    "intervals": { "$push": "$_id" }  
  }}
])

S výstupem:

{ "_id" : "B", "intervals" : [ 12 ] }
{ "_id" : "D", "intervals" : [ 10, 11 ] }
{ "_id" : "A", "intervals" : [ 10, 11, 12 ] }

Je to zjednodušená demonstrace, ale tam, kde data, která máte, by to umožnila pro požadovaný druh analýzy, pak je to mnohem efektivnější přístup. Pokud tedy dokážete ponechat „zrnitost“, která má být fixována na „nastavené“ intervaly, které lze běžně zaznamenávat na každém dokumentu, pak analýza a reportování mohou použít druhý přístup k rychlé a efektivní identifikaci takových překrývání.

V podstatě takto byste implementovali to, co jste v zásadě zmínili jako „lepší“ přístup, a první by bylo „nepatrné“ zlepšení oproti tomu, co jste původně teoretizovali. Podívejte se, který z nich skutečně vyhovuje vaší situaci, ale to by mělo vysvětlit implementaci a rozdíly.




  1. c# mongodb vyhledávání citlivé na velká a malá písmena

  2. MongoDB - Promítněte pouze odpovídající prvek v poli

  3. Jak plyne z tohoto scénáře?

  4. C# Mongodb kartézský součin více dokumentů pole objektů