Starcounter
HomeDownloadDocsCommunity
2.3.1
2.3.1
  • Starcounter Documentation
  • Getting Started
  • Starcounter
    • Collapsing the Stack
      • Complexity and Scalability Tradeoff
      • The Future of Micro-Services
      • 10 Benefits of Collapsing the Stack
    • Integrated Database and Web Server
  • Hello World - Tutorial
    • Create a Database Class
    • Create a Real Time UI
    • First Interactive UI
    • Computed Properties
    • Expense Tracker
    • Cancel and Delete
    • The Next Step
  • Guides
    • Database
      • Database Classes
      • Data manipulation
      • Object Identity and Object References
      • Querying with SQL
      • Data Types
      • Relations
      • Inheritance
      • Sharing data
      • Database Configuration
      • Comparing Database Objects
      • Referential Integrity and Constraints
    • SQL
      • Identifiers
      • Path Expressions
      • Data operators
      • Joins
      • Aggregates
      • Comparisons and Logical Operators
      • Sorting
      • Fetch
      • Offset Key
      • Indexes
      • Literals
      • Query Plan Hints
      • Reserved words
      • Query for Database Classes
      • SQL Isolation Between Applications
    • Transactions
      • Short-Running Transactions
      • Long running transactions
      • Using Transactions
      • Running Background Jobs
      • Commit Hooks
    • Typed JSON
      • JSON-by-example
      • Code-Behind
      • Data Bindings
      • Callback Methods
      • Responding with JSON
      • Accepting JSON in Requests
      • Primitive Arrays and Single Value Types
      • Typed JSON Internals
    • Blendable Web Apps
      • Starcounter MVVM
      • Palindrom
      • Client-Side Stack
      • Sessions
      • HTML Views
      • App Shell
      • Web Components
      • View Attaching
      • View Composing
      • HTML Compositions
      • HTML Views Blending Guidelines
      • Avoiding CSS conflicts
      • Debugging
    • Network
      • HTTP
      • Internal Self Calls
      • Middleware
      • Anonymous or Substitute Handlers
      • URL Aliases and Redirects
      • Network Gateway
      • Static File Server
      • External HTTP Calls
      • WebSocket
      • Avoiding URI conflicts
      • TCP Sockets
      • UDP Sockets
    • Publishing Apps
    • Working with Starcounter
      • Release Channels
      • Starting and Stopping Apps
      • Administrator Web UI
      • Star CLI
      • StarAdmin CLI
      • StarDump CLI
      • Working in Visual Studio
      • Error Log
      • Using HTTPS on NGINX
      • Using HTTPS on IIS
      • Run Starcounter in Production
      • Weaver
      • Investigating App Crashes
      • Configuration Structure
      • Database Refactoring
      • Using Unload/Reload to Modify Database Schema
      • Kernel Questions and Answers
      • Log Files
  • Cookbook
    • Attach an HTTP Request to an Existing Long-Running Transaction
    • Cookie-Based Authentication
    • Timestamp on Object Creation
    • Creating Strongly Typed JSON Collections
    • Migrating From 2.2 to 2.3+
    • Multiple Pages
    • Icons
    • Proposed Project Structure
    • Acceptance Testing with Selenium
    • Requesting a User to Authenticate
    • How to delete unused tables and columns
Powered by GitBook
On this page
  • Introduction
  • How to Create Queries with Offset Key
  • Query Limitation
  • Getting an Offset Key in Initial Query
  • Continue to Retrieve Data with an Offset Key Query
  • Example
  1. Guides
  2. SQL

Offset Key

PreviousFetchNextIndexes

Last updated 7 years ago

Introduction

Starcounter allows to retrieve query results in portions without need to keep an Enumerator (a cursor) with query result open. In addition to standard clause, Starcounter SQL is extended with OFFSETKEY clause, which re-creates the query enumerator and continues after the last retrieved record.

While a server side cursor can provide a snapshot isolation, the OFFSETKEY functionality is a good compromise. It provides for client side cursors with no server side state, just as OFFSET does, but without the duplicate or missing records common with OFFSET.

How to Create Queries with Offset Key

You retrieve each portion of the query result by sending a new query, just as you do with a standard OFFSET query. Instead of a number indicating the position you should skip to, you instead retrieve a string value (the offset key) at the end of each portion. For the next portion, you provide the exact same query, but with the new key as a parameter value.

The query usually includes clause, which limits each retrieval (each portion). Each query will will begin to retrieve result after the last fetched row from the previous portion, taking into account any deletions, changes or insertions.

The initial query must be identical with the subsequent queries apart from the FETCH and OFFSETKEY clauses and actual fetch and offset key values supplied. The first time the query is executed (i.e. for the first portion), the offset key value should always be set to null or omitted.

The OFFSETKEY clause is placed at the end of the SELECT statement and can be together with clause, e.g.:

SELECT u FROM User u FETCH ? OFFSETKEY ?

The first time the query is used, you should supply the value null as the OFFSETKEY.

The input parameter is a string key, which is obtained on an enumerator to be re-created by calling method GetOffsetKey(). The string key for OFFSETKEY clause, offset key, can be also retrieved from the query already having OFFSETKEY clause.

Query Limitation

You can't use OFFSETKEY with ORDER BY or GROUP BY clauses.

Getting an Offset Key in Initial Query

The offset key is obtained by calling method GetOffsetKey() on enumerable or on enumerator, which is instance of IRowEnumerator. Getting offset key on enumerable can be done only if one enumerator was open.

The enumerator is obtained for initial query by calling standard interface method GetEnumerator, e.g.:

IRowEnumerator<User> e = Db.SQL<User>("SELECT u FROM User u FETCH ? OFFSETKEY ?", 10, null).GetEnumerator();

An offset key is obtained by calling method GetOffsetKey, which has the following signature:

byte[] GetOffsetKey();

The offset key is obtained at any valid state of enumerator, i.e., after MoveNext was called and was true:

byte[] key = null;
using (IRowEnumerator<User> rows = Db.SQL<User>("SELECT u FROM User u").GetEnumerator())
{
    int i = 0;
    while (rows.MoveNext())
    {
        User u = rows.Current;
        ...
        i++;
        if (i == 3)
        {
          key = rows.GetOffsetKey();  
        }
    }
}

The offset key can be obtained after query with FETCH clause was enumerated. The offset key will be valid, if there are more rows exist after the fetched number of rows:

byte[] key = null;
using (IRowEnumerator<User> rows = Db.SQL<User>("SELECT u FROM User u").GetEnumerator())
{
    while (rows.MoveNext())
    {
        User u = rows.Current;
        ...
    }
    key = rows.GetOffsetKey();
}

Continue to Retrieve Data with an Offset Key Query

To be able to recreate the Enumerator and continue query execution, the query with offset key, offset key query, should be the same as the initial query, which was used to obtain the offset key, original query. Both query string and query variable values should be the same. Only FETCH and OFFSETKEY clauses can be different between queries. Note that OFFSET and OFFSETKEY clauses cannot be presented in the same query.

If an offset key query is not exactly the same as the original query (apart from the FETCH or OFFSETKEY clauses and values), then Starcounter will throw an exception.

If the offset key query is the same as the original query and the offset key is not null, then the first row of the result of the offset key query will be the next row after the row of the original query, which was retrieved last before the offset key was gotten. The next row is defined for the moment when the offset key query is called. Thus if there were rows inserted after the last row of the original query, then the offset key query will retrieve them. Deleting the last row of the original query does not affect the result of the offset key query.

Query with null value for OFFSETKEY clause is equivalent to query with omitted OFFSETKEY clause.

If a new row, which has the same values as the last row of original query, (with or without deleting the last row) is inserted, then depending on its place in an index used in the query plan the offset key query will either start from it or after it and the last row.

Example

byte[] offsetKey  = null;
var accounts = Db.SQL<Account>(
   "SELECT a FROM Account a WHERE a.AccountId < ? FETCH ?", 100, 10);
    
using (var enumerator = accounts.GetEnumerator())
{
   while (enumerator.MoveNext()
   {
       Account account = enumerator.Current;
       Console.Write(account.AccountId + " ");
   }
   offsetKey = enumerator.GetOffsetKey();
}

if (offsetKey == null) 
   return;
   
Console.WriteLine();

var accounts = Db.SQL<Account>(
   "SELECT a FROM Account a WHERE a.AccountId < ? FETCH ? OFFSETKEY ?",
   100, 5, offsetKey);
   
using (var enumerator = accounts.GetEnumerator())
{
   while (enumerator.MoveNext()
   {
       Account account = enumerator.Current;
       Console.Write(account.AccountId + " ");
   }
   offsetKey = enumerator.GetOffsetKey();
}
...

If the database contains accounts with following AccountIds:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 ...

The code above will return:

1 2 3 4 5 6 7 8 9 10
11 12 13 14 15
...

If the database contains accounts with following AccountIds:

10 20 30 40 50 60 70 80 90 100 110 120 130 140 150 160 170 180 190 200 210 ...

The code above will return:

10 20 30 40 50 60 70 80 90
OFFSET
FETCH
FETCH