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

Skupina ovladačů MongoDB .NET podle časového rozsahu

Pokud hledáte "přesnou věc" jako odkazovaný příspěvek týkající se .NET, pak to pravděpodobně nebude ve skutečnosti takto implementováno. Můžete to udělat, ale pravděpodobně se nebudete pouštět do všech problémů a ve skutečnosti se rozhodnete pro jednu z jiných alternativ, pokud nepotřebujete „flexibilní intervaly“ v takové míře jako já..

Plynulý agregát

Pokud máte k dispozici moderní server MongoDB 3.6 nebo vyšší, můžete použít $dateFromParts za účelem rekonstruování data ze "zaoblených" částí extrahovaných z data:

DateTime startDate = new DateTime(2018, 5, 1, 0, 0, 0, DateTimeKind.Utc);
DateTime endDate = new DateTime(2018, 6, 1, 0, 0, 0, DateTimeKind.Utc);

var result = Collection.Aggregate()
  .Match(k => k.Timestamp >= startDate && k.Timestamp < endDate)
  .Group(k =>
    new DateTime(k.Timestamp.Year, k.Timestamp.Month, k.Timestamp.Day,
        k.Timestamp.Hour, k.Timestamp.Minute - (k.Timestamp.Minute % 15), 0),
    g => new { _id = g.Key, count = g.Count() }
  )
  .SortBy(d => d._id)
  .ToList();

Výpis odeslán na server:

[
  { "$match" : {
    "Timestamp" : {
      "$gte" : ISODate("2018-05-01T00:00:00Z"),
      "$lt" : ISODate("2018-06-01T00:00:00Z")
    }
  } },
  { "$group" : {
    "_id" : { 
      "$dateFromParts" : {
        "year" : { "$year" : "$Timestamp" },
        "month" : { "$month" : "$Timestamp" },
        "day" : { "$dayOfMonth" : "$Timestamp" },
        "hour" : { "$hour" : "$Timestamp" },
        "minute" : { "$subtract" : [
          { "$minute" : "$Timestamp" },
          { "$mod" : [ { "$minute" : "$Timestamp" }, 15 ] }
        ] },
        "second" : 0
      }
    },
    "count" : { "$sum" : 1 }
  } },
  { "$sort": { "_id": 1 } }
]

Pokud tuto funkci nemáte k dispozici, můžete ji jednoduše nechat vypnutou a ponechat datum „rozebrané“, ale poté jej znovu sestavit, když budete zpracovávat kurzor. Jen pro simulaci se seznamem:

var result = Collection.Aggregate()
 .Match(k => k.Timestamp >= startDate && k.Timestamp < endDate)
 .Group(k => new
    {
      year = k.Timestamp.Year,
      month = k.Timestamp.Month,
      day = k.Timestamp.Day,
      hour = k.Timestamp.Hour,
      minute = k.Timestamp.Minute - (k.Timestamp.Minute % 15)
    },
    g => new { _id = g.Key, count = g.Count() }
  )
  .SortBy(d => d._id)
  .ToList();

foreach (var doc in result)
{
  //System.Console.WriteLine(doc.ToBsonDocument());
  System.Console.WriteLine(
    new BsonDocument {
      { "_id", new DateTime(doc._id.year, doc._id.month, doc._id.day,
        doc._id.hour, doc._id.minute, 0) },
      { "count", doc.count }
    }
  );
}

Výpis odeslaný na server:

[
  { "$match" : {
    "Timestamp" : {
      "$gte" : ISODate("2018-05-01T00:00:00Z"),
      "$lt" : ISODate("2018-06-01T00:00:00Z")
    }
  } },
  { "$group" : {
    "_id" : {
      "year" : { "$year" : "$Timestamp" },
      "month" : { "$month" : "$Timestamp" },
      "day" : { "$dayOfMonth" : "$Timestamp" },
      "hour" : { "$hour" : "$Timestamp" },
      "minute" : { "$subtract" : [
        { "$minute" : "$Timestamp" }, 
        { "$mod" : [ { "$minute" : "$Timestamp" }, 15 ] }
      ] }
    },
    "count" : { "$sum" : 1 }
  } },
  { "$sort" : { "_id" : 1 } }
]

Z hlediska kódu je mezi nimi velmi malý rozdíl. Jde jen o to, že v jednom případě "přehození zpět" do DateTime ve skutečnosti se děje na serveru s $dateFromParts a v druhém děláme přesně to samé přetypování pomocí DateTime konstruktoru v kódu při iteraci každého výsledku kurzoru.

Jsou tedy skutečně téměř stejné s jediným skutečným rozdílem v tom, kde „server“ přetypuje, že vrácené datum používá mnohem méně bajtů na dokument. Ve skutečnosti "5krát" méně, protože všechny číselné formáty zde (včetně BSON Date) jsou založeny na 64bitových celých číslech. I tak jsou všechna tato čísla ve skutečnosti stále „lehčí“ než zasílání jakékoli „řetězcové“ reprezentace data.

Dotazovatelný LINQ

Toto jsou základní formy, které při mapování na tyto různé formy skutečně zůstávají stejné:

var query = from p in Collection.AsQueryable()
            where p.Timestamp >= startDate && p.Timestamp < endDate
            group p by new DateTime(p.Timestamp.Year, p.Timestamp.Month, p.Timestamp.Day,
              p.Timestamp.Hour, p.Timestamp.Minute - (p.Timestamp.Minute % 15), 0) into g
            orderby g.Key
            select new { _id = g.Key, count = g.Count() };

Výpis odeslaný na server:

[
  { "$match" : {
    "Timestamp" : {
      "$gte" : ISODate("2018-05-01T00:00:00Z"),
      "$lt" : ISODate("2018-06-01T00:00:00Z")
    }
  } },
  { "$group" : {
    "_id" : {
      "$dateFromParts" : {
        "year" : { "$year" : "$Timestamp" }, 
        "month" : { "$month" : "$Timestamp" },
        "day" : { "$dayOfMonth" : "$Timestamp" }, 
        "hour" : { "$hour" : "$Timestamp" }, 
        "minute" : { "$subtract" : [
          { "$minute" : "$Timestamp" },
          { "$mod" : [ { "$minute" : "$Timestamp" }, 15 ] }
        ] },
        "second" : 0
      }
    },
    "__agg0" : { "$sum" : 1 }
  } },
  { "$sort" : { "_id" : 1 } },
  { "$project" : { "_id" : "$_id", "count" : "$__agg0" } }
]

Nebo pomocí GroupBy()

var query = Collection.AsQueryable()
    .Where(k => k.Timestamp >= startDate && k.Timestamp < endDate)
    .GroupBy(k =>
      new DateTime(k.Timestamp.Year, k.Timestamp.Month, k.Timestamp.Day,
            k.Timestamp.Hour, k.Timestamp.Minute - (k.Timestamp.Minute % 15), 0),
      (k, s) => new { _id = k, count = s.Count() }
    )
    .OrderBy(k => k._id);

Výpis odeslaný na server:

[
  { "$match" : {
    "Timestamp" : {
      "$gte" : ISODate("2018-05-01T00:00:00Z"),
      "$lt" : ISODate("2018-06-01T00:00:00Z")
    }
  } },
  { "$group" : {
    "_id" : {
      "$dateFromParts" : {
        "year" : { "$year" : "$Timestamp" },
        "month" : { "$month" : "$Timestamp" },
        "day" : { "$dayOfMonth" : "$Timestamp" },
        "hour" : { "$hour" : "$Timestamp" },
        "minute" : { "$subtract" : [ 
          { "$minute" : "$Timestamp" }, 
          { "$mod" : [ { "$minute" : "$Timestamp" }, 15 ] } 
        ] },
        "second" : 0
      }
    },
    "count" : { "$sum" : 1 }
  } },
  { "$sort" : { "_id" : 1 } }
]

Jak můžete vidět, je to v podstatě stejná forma

Převod originálu

Pokud chcete replikovat původní formulář "datové matematiky", jak byl zveřejněn, pak to v současné době spadá mimo rozsah toho, co můžete skutečně dělat s LINQ nebo Fluent buildery. Jediný způsob, jak získat stejnou sekvenci, je pomocí BsonDocument konstrukce:

DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);

var group = new BsonDocument { {
  "$group",
  new BsonDocument {
    { "_id",
    new BsonDocument { {
      "$add", new BsonArray
      {
        new BsonDocument { {
            "$subtract",
            new BsonArray {
              new BsonDocument { { "$subtract", new BsonArray { "$Timestamp", epoch } } },
              new BsonDocument { {
                "$mod", new BsonArray
                {
                 new BsonDocument { { "$subtract", new BsonArray { "$Timestamp", epoch } } },
                 1000 * 60 * 15
               }
             } }
           }
         } },
         epoch
       }
     } }
     },
     {
       "count", new BsonDocument("$sum", 1)
     }
   }
} };

var query = sales.Aggregate()
  .Match(k => k.Timestamp >= startDate && k.Timestamp < endDate)
  .AppendStage<BsonDocument>(group)
  .Sort(new BsonDocument("_id", 1))
  .ToList();

Požadavek odeslán na server:

[
  { "$match" : {
    "Timestamp" : {
      "$gte" : ISODate("2018-05-01T00:00:00Z"),
      "$lt" : ISODate("2018-06-01T00:00:00Z")
    }
  } },
  { "$group" : {
    "_id" : { 
      "$add" : [
        { "$subtract" : [ 
          { "$subtract" : [ "$Timestamp", ISODate("1970-01-01T00:00:00Z") ] },
          { "$mod" : [ 
            { "$subtract" : [ "$Timestamp", ISODate("1970-01-01T00:00:00Z") ] },
            900000
          ] }
        ] },
        ISODate("1970-01-01T00:00:00Z")
      ]
    },
    "count" : { "$sum" : 1 }
  } },
  { "$sort" : { "_id" : 1 } }
]

Velký důvod, proč to teď nemůžeme udělat, je ten, že současná serializace příkazů v podstatě nesouhlasí v tom, že .NET Framework říká, že odečtení dvou DateTime hodnoty vrátí TimeSpan a konstrukce MongoDB odečítání dvou dat BSON vrací „milisekundy od epochy“, což je v podstatě způsob, jakým matematika funguje.

„Doslovný“ překlad výrazu lamdba je v podstatě:

p =>  epoch.AddMilliseconds(
       (p.Timestamp - epoch).TotalMilliseconds
       - ((p.Timestamp - epoch).TotalMilliseconds % 1000 * 60 * 15))

Mapování však ještě potřebuje nějakou práci, aby bylo možné buď rozpoznat příkazy, nebo formalizovat, jaký druh příkazů je skutečně určen pro tento účel.

Zejména MongoDB 4.0 zavádí $convert operátor a běžné aliasy $toLong a $toDate , které lze všechny použít v potrubí namísto současného zpracování „sčítání“ a „odčítání“ pomocí dat BSON. Ty začnou tvořit „formálnější“ specifikaci pro takové převody spíše než způsob, jak je ukázán, který se spoléhal pouze na ono „sčítání“ a „odčítání“, které je stále platné, ale takto pojmenované operátory mají mnohem jasnější záměr v kódu:

{ "$group": {
  "_id": {
    "$toDate": {
      "$subtract": [
        { "$toLong": "$Timestamp" },
        { "$mod": [{ "$toLong": "$Timestamp" }, 1000 * 60 * 15 ] }
      ]
    }
  },
  "count": { "$sum": 1 }
}}

Je docela jasně vidět, že s „formalizovanými“ operátory pro konstrukci příkazu s LINQ pro takové funkce „DateToLong“ a „LongToDate“ se příkaz stává mnohem čistším, aniž by byly typy „nátlaků“ zobrazené v „nefunkčním“ lambda výrazu hotovo.




  1. Redis přístup z MS Excel?

  2. Jak získat zpět smazaný prostor bez `db.repairDatabase()`?

  3. Mongoose vnořil dotaz na Model podle pole svého odkazovaného modelu

  4. Reference dokumentu Mongoose se vztahem jedna k mnoha