sql >> Database teknologi >  >> NoSQL >> MongoDB

MongoDB .NET-drivergruppe efter tidsinterval

Hvis du ser på den "præcise ting" som det refererede indlæg, der har at gøre med .NET, så vil det sandsynligvis ikke blive implementeret sådan. Det kan du godt, men du kommer nok ikke til at gå til besværet og faktisk gå efter et af de andre alternativer, medmindre du har behov for "fleksible intervaller" i det omfang, jeg gør.

Flydende aggregat

Hvis du har en moderne MongoDB 3.6 eller nyere server tilgængelig, kan du bruge $dateFromParts for at rekonstruere datoen fra de "afrundede" dele udtrukket fra datoen:

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

Udtalelse sendt til 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 } }
]

Hvis du ikke har den funktion tilgængelig, så kan du simpelthen lade den være fra og lade datoen være "skilt ad", men derefter samle den igen, mens du behandler markøren. Bare for at simulere med en liste:

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

Erklæring sendt til serveren:

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

Der er meget lidt forskel mellem de to med hensyn til koden. Det er bare, at i et tilfælde "casting back" til DateTime sker faktisk på serveren med $dateFromParts og i den anden laver vi netop den samme casting ved hjælp af DateTime konstruktør i kode, mens du gentager hvert markørresultat.

Så de er næsten de samme, med den eneste reelle forskel er, hvor "serveren" caster, og den returnerede dato bruger meget færre bytes pr. dokument. Faktisk "5 gange" mindre, da alle numeriske formater her (inklusive BSON-datoen) er baseret på 64 bit heltal. Alligevel er alle disse tal faktisk stadig "lettere" end at sende en "streng"-repræsentation af en dato tilbage.

LINQ kan forespørges

Det er de grundlæggende former, som virkelig forbliver de samme, når de kortlægges på disse forskellige 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() };

Erklæring sendt til serveren:

[
  { "$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 ved at bruge 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);

Erklæring sendt til serveren:

[
  { "$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, er det hele grundlæggende den samme form

Konvertering af originalen

Hvis du ønsker at replikere den originale "date math"-formular som opslået, falder den i øjeblikket uden for rækkevidden af, hvad du faktisk kan gøre med enten LINQ eller Fluent Builders. Den eneste måde at få den samme sekvens på er 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();

Anmodning sendt til 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 } }
]

Den store grund til, at vi ikke kan gøre dette lige nu, er, at den aktuelle serialisering af udsagn dybest set er uenig med det punkt, at .NET Framework siger, at man trækker to DateTime fra. værdier returnerer en TimeSpan , og MongoDB-konstruktionen med at subtrahere to BSON-datoer returnerer "millisekunder siden epoke", hvilket i bund og grund er, hvordan matematikken fungerer.

Den "bogstavelige" oversættelse af lamdba-udtrykket er i det væsentlige:

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

Men kortlægningen kræver stadig noget arbejde for enten at genkende udsagn eller formalisere, hvilken slags udsagn der faktisk er beregnet til dette formål.

Især MongoDB 4.0 introducerer $convert operator og de almindelige aliasser for $toLong og $toDate , som alle kan bruges i pipelinen i stedet for den nuværende håndtering af "addition" og "subtraktion" med BSON Dates. Disse begynder at danne en mere "formel" specifikation for sådanne konverteringer snarere end metoden som vist, som udelukkende var afhængig af den "addition" og "subtraktion", som stadig er gyldige, men sådanne navngivne operatorer er meget tydeligere i hensigten i koden:

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

Det er ret tydeligt at se, at med "formaliserede" operatorer til sætningskonstruktion med LINQ for sådanne "DateToLong"- og "LongToDate"-funktioner, så bliver sætningen meget renere, uden at de typer af "tvang", der vises i lambdaudtrykket "ikke-fungerende" er færdig.




  1. Laravel + predis + Redis klynge - FLYTTET / ingen forbindelse til 127.0.0.1:6379

  2. MongoDB $minut

  3. Indlejret dokument vs reference i mongoose-designmodel?

  4. Hvordan kan jeg gemme tidspunktet på dagen i MongoDB? Som en snor? Giv vilkårligt år/måned/dag?