sql >> Databasteknik >  >> NoSQL >> MongoDB

MongoDB .NET-drivrutingrupp efter tidsintervall

Om du ser den "exakta saken" som det refererade inlägget att göra med .NET, så kommer det förmodligen inte att implementeras så. Det kan du göra, men du kommer antagligen inte att ta dig an och faktiskt gå på något av de andra alternativen, om du inte har ett behov av "flexibla intervaller" i den utsträckning som jag gör.

Flytande aggregat

Om du har en modern MongoDB 3.6 eller senare server tillgänglig kan du använda $dateFromParts för att rekonstruera datumet från de "rundade" delarna som extraherats från datumet:

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

Uttalandet skickat till servern:

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

Om du inte har den funktionen tillgänglig kan du helt enkelt lämna den avstängd och lämna datumet "demonterat", men sedan montera det igen när du bearbetar markören. Bara för att simulera med en lista:

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

Uttalandet skickat till servern:

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

Det är väldigt liten skillnad mellan de två när det gäller koden. Det är bara det att i ett fall "casting back" till DateTime händer faktiskt på servern med $dateFromParts och i den andra gör vi precis samma casting med DateTime konstruktor i kod när du itererar varje markörresultat.

Så de är verkligen nästan likadana med den enda verkliga skillnaden är var "servern" gör casting datumet returneras använder mycket mindre byte per dokument. Faktum är att "5 gånger" mindre eftersom alla numeriska format här (inklusive BSON-datumet) är baserade på 64-bitars heltal. Trots det är alla dessa siffror fortfarande faktiskt "lättare" än att skicka tillbaka någon "sträng"-representation av ett datum.

LINQ frågebar

Det är de grundläggande formerna som verkligen förblir desamma när man kartlägger dessa olika former:

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

Uttalandet skickat till servern:

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

Eller med 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);

Uttalandet skickat till servern:

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

Som du kan se är allt i princip samma form

Konvertera originalet

Om du funderar på att replikera det ursprungliga "date math"-formuläret som det publicerades, faller det för närvarande utanför räckvidden för vad du faktiskt kan göra med antingen LINQ eller Fluent-byggarna. Det enda sättet att få samma sekvens är med BsonDocument konstruktion:

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

Begäran skickad till servern:

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

Den stora anledningen till att vi inte kan göra detta just nu är att den nuvarande serialiseringen av påståendena i princip inte håller med om att .NET Framework säger att subtrahera två DateTime värden returnerar en TimeSpan , och MongoDB-konstruktionen för att subtrahera två BSON-datum returnerar "millisekunder sedan epok", vilket i huvudsak är hur matematiken fungerar.

Den "bokstavliga" översättningen av lamdba-uttrycket är i huvudsak:

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

Men kartläggningen kräver fortfarande en del arbete för att antingen känna igen påståendena eller formalisera vilken sorts påståenden som faktiskt är avsedda för detta ändamål.

Särskilt MongoDB 4.0 introducerar $convert operatorn och de vanliga aliasen för $toLong och $toDate , som alla kan användas i pipelinen istället för den nuvarande hanteringen av "addition" och "subtraktion" med BSON-datum. Dessa börjar bilda en mer "formell" specifikation för sådana konverteringar snarare än metoden som visas som enbart förlitade sig på "addition" och "subtraktion", som fortfarande är giltiga, men sådana namngivna operatorer är mycket tydligare i avsikten i koden:

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

Det är ganska uppenbart att med "formaliserade" operatorer för satskonstruktion med LINQ för sådana "DateToLong" och "LongToDate"-funktioner, så blir satsen mycket renare utan att de typer av "tvång" som visas i lambdauttrycket "icke fungerande" är klar.




  1. MongoDB atomic findOrCreate:findOne, infoga om det inte finns, men uppdatera inte

  2. installera mongoDB (underordnad process misslyckades, avslutades med felnummer 100)

  3. Varför rekommenderas det att inte stänga en MongoDB-anslutning någonstans i Node.js-koden?

  4. Node.js, (Hi)Redis och multikommandot