Declarative Objectivity (DO) Language : Querying for Data : Querying by Value
2 
Querying by Value
In this chapter, you’ll learn how to use the DO query language to read data from a federated database. In particular, you’ll learn how to use DO to query by value, which means that you’ll base your search criteria on particular values within the data set. For example, you could retrieve a list of funny movies from a federated database by querying for movie data storing the value 'Comedy'.
Data Stored as Objects
Before diving into the details of the DO query language, you need to know a bit about how persistent data is modeled in a federated database. This section will talk about basic object models; later, we will talk about modeling and querying objects in graphs; see Data Stored as Graphs.
Tables vs. Object Model
You may be familiar with relational database management systems (RDBMS), which store data as rows in a table. Each row in a table combines some number of values, which are represented as the table’s columns.
For example, data about movies and genres might be modeled in tables like this:
A federated database uses an object model for its persistent data. Instead of rows (or records), the fundamental units in a federated database are objects, where each object consists of some number of named values.
In an object model, data representing movies and genres might look something like this:
Notice that all of the objects representing movies have a common inventory of names for their values (catalogNum, title, year, genres), although the values themselves differ from movie to movie. These objects are all based on the same class—in this case, a class called Movie.
A class is a “blueprint” for objects; it defines the attributes (named values) that its objects can hold. Each attribute determines the type of the corresponding value—for example, the catalogNum attribute is for holding integer-typed values such as 1, and the title attribute is for holding string-typed values such as "Toy Story". The class itself is the type for the objects based on it, so, for example, we can talk about objects of type Movie.
In the Unified Modeling Language (UML), you can diagram the classes for objects representing movies and genres like this:
Notice the correlations between object models and tables:
Classes generally correspond to tables.
Objects generally correspond to rows in a table.
Attribute values generally correspond to values in a table column.
Relationships Between Objects
From the diagrams, you can see that Movie and Genre objects are connected by relationships. Each movie is connected to the genres it belongs to, and each genre is connected to all of the movies that belong to it.
In an object model, relationships are simply attributes whose values are references to other objects. More precisely, a relationship attribute may hold either a single reference value (for a to-one relationship) or a list of reference values (for a to-many relationship). For example, because a movie may belong to multiple genres, the Movie class defines a to-many relationship called genres, and so the genres attribute of each Movie object holds a list of references to Genre objects. (Note that the actual number of listed references may differ from Movie object to Movie object.)
References to objects are based on object identifiers (also called OIDs), which uniquely identify the objects within the federated database. So, in effect, the Movie object for Toy Story holds a list of object identifiers for the Genre objects representing animation, comedy, and children’s genres.
Relationships and references make it fast and easy for a DO query to find objects from other objects. The query can simply follow references from one object to another, until the objects of interest are found. For example, finding other movies in the same genres as Toy Story is a matter of traversing from a Movie object to the Genre objects it references, and then traversing again from those Genre objects to the other Movie objects they reference.
Note that if the data were in tables, such a query would involve two potentially time-consuming join operations. Relationships in an object model makes join operations unnecessary.
Schema
A federated database maintains a schema to describe the classes of objects that can be stored persistently. A class’s schema description specifies the details about the attribute values held by objects of the class. These details enable you to store, find, and retrieve such values quickly.
Before you run DO statements to access stored values, it’s helpful to know a bit about the portion of the schema that describes the values of interest. In particular, you’ll need to know the names of the relevant classes and attributes, as well as general type of each attribute—for example, whether the attribute is for values that are:
Simple values, such as Boolean, numeric, strings, dates, times, and datetimes
References to other objects
Collections of elements, which may be simple values or references to other objects
It’s not necessary, however, to know all the specific details of stored values, such as size and encoding of stored numeric values, or whether stored collections can have a fixed or variable number of elements. (These details are left to the person whose job it is to define and create the schema.)
You can find more information about schemas, including a complete list of supported data types, in About Schemas.
Sample Schema and Data Set
The examples for illustrating DO statements will be based on the following sample schema for a simple movie-rating data set. In this data, users of various occupations give ratings (a score from 1 to 5) to movies of various genres.
Note:The examples that use this schema are based on the publicly available MovieLens data set, which is a corpus of movie ratings made available by the GroupLens research group.
Finding the Objects of a Class
The simplest way to read data is to find and return all objects of a particular class. For example, you can execute the following DO statement to find all of the genres in the data set:
SELECT * FROM Genre;
Like all DO statements, this simple statement consists of clauses and ends in a semicolon. More will be said later about the particular clauses, but for now it is enough to say that the clauses in a statement work together. In particular, the FROM clause sets up a context against which the SELECT clause is interpreted.
Statement Styles
The preceding example statement is shown the way it might appear in SQL—with the SELECT keyword at the beginning of the statement. DO also supports the following statement style, which starts with the FROM clause, and substitutes the RETURN keyword instead of SELECT:
FROM Genre RETURN *;
This style is used in the documentation because the RETURN keyword more clearly expresses the statement’s result, which is to return retrieved objects or values.
Casing Rules
The names of classes and attributes are defined in the schema, so must you must use the correct casing and spelling when you use the names in DO statements. For example, the sample schema defines the class name Genre with a capital G, so you must also capitalize the name in DO statements that refer to that class.
FROM Genre RETURN *; // This works with the sample schema
FROM genre RETURN *; // This doesn’t
All keywords in DO are defined so that you can type them in any case. For example, the following statements are equivalent:
FROM Genre RETURN *;
from Genre RETURN *;
From Genre Return *;
fRom Genre rETURn *;
For clarity and readability, examples will present keywords in uppercase.
Viewing the Result Set
When you execute a statement using the DO runner, the default output format displays the result set as a list of objects:
objy DO -statement "FROM Genre RETURN *;" -bootFile movieRatings.boot
Here is an excerpt from the command’s output:
{
  ratings.Genre
  {
    __identifier__:3-8-1-4,
    name:'Drama',
    description:'A serious presentation or story with settings or life situations 
    that portray realistic characters in conflict with either themselves, others, or forces of nature.',
    movies:LIST
    {
      3-5-1-10,
      3-5-1-17,
      3-5-1-32,
      ...
      3-13-2-234
    }
  },
 
  ratings.Genre
  {
    __identifier__:3-8-1-8,
    name:'Animation',
    description:'Film in which the illusion of motion and change is achieved 
    by means of the rapid display of a sequence of static images that minimally differ from each other.',
    movies:LIST
    {
      3-5-1-121,
      4-5-1-97,
      4-5-1-200,
      ...
      3-5-2-82,
    }
  },
...
This excerpt shows the first two Genre objects in the query’s result set. The output for each Genre object includes:
The namespace-qualified class name (ratings.Genre).
The object’s unique object identifier (OID) within the federated database.
The values of the object’s name, description, and movies attributes.
Because the value of each movies attribute is a list of Movie objects, the value is shown as a list of OIDs. For convenience, the lists of OIDs are omitted here, but are included in the default DO runner output. (When you know the output will be lengthy, you can include the DO runner’s -outFile option to record the output to a file.)
Specifying the Query Source
The source of a query is the group of objects that serve as the input to the query. In the example under consideration, the query source is specified by the FROM clause (in later sections, we’ll see other keywords):
The FROM clause specifies a class name (Genre) to indicate that all objects of the class are the input to the query. These objects serve as the initial context against which the next clause is interpreted. The clause says, in effect, “consider each Genre object, and pass it along to the next clause.”
Note:Sometimes, for clarity, we will follow a single source object through a query, and refer to it as the current context object.
For readability, an optional s can be appended to any class name, provided there is no ambiguity. For example, if the schema includes a Genre class (but no Genres class), the following statements are equivalent:
FROM Genre RETURN *;
FROM Genres RETURN *;
Returning Objects and Their Values
The RETURN clause specifies the values to be returned from the statement’s source objects.
Returning Whole Objects
In the example under consideration, the RETURN clause specifies an asterisk (*), which is a special expression that stands for all attribute values of a source object. In effect, this return clause causes whole objects to be returned.
Returning a Subset of Values
You can use a RETURN clause to request the return of “partial objects”—that is, you can specify one or more attributes whose values are to be returned from each source object. For example, the following statement returns the value of the name attribute from each Genre object:
FROM Genre RETURN name;
More precisely, the RETURN clause causes a projection to be returned for each source object, where the projection holds one or more attribute values from that source object. The RETURN clause specifies which attributes to include in the returned projections. For example, here is a statement in which the RETURN clause specifies two comma-separated attributes:
FROM Genre RETURN name, description;
Each returned projection is a temporary object of an ad-hoc projection class that is similar to Genre but has just the two specified attributes, instead of all three.
Note:The special expression * is a wildcard that stands for all attributes of the source class. This causes whole source objects to be returned, which is logically equivalent to returning projections consisting of all attribute values.
Viewing the Result Set
If you execute the preceding example statement using the DO runner, the output lists the projections as objects of the ad hoc class called Genre_Projection:
{
  Genre_Projection
  {
    __identifier__:3-8-1-4,
    name:'Drama',
    description:'A serious presentation or story with settings or life situations 
    that portray realistic characters in conflict with either themselves, others, or forces of nature.',
  },
 
  Genre_Projection
  {
    __identifier__:3-8-1-8,
    name:'Animation',
    description:'Film in which the illusion of motion and change is achieved 
    by means of the rapid display of a sequence of static images that minimally differ from each other.',
  },
...
This excerpt shows the first two Genre_Projection objects in the query’s result set. The output for each projection includes:
The unique object identifier (OID) of the Genre object on which the projection is based.
The two attributes (name and description) that are specified in the RETURN clause of the query statement.
Returning Collection Values
Projections may include values that are collections of other values. For example, the following statement returns the value of the movies attribute from the current Genre object. The resulting projection has a single attribute whose value is a collection of references to Movie objects.
FROM Genres RETURN movies;
Here is an excerpt of the DO runner output for this query:
{
  Genre_Projection
  {
    __identifier__:3-8-1-4,
    movies:LIST
    {
      3-5-1-10,
      3-5-1-17,
      3-5-1-32,
      ...
      3-13-2-234
    }
  },
 
  Genre_Projection
  {
    __identifier__:3-8-1-8,
    movies:LIST
    {
      3-5-1-121,
      4-5-1-97,
      4-5-1-200,
      ...
      3-5-2-82,
    }
  },
...
The projection of each Genre object lists the OIDs of the Movie objects belonging to that Genre.
Returning Reachable Values
Relationships in the schema enable you to request projections that include indirect attribute values of source objects. For example, here is a statement that returns the titles of the movies in each genre:
FROM Genre RETURN movies.title;
This query starts from a current Genre object, accesses its movies attribute to obtain the collection of referenced Movie objects, and then gets the title attribute value from each Movie. The resulting projection for that Genre object consists of a single attribute whose value is a collection of strings (the title values).
Note:The expression in the RETURN clause uses the dot operator to access the values of attributes that are reachable from the current context object.
Returning Transformed Values
A RETURN clause can specify expressions that transform attribute values. For example, here is a statement that returns genre names in which all of the characters have been uppercased.
FROM Genre RETURN UPPER(name);
Types of Returned Values
You don’t need to know attribute types in detail. For example, a Genre object’s name value could be fixed- or variable-length string, or could be a Unicode string or some ASCII encoding.
You do need to know the exact spelling and casing of the attribute names. You also need to know about relationships from the source objects to other objects if you plan to access indirect attributes.
Returning Values Under an Alias
By default, each attribute value returned by a query keeps the name it had in the source objects. For example, the strings returned by the following statement are named description in the output:
FROM Genre RETURN description;
If the returned values are indirectly reachable from the source objects, they are given the composite name that was used to access them. For example, the strings returned by the following statement are named movies.title in the output:
FROM Genre RETURN movies.title;
You can use the AS keyword to provide a nondefault name, or alias, for a returned value—typically, to clarify or improve the readability of the query results. For example, the following statement returns a genre’s name under the alias category, and returns the collection of associated movie titles under the alias movie titles:
FROM Genre RETURN name AS Category, movies.title AS 'Movie Titles';
This example also shows that you can specify multiple words as an alias if you enclose in them quotation marks.
Here is an excerpt of the DO runner output for this query:
{
  Genre_Projection
  {
    __identifier__:3-8-1-4,
    Category:'Drama',
    Movie Titles:LIST
    {
      'Faraway, So Close (In Weiter Ferne, So Nah!)',
      'Beautiful Girls',
      'Umbrellas of Cherbourg, The (Parapluies de Cherbourg, Les)',
      ...
      'Anatomy of a Murder'
    }
  },
 
  Genre_Projection
  {
    __identifier__:3-8-1-8,
    Category:'Animation',
    Movie Titles:LIST
    {
      'Fun and Fancy Free',
      'Chicken Run',
      'Fantasia',
      ...
      'Little Nemo: Adventures in Slumberland',
    }
  },
...
Note:If you request the output formatted as a table, attribute names or their aliases appear as column names.
Filtering the Values to be Returned
So far we’ve returned values from every source object identified by the FROM clause in a DO statement. But what if you are interested in only a subset of the source objects? For example, instead of finding the titles of movies of every genre, you might be interested only in the titles of comedies.
You can filter out irrelevant results by providing a WHERE clause after the FROM clause, to introduce conditions that must be met by the source objects or their values. As in SQL, you specify such conditions using predicate expressions. A predicate expression is tested against each source object (and its values), and evaluates to true if the object meets the specified condition, or to false if the condition is not met. Any source object for which the predicate returns true is passed along to the RETURN clause.
For example, the WHERE clause in the following statement tests each source Genre object to see whether the value of its name attribute is the string 'Comedy'. Any Genre object with this attribute value is then processed by the RETURN clause. The other Genre objects are ignored.
Some Common Filters
You typically filter out irrelevant results by using predicate expressions that compare one or more attribute values to specific literal values. In the example above, we tested whether the value of a Genre object’s name attribute is equal to the string 'Comedy'.
You can test an attribute value of any type for equality (==) or inequality (!=), provided you specify a comparison value, such as a literal expression of a compatible type. (You can find a list of literal value formats in Literal Expressions.) For example, the following statement tests for User objects representing women, and returns their occupations:
FROM User WHERE isMale != true RETURN occupation.name;
Note:When testing the value of a Boolean attribute, you can use just the attribute’s name as a shortcut:
FROM User WHERE isMale RETURN occupation.name;  // Selects men
FROM User WHERE
!isMale RETURN occupation.name; // Selects women
Additional kinds of comparisons are valid for attributes of simple types, such as Boolean, numeric, string, date, time, or datetime. For example, the following statement uses less-than-or-equal-to (<=) to test for Movie objects released in or before the year 1942:
FROM Movie WHERE YEAR(year) <= 1942 RETURN title, YEAR(year) AS year;
Note:The keyword YEAR extracts just the year component from the year attribute’s date value, which also includes a month and a day.
You can use a regular expression to test whether the value of a string attribute matches a particular pattern. For example, the following statement tests for Movie objects whose title starts with any number of characters and ends with the string 'Story'.
FROM Movie WHERE title =~ '.*Story$' RETURN title, year;
When the value of an attribute is a collection, you can test whether at least one of its elements satisfies a particular condition. For example, the following statement tests for Movie objects that are comedies, by testing whether the genres collection includes at least one referenced Genre object whose name attribute has the value 'Comedy'.
FROM Movie WHERE ANY(genres, name == 'Comedy') RETURN title, year;
If you want to know which movies do not include 'Comedy' in their list of genres, you can use the logical operator NOT:
FROM Movie WHERE NOT(ANY(genres, name == 'Comedy')) RETURN title;
Combining Filters
You can combine predicate expressions with logical operators AND, OR, and XOR. For example, here is how you can find comedies released between 1934 and 1942:
FROM Movies WHERE YEAR(year) >= 1934 AND YEAR(year) <= 1942 AND ANY(genres, name == 'Comedy') RETURN title, year;
And here is how you can find out which movies are either comedies or dramas, but not both:
FROM Movie WHERE ANY(genres, name == 'Comedy') XOR ANY(genres, name == 'Drama') RETURN title;
A Closer Look at Expressions
A predicate expression is one of several kinds of expression supported by DO. In general, an expression is a building block within a DO clause that describes, or expresses, the details about the work to be done within that clause. An expression is evaluated within the context of a clause, and returns a set of results for the clause to use.
Predicate expressions are commonly a kind of operator expression, which is composed of:
An operator, which determines the overall expression’s action.
Some number of operands, which provide values for the operator to work with.
The operands are themselves expressions that evaluate to values of particular types. (The operator determines the number and types of operands.)
For example, the predicate expression in the following statement consists of the equality operator (==), which compares the string values provided by the two operands (name and 'Comedy'):
The operands in this example illustrate two important kinds of expression that are commonly used within larger expressions:
Operand 1 is an attribute expression, which consists of the name of an attribute, and evaluates to that attribute’s value.
Operand 2 is a literal expression, which represents a specific value of a particular type. Literal expressions use different formatting conventions to represent values such as Boolean, numeric, string, date, time, and so on. (You can find a list of literal value formats in Literal Expressions.)
When you use expressions as operands, the operator processes their values and produces a set of results for the operator expression to return. In the above example, the result returned by the equality operator (==) is a single value of a Boolean type (true or false). Other operators may return values of other types, or even groups of values.
An expression’s results determine where it can be used:
Predicate expressions return either true or false, and so are well suited for testing objects in a WHERE clause.
FROM Genre WHERE name == 'Comedy' RETURN name, description;
Attribute expressions return the values of attributes, and so are well suited for specifying projections in RETURN clauses:
FROM Genre WHERE name == 'Comedy' RETURN name, description;
In general, expressions can be used recursively as operands within other expressions, provided the result types satisfy the operators at each level. For example, the predicate expression in the following statement consists of four nested expressions:
Note:For details about operator expressions and their formats, see Operator Expressions.
Reachable Attributes and Elements
Earlier, in Returning Reachable Values, we saw how to request projections that include indirect values of source objects—that is, values that can be reached through a chain of relationships starting from a source object. This is actually accomplished through an expression that uses a special operator (the dot operator) with attribute expressions as its operands. (The dot operator gets its name from the symbol that represents it, which is a period, or “dot”).
In this example, the expression in the RETURN clause accesses the movie attribute of a Rating object, obtains the referenced Movie object, and then uses the dot operator to reach through that object and obtain the value of its title attribute. The expression evaluates to a string value:
When the value of an attribute is a collection of references, the dot operator reaches through each referenced object in the collection, and returns a collection of values. In this example, the expression in the RETURN clause accesses the movies attribute of a Genre object, obtains the collection of referenced Movie objects, and then uses the dot operator to reach through each Movie to obtain the value of its title attribute. The expression evaluates to a list of title values:
FROM Genre WHERE name == 'Comedy' RETURN movies.title;
You can use dot-operator expressions wherever simple attribute expressions are allowed:
FROM Ratings WHERE score == 5 AND user.age < 18 RETURN movie.title;
When the value of an attribute is a collection (of references or other values), you can use another special operator (the subscript operator) to access individual elements of the collection. For example, the following statement returns the first and second genre of a particular movie by specifying their index positions within the Movie object’s genres collection. The first element has the index 0:
When the collection elements are references to objects:
You can combine the dot operator with the subscript operator to reach further into the data. The preceding example does this to obtain the names of the indexed genres.
You can use a predicate (instead of an index) within the subscript operator to qualify particular elements according to their attribute values. The following example does this to access the 5-star ratings of movies released in or before 1920, and then returns the ages of the users who gave those ratings.
FROM Movie WHERE YEAR(year) <= 1920 RETURN title, ratings[score == 5].user.age;
You can specify a range of indexes within the subscript operator to obtain a slice of a collection (a range of consecutive elements). The following example does this to access the first and second genre within a Movie object’s genres collection.
FROM Movie WHERE title CONTAINS('Toy Story') RETURN title, genres[0:2].name;
Finding Out About Supported Operators
The DO language supports a wide variety of operators, so your statements can specify a wide variety of expressions. You can read about the operators supported by DO in Operator Expressions. Alternatively, you can obtain a list of the supported operators by running the ListOperators tool at a command prompt.
Aggregating Values
Sometimes it is useful for a query to combine (or aggregate) multiple values into a single value.
Aggregating Values Through Collections
When a source object provides access to a collection, you can count the collection’s elements using the collection operator COUNT(). For example, the following statement finds movies that belong to more than five genres. More precisely, this statement returns a Movie object only if its genres collection has more than five elements:
FROM Movies WHERE COUNT(genres) > 5 RETURN title, genres.name;
If the elements of the accessed collection are referenced objects, you can use a predicate to filter them before you count them. For example, the following statement returns just the occupations where women outnumber men in this data set.
FROM  Occupation WHERE COUNT(users[!isMale]) > COUNT(users[isMale]) RETURN name;
Notice that the operand of the each COUNT() operator is an expression that returns a subset of User objects according to the value of their isMale attribute.
If the elements of the accessed collection either are or provide access to numeric values, you can use the aggregate operators AVG(), MAX(), MIN(), or SUM() to compute the average, maximum, minimum, or sum of those values. For example, the following statement returns the titles of movies whose maximum rating is 2 stars:
FROM Movies WHERE MAX(ratings.score) == 2 RETURN title;
Notice that the operand of the MAX operator is an expression that evaluates to a group of numeric values through a collection. More specifically, the expression uses the dot operator to access the numeric score attribute from each Ratings object in a Movie object’s ratings collection.
Grouping Source Objects Based on Shared Values
A common query goal is to find out whether groups of source objects have values in common, and, if so, to investigate some quality of each group. You can accomplish this by using a GROUP BY clause to create temporary groupings of source objects. Then you can use an aggregate operator to process the results in each group.
For example, you might want to know how many movies were released each year. The following statement subdivides the set of Movie objects into temporary groups according to the year in which they were released. Then the aggregate operator COUNT(), used without an operand, returns the number of objects in each group.
FROM Movie GROUP BY YEAR(year) RETURN YEAR(year) AS 'Release year', COUNT() AS 'Number of releases';
Or, you might want to know the average age of the users who gave each rating score to the movie Toy Story. The following statement subdivides the Rating objects for the movie Toy Story into five groups (one for each possible score), and then computes the average age of the users within each group.
FROM Ratings WHERE movie.title == 'Toy Story' GROUP BY score RETURN score, AVG(user.age);
Managing the Results
You can control the ordering and pagination of a query’s output—typically, to enhance readability or to accommodate large result sets.
Ordering
You can include an ORDER BY clause to sort a query’s results according to values that can be accessed from the source objects. For example, the following statement finds old movies, and lists their titles in ascending alphabetical order:
FROM Movies WHERE YEAR(year) <= 1925 ORDER BY title ASC RETURN title;
Here’s how to list the titles in descending (reverse) alphabetical order:
FROM Movies WHERE YEAR(year) <= 1925 ORDER BY title DESC RETURN title;
You can sort the results according to any values that can be computed from the source objects. For example, this statement lists the titles of old movies according to their average score. Results are listed in descending numerical order, so movies with the highest average score are listed first:
FROM Movies WHERE YEAR(year) <= 1925 ORDER BY AVG(ratings.score) DESC RETURN title;
Notice that the returned values may be different from those used as the sort key.
Pagination
When you anticipate a large number of results from a query, you can return the results in batches. Doing so is typically called pagination, because you can limit the results to fit within a window or on a web page.
Limiting the Number of Results
You can limit the number of returned results by including TAKE and SKIP clauses in your statements. The TAKE clause causes the statement to return just the first set of results, up to the specified numeric limit. The SKIP clause instructs the statement to skip over some number of results, and then start returning later in the result set. For example, the following statements order Movie objects by title, and then return the titles in three consecutive batches of five:
FROM Movie WHERE YEAR(year) <= 1930 ORDER BY title TAKE 5 RETURN title;
FROM Movie WHERE YEAR(year) <= 1930 ORDER BY title SKIP 5 TAKE 5 RETURN title;
FROM Movie WHERE YEAR(year) <= 1930 ORDER BY title SKIP 10 TAKE 5 RETURN title;
You might simply want to obtain a sampling of a query’s results at one end of a spectrum. For example, the following statement returns the titles of the 3 movies with the most reviews out of all movies released in 1930 or earlier.
FROM Movie WHERE YEAR(year) <= 1930 ORDER BY COUNT(ratings) DESC TAKE 3 RETURN title;
Note:Although an ORDER BY clause is not required for use with SKIP and TAKE, the results are generally much clearer when you include it.
Limiting the Results Using a Qualifying Condition
You can limit the set of returned results according to criteria other than numbers by including TAKE WHILE and SKIP WHILE clauses in your statements. When you include a TAKE WHILE clause with a predicate expression, the statement returns the first set of results as long as they satisfy the expression. Similarly, when you include the SKIP WHILE clause with a predicate expression, the statement skips over the first set of results as long as they satisfy the expression, allowing subsequent results to be returned.
For example, the following statements order Movie objects by title and return those titles in batches according to the letter they start with. The first statement returns just the titles starting with A, the second returns just the titles starting with B, and the third returns just the titles starting with C:
FROM Movie WHERE YEAR(year) <= 1930 ORDER BY title TAKE WHILE title =~~ '^A.*' RETURN title;
FROM Movie WHERE YEAR(year) <= 1930 ORDER BY title SKIP WHILE title =~~ '^A.*' TAKE WHILE title =~~ '^B.*' RETURN title;
FROM Movie WHERE YEAR(year) <= 1930 ORDER BY title SKIP WHILE title =~~ '^A.*' OR title =~~'^B.*' TAKE WHILE title =~~ '^C.*' RETURN title;
Note:TAKE WHILE and SKIP WHILE are sensitive to the order of the returned results. If the first result does not satisfy the predicate expression, then no results are returned. The use of ORDER BY is highly recommended to produce a predictable ordering.
Next Step
In this chapter, we saw how to use values in your data set to find other values. To really start to see the power of the DO language, you need to think of your data set as a graph. Read:
Performing Graph Queries