1

Update 1、Ayendeの回答に続く

これは RavenDb への私の最初の旅であり、それを試すために小さなマップ/リデュースを書きましたが、残念ながら結果は空ですか?

RavenDb には約 160 万のドキュメントが読み込まれています

文書:

public class Tick
{
    public DateTime Time;
    public decimal Ask;
    public decimal Bid;
    public double AskVolume;
    public double BidVolume;
}

そして、特定の期間にわたってAskの最小値と最大値を取得したいと考えていました.

Time ごとのコレクションは次のように定義されます。

var ticks = session.Query<Tick>().Where(x => x.Time > new DateTime(2012, 4, 23) && x.Time < new DateTime(2012, 4, 24, 00, 0, 0)).ToList();

これにより、90280 個のドキュメントが得られます。これまでのところ、問題ありません。

しかし、次に map/reduce:

Map = rows => from row in rows 
                          select new
                          {
                              Max = row.Bid,
                              Min = row.Bid, 
                              Time = row.Time,
                              Count = 1
                          };

Reduce = results => from result in results
                                group result by new{ result.MaxBid, result.Count} into g
                                select new
                                {
                                    Max = g.Key.MaxBid,
                                    Min = g.Min(x => x.MaxBid),
                                    Time = g.Key.Time,
                                    Count = g.Sum(x => x.Count)

                                };

...

private class TickAggregationResult
{
    public decimal MaxBid { get; set; }
        public decimal MinBid { get; set; }
        public int Count { get; set; }

    }

次に、インデックスを作成してクエリを実行します。

Raven.Client.Indexes.IndexCreation.CreateIndexes(typeof(TickAggregation).Assembly, documentStore);


        var session = documentStore.OpenSession();

        var g1 = session.Query<TickAggregationResult>(typeof(TickAggregation).Name);


        var group = session.Query<Tick, TickAggregation>()
                         .Where(x => x.Time > new DateTime(2012, 4, 23) && 
                                     x.Time < new DateTime(2012, 4, 24, 00, 0, 0)
                                  )
            .Customize(x => x.WaitForNonStaleResults())
                                           .AsProjection<TickAggregationResult>();

しかし、グループはただ空です:(

ご覧のとおり、2 つの異なるクエリを試してみましたが、違いがよくわかりません。誰か説明してもらえますか?

今、私はエラーが発生します: ここに画像の説明を入力

グループはまだ空です:(

純粋なSQLで達成しようとしていることを説明しましょう:

select min(Ask), count(*) as TickCount from Ticks 
where Time between '2012-04-23' and '2012-04-24)
4

1 に答える 1

3

残念ながら、Map/Reduce はそのようには機能しません。少なくとも、Reduce の部分はそうではありません。セットを減らすには、特定の時間範囲を事前に定義してグループ化する必要があります。たとえば、毎日、毎週、毎月などです。毎日減らすと、1 日あたりの最小/最大/カウントを取得できます。

必要なものを取得する方法はありますが、パフォーマンスに関する考慮事項がいくつかあります。基本的には全く還元はしませんが、時間でインデックスをつけてから、結果を変換する際に集計を行います。これは、最初のクエリを実行してフィルター処理し、クライアント コードで集計した場合と似ています。唯一の利点は、集計がサーバー側で行われるため、そのすべてのデータをクライアントに送信する必要がないことです。

ここでのパフォーマンス上の懸念は、フィルタリングする時間範囲の大きさ、またはより正確には、フィルター範囲内にいくつの項目があるかということです。比較的小さい場合は、このアプローチを使用できます。大きすぎると、サーバーが結果セットを処理するまで待機することになります。

この手法を説明するサンプル プログラムを次に示します。

using System;
using System.Linq;
using Raven.Client.Document;
using Raven.Client.Indexes;
using Raven.Client.Linq;

namespace ConsoleApplication1
{
  public class Tick
  {
    public string Id { get; set; }
    public DateTime Time { get; set; }
    public decimal Bid { get; set; }
  }

  /// <summary>
  /// This index is a true map/reduce, but its totals are for all time.
  /// You can't filter it by time range.
  /// </summary>
  class Ticks_Aggregate : AbstractIndexCreationTask<Tick, Ticks_Aggregate.Result>
  {
    public class Result
    {
      public decimal Min { get; set; }
      public decimal Max { get; set; }
      public int Count { get; set; }
    }

    public Ticks_Aggregate()
    {
      Map = ticks => from tick in ticks
               select new
                    {
                      Min = tick.Bid,
                      Max = tick.Bid,
                      Count = 1
                    };

      Reduce = results => from result in results
                group result by 0
                  into g
                  select new
                         {
                           Min = g.Min(x => x.Min),
                           Max = g.Max(x => x.Max),
                           Count = g.Sum(x => x.Count)
                         };
    }
  }

  /// <summary>
  /// This index can be filtered by time range, but it does not reduce anything
  /// so it will not be performant if there are many items inside the filter.
  /// </summary>
  class Ticks_ByTime : AbstractIndexCreationTask<Tick>
  {
    public class Result
    {
      public decimal Min { get; set; }
      public decimal Max { get; set; }
      public int Count { get; set; }
    }

    public Ticks_ByTime()
    {
      Map = ticks => from tick in ticks
               select new {tick.Time};

      TransformResults = (database, ticks) =>
                 from tick in ticks
                 group tick by 0
                 into g
                 select new
                      {
                        Min = g.Min(x => x.Bid),
                        Max = g.Max(x => x.Bid),
                        Count = g.Count()
                      };
    }
  }

  class Program
  {
    private static void Main()
    {
      var documentStore = new DocumentStore { Url = "http://localhost:8080" };
      documentStore.Initialize();
      IndexCreation.CreateIndexes(typeof(Program).Assembly, documentStore);


      var today = DateTime.Today;
      var rnd = new Random();

      using (var session = documentStore.OpenSession())
      {
        // Generate 100 random ticks
        for (var i = 0; i < 100; i++)
        {
          var tick = new Tick { Time = today.AddMinutes(i), Bid = rnd.Next(100, 1000) / 100m };
          session.Store(tick);
        }

        session.SaveChanges();
      }


      using (var session = documentStore.OpenSession())
      {
        // Query items with a filter.  This will create a dynamic index.
        var fromTime = today.AddMinutes(20);
        var toTime = today.AddMinutes(80);
        var ticks = session.Query<Tick>()
          .Where(x => x.Time >= fromTime && x.Time <= toTime)
          .OrderBy(x => x.Time);

        // Ouput the results of the above query
        foreach (var tick in ticks)
          Console.WriteLine("{0} {1}", tick.Time, tick.Bid);

        // Get the aggregates for all time
        var total = session.Query<Tick, Ticks_Aggregate>()
          .As<Ticks_Aggregate.Result>()
          .Single();
        Console.WriteLine();
        Console.WriteLine("Totals");
        Console.WriteLine("Min: {0}", total.Min);
        Console.WriteLine("Max: {0}", total.Max);
        Console.WriteLine("Count: {0}", total.Count);

        // Get the aggregates with a filter
        var filtered = session.Query<Tick, Ticks_ByTime>()
          .Where(x => x.Time >= fromTime && x.Time <= toTime)
          .As<Ticks_ByTime.Result>()
          .Take(1024)  // max you can take at once
          .ToList()    // required!
          .Single();
        Console.WriteLine();
        Console.WriteLine("Filtered");
        Console.WriteLine("Min: {0}", filtered.Min);
        Console.WriteLine("Max: {0}", filtered.Max);
        Console.WriteLine("Count: {0}", filtered.Count);
      }

      Console.ReadLine();
    }
  }
}

スコープが大きくなる可能性がある時間フィルターを使用して集計するという問題の解決策を想像できます。reduce では、さまざまなレベルで徐々に小さくなる時間単位に分解する必要があります。このコードは少し複雑ですが、私は自分の目的のために取り組んでいます。完了したら、www.ravendb.net のナレッジ ベースに投稿します。


アップデート

私はこれをもう少しいじっていて、最後のクエリで 2 つのことに気付きました。

  1. ToList()完全な結果セットを取得するには、single を呼び出す前にa を実行する必要があります。
  2. これはサーバー上で実行されますが、結果の範囲に含めることができる最大値は 1024 であり、a を指定するTake(1024)か、デフォルトの最大値である 128 を取得する必要があります。これはサーバー上で実行されるため、これは予期していませんでした。しかし、通常は TransformResults セクションで集計を行わないためだと思います。

このコードを更新しました。ただし、範囲がこれが機能するのに十分小さいことを保証できない限り、私が話したより良い完全なマップ/縮小を待ちます. 私はそれに取り組んでいます。:)

于 2012-06-01T18:42:50.383 に答える