sql >> Database >  >> NoSQL >> MongoDB

MongoDB .NET-stuurprogramma groeperen op tijdbereik

Als je het "exacte ding" zoekt als het bericht waarnaar wordt verwezen met .NET, dan zal het waarschijnlijk niet zo worden geïmplementeerd. Dat kan, maar je gaat waarschijnlijk niet al die moeite doen en daadwerkelijk voor een van de andere alternatieven gaan, tenzij je behoefte hebt aan "flexibele intervallen" in de mate dat ik dat doe..

Vloeiend aggregaat

Als u een moderne MongoDB 3.6 of hoger server beschikbaar heeft, kunt u $dateFromParts gebruiken om de datum te reconstrueren uit de "afgeronde" delen die uit de datum zijn gehaald:

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

Verklaring verzonden naar 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 } }
]

Als je die functie niet beschikbaar hebt, kun je dat gewoon weglaten en de datum "gedemonteerd" laten, maar dan weer in elkaar zetten terwijl je de cursor verwerkt. Gewoon om te simuleren met een lijst:

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

Verklaring verzonden naar de 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 } }
]

Er is heel weinig verschil tussen de twee in termen van de code. Het is alleen dat in één geval het "terugwerpen" naar DateTime gebeurt eigenlijk op de server met de $dateFromParts en in de andere doen we precies dezelfde casting met de DateTime constructor in code terwijl u elk cursorresultaat herhaalt.

Dus ze zijn echt bijna hetzelfde met het enige echte verschil dat waar de "server" de casting doet, de geretourneerde datum veel minder bytes per document gebruikt. In feite "5 keer" minder omdat alle numerieke formaten hier (inclusief de BSON-datum) zijn gebaseerd op 64-bits gehele getallen. Toch zijn al die getallen eigenlijk nog steeds "lichter" dan het terugsturen van een "string"-representatie van een datum.

LINQ opvraagbaar

Dit zijn de basisvormen die echt hetzelfde blijven bij het in kaart brengen op deze verschillende vormen:

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

Verklaring verzonden naar de 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" } }
]

Of gebruik 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);

Verklaring verzonden naar de 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 } }
]

Zoals je kunt zien is het allemaal in principe dezelfde vorm

Het origineel converteren

Als je het originele "datum-wiskunde"-formulier wilt repliceren zoals gepost, dan valt het momenteel buiten het bereik van wat je daadwerkelijk kunt doen met LINQ of de Fluent-builders. De enige manier om dezelfde reeks te krijgen is met BsonDocument constructie:

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

Verzoek verzonden naar 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 } }
]

De belangrijkste reden waarom we dit nu niet kunnen doen, is omdat de huidige serialisatie van de uitspraken in principe niet in overeenstemming is met het punt dat het .NET Framework zegt dat het aftrekken van twee DateTime waarden retourneren een TimeSpan , en de MongoDB-constructie van het aftrekken van twee BSON-datums retourneert de "milliseconden sinds epoch", wat in wezen is hoe de wiskunde werkt.

De "letterlijke" vertaling van de lamdba-uitdrukking is in wezen:

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

Maar het in kaart brengen heeft nog wat werk nodig om ofwel de uitspraken te herkennen of te formaliseren over welk soort uitspraken eigenlijk voor dit doel bedoeld zijn.

Met name MongoDB 4.0 introduceert de $convert operator en de algemene aliassen van $toLong en $toDate , die allemaal in de pijplijn kunnen worden gebruikt in plaats van de huidige afhandeling van "optellen" en "aftrekken" met BSON-datums. Deze beginnen een meer "formele" specificatie te vormen voor dergelijke conversies in plaats van de getoonde methode die uitsluitend gebaseerd is op die "optellen" en "aftrekken", die nog steeds geldig zijn, maar dergelijke benoemde operators zijn veel duidelijker van opzet in de code:

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

Het is vrij duidelijk om te zien dat met "geformaliseerde" operators voor het bouwen van instructies met LINQ voor dergelijke "DateToLong" en "LongToDate" functies, de verklaring veel schoner wordt zonder dat de soorten "dwang" getoond in de "niet-werkende" lambda-expressie zijn klaar.




  1. ModuleNotFoundError:Geen module met de naam 'grp' in windows

  2. mongodb zoekt zowel met AND als OR

  3. Node.js multi-server cluster:hoe een object te delen in meerdere nodes cluster

  4. CouchDB-stijlsynchronisatie en conflictoplossing op Postgres met Hasura