Search

Query Builder API

API to fluently build CDS QL statements in Java

Content

Introduction

The CDS Query Builder API allows to fluently construct CDS Query Language (CDS QL) statements, which can be handled by the Persistence Service or, on a lower level, be executed by the Data Store.

Concepts

The Query Builder’s Java API

Maven bundle com.sap.cds:cds4j-api  
Package com.sap.cds.ql Classes and interfaces of the Query Builder API to compose statements
Package com.sap.cds.ql.cqn Read-only view on the CDS QL (CQN) statements

Starting point for the construction of CDS QL statements are the builder classes Select, Insert, Upsert, Update, and Delete. These factories allow to create builder instances. Use these instances to compose the statements’ clauses.

Lambda Expressions

The Query Builder API leverages Java lambda expressions to fluently compose expressions and path expressions that are used by the statements’ clauses.

The following example shows a CQL query and how it’s composed by the Query Builder API.

--CQL query
SELECT from bookshop.Books { title } where year < 2000
//Query Builder API:
Select.from("bookshop.Books")
    .columns(b -> b.get("title")
    .where(b -> b.get("year").lt(2000));

Here, the lambda expression b -> b.get("title") is a function that accesses the element title in the book b. Hence, the element title is put on the query’s select list.

The function b -> b.get("year").lt(2000) accesses the book’s element ‘year’ that it compares with the value 2000 and is used by the where clause.

Path Expressions

Use path expressions to access elements of related entities.

The following example shows a CQL query and how it’s composed by the Query Builder API.

--CQL query
SELECT from bookshop.Books { title, author.name }
//Query Builder API
Select.from("bookshop.Books")
    .columns(b -> b.get("title"), b -> b.to("author").get("name"));

The CQL query accesses the name element in the Authors entity, which is reached from Books via the association author. In the Query Builder API, navigate to a related entity using the to method.

Alternatively paths can be specified directly in get by separating the path segments with a dot:

//Query Builder API
Select.from("bookshop.Books")
    .columns(b -> b.get("author.name"));

Target Entity Sets

All CDS QL statements operate on a target entity set, which is specified by from, into, and entity methods of Select/Delete, Insert/Upsert, and Delete statements.

In the simplest case, the target entity set identifies a complete CDS entity set:

Select.from("bookshop.Books").byId(10);
Map<String, Object> data = ...;
Insert.into("bookshop.Authors").data(data);

However, the target entity set can equally well by a path expression that references a subset of entities or entities reached via an association.

The following example shows a CQL query and how it’s composed by the Query Builder API.

--CQL query
SELECT from Orders[23].items { amount, product.name as "productName" } 
//Query Builder API
Select.from("bookshop.Orders", o -> o.filter(o.get("ID").eq(23))
                                     .to("items"));
      .columns(i -> i.get("amount"), 
               i -> i.to("product").get("name").as("productName"));                         

The target entity set, in the CQL query, is the set of items of the product with ID 23. From this target entity set (of type Items), the query selects the amount and the name of the product.

In the Query Builder API, the lambda expression o.filter(o.get("ID").eq(23)).to("items") is evaluated relative to the root, Orders in this example. All lambda expressions that occur in the other clauses of the query are relative to the target entity set. In the example i -> i.get("amount") accesses the element amount of Items.

To target components of a structured document, we recommend using path expressions with infix filters.

Dynamic Usage Compared to Static Usage

The Query Builder API can be used dynamically. Use strings to refer to CDS entities and elements or use the corresponding components obtained from the Model Reflection API. Then, names and types of the entities and elements used in the composition of CDS QL queries can be checked only at runtime.

If you want to avoid this restriction, use a static model, generated from the CDS model, which can then be used to compose queries.

The following example compares using the Query Builder API in the dynamic and the static style:

//dynamic
Select.from("bookshop.Books")
    .columns(b -> b.get("title"),
             b -> b.to("author").get(".name").as("authorName"))
    .where(b -> b.get("year").lt(2000));
//static
import static bookshop.Bookshop_.BOOKS;

Select.from(BOOKS)
    .columns(b -> b.title(),
             b -> b.author().name().as("authorName"))
    .where(b -> b.year().lt(2000));

Using a static model has several advantages:

In general, it’s recommended to use the static style when implementing business logic that requires accessing particular elements of entities. Using the dynamic style is appropriate for generic code.

Parameters

The Query Builder API supports using parameters for parameterized execution.

import static com.sap.cds.ql.CqnBuilder.param;

Select.from("bookshop.Authors")
	.columns("name").byId(param());
dataStore.execute(query, 111);

Select

Projections

SELECT statements with projections including path expressions such as author.name, can be constructed as follows:

CqnSelect query = Select.from("bookshop.Books")
    .columns("title", "author.name");

Instead of defining projections through strings, lambda expressions can be used which allow using aliases through as:

//dynamic
Select.from("bookshop.Books")
    .columns(b -> b.get("title"),
             b -> b.get("author.name").as("authorName"));
//static
import static bookshop.Bookshop_.BOOKS;

Select.from(BOOKS)
    .columns(b -> b.title(),
             b -> b.author().name().as("authorName"));

The path expression b.author().name() is automatically evaluated at runtime. For an SQL data store, it’s converted to a LEFT OUTER join.

Using inline

If the select list contains multiple path expressions that follow the same association, using inline is an option and a shortcut notation:

// Java example
import static bookshop.Bookshop_.AUTHORS;

// using multiple path expressions
Select.from(AUTHORS)
    .columns(a -> a.name(),
             a -> a.books().title().as("book"),
             a -> a.books().isbn(),
             a -> a.books().year())
    .where(a -> name().eq("Bram Stoker"));

// using inline
Select.from(AUTHORS)
    .columns(a -> a.name(),
             a -> a.books().inline(
                      b -> b.title().as("book"),
                      b -> b.isbn(),
                      b -> b.year())
    .where(a -> name().eq("Bram Stoker"));

Both queries are equivalent and have the same result: a flat structure.

[
  {
    'name' : 'Bram Stoker',
    'book' : 'Dracula',
    'isbn' : '978-1503261389'
    'year' : 1897
  },
  {
    'name' : 'Bram Stoker',
    'book' : 'Miss Betty',
    'isbn' : '978-1079982206'
    'year' : 1898
  }
]

Using expand

The expand function enables query expansion of elements of associated entities. Using expand results in a deep data structure, where the expanded entities are nested substructures.

// Java example
// using expand
import static bookshop.Bookshop_.AUTHORS;

Select.from(AUTHORS)
    .columns(a -> a.name(),
             a -> a.books().expand(
                      b -> b.title().as("book"),
                      b -> b.isbn(),
                      b -> b.year())
    .where(a -> name().eq("Bram Stoker"));

It expands the elements title, isbn, and year of the Books entity into a substructure named books:

[
  {
    'name' : 'Bram Stoker',
    'books' : 
    [
      {
        'book' : 'Dracula',
        'isbn' : '978-1503261389'
        'year' : 1897
      }
      {
        'book' : 'Miss Betty',
        'isbn' : '978-1079982206'
        'year' : 1898
      }
    ]
  }
]

If expand() is invoked on an association without parameters, it expands all nonassociation elements of the associated entity. The following query b.title() selects the title of the book and b.author.expand() expands all elements of the book’s author:

Select.from("bookshop.Books")
      .columns(b -> b.title(), 
               b -> b.author.expand());

Filtering

The Query Builder API offers different ways to restrict the result of a query by applying a filter.

Using byID

To find an entity identified by a given ID, the byId method can be used. This method adds a predicate to the query that filters out the entity with the given ID value. The byId method currently doesn’t support compound keys. The following example retrieves the Author entity with ID 0.

  Select.from("bookshop.Authors").byId(0);

Using matching

matching is a query-by-example style alternative to explicitly define the where clause. This method adds a predicate to the query that filters out all entities where the elements’ values are equal to values given by a key-value map.

The following example uses ‘key’ as the element to be matched.

Select.from("bookshop.Books").matching(singletonMap("id", 42));

The key-value map object can contain keys based on path expressions, referring to elements of an associated entity. In the following example, bookshop.Books has a many to one association to the Author entity. Thus, author.name refers to the name element within the Author entity.

Map<String, Object> example = new HashMap<>();
example.put("author.name", "Edgar Allen Poe");

Select.from("bookshop.Books").matching(example);

Using search

The search method adds a predicate to the query that filters out all entities where any searchable element contains a given search term or matches a search expression.

  1. Define searchable elements

    By default all elements of type cds.String of an entity are searched. However, if any element is annotated with @Search.defaultSearchElement : true, only elements with this annotation are searched.

    Consider following CDS Entity. There are 2 elements, title and name, of type String, none of which is annotated, making them both searchable by default.

     entity Book {
       key ID : Integer;
       name   : String;
       title  : String;
     }
    

    In the following example, the element title is annotated. Only this particular element is searchable.

     entity Book {
       key ID : Integer;
       name   : String;
    
       @Search.defaultSearchElement : true
       title  : String;
     }
    
  2. Construct queries with search

    Let’s consider the following Book entity once again:

     entity Book {
       key ID : Integer;
       name   : String;
       title  : String;
     }
    

Using where clause

In a where clause, leverage the full power of CDS QL expressions to compose the query’s filter:

Select.from("bookshop.Books")
	.where(b -> b.get("ID").eq(251).or(
              b.get("title").startsWith("Wuth")));

Grouping

The Query Builder API offers a way to group the results into summarized rows (in most cases these are aggregate functions) and apply certain criteria on it.

Let’s assume the following dataset for our examples:

ID NAME
100 Smith
101 Miller
102 Smith
103 Hugo
104 Smith

Group By

The groupBy clause groups by one or more elements and usually involves aggregate functions, such as count, sum, max, avg, and so on. It returns one row for each group.

In the following example, we select the authors’ name and, using the aggregate function count, determine how many authors with the same name exist in bookshop.Authors.

Select.from("bookshop.Authors")
		.columns(c -> c.get("name")), c -> func("count", c.get("name")).as("count")
		.groupBy(c -> c.get("name"));

If we execute the query on our dataset, we get the following result:

name count
Smith 3
Miller 1
Hugo 1

Having

To filter the grouped result, having is used. Both, having and where, filter the result before group by is applied and can be used in the same query.

The following example selects authors where count is higher than 2:

Select.from("bookshop.Authors")
		.columns(c -> c.get("name")), c -> func("count", c.get("name")).as("count")
		.groupBy(c -> c.get("name"))
		.having(c -> func("count", a.get("name")).gt(2));

If we execute the query on our dataset, we get the following result:

name count
Smith 3

Ordering and Pagination

The Query Builder API allows to specify the sort order of query results. The sort specification governs, according to which elements the result is sorted, and which sort order (ascending or descending) is applied.

By default Select returns the rows in no particular order.

Order By

To ensure a specific order in a query use orderBy, which allows sorting by one or more columns in ascending or descending order.

Select.from("bookshop.Books")
		.columns(c -> c.get("ID"), c -> c.get("title"))
		.orderBy(c -> c.get("ID").desc(), c -> c.get("title").asc());

Pagination

Pagination (dividing the result set into discrete subsets of a certain size) can be achieved by using limit, which has the following optional parameters:

The following example will select all books, skip the first 20 rows, and return only 10 subsequent books.

Select.from("bookshop.Books").limit(10, 20);

In this example, it’s assumed that the total number of books is more or equal to 20. Otherwise, result set will be empty.

The pagination isn’t stateful. If rows are inserted or removed before a subsequent page is requested, the next page may contain rows that were already contained in a previous page or rows may be skipped.

Insert

The Insert statement inserts new data into a target entity set. An Insert statement is created by the Insert builder class.

The target of the insert is specified by the into method.

As in the following example, the target of the insert can be specified by a fully qualified entity name or by a CdsEntity you obtain from the Reflection API:

Map<String, Object> book = new HashMap<>();
book.put("ID", 101);
book.put("title", "Capire");

CqnInsert insert = Insert.into("bookshop.Books").entry(book);

or it can be a path expression, for example:

import static bookshop.Bookshop_.BOOKS;
 
Map<String, Object> bookId = Collections.singletonMap("ID", 85);

Map<String, Object> publisher = new HashMap<>();
publisher.put("ID", 101);
publisher.put("name", "Penguin");

CqnInsert insert = Insert.into(BOOKS, b -> b.matching(bookId)).publisher())
                         .entry(publisher);

Simple Insert

To insert a single entry, provide the data as a map to the entry method:

Map<String, Object> book;
book.put("ID", 101);
book.put("title", "Capire 2");

CqnInsert insert = Insert.into("bookshop.Books").entry(book);

Bulk Insert

Insert also supports a bulk operation. Here the data is passed as an Iterable of maps to the entries method.

import static bookshop.Bookshop_.BOOKS;

Map<String, Object> b1;
b1.put("ID", 101);
b2.put("title", "Capire 1");

Map<String, Object> b2;
b2.put("ID", 103);
b2.put("title", "Capire 2");

List<Map<String, Object>> data = new ArrayList<>();
data.add(b1);
data.add(b2);

CqnInsert insert = Insert.into(BOOKS).entries(data);

Deep Insert

To build a deep insert for compositions, the input data maps can contain maps or list of maps as values, such as items of an order.

CDS Model
entity Orders {
  key OrderNo : String;
  Items       : Composition of many OrderItems on Items.parent = $self;
  ...
}
entity OrderItems {
  key ID : Integer;
  book   : Association to Books;
  amount : Integer;
  ...
}


Java
import static bookshop.Bookshop_.ORDERS;

Map<String, Object> item;
item.put("ID", 1);
item.put("book_ID", 101);
item.put("amount", 1);
List<Map<String, Object>> items;
items.add(item);
Map<String, Object> order;
order.put("OrderNo", "1000");
order.put("Items", items);

CqnInsert insert = Insert.into(ORDERS).entry(order);


Upsert

The Upsert statement either inserts a new entry or, if an entry with the same ID previously existed, completely replaces it with given data. An Upsert statement is created by the Upsert builder class.

Simple Upsert

To upsert a single entry, provide the data as a map to the entry method:

Map<String, Object> book;
book.put("ID", 101);
book.put("title", "Capire 2");

CqnUpsert upsert = Upsert.into("bookshop.Books").entry(book);

Bulk Upsert

The Upsert also supports a bulk operation. Here the data is passed as an Iterable of maps to the entries method:

import static bookshop.Bookshop_.BOOKS;

Map<String, Object> b1;
b1.put("ID", 101);
b2.put("title", "Capire 1");

Map<String, Object> b2;
b2.put("ID", 103);
b2.put("title", "Capire 2");

List<Map<String, Object>> data = new ArrayList<>();
data.add(b1);
data.add(b2);

CqnUpsert upsert = Upsert.into(BOOKS).entries(data);

Deep Upsert

To build a deep upsert for compositions, the input data maps can contain maps or list of maps as values, such as items of an order. A deep upsert is equivalent to a cascading delete followed by a deep insert, which means the provided data has to be complete.

CDS Model
entity Orders {
  key OrderNo : String;
  Items       : Composition of many OrderItems on Items.parent = $self;
  ...
}
entity OrderItems {
  key ID : Integer;
  book   : Association to Books;
  amount : Integer;
  ...
}


Java
import static bookshop.Bookshop_.ORDERS;

Map<String, Object> item;
item.put("ID", 1);
item.put("book_ID", 101);
item.put("amount", 2);
List<Map<String, Object>> items;
items.add(item);
Map<String, Object> order;
order.put("OrderNo", "1000");
order.put("Items", items);

CqnUpsert upsert = Upsert.into(ORDERS).entry(order);

The Upsert statement deletes the order 1,000 including its order items and does a deep insert with item 1 as defined in the provided items list.



Update

The Update statement updates existing entities with new data. It either updates individual entities or all entities that match a filter, using searched update.

An Update statement is created by the Update builder class.

Define the Target

The target entity of the update is specified by the entity method.

The following example shows how to specify the target of the update using a fully qualified entity name or a CdsEntity you obtain from the Reflection API. Here the ID values are extracted from the data.

Map<String, Object> data = new HashMap<>();
data.put("ID", 100);
data.put("title", "CAP Matters");

CqnUpdate update = Update.entity("bookshop.Books").data(entry);

In the following example, the target is identified by a structured type of the static model:

import static bookshop.Bookshop_.BOOKS;

CqnUpdate update = Update.entity(BOOKS).data(entry);

Provide the Data

The new data is provided as a map to the data method. Also accessor interfaces allow to construct the data in a typed way:

import static bookshop.Bookshop_.BOOKS;

Books book = Books.create();
book.setID(100);
book.setTitle("CAP Matters");

CqnUpdate update = Update.entity(BOOKS).data(book);

Update Individual Entities

Typically, Update is used to update individual entities with new data. There are different options to specify the ID of the entity to be updated.

Extracting the ID from the Data

In the simplest case, the ID value is part of the new data. Then it’s automatically extracted from the data. In the example below the ID value 100 is extracted from the given data and the book with ID 100 will be updated.

Map<String, Object> data = new HashMap<>();
data.put("ID", 100);
data.put("title", "CAP Matters");

CqnUpdate update = Update.entity("bookshop.Books").data(entry);

Using byId

If the ID value isn’t part of the data, it can be specified with the byId method:

Map<String, Object> data = Collections.singletonMap("title", "CAP Matters");

CqnUpdate update = Update.entity("bookshop.Books").data(entry).byId(100);

Using matching

Alternatively, the ID values can be specified as a map using matching

Map<String, Object> data = Collections.singletonMap("title", "CAP Matters");
Map<String, Object> keys = Collections.singletonMap("ID", 100);

CqnUpdate update = Update.entity("bookshop.Books").data(entry).matchings(keys);

Using path expression

The individual target entity, can also be directly specified using a path expression:

Map<String, Object> data = Collections.singletonMap("title", "CAP Matters");
Map<String, Object> keys = Collections.singletonMap("ID", 100);

CqnUpdate update = Update.entity("bookshop.Books", b -> b.macthing(keys)).data(entry);

Bulk Update

Update multiple individual entities in a target entity set, using bulk update. The ID values are then extracted from the data, which is given as a collection of maps using the entries method. The following example illustrates this, using accessor interfaces:

Books b1 = Books.create();
b1.setID(100);
b1.setStock(10);
Books b2 = Books.create();
b2.setID(101);
b2.setStock(11);
List<Books> data = Arrays.asList(b1, b2);

CqnUpdate update = Update.entity("bookshop.Books").entries(data);

A bulk update can be executed more efficiently than individual update operations.

A bulk update is supported only if all entries contain the same elements.

Searched Update

Update all entities that match a filter specified with the where method, using a searched update. In the following example the stock of all books with the title containing “CAP” is set to 1000.

Map<String, Object> data = Collections.singletonMap("stock", 1000);

CqnUpdate update = Update.entity("bookshop.Books")
                         .data(data)
                         .where(b -> b.get("title").contains("CAP"));

Not all data stores support a searched update.

Delete

The Delete operation is cascaded along the entity’s compositions and can be constructed as follows.

//CDS model 
entity Orders {
  key OrderNo : String;
  Items       : Composition of many OrderItems on Items.parent = $self;
  ...
}
entity OrderItems {
  book   : Association to Books;
  ...
}
//dynamic
CqnDelete delete = Delete.from("my.bookshop.Orders")
    .where(b -> b.get("OrderNo").eq(1000));
//static
CqnDelete delete = Delete.from(Order_.class)
    .where(b -> b.OrderNo().eq(1000));

The delete statement would delete the order 1,000 including its order items, but no books since this relationship is modeled as an association.

Alternatively matching can be used to identify the rows marked for deletion as follows:

Map<String, Object> keys = new HashMap<>();
keys.put("OrderNo", 1000);

CqnDelete delete = Delete.from("my.bookshop.Orders").matching(keys);

Expressions

The Query Builder API supports using expressions in many places. Expressions consist of values, which can be used, for example, in Select.columns to specify the select list of the statement. Values can be composed to predicates that allow, for example, to specify filter criteria for Select or Delete statements.

Values

Use values in a query’s select list as well as in order-by. In addition, values are useful to compose filter expressions.

References

References access elements in entities. To compose references, the Query Builder API uses lambda expressions. Here the function b -> e.title() accesses the book’s title. The function b.to("author").get("name") accesses the name of a book’s author.

import static com.sap.cds.ql.CqnBuilder.literal;

Select.from("Books")
	.columns(b -> b.title(),
             b -> b.to("author").get("name"));

Literal Values

Specify values that are already known when the query is built. The literal method of CqnBuilder is used to create a literal value that can be used in the Query Builder API:

import static com.sap.cds.ql.CqnBuilder.literal;

Select.from(EMPLOYEE)
	.columns(e -> e.name())
	.where(e -> literal(50).gt(e.age());

Alternatively, the factory methods for comparison predicates directly accept Java values. The query could also be written as:

import static com.sap.cds.ql.CqnBuilder.literal;

Select.from(EMPLOYEE)
	.columns(e -> e.name())
	.where(e -> e.age().le(50));

Parameters

The param method can be statically imported from the CqnBuilder. It provides an option to use a parameter marker in a query that is bound to an actual value only upon query execution. Using parameters you can execute a query multiple times with different parameter values. Parameters are either positional or named. Using positional parameters means, the values are bound to the parameters according to their ordinal position in the query. Using named parameters means, the values are given as a map.
//positional
import static com.sap.cds.ql.CqnBuilder.param;

Select.from("bookshop.Authors")
	.columns("name").byId(param());
dataStore.execute(query, 111);
//named
import static com.sap.cds.ql.CqnBuilder.param;

Select.from("bookshop.Authors")
	.where(a -> a.firstName().eq(param("first")).and(
                a.lastName().eq(param("last"))));

Map<String, Object> paramValues = new HashMap<>();
paramValues.put("first", "Paul");
paramValues.put("last", "Mueller");

dataStore.execute(query, paramValues);

We recommend using named parameters.

Scalar Functions

Scalar functions are values that are calculated from other values. This calculation can be executing a function on the underlying data store or applying an operation, like an addition, to its parameters. The Query Builder API supports the generic func function, as well as a number of build-in functions.

Predicates

Predicates are expressions with a Boolean value, which are used in filters to restrict the result set or to specify a target entity set.

The Query builder API supports the following predicates:

Predicate Description Example
EQ Test if this value equals a given value.
Select.from("bookshop.Books")
  .where(b -> b.get("stock")
  .eq(15));
NE Test if this value is NOT equal to a given value.
Select.from("bookshop.Books")
  .where(b -> b.get("stock")
  .ne(25));
GT Test if this value is greater than a given value.
Select.from("bookshop.Books")
  .where(b -> b.get("stock")
  .gt(5));
LT Test if this value is less than a given value.
Select.from("bookshop.Books")
  .where(b -> b.get("stock")
  .lt(5));
LE Test if this value is less than or equal to a given value.
Select.from("bookshop.Books")
  .where(b -> b.get("stock")
  .le(5));
IN Test if this value is equal to any value in a given list.
Select.from("bookshop.Books")
  .where(b -> 
    b.get("author.name")
     .in("Poe", "Hemingway"));
BETWEEN Test if this value is between a range of values.
Select.from("bookshop.Books")
  .where(b -> b.get("stock")
  .between(5,10));
IS NULL Test if this value is null.
Select.from("bookshop.Books")
  .where(b -> b.get("stock")
  .isNull());
IS NOT NULL Test if this value isn't null.
Select.from("bookshop.Books")
  .where(b -> b.get("stock")
  .isNotNull());
CONTAINS Test if this string value contains a given substring.
Select.from(EMPLOYEE)
  .where(e -> e.name()
  .contains("oni"));
STARTS WITH Test if this string value starts with a given prefix.
Select.from("bookshop.Books")
  .where(b -> b.get("title")
  .startsWith("The"));
ENDS WITH Test if this string value ends with a given suffix.
Select.from("bookshop.Books")
  .where(b -> b.get("title")
  .endsWith("Raven"));
AND Returns a predicate that represents a logical AND of this predicate and another.
Select.from("bookshop.Authors")
.where(a -> 
  a.get("name").eq("Peter)
   .and(a.get("Id").eq(1)));
OR Returns a predicate that represents a logical OR of this predicate and another.
Select.from("bookshop.Authors")
.where(a -> 
  a.get("name").eq("Peter)
   .or(a.get("Id").eq(1)));
NOT Returns a predicate that represents logical negation of this predicate.
Select.from("bookshop.Authors")
.where(a -> 
  not(a.get("Id").eq(3)));

Parsing CQN

CDS QL queries can also be constructed from a CQN string*:

String cqnQuery = "{'SELECT': {'from': {'ref': ['my.bookshop.Books']},
    'where': [{'ref': ['title']}, '=', {'val': 'Capire'}]}}";
CqnSelect query = Select.cqn(cqnQuery);

* For readability reasons, we used single quotes instead of double quotes as required by the JSON specification.

The constructed queries can then be modified using the query builder API:

String cqnQuery = ...
CqnSelect query = Select.cqn(cqnQuery).columns("price");

For Insert, Update and Delete this is supported as well.