© The Author(s), under exclusive license to APress Media, LLC, part of Springer Nature 2022
D. MiličićIntroducing RavenDBhttps://doi.org/10.1007/978-1-4842-8919-8_3

3. Querying

Dejan Miličić1  
(1)
Novi Sad, Serbia
 

Most modern web applications perform a relatively small number of writes but a large number of reads. This chapter will look into the most common activity your application will perform and how RavenDB supports it. We will learn the basics of Raven Query Language and the basics of querying using it.

Querying in RavenDB Studio

RavenDB Studio has a dedicated panel for running queries. To reach it, open Documents area where all collections are listed. Below them, as visible in Figure 3-1, there are additional options. You will find Query as a second one.

A screenshot of the documents page of the Raven D B studio. A list of collections and tabs labelled patch and query are on the left, and a number of documents are listed on the right.

Figure 3-1

Query Option Below List of Collections

After you click on this menu option, you will be presented with a panel for running queries within Studio, shown in Figure 3-2.

A screenshot of the query page has 2 panels. The top panel depicts a text box to enter the query. The bottom panel depicts space to display the results. To the right of the top panel is a play button, and 3 more buttons at the top.

Figure 3-2

Query Panel in RavenDB Studio

As you can see, this panel has two fields – the upper one where you write your queries and the lower one where the result of the query execution is displayed.

After you write a query, you can run it by pressing ctrl+enter on your keyboard or clicking on the Play button to the right.

The following section will look into the basics of querying with Raven Query Language.

RavenDB Query Language Basics

Raven Query Language or RQL is a SQL-like query language of RavenDB. Chapter 1 mentioned that relational databases share a standardized query language called Structured Query Language - SQL. This common declarative language was one of the significant success factors for RDBMS.

Compared to that, NoSQL databases do not have standardized query language. Considering the dynamic and decentralized nature of the NoSQL ecosystem, there is a low chance any standard will ever emerge. Hence, every time a new NoSQL database is incepted, authors have the tedious task of coming up with a query language for their database. And this assignment brings a heavy burden – once you decide and create language, developers will start using it to build applications. From that point onward, changes you can make to your query language are limited – any significant design change will break backward compatibility and annoy your users.

The team behind RavenDB faced the very same challenge. In the end, a decision was made to use SQL as a basis but to make several extensions and modifications that would make RQL a powerful language. That way, developers familiar with relational databases and SQL would recognize similar concepts and learn the basics of RQL quickly. Let us not forget – querying is the main activity we perform in applications, and fast queries directly contribute to the perception of your application speed.

Let’s start with the most straightforward possible query, which I show in Listing 3-1.
from Employees
Listing 3-1

Keyword “from”

When executed, this query will return all documents from Employees collection, as shown in Figure 3-3.

A screenshot of the query page has 2 panels. The text typed on the top panel reads from employees. The results panel at the bottom depicts 9 documents under the label employees, which are listed under 11 columns.

Figure 3-3

Selecting All Documents from Employees Collection

You can see results in a table, but this is just a visual presentation. Each row is showing one JSON document from the Employees collection, with their properties displayed in cells. By clicking on a Display button, as visible in Figure 3-4, you can show or hide specific properties.

A screenshot of the result panel has 5 options at the top that are, delete documents, statistics, export as C S V, display, and expand result, in which display is selected. The selected options in the display are I d, last name, first name, title, address, and hired at.

Figure 3-4

Adjusting Visibility of Employee Properties

The initial configuration of visible properties will show you only some of them. If you would like to preview the whole document, there is a Preview icon in the first column of the table. Click on it, and you will get a complete JSON preview for a specific document.

Filtering

In the previous section, we learned from, first, and essential RQL keyword. It will be a part of every RQL query you make. We saw it used with a collection name, from Employees, to return all nine Employee documents.

However, this is a trivial example exercised on sample data. In real-life applications, you will rarely have unbounded queries of this kind. Imagine a company with 10,000 employees – not only that it would take a long time to fetch all of them, but you would be able to show just a few of them listed on a screen. What we usually want to do in applications is fetching and showing a specific subset of collection documents. Looking again at the structure of an Employee with an id employees/2-A
{
    "LastName": "Fuller",
    "FirstName": "Andrew",
    ...
We can see that it contains the LastName property with the value Fuller. Let’s see a list of all employee with that same last name using the query in Listing 3-2.
from Employees where LastName = 'Fuller'
Listing 3-2

Keyword “where”

Executing this query will show that Andrew is the only employee of Northwind traders with this last name, as shown in Figure 3-5.

A screenshot of the query page has 2 panels. The text in the top query panel reads, from employees where last name equals fuller. The results panel at the bottom depicts a document in which the last name is fuller.

Figure 3-5

Filtering Employees by the Value of LastName Property

Compared with a Listing 3-1, we took from Employees and added where LastName = 'Fuller'. Keyword where is used for filtering, i.e., selecting all documents by a certain condition. In this case, the filtering condition is expressed in the form [propertyName]='[value]'. Following this pattern, we can fetch all employees with the first name Andrew using the query in Listing 3-3.
from Employees where FirstName = 'Andrew'
Listing 3-3

Filtering all Employees with the first name Andrew

Executing this query will produce the same result of just one document – Andrew Fuller has a unique first and last name on the company level .

In our examples, the string value that we use for filtering is surrounded by apostrophes, but we can use quotation marks as well:
from Employees where FirstName = "Andrew"

which will produce the same result.

Query by Nonexistent Property

What happens if we attempt to filter by nonexistent property , as in Listing 3-4?
from Employees where FistName = 'Andrew'
Listing 3-4

Filtering all Employees by absent property

In Listing 3-4, we made an intentional typo – FistName. If you are a developer accustomed to relational databases, you will expect to receive an error upon executing this query.

However, in RavenDB, an attempt to filter a collection by the nonexistent property name will not produce any errors. You will get an empty result set. Why is that? If you recall, RavenDB is a schemaless database. In relational databases, a schema is a set of mandatory fields for each table. The schemaless nature of RavenDB collections means the absence of any compulsory structure imposed upon documents belonging to the same collection. There is no such thing as a set of mandatory properties such documents must have. Hence, filtering by any property name you can think of will not produce an error – it may only make an unexpected empty result set.

Query by Non-string Properties

Besides filtering by string values, you can also filter by the property containing a numerical value :
from Orders where Freight = 8.53
or by the property that holds a Boolean value
from Products where Discontinued = true

Filtering by Complex Properties

In the previous section, we filtered documents by the value of various properties. All these properties were simple ones, located at the first level of the document. Coming back to the Employee document, shown in Listing 3-5, and looking at the Address property .
{
    "LastName": "Fuller",
    "FirstName": "Andrew",
    "Title": "Vice President, Sales",
    "Address": {
        "Line1": "908 W. Capital Way",
        "Line2": null,
        "City": "Tacoma",
        "Region": "WA",
        "PostalCode": "98401",
        "Country": "USA",
        "Location": {
            "Latitude": 47.614329,
            "Longitude": -122.3251939
        }
    },
Listing 3-5

Structure of Employee JSON Document

We can see it is complex, consisting of nested properties on several levels. RavenDB enables querying over such properties as well, like in Listing 3-6.
from Employees where Address.Country = 'USA'
Listing 3-6

Filtering all Employees by nested property

Query from Listing 3-6 will select all employees living in the United States. Pay attention to the format of the property name Address.Country – looking at the JSON from Listing 3-5, you will observe that Country is a nested property within Address property.

Transforming the nested structure of JSON properties into a linear form suitable for filtering queries is straightforward. You will write parent property on the first level (in this case, Address), and then add desired property from the second level (in this case, Country), separated by a dot.

The same approach is applied with properties on deeper levels. Hence, to filter by Latitiude property visible in Listing 3-5, we will first state top-level ancestor Address and then descend over Location to the Latitude. Every time we go one level deeper, we will add a dot as a separator between property names. Hence, our final flattened form is Address.Location.Latitude. When reading this concatenated property from left to right, you descend one level every time you run into a dot.

Finally, our filtering query accessing the property on a third level looks like this :
from Employees where Address.Location.Latitude = 47.614329

and will return all employees living along specified Latitude.

Filtering by Id

Following the convention for querying the property name, we might think that filtering by identifier follows the same pattern. However, an attempt to execute
from Employees where id = 'employees/2-A'

will return an empty result set, even though we can inspect that employee with id employees/2-A exists.

Why is this the case? As we saw in the previous chapter, an identifier is a unique property called @id located within @metadata. However, this is a default location of an identifier which is also configurable. Because of this, you need to use function id() that will return the name of the property holding the document identifier .

Aware of this, we can rewrite the previous query as
from Employees where id() = 'employees/2-A'

and results will show this is our old friend, Andrew Fuller.

Cross-Collection Query

In Listing 3-3, we demonstrated how to query all Employees by their first name . It is also possible to query all documents that contain a specific property with a specified value. RavenDb offers @all_docs keyword, which denotes all documents in all collections within your database. Hence
from @all_docs where FirstName = 'Andrew'

will find all documents across all collections with a FirstName property containing value Andrew. This query result is the same as Listing 3-3, where we specified the collection name. The reason for this is apparent – only Employee documents have FirstName property.

However, if we query by a property that is present in various collections
from @all_docs where Address.Country = 'USA'
we will get a result set consisting of documents from various collections, as can be seen in Figure 3-6.

A screenshot of the results panel on the query page has a list of documents under the label, index auto slash all docs slash by address country. The documents are listed under the column labels: preview, I d, name, contact, address, phone, fax, home page, external I d, last name, and first name.

Figure 3-6

Result of a Query Across All Collections

This query returned 22 documents in total from Suppliers, Companies, and Employees collection. All these documents represent business entities located in the United States.

It is also possible to write a query that will check which documents from all collections or specific one contain a particular property. Following a query
from @all_docs where exists(Address.Country)

will return a total of 129 documents that contain Country property within Address property. However, pay attention that this will also return those documents containing a null value for a specific property. From the perspective of RavenDB, nothing is distinguishing null – it is a regular value property can have. Hence, exists() will check the presence of a specified property, ignoring its content .

Inequality Query

With inequality operator != we can express filtering condition that is the opposite of equality operator = usage. For example, executing query
from Employees where FirstName != 'Andrew'

will select all Employees where the first name is not Andrew.

There is one more way to write this query; you can use <> operator, which is a synonym for != operator :
from Employees where FirstName <> 'Andrew'

Logical Operators

Logical operators are returning Boolean values true and false. Recalling query from Listing 3-6
from Employees where Address.Country = 'USA'
we were able to select all employees living in the United States. But what if we want to get all employees living in the United States and United Kingdom? For that purpose, we can use or operator:
from Employees where Address.Country = 'USA'
or Address.Country = 'UK'
Executing this query will result in a union - joint list of employees from one and the other country. However, looking at the previous query, it is obvious that adding additional countries would require lots of typing to produce a chain of conditions. For those cases, we can use IN operator to achieve the same results with a shorter query:
from Employees where Address.Country IN ('USA', 'UK')

IN operator accepts a list of values and will return true if the stated property has any of the values from the list.

Similar to or operator, and will filter out documents satisfying all stated conditions:
from Products where PricePerUnit = 14 and UnitsInStock = 16

This query will return two products, products/34-A and products/67-A, which have 16 units in stock and cost 14 per unit.

Following the logic behind or/IN pair, it is logical to ask – is there anything similar to IN, but for and operator. Looking more closely for the applicable case, we would take query like this :
from Companies where Address.City = 'London' and Address.City = 'New York'

and shorten it. However, a query like this will always return an empty result set – you cannot have a property with two values at once. And, indeed, the single value of a simple property cannot, but complex property that contains multiple values can.

Figure 3-7 shows regions/1-A document with a Territories property which is a collection of territories.

A screenshot depicts a document titled regions slash 1 A. The options below the title are, save, clone, and delete. The document depicts 19 lines of code.

Figure 3-7

Region Document

With Territories property, we can now formulate a query that contains and condition
from Regions where Territories[].Name = 'Wilton' and Territories[].Name = 'Neward'.
This query will return all Regions that have both Wilton and Neward in Territories property. We can shorten it to
from Regions where Territories[].Name ALL IN ('Wilton', 'Neward').

We can observe two interesting features here. The first one is ALL IN operator that provides a way to match multiple values against an array. The second one is the expression Territories[].Name which is taking all elements of Territories collection and extracting Name property from every element.

A typical use case of the ALL IN operator is selecting all documents tagged with a specified set of tags .

Range Queries

So far, we have been using equality and inequality operator to filter by exact matches. It is also possible to use additional operators to create range queries .

Following query
from Products where UnitsInStock > 26
will return all products with more than 26 units in stock while
from Products where UnitsInStock >= 26

will list all products with 26 or more units in stock. We will leave as an exercise usage of analogous operators < and <=.

We can combine these operators, so the following query
from Products where UnitsInStock > 1 and UnitsInStock < 3
will return all products with two units in stock, while
from Products where UnitsInStock >= 1 and UnitsInStock <= 3

lists all products that have one, two, or three units in stock.

We can shorten the last query – in the spirit of IN and ALL IN operators, RavenDB also has BETWEEN operator so that we can rewrite the previous query to
from Products where UnitsInStock BETWEEN 1 and 3

Note that BETWEEN is inclusive on the lower and higher end of an interval .

Casing

Revisiting our familiar example from Listing 3-3, we can modify it like this :
from Employees where FirstName = 'ANDREW'

Even though you might expect no results, since we capitalized all letters in the name, this query will return Andrew Fuller as the only result. Why is that?

RavenDB defaults to case-insensitive matching in queries. In this case, the value of the filtering condition was different from the actual property in the document, but Andrew and ANDREW matched since RavenDB ignores casing differences. This case insensitivity was a conscious decision since, in most scenarios, case-insensitive comparison of strings will produce results as you desire.

However, in some specific cases, like matching BASE64 encoded strings, you need exact matching. RavenDB supports that via the exact() method, and the following query will return an empty list of results :
from Employees where exact(FirstName = 'ANDREW')

Full-Text Searching

The ability to perform a full-text search over data is one of the standard features in modern applications. This kind of search will inevitably be present in most of the applications you will be building over time. Fortunately, this is one of the areas where RavenDB excels. We will provide more information in chapters to come, but for now, let’s just say that RavenDB uses Lucene.net internally for indexing purposes. Lucene is one of the best indexing engines available today, and over the years, it has established itself as a reliable and standard solution. As a part of an infrastructure, it has been present in several brand-name products, like Solr and Elastic Search. So, RavenDB can provide you with first-class full-text searching capabilities and eliminate a need for any additional solution that would provide you with this.

An example of this type of search would be querying all employees for a common prefix of their first name :
from Employees where StartsWith(FirstName, 'an')
This query uses the StartsWith() function that will match the content of the FirstName property of every employee with the prefix an. After executing it, you will get results, as shown in Figure 3-8.

A screenshot of the results panel in the query window has 2 documents under the label, index auto slash employees slash by first name. The first names in the 2 documents are Anne and Andrew, respectively.

Figure 3-8

Searching Employees by the Prefix of FirstName

We got two results, two employees, both having first names that start with “an.” Note that in this case, we have a case-insensitive query as well.

Besides searching by prefix, it is also possible to search for a term at any position within a field. For example, if we want to search for all companies that contain word stop within the name, we would write the following query :
from Companies where Search(Name, 'stop')
When executed, this query will return two companies, “Let’s Stop N Shop” and “QUICK-Stop” as shown in Figure 3-9.

A screenshot of the results panel in the query window has 2 documents under the label, index auto slash companies slash by search name. The names in the 2 documents are let’s stop N shop and quick stop respectively.

Figure 3-9

Searching Companies by the Name Content

Both of these companies contain the word “stop” as a part of their name .

It is clear what happens when we perform equality filtering, where exact matching is done, but what happens behind the scene in this case, where partial matching on the arbitrary position can happen? To provide this functionality, RavenDB will take the Name of the company and apply tokenization: the name will be split into words, and every such word will be indexed. As an example, the company name “Let’s Stop N Shop” would be divided into four tokens: “Let”, “Stop”, “N”, “Shop”. After you execute a full-text search query, RavenDB will match your search term against a set of tokens and show you results. So, you could say that full-text search is still exact matching, but not against full property value – instead, components (tokens) are matched.

We can also search by more than one term. Following query
from Companies where Search(Name, "monde cheese")
will search for all companies that contain “monde” or “cheese” in their name, returning three companies as a result set, as shown in Figure 3-10.

A screenshot of the results panel in the query window depicts 3 documents under the label, index auto slash companies slash by search name. The names in the 3 documents are spécialités du monde, Du monde entire, and the big cheese respectively.

Figure 3-10

Searching Companies by the Name Content with Multiple Terms

What about properties which are not simple, but complex instead? Address is a familiar example of such nested property
"Address": {
    "Line1": "87 Polk St. Suite 5",
    "Line2": null,
    "City": "San Francisco",
    "Region": "CA",
    "PostalCode": "94117",
    "Country": "USA",
    "Location": {
        "Latitude": 37.7774357,
        "Longitude": -122.4180503
    }
}
RavenDB will apply a tokenization process with nested properties as well; first, it will separate complex property into a set of nested properties. After that, every nested property will be tokenized – separated into simple components – and indexed. As a result, when you execute a query
from Companies where Search(Address, "London Sweden")

you will get a list of companies located in London or Sweden. As you can see, all properties of the Address are indexed, so the full-text search is simultaneously checking both City and Country fields .

Sorting

Revisiting our first example query from Listing 3-1
from Employees
After executing it, you will get a result as shown in Figure 3-11.

A screenshot of the results panel in the query window has 9 documents under the label, employees. The documents are listed under the column labels: preview, I d, last name, first name, title, address, and hired at.

Figure 3-11

Unsorted Listing of All Employees

As you can see, we got all employees, but their order is arbitrary; they are unsorted. RavenDB provides order by clause for sorting results; hence
from Employees order by LastName asc
will sort employees by the last name in ascending order. You can also specify more than one field for sorting, so
from Employees order by LastName asc, FirstName asc

will apply secondary sorting by the first name in all those cases when two employees have the same last name.

Sorting is also possible on numeric fields:
from Products order by PricePerUnit desc
Executing this query results in sorted products, as shown in Figure 3-12 .

A screenshot of the results panel in the query window has a list of documents. The documents are listed under the column labels: preview, I d, name, supplier, category, quantity per unit, and price per unit.

Figure 3-12

Sorted Listing of All Products

However, looking closely at the column PricePerUnit reveals it is not sorted in the descending order as we expect. Why is this happening , and how to correctly sort Products by this column?

The cause of this lies in the way that RavenDB treats stored data. Fields are not typed, and without us expressing intention, RavenDB will default to lexical ordering, i.e., fields are treated as strings by default. With descending lexical ordering, 81 will come before 9, and that explains the sorting order we got.

Luckily, we can quickly fix this. We need to tell RavenDB which sorting order to apply :
from Products order by PricePerUnit as double desc
This query will use ordering by treating the field as values of type double. We can also apply as long to truncate any decimal parts and to compare such truncated values as integers
from Products order by PricePerUnit as long desc.

Paging

Paging is a common feature in most business applications. Every time you need to show results listing that is longer than the size of the screen, you will most likely use paging. RavenDB natively supports paging with a syntax that looks like this:
from Companies limit 10, 5
This query will skip the first ten results and return the next five. Of course, paging can be combined with other features, so query
from Companies where Address.Country = 'USA' order by Name asc limit 5, 5

will filter companies from the United States, order them by name in ascending order, skip the first five, and take the next five .

Advanced Querying

In the previous section, we saw an introduction to RQL and basic filtering, ordering, and paging operations. Now we will look into advanced functions – projections, aggregations, and includes.

Projecting Results

So far, all of our queries were returning complete documents. For example
from Employees order by LastName asc
will sort employees by the last name and return all of them as full documents. However, when building applications, you will rarely need whole documents. You will usually display a subset of document fields. For example, we might want to show just the first and last name of each employee. RavenDB has a dedicated keyword select that is used precisely for this purpose and works similarly to SQL equivalent, as shown in Listing 3-7.
from Employees order by LastName asc
select FirstName, LastName
Listing 3-7

Usage of select keyword

Executing this query will produce results as shown in Figure 3-13.

A screenshot of the results panel in the query window has 9 documents under the label, index auto slash employees slash by last name. The last names in the list of documents are in ascending order.

Figure 3-13

Results of Using Select Keyword

As you can see , select is very similar to the SQL variant – it will select and return only a subset of fields.

You can rename returned fields by using aliases with select statements. Hence, query
from Employees
select FirstName as Name, Address.City as City
will return a list of documents with the following structure:
{
    "Name": "Andrew",
    "City": "Tacoma",
}
Projections can also operate on values that are not simple. In the following example, we are using projection on the object and an array:
from Orders
select ShipTo, Lines[].ProductName as Products
Executing this query will return documents with the following structure :
{
    "ShipTo": {
        "City": "Reims",
        "Country": "France",
        "Line1": "59 rue de l'Abbaye",
        "Line2": null,
        "Location": {
            "Latitude": 49.25595819999999,
            "Longitude": 4.1547448
        },
        "PostalCode": "51100",
        "Region": null
    },
    "Products": [
        "Queso Cabrales",
        "Singaporean Hokkien Fried Mee",
        "Mozzarella di Giovanni"
    ]
}

ShipTo object is selected as it is, and Products property contains projection composed of product name selected from all order lines.

Projecting with Object Literals

The projections we saw so far were simple, flat, and linear. They essentially mimicked SQL projections, where you would select a subset of properties to return as a simple collection of linear values. RQL can do much more – you can project a complex result using object literal syntax, like in Listing 3-8.
from Orders as o
select {
    Country: o.ShipTo.Country,
    FirstProduct: o.Lines[0].ProductName,
    LastProduct:  o.Lines[o.Lines.length - 1].ProductName
}
Listing 3-8

Projecting with object literal

When executed, this query returns a set of projections. For document orders/1-A, it looks like this:
{
    "Country": "France",
    "FirstProduct": "Queso Cabrales",
    "LastProduct": "Mozzarella di Giovanni",
    "@metadata": {
          "@id": "orders/1-A"
    }
}

As you can see, we used a simple path to select shipping country and complex expressions to perform a deep selection of first and last product from order lines collection. Notice that in Listing 3-8, we had to use an alias as o to be able to reference o in complex projection expressions.

What is also essential about Listing 3-8 is that object literal is not JSON expression – it is JavaScript literal, and any valid JavaScript expression will be executed. As an example of this, we can look into the HiredAt property of an Employee document:
"HiredAt": "1994-03-05T00:00:00.0000000"
This string represents a date in ISO 8601 format, and with object literal, it is possible to write JavaScript, like one in Listing 3-9, that will process this date and extract the year.
from Employees as e
select {
    Id: id(e),
    Year: new Date(e.HiredAt).getFullYear(),
    Fullname: e.FirstName + " " + e.LastName
}
Listing 3-9

Projecting with JavaScript within object literal

will return a set of documents with the following structure:
{
    "Id": "employees/9-A",
    "Year": 1994,
    "Fullname": "Anne Dodsworth"
}
Query in Listing 3-9 is using JavaScript to populate all three fields :
  • Id – calling function id() that is taking document as an argument and determines its identifier.

  • Year – calling JS constructor for Date object with ISO 8601 string as an argument. After that, method Date.getFullYear() returns year.

  • Fullname – concatenates two properties of the document with a separator.

Declaring Functions in Queries

With RQL, you can extract JavaScript code into functions that you can call from object literals , as we did in Listing 3-10.
declare function getFullName(e)
{
    return e.FirstName + " " + e.LastName;
}
from Employees as e
select {
    Id: id(e),
    Year: new Date(e.HiredAt).getFullYear(),
    Fullname: getFullName(e)
}
Listing 3-10

Calling JavaScript function from object literal

In this example, we extracted code that was joining first and last name into function getFullname() , which is then called from object literal .

You can have several of these functions. Their limitations are standard limitations of JavaScript with additional constraints from the nature of their usage. If JavaScript code takes 5 seconds to execute, your query will be additionally 5 seconds longer. Taking all of this into account, you can produce arbitrary complex functionality, demonstrated in an example from Listing 3-11.
declare function lineItemPrice(l) {
    return l.PricePerUnit * l.Quantity * (1 - l.Discount);
}
from Orders as o
select {
    TopProducts: o.Lines
        .sort((a, b) => lineItemPrice(b) - lineItemPrice(a) )
        .map(x => x.ProductName)
        .slice(0,2),
    Total: o.Lines.reduce((acc, l) => acc + lineItemPrice(l), 0)
}
Listing 3-11

Example of complex JavaScript code in object literal

When executed, code from Listing 3-11 will produce a list of documents with a total value of the order and two most expensive products from each order, e.g .
{
    "TopProducts": [
        "Mozzarella di Giovanni",
        "Queso Cabrales"
    ],
    "Total": 440
}

Aggregation

Aggregation is the process of grouping data. In this section, we will examine how RQL can provide data aggregations and enable you to sum up your documents in various ways.

If you recall, every order has Company property that contains the Id of the company, as shown in Figure 3-14.

A screenshot of a table has 5 columns and 10 rows. The column labels are I d, company, employee, freight, and ordered at.

Figure 3-14

Orders have Company Property with Company Identifier

Let’s find the number of the orders every company has in our database, shown in Listing 3-12 .
from Orders
group by Company
select Company, count()
Listing 3-12

Grouping orders by companies

As you might have expected, an appropriate keyword for this is group indeed. After executing this query, we will get results as shown in Figure 3-15.

A screenshot of the results panel in the query window has 7 documents under the label, index auto slash orders slash by count reduced by company. The documents are listed under the column labels: preview, company, and count.

Figure 3-15

Orders Grouped by the Company

The next step in our analysis would be sorting this list in descending order:
from Orders
group by Company
order by count() as long desc
select Company, count()
and after that, filtering only those with more than 20 orders
from Orders
group by Company
where count() > 20
order by count() as long desc
select Company, count()
We should always think of queries as a way to extract data for displaying on the application screen. From that perspective, it is easy to see that informing users about companies “companies/71-A,” “companies/20-A,” and “companies/63-A” is not too user-friendly. So, as the last step in this brief analysis, we will replace company identifiers with the actual name of the company:
from Orders
group by Company
where count() > 20
order by count() as long desc
load Company as c
select c.Name, count()

Compared with the previous query, you will notice a new line load Company as c. Load command will instruct RQL to load the document with id contained in property Company and assign it alias c. After this is done, we can access the company’s name in the last line by referencing c.Name. Finally, we can show our users that “Save-a-lot Markets,” “Ernst Handel,” and “QUICK-Stop” are 3 companies with more than 20 orders.

In most applications, aggregation queries are the primary tool for fetching helpful information from the database. RQL aggregating basics we just demonstrated are one way of summing up data. RavenDB has another much more powerful mechanism, which will be covered in chapters to come .

Handling Relationships

In the previous chapter on Modeling, we learned about suitable and appropriate modeling principles that will result in documents with a balanced level of independency, isolation, and coherency. However, even with such documents, you will still need to combine them into view models that combine properties from two or more documents into a form suitable for visual representation . Let’s look at how RavenDB can support you in such scenarios.

Accessing Related Documents

At the end of a previous section, we used Load() to access related documents. In a nontrivial application, your model will consist of aggregates referencing other documents. Let’s look at one such document, order with id orders/830-A:
{
    "Company": "companies/65-A",
    "Employee": "employees/1-A",
    "Freight": 8.53,
    "Lines": [
    ...

Displaying the order in this form to the user does not bring too much value – the user would have to check which company hides behind id companies/65-A manually.

To show this order on the screen with complete information, we would have to make two more calls to the database to load referenced company and employee. To optimize this, RQL provides you with a Load() function that accepts a reference to the document and returns referenced document. This way, instead of three calls to the database, we can perform just one call and fetch complete information about this order, as demonstrated in Listing 3-13.
from Orders as o
where id() = 'orders/830-A'
load o.Company as c, o.Employee as e
select {
    CompanyName: c.Name,
    EmployeeName: e.FirstName + " " + e.LastName
}
Listing 3-13

Loading reference Company and Employee

When executed, this query will return the following projection:
{
    "CompanyName": "Rattlesnake Canyon Grocery",
    "EmployeeName": "Nancy Davolio"
}

As you can see in Listing 3-13, we provided load with a list of references and aliases for dereferenced documents. After that, in select projection, we can use documents c and e.

An important thing to notice here is the order of the execution. Going over keywords in Listing 3-13, there is the following order :
  • From

  • Where

  • Load

  • Select

RavenDB will take the from-where part of the query and run it, producing interim results. After that, RavenDB will execute load() to fetch documents referenced from interim results. Finally, select will create a projection that is the final result set of this query. It is essential to note the order here – load is applied in the end, after results have been filtered and fetched and basic query execution completes. Because of this, load() does not impact the cost of the query.

If you compare this with relational databases, you will see that the order of execution is significantly different. You would write a query that would first perform join that would provide access to referenced rows and then apply filtering on joined tables. However, please pay attention that we would join all rows from these tables and then perform filtering. In other words, the RDBMS engine would spend cycles joining rows, only to discard them after filtering is applied. RavenDB optimizes not only on this but also on many other things. As a result, your applications will be faster, and the amount of work for the same queries will be lower.

You can also use load() directly in projections or in JavaScript functions, as can be seen in the following example:
declare function getFullName(empId)
{
    var e = load(empId);
    return e.FirstName + " " + e.LastName;
}
from Orders as o
where id() = 'orders/830-A'
select {
    CompanyName: load(o.Company).Name,
    EmployeeName: getFullName(o.Employee)
}

Include

Besides using projections , there is one more way to access related documents. RQL provides include, which is another way to reduce the number of round trips you need to make to fetch complete information:
from Orders
where id() = 'orders/830-A'
include Company, Employee
This query will return a complete order document, but also, in the same round trip to the database, it will produce two additional collections, as shown in Figure 3-16.

A screenshot of the results panel in the query window has a document under the label, orders. The document is listed under the column labels: preview, I d, company, employee, freight, and lines.

Figure 3-16

Order with Included Company and Employee

At the bottom of Figure 3-16, you can see two more result sets. Since we have just one order returned, these two collections contain just one company and just one employee.

Include syntax is intended to be used when you want to fetch the complete document from the database and pull along all related documents in the same round trip to the database. You will then perform some additional operations on these documents without being forced to make any more requests to the database.

Summary

In this chapter, we covered writing queries in RavenDB. Besides filtering, querying, and paging, we also covered advanced topics - projections, aggregations, and dereferencing relations.

In the next chapter, we will introduce indexes, a crucial data structure all databases use to optimize and speed up queries.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
13.59.165.101