Skip to main content

Database Queries

The guide will show you how to build and use the database queries.

Get-by-key queries

Running a simple get-by-key query

The get-by-key query retrieves the record corresponding to a key.
For example, given a table trades where rows are indexed by their trade id, we can retrieve the trader that made the trade 1023:

final GetByKeyQuery query = databaseVersion.getQueryManager()
.getByKeyQuery()
.forTable("trades")
.withFields(FieldPath.of("trader"))
.toQuery();
final IRecordReader record = databaseVersion.getQueryRunner().getByKeyQuery(query).withKey(1023).run();
if (record != null) {
final var trader = record.read(0);
}

You can find the list of key fields for a given store using IDataTable#getKeyFieldNames.

Compiling once, run twice

If a query will be run several times, it is better to compile it once:

final IPreparedGetByKeyQuery query = database.getQueryManager()
.getByKeyQuery()
.forTable("trader")
.withFields(FieldPath.of("trader"))
.compile();
And run it multiple times:
for (int i = 0; i < 10; i++) {
databaseVersion.getQueryRunner().getByKeyQuery(query).withKey(i).run();
}

Using a get-by-key query

The get-by-key query returns the result as a RecordReader. For example, we can create a service to retrieve the trader name for a trade id:

private static class TraderNameService {
IDatabase database;
IPreparedGetByKeyQuery traderQuery;
public TraderNameService(IDatabase database) {
this.database = database;
this.traderQuery = database.getQueryManager()
.getByKeyQuery()
.forTable("trader")
.withFields(FieldPath.of("trader"))
.compile();
}
public String getTraderName(int tradeId) {
final IRecordReader recordReader = database.getHead(IEpoch.MASTER_BRANCH_NAME)
.getQueryRunner()
.getByKeyQuery(this.traderQuery)
.withKey(tradeId)
.run();
return (String) recordReader.read(0);
}
}

List queries

Minimal list query

The minimal list query asks a table name and retrieve all records:

final IPreparedListQuery query = database.getQueryManager()
.listQuery() // the builder guides you and impose the steps order
.forTable("trades")
.withoutCondition()
.selectingAllTableFields()
.compile();

To run the query, use the QueryRunner of your database version:

databaseVersion.getQueryRunner().listQuery(preparedListQuery).withoutParameters().run();

Adding a condition

The list query can be filtered using a condition builder.

Let's retrieve the trades made by John:

final IPreparedListQuery preparedListQuery = database.getQueryManager()
.listQuery() // the builder guides you and impose the steps order
.forTable("trades")
.withCondition(BaseConditions.equal(FieldPath.of("trader"), "John"))
.selectingAllTableFields()
.compile();

Dynamic conditions

The condition can be parameterized at the query compilation, and its parameter values and the parameter values specified at query execution.

Let's retrieve the trades made by Paul with that mechanism:

final IPreparedListQuery preparedListQuery = database.getQueryManager()
.listQuery() // the builder guides you and impose the steps order
.forTable("trades")
.withCondition(BaseConditions.equal(FieldPath.of("trader")).as("parameter"))
.selectingAllTableFields()
.compile();
databaseVersion.getQueryRunner()
.listQuery(preparedListQuery)
.withParameters(Map.of("parameter", "Paul"))
.run();

Selecting fields

If not all fields are useful, we can select the fields returned by the query:

final IPreparedListQuery preparedListQuery = database.getQueryManager()
.listQuery() // the builder guides you and impose the steps order
.forTable("trades")
.withCondition(BaseConditions.equal(FieldPath.of("trader"), "John"))
.withFields(FieldPath.of("tradeId"), FieldPath.of("date"))
.compile();

Aliasing fields

We can alias the fields returned by the query like in SQL queries:

final IPreparedListQuery preparedListQuery = database.getQueryManager()
.listQuery() // the builder guides you and impose the steps order
.forTable("trades")
.withCondition(BaseConditions.equal(FieldPath.of("trader"), "John"))
.withAliasedFields(
AliasedField.create("alias1", FieldPath.of("tradeId")),
AliasedField.create("alias2", FieldPath.of("date")))
.compile();
final var alias1Index =
preparedListQuery.getQueryResultFormat().getPlainRecordFormat().getFieldIndex("alias1");

Adding a timeout or a result limit

For performance reasons, we can restrict the query by adding a result limit and/or a timeout. The timeout will cancel the query if reached and an exception will be thrown. The result limit set the maximal number of rows that will be returned.

final IPreparedListQuery preparedListQuery = database.getQueryManager()
.listQuery() // the builder guides you and impose the steps order
.forTable("trades")
.withCondition(BaseConditions.equal(FieldPath.of("trader"), "John"))
.withFields(FieldPath.of("tradeId"), FieldPath.of("date"))
.withResultsLimit(1000)
.compile();
databaseVersion.getQueryRunner()
.listQuery(preparedListQuery)
.withoutParameters()
.withTimeout(Duration.ofSeconds(10L))
.run();

Adding query tags

Each query can have a map of tags to help monitoring the application. For example, we can add the name of the operation:

final IPreparedListQuery preparedListQuery = database.getQueryManager()
.listQuery() // the builder guides you and impose the steps order
.forTable("trades")
.withCondition(BaseConditions.equal(FieldPath.of("trader"), "Paul"))
.withFields(FieldPath.of("tradeId"), FieldPath.of("date"))
.withQueryTags(Map.of("operation", "trade-inspection"))
.compile();
databaseVersion.getQueryRunner().listQuery(preparedListQuery).withoutParameters().run();

Using a list query

The list query will return a ICursor that you can use to manipulate every record.

For example, we can retrieve the sum of "value" by date:

public Map<Object, Double> getValueByDates() {
final IPreparedListQuery query = database.getQueryManager()
.listQuery()
.forTable("trades")
.withCondition(BaseConditions.in(FieldPath.of("City")).as("city"))
.withFields(FieldPath.of("date"), FieldPath.of("value"))
.compile();
final ICursor result = databaseVersion.getQueryRunner()
.listQuery(query)
.withParameters(Map.of("city", Set.of("Paris", "London")))
.run();
final Map<Object, Double> valuesByDates = new HashMap<>();
while (result.next()) {
final Object date = result.getRecord().read(0);
final double value = result.getRecord().readDouble(1);
valuesByDates.merge(date, value, Double::sum);
}
return valuesByDates;
}

Distinct queries

The distinct query allows to retrieve all distinct values for a field or a field combination from a record list. Record list could be filtered with some specified conditions.

Its characteristics are very similar to list query ones. It returns a ICursor, and provides following options:

  • Field aliasing
  • Limit
  • Timeout
  • Query tags

This sample query extracts all the counterparties john has traded with:

final IPreparedDistinctQuery preparedDistinctQuery = database.getQueryManager()
.distinctQuery() // the builder guides you and impose the steps order
.forTable("trades")
.withCondition(BaseConditions.equal(FieldPath.of("trader"), "John"))
.withFields(FieldPath.of("counterParty"))
.withResultsLimit(1000)
.withQueryTags(Map.of("operation", "trade-inspection"))
.compile();
databaseVersion.getQueryRunner()
.distinctQuery(preparedDistinctQuery)
.withoutParameters()
.withTimeout(Duration.ofSeconds(10L))
.run();

Note on joined projection fields

Projection fields belonging to a table other than the base table are joined with a left join. This implies that default or null values can be present in the distinct result.