Querying Time Series Indexes


  • Time series index:

    • STATIC-time-series-indexes can be defined from the Client API or using Studio.
      Such an index can be queried in the same way as a regular index that indexes documents.
      (See Querying an index).

    • AUTO-time-series-indexes are Not generated automatically by the server when making a time series query.

  • The contents of the query results:

    • Unlike a document index, where the source data are your JSON documents,
      the source data for a time series index are the time series entries within the documents.

    • When querying a document index:
      the resulting objects are the document entities (unless results are projected).

    • When querying a time series index:
      each item in the results is of the type defined by the index-entry in the index definition,
      (unless results are projected). The documents themselves are not returned.

  • In this page:


Sample Index

  • The following is a time series map-index that will be used in the query examples throughout this article.

  • Each index-entry consists of:

    • Three index-fields obtained from the "HeartRates" time series entries: BPM, Date, and Tag.
    • One index-field obtained from the time series segment header: EmployeeID.
    • One index-field obtained from the loaded employee document: EmployeeName.
  • When querying this time series index:

    • The resulting items correspond to the time series entries that match the query predicate.
    • Each item in the results will be of type TsIndex.IndexEntry, which is the index-entry.
      Different result types may be returned when the query projects the results.

public class TsIndex : AbstractTimeSeriesIndexCreationTask<Employee>
{
    // The index-entry:
    // ================
    public class IndexEntry
    {
        // The index-fields:
        // =================
        public double BPM { get; set; }
        public DateTime Date { get; set; }
        public string Tag { get; set; }
        public string EmployeeID { get; set; }
        public string EmployeeName { get; set; }
    }

    public TsIndex()
    {
        AddMap("HeartRates", timeSeries => 
            from segment in timeSeries
            from entry in segment.Entries
            
            let employee = LoadDocument<Employee>(segment.DocumentId)
            
            // Define the content of the index-fields:
            // =======================================
            select new IndexEntry()
            {
                BPM = entry.Values[0],
                Date = entry.Timestamp,
                Tag = entry.Tag,
                EmployeeID = segment.DocumentId,
                EmployeeName = employee.FirstName + " " + employee.LastName 
            });
    }
}

Querying the index

Query all time series entries:

No filtering is applied in this query.
Results will include ALL entries from time series "HeartRates".

using (var session = store.OpenSession())
{
    List<TsIndex.IndexEntry> results = session
         // Query the index
        .Query<TsIndex.IndexEntry, TsIndex>()
         // Query for all entries w/o any filtering
        .ToList();
    
    // Access results:
    TsIndex.IndexEntry entryResult = results[0];
    string employeeName = entryResult.EmployeeName;
    double BPM = entryResult.BPM;
}
using (var asyncSession = store.OpenAsyncSession())
{
    List<TsIndex.IndexEntry> results = await asyncSession
         // Query the index
        .Query<TsIndex.IndexEntry, TsIndex>()
         // Query for all entries w/o any filtering
        .ToListAsync();
}
using (var session = store.OpenSession())
{
    List<TsIndex.IndexEntry> results = session.Advanced
         // Query the index
        .DocumentQuery<TsIndex.IndexEntry, TsIndex>()
         // Query for all entries w/o any filtering
        .ToList();
}
using (var session = store.OpenSession())
{
    List<TsIndex.IndexEntry> results = session.Advanced
         // Query the index for all entries w/o any filtering
        .RawQuery<TsIndex.IndexEntry>($@"
              from index 'TsIndex'
         ")
        .ToList();
}
from index "TsIndex"

Filter query results:

In this example, time series entries are filtered by the query.
The query predicate is applied to the index-fields.

using (var session = store.OpenSession())
{
    List<TsIndex.IndexEntry> results = session
        .Query<TsIndex.IndexEntry, TsIndex>()
         // Retrieve only time series entries with high BPM values for a specific employee
        .Where(x => x.EmployeeName == "Robert King" && x.BPM > 85)
        .ToList();
}
using (var asyncSession = store.OpenAsyncSession())
{
    List<TsIndex.IndexEntry> results = await asyncSession
        .Query<TsIndex.IndexEntry, TsIndex>()
         // Retrieve only time series entries with high BPM values for a specific employee
        .Where(x => x.EmployeeName == "Robert King" && x.BPM > 85)
        .ToListAsync();
}
using (var session = store.OpenSession())
{
    List<TsIndex.IndexEntry> results = session.Advanced
        .DocumentQuery<TsIndex.IndexEntry, TsIndex>()
         // Retrieve only time series entries with high BPM values for a specific employee
        .WhereEquals(x => x.EmployeeName, "Robert King")
        .AndAlso()
        .WhereGreaterThan(x => x.BPM, 85)
        .ToList();
}
using (var session = store.OpenSession())
{
    List<TsIndex.IndexEntry> results = session.Advanced
         // Retrieve only time series entries with high BPM values for a specific employee
        .RawQuery<TsIndex.IndexEntry>($@"
              from index 'TsIndex'
              where EmployeeName == 'Robert King' and BPM > 85.0 
         ")
        .ToList();
}
from index "TsIndex"
where EmployeeName == "Robert King" and BPM > 85.0

Order query results:

Results can be ordered by any of the index-fields.

using (var session = store.OpenSession())
{
    List<TsIndex.IndexEntry> results = session
        .Query<TsIndex.IndexEntry, TsIndex>()
         // Retrieve time series entries where employees had a low BPM value
        .Where(x => x.BPM < 58)
         // Order by the 'Date' index-field (descending order)
        .OrderByDescending(x => x.Date)
        .ToList();
}
using (var asyncSession = store.OpenAsyncSession())
{
    List<TsIndex.IndexEntry> results = await asyncSession
        .Query<TsIndex.IndexEntry, TsIndex>()
         // Retrieve time series entries where employees had a low BPM value
        .Where(x => x.BPM < 58)
         // Order by the 'Date' index-field (descending order)
        .OrderByDescending(x => x.Date)
        .ToListAsync();
}
using (var session = store.OpenSession())
{
    List<TsIndex.IndexEntry> results = session.Advanced
        .DocumentQuery<TsIndex.IndexEntry, TsIndex>()
         // Retrieve time series entries where employees had a low BPM value
        .WhereLessThan(x => x.BPM, 58)
         // Order by the 'Date' index-field (descending order)
        .OrderByDescending(x => x.Date)
        .ToList();
}
using (var session = store.OpenSession())
{
    List<TsIndex.IndexEntry> results = session.Advanced
         // Retrieve entries with low BPM value and order by 'Date' descending
        .RawQuery<TsIndex.IndexEntry>($@"
              from index 'TsIndex'
              where BPM < 58.0
              order by Date desc
         ")
        .ToList();
}
from index "TsIndex"
where BPM < 58.0
order by Date desc

Project results:

  • Instead of returning the entire TsIndex.IndexEntry object for each result item,
    you can return only partial fields.

  • Learn more about projecting query results in Project Index Query Results.

  • In this example, we query for time series entries with a very high BPM value.
    We retrieve entries with BPM value > 100 but return only the EmployeeID for each entry.

using (var session = store.OpenSession())
{
    List<string> results = session
        .Query<TsIndex.IndexEntry, TsIndex>()
        .Where(x => x.BPM > 100)
         // Return only the EmployeeID index-field in the results
        .Select(x => x.EmployeeID)
         // Optionally: call 'Distinct' to remove duplicates from results
        .Distinct()
        .ToList();
}
using (var asyncSession = store.OpenAsyncSession())
{
    List<string> results = await asyncSession
        .Query<TsIndex.IndexEntry, TsIndex>()
        .Where(x => x.BPM > 100)
         // Return only the EmployeeID index-field in the results
        .Select(x => x.EmployeeID)
         // Optionally: call 'Distinct' to remove duplicates from results
        .Distinct()
        .ToListAsync();
}
var fieldsToProject = new string[] {
    "EmployeeID"
};

using (var session = store.OpenSession())
{
    List<EmployeeDetails> results = session.Advanced
        .DocumentQuery<TsIndex.IndexEntry, TsIndex>()
        .WhereGreaterThan(x => x.BPM, 100)
         // Return only the EmployeeID index-field in the results
        .SelectFields<EmployeeDetails>(fieldsToProject)
         // Optionally: call 'Distinct' to remove duplicates from results
        .Distinct()
        .ToList();
}
// This class is used when projecting index-fields via DocumentQuery
public class EmployeeDetails
{
    public string EmployeeName { get; set; }
    public string EmployeeID { get; set; }
}
using (var session = store.OpenSession())
{
    List<TsIndex.IndexEntry> results = session.Advanced
         // Return only the EmployeeID index-field in the results
        .RawQuery<TsIndex.IndexEntry>($@"
              from index 'TsIndex'
              where BPM > 100.0
              select distinct EmployeeID
         ")
        .ToList();
}
from index "TsIndex"
where BPM > 100.0
select distinct EmployeeID

Syntax

  • session.Query

    IRavenQueryable<T> Query<T, TIndexCreator>() where TIndexCreator 
        : AbstractCommonApiForIndexes, new();
  • DocumentQuery

    IDocumentQuery<T> DocumentQuery<T, TIndexCreator>() where TIndexCreator 
        : AbstractCommonApiForIndexes, new();
Parameter Description
T The results class
TIndexCreator Index