see on GitHub

Indexing: Time Series


Time series indexes process time series segments, rather than document fields.
The API for creating time series indexes is very similar to (and it inherits from) the API for creating document indexes.

Unlike document indexes, time series indexes currently support only LINQ syntax. JavaScript syntax is not supported.

RavenDB does not create dynamic time series indexes in response to queries, but can be created as static indexes from a client application or from the Studio.


Syntax

There are two main ways to create a time series index:

  1. Create a class that inherits from one of the abstract index creation task classes:

  2. Create a TimeSeriesIndexDefinition.


AbstractTimeSeriesIndexCreationTask

public abstract class AbstractTimeSeriesIndexCreationTask : 
                      AbstractIndexCreationTaskBase<TimeSeriesIndexDefinition> 
{
    protected void AddMap(string timeSeries, 
                          Expression<Func<IEnumerable<TimeSeriesSegment>, 
                                          IEnumerable>> map);

    protected void AddMapForAll(Expression<Func<IEnumerable<TimeSeriesSegment>, 
                                                IEnumerable>> map);
}

// Index only time series that belong to documents
// of a specified type
public class AbstractTimeSeriesIndexCreationTask<TDocument> { }

// Specify both a document type and a reduce type
public class AbstractTimeSeriesIndexCreationTask<TDocument, TReduceResult> { }
Method Parameters Description
AddMap() string timeseries, Expression map Sets a map function for all time series in the database with specified name (the first parameter)
AddMapForAll() Expression map Sets a map function for all time series in the database

See the example below.


AbstractMultiMapTimeSeriesIndexCreationTask

public abstract class AbstractMultiMapTimeSeriesIndexCreationTask
{
    protected void AddMap<TSource>(string timeSeries, 
                          Expression<Func<IEnumerable<TimeSeriesSegment>, 
                                          IEnumerable>> map);

    protected void AddMapForAll<TBase>(
                          Expression<Func<IEnumerable<TimeSeriesSegment>, 
                                          IEnumerable>> map);
}

// Specify a type for the reduce result  
public abstract class AbstractMultiMapTimeSeriesIndexCreationTask<TReduceResult> { }
Method Parameters Description
AddMap<TSource>() string timeseries, Expression map Sets a map function for all time series with the specified name (the first parameter) that belong to documents with the type TSource
AddMapForAll<TBase>() Expression map Sets a map function for all time series that belong to documents with either the type TBase or any type that inherits from TBase

See the example below.


AbstractJavaScriptTimeSeriesIndexCreationTask

public abstract class AbstractJavaScriptTimeSeriesIndexCreationTask : AbstractTimeSeriesIndexCreationTask
{
    public HashSet<string> Maps;
    protected string Reduce;
}
Property Type Description
Maps HashSet<string> The set of javascript map functions
Reduce string The javascript reduce function

See the example below.
Learn more about JavaScript indexes.


TimeSeriesIndexDefinition

public class TimeSeriesIndexDefinition : IndexDefinition

For now, TimeSeriesIndexDefinition is functionally equivalent to the normal IndexDefinition. Using it for time series indexes is recommended - it exists in case additional functionality is added in future versions of RavenDB.

See the example below.


TimeSeriesSegment Object

Time series entries are indexes through the segment they are stored in, using LINQ syntax that resembles this one:

from segment in timeseries
from entry in segment

Segment properties include the entries data, and the aggregated values that RavenDB automatically updates in the segment's header.

Property Type Description
DocumentId string The ID of the document this time series belongs to
Name string The name of the time series this segment belongs to
Min double[] The smallest values from all entries in the segment. The places in this array correspond to the
Max double[] The largest values from all entries in the segment
Sum double[] The sums of values from all entries in the segment. The first Sum is the sum of all first values, and so on.
Count int The number of entries in this segment
Start DateTime The timestamp of the first entry in the segment
End DateTime The timestamp of the last entry in the segment
Entries TimeSeriesEntry[] The segment's entries themselves

These are the properties of a TimeSeriesEntry, which are exposed in the LINQ syntax:

public class TimeSeriesEntry
{
    public DateTime Timestamp;
    public string Tag;
    public double[] Values;

    // This is exactly equivalent to Values[0]
    public double Value;
}

Samples


Creating a time series index using TimeSeriesIndexDefinition:
documentStore.Maintenance.Send(new PutIndexesOperation(
                          new TimeSeriesIndexDefinition
                          {
                              Name = "Stocks_ByTradeVolume",
                              Maps = {
                              "from ts in timeSeries.Companies.StockPrice " +
                              "from entry in ts.Entries " +
                              "select new " +
                              "{ " +
                              "    TradeVolume = entry.Values[4], " +
                              "    entry.Timestamp.Date " +
                              "}"
                              }
                          }));

AbstractTimeSeriesIndexCreationTask

public class Stocks_ByTradeVolume : AbstractTimeSeriesIndexCreationTask<Company>
{
    public Stocks_ByTradeVolume()
    {
        AddMap("StockPrice",
                timeseries => from ts in timeseries
                              from entry in ts.Entries
                              select new
                              {
                                  TradeVolume = entry.Values[4],
                                  entry.Timestamp.Date
                              });
    }
}

AbstractMultiMapTimeSeriesIndexCreationTask

public class Vehicles_ByLocation : AbstractMultiMapTimeSeriesIndexCreationTask
{
    public Vehicles_ByLocation()
    {
        AddMap<Plane>(
            "GPS_Coordinates",
            timeSeries => from ts in timeSeries
                          from entry in ts.Entries
                          select new 
                          {
                              Latitude = entry.Values[0],
                              Longitude = entry.Values[0],
                              entry.Timestamp
                          });

        AddMap<Ship>(
            "GPS_Coordinates",
            timeSeries => from ts in timeSeries
                          from entry in ts.Entries
                          select new
                          {
                              Latitude = entry.Values[0],
                              Longitude = entry.Values[0],
                              entry.Timestamp
                          });
    }
}

AbstractJavaScriptTimeSeriesIndexCreationTask

public class Company_TradeVolume_ByDate : AbstractJavaScriptTimeSeriesIndexCreationTask
{
    public Company_TradeVolume_ByDate()
    {
        Maps = new HashSet<string> { @"
            timeSeries.map('Companies', 'StockPrices', function (ts) {
                return ts.Entries.map(entry => ({
                        Volume: entry.Values[0],
                        Date: entry.Timestamp,
                        Company: ts.DocumentId
                }));
            })"
        };
    }
}

Map-Reduce Time Series Index

public class TradeVolume_PerDay_ByCountry : 
             AbstractTimeSeriesIndexCreationTask<Company, TradeVolume_PerDay_ByCountry.Result>
{
    public class Result
    {
        public double TradeVolume { get; set; }
        public DateTime Date { get; set; }
        public string Country { get; set; }
    }

    public TradeVolume_PerDay_ByCountry()
    {
        AddMap(
            "StockPrice",
            timeSeries => from ts in timeSeries
                          let company = LoadDocument<Company>(ts.DocumentId)
                          from entry in ts.Entries
                          select new Result
                          {
                              TradeVolume = entry.Values[4],
                              Date = entry.Timestamp.Date,
                              Country = company.Address.Country
                          });

            Reduce = results => 
                          from r in results
                          group r by new { r.Date, r.Country } into g
                          select new Result
                          {
                              TradeVolume = g.Sum(x => x.TradeVolume),
                              Date = g.Key.Date,
                              Country = g.Key.Country
                          };
    }
}

Yet another way to create a time series index is to create a TimeSeriesIndexDefinitionBuilder, and use it to create a TimeSeriesIndexDefinition.

var TSIndexDefBuilder = 
    new TimeSeriesIndexDefinitionBuilder<Company>("Stocks_ByTradeVolume");

TSIndexDefBuilder.AddMap("StockPrice",
            timeseries => from ts in timeseries
                            from entry in ts.Entries
                            select new
                            {
                                TradeVolume = entry.Values[4],
                                entry.Timestamp.Date
                            });

documentStore.Maintenance.Send(new PutIndexesOperation(
                TSIndexDefBuilder.ToIndexDefinition(documentStore.Conventions)));