The Java Blend tool supports OQL, the ODMG Object Query Language. However, the Java Blend tool in some aspects differs from the ODMG standard. These differences are noted where appropriate. For a complete summary of OQL, see The Object Database Standard: ODMG 2.0,by R.G.G. Cattell et al (Morgan Kaufmann Publishers, Inc., 1997). In particular, refer to Chapter 4, "Object Query Language". The URL for the ODMG standard is http://www.odmg.org.
Applications use queries to access persistent Java objects. The Java Blend mapping tool generates a set of query files. You can modify these generated queries. You can also write your own queries. This chapter focuses on the OQL syntax and shows you how to write your own queries. This chapter includes the following sections:
The generated default query simply returns all objects of the class. Thus, for a customer class, the default query returns all customers in the database that are specified by the customer mapping. You can edit these query files to be more selective.
Note: The examples that follow show OQL reserved words in uppercase. You can use either uppercase or lowercase for OQL reserved words. However, mixed case is not allowed. For example, you can use select or SELECT, but you cannot use Select.
An example of a default generated query to get all customers, such as OQLGetAllCustomer.oql, is:
import demo.dbo.Customer; SELECT o FROM Customer o;
OQL syntax follows the Java programming language expression syntax. It enables you to easily specify a query using Java programming language classes. OQL is also similar to the query portion of SQL-92, and it relies on the ODMG object model.
Although the syntax of OQL closely resembles that of SQL-92, the two languages are based on different models. SQL is based on the relational model, in which queries access tables, columns, and rows. In contrast, OQL is based on an object model, in which queries access classes, objects, attributes, relationships, and methodsjust as programs written in the Java programming language do.
In OQL queries, the schema of the underlying database is invisible; OQL users have no direct access to database tables, columns, rows. For example, if a database has an Orders table with a foreign key relationship to a Customer table, the following OQL query selects an order for a specific customer:
SELECT o FROM Orders o WHERE o.customer.name = "ABC Company"Unlike this OQL query, the equivalent SQL query must define the join condition between the Orders table and the Customer table:
SELECT o.no, ... FROM Orders o, Customer c WHERE c.name = "ABC Company" AND c.cid = o.cidAs the OQL query in the preceding example shows, writing an OQL query is much simpler than performing the same query using the Java programming language. With just a few English-like phrases, you can quickly construct a powerful query. To do the equivalent in an application requires more sophisticated programming knowledge and more lines of code.
A Java application uses a query resource file to obtain a collection of objects from the database. The application creates an instance of a generic query object, opens the resource file, and runs the query. The query returns all matching values or objects as a collection. The application can then use the objects in this collection according to its needs.
Recall that you place each OQL query in a separate .oql file. You must also import the classes that the query expects to access.
For example, suppose you construct a query that looks at parts ordered by a certain customer. This query needs information from three different classesa Customer class, an Orders class, and a Part class. At the top of the query file, prior to the select statement, you would include three import statements, in any order, as follows:
import mydemo.Customer; import mydemo.Orders; import mydemo.Part; SELECT c FROM Customer c, Orders o, Parts p WHERE c.Id = 1011 ...;
Typically, you construct a select statement to select and return objects or values from a specified class or classes. Normally, a select statement returns a collection or BAG unless directed otherwise. A BAG object is an unordered collection of elements that might contain duplicates. A select statement can also return a SET.ASET object is an unordered collection of elements with no duplicates. In addition, a select statement can return a LIST.ALIST object is an ordered collection of elements.
You can limit the result of the selection with the WHERE clause, which lets you apply a Boolean test to the selection. Thus, the simple form of a select statement is:
SELECTA select statement always begins with the SELECT clause. The SELECT clause specifies the objects or values that are returned from the classes or collections identified by the FROM clause. These objects can be entire objects, such as customers, or they can be attributes or fields within an object, such as customer names. The SELECT clause specifies which object, or value held by the object, that the query will extract and return to the application program. When the SELECT clause specifies parts of objects, it should list the parts or elements that it wants to select. It might also show how to extract the value. For example, the following SELECT clause directs the query to return only the customer name element in the customer object, rather than the entire customer object. It extracts the name using the getName method, which the Customer class defines.
The Java Blend tool requires parentheses after all method names, while the ODMG standard does not require parentheses for methods that have no parameters.
SELECT c.getName() FROM Customer c;Rather than using the method getName, the query would have better performance if it used the member field name:
SELECT c.name FROM Customer c;If you select objects, you can do further manipulation on these objects. The query:
SELECT c FROM Customer c;selects the Customer objects and enables you to do further processing on them in your application. For example, you might print or update the customer addresses.
Even though you select the entire object, you will not see changes made to this same object by separate transactions.
If you select portions of an object, that is, fields, you get a copy of the objects fields. The Java Blend tool, when it returns portions of objects, returns these values as literals. Because they are literals, their values do not change.
The Java Blend tool processes a query by evaluating portions of the query in the database, thus preselecting potential results. However, because a method call cannot be evaluated in the database, the Java Blend tool, before processing the actual method call, constructs an object for each row in a table and then evaluates the method in memory. Thus, a performance impact results if a query calls a method defined in the underlying class. Queries that do not include method calls do their processing in the database, and this is more efficient. For example, you might want to query a Customer class for those customers whose credit is on hold and who have orders greater than a certain amount.
You could write your query as follows:
SELECT c FROM Customer c WHERE c.getCredit() = hold AND c.getOrderAmount() > 100000;This would require that the Java Blend tool read the entire Customer table, create an object for each customer row, then evaluate the methods. As a result the query would be inefficient from a performance standpoint.
You could write the same query as:
SELECT c FROM Customer c WHERE c.credit = hold AND c.getOrderAmount() > 100000;When written this way, the database returns only those customers whose credit is on hold. Then, the runtime code checks each of these customers to see if it passes the order amount test, if c.getOrderAmount() > 100000. The Java Blend tool does some processing in the database, but it also does some in memory.
If the order amount is stored as a field rather than calculated using business logic, the query can run even faster when written to use the fields credit and orderAmount in the select statement, as follows:
SELECT c FROM Customer c WHERE c.credit = hold AND c.orderAmount > 100000;When written this way, the Java Blend tool processes the entire query in the database. Your query runs more efficiently and performance is much better.
Use the DISTINCT keyword to return a set with no duplicates. SELECT DISTINCT works the same in OQL as it does in SQL. You use DISTINCT once in the select statement. DISTINCT must immediately follow the SELECT keyword in the clause.
The following query returns a set of unique department names, even if the database held many employees who worked for the same department:
SELECT DISTINCT e.deptname FROM Employee e;
When you access a property of a NULL or NIL object, the result is UNDEFINED. OQL provides two operations to test on UNDEFINED: IS_UNDEFINED applied to UNDEFINED returns TRUE while IS_DEFINED returns FALSE. If the Boolean expression defined by the WHERE clause of a SELECT-FROM-WHERE returns UNDEFINED, this is handled as if the expression returns FALSE. UNDEFINED is a valid element of collection. Any other operation with an UNDEFINED operand returns UNDEFINED. The value UNDEFINED is represented as NULL in the Java programming environment.
To illustrate, suppose you have a Customer class that refers to sales region. You have three customers, one in the east region, another in the west region, and a third with a NULL region.
The SELECT statement:
SELECT c FROM Customer c WHERE c.sales_region.name = "East";returns the customer in the east sales region. For the customer with no region, the Boolean expression c.sales_region.name is evaluated to UNDEFINED, which means this customer is not included in the result of the query.
The statement:
SELECT c FROM Customer c WHERE IS_DEFINED(c.sales_region.name);returns two customers, one from the east region and the other from the west sales region. If the same SELECT statement used the IS_UNDEFINED operation, it would return the one customer not yet assigned to a sales region.
Lastly, the statement:
SELECT c.sales_region FROM Customer c;returns sales regions East, West, and NULL.
For example, you might want a query that selects customer name, sales region, and the number of orders placed by that customer and have that information returned as a STRUCT. Your STRUCT declares the attributes name, sales_region, and order_number, from which it extracts values from the customer record, as follows:
SELECT STRUCT (name : c.custName, sales_region : c.sales_region, order_number : COUNT (c.ordersForCustomer) ) FROM Customer c;Note that the COUNT keyword extracts an integer value. In this example, it extracts the number of orders for a customer. Refer to Handling struct Results on page 123 for information on how to handle struct results in your application.
There are three equivalent forms:
SELECT c FROM c IN Customer; SELECT c FROM Customer c; SELECT c FROM Customer AS c;Each select statement indicates that all Customer objects should be returned.
Because select expressions also return collections, you can nest select statements within the FROM clause. For example, suppose you want to query Employee objects, but you want to see only employees whose age is less than 40. You can nest a select statement within the FROM clause so that it selects only employees whose ages are less than 40, as follows:
SELECT e FROM (SELECT x FROM Employee x WHERE x.getAge() < 40) e;The statement above, of course, is equivalent to the simpler query:
SELECT e FROM Employee e WHERE e.getAge() < 40;In addition, the same query would run more efficiently if you used the variable age instead of the method getAge. This is the preferred form of the query, and it looks as follows:
SELECT e FROM Employee e WHERE e.age < 40;
For example, suppose you have a set of customers. Each customer has a set of orders, and each order might include any number of parts. In OQL, you could query the parts ordered by a particular customer as follows, using the Customer field name to identify the customer:
SELECT p FROM Customer c, c.orders o, o.part p WHERE c.name = ABC Company;In this example, you know from examining the three classesCustomer, Orders, and Partthat relationships exist among the three classes. Each class includes members that reference one or more of the other classes. For example, the Customer class includes a collection member for orders, while the Orders class has both a collection member for parts and an object reference for customer. Your OQL query uses these members for its navigation. That is, the query uses the orders member to reach the orders for a customer; then it uses the part member to reach the parts on a particular order. However, notice that you need not specify these relationships when you write the query.
To do the equivalent in SQL, you must specify all the relationships between the tables. This results in a more complex statement and requires much more knowledge of the organization of the database tables. For example, you must know how the different tables are related to each other and the key column names. The equivalent SQL statement might be:
SELECT p.partno, p.partname, p.partquantity ... FROM Customer c, Orders o, Part p WHERE c.name = ABC Company AND o.cid = c.cid AND p.partno = o.partno;Notice also that you can select an entire object in the OQL statement and then reference individual fields or members from that object in the FROM and WHERE clauses.
SELECT DISTINCT s.company FROM Manager m, m.shares s;This query returns a set of all company names in which managers own shares of stock. The keyword DISTINCT is added to eliminate duplicates in the returned set.
Always enclose string literals used in Java Blend queries in double () quotes. This differs from the ODMG standard, which allows you to use either single quotes or double quotes to enclose string literals. However, you enclose character variables in single () quotes in OQL.
You also have the capability of doing simple pattern matching, comparing one value to another using the LIKE operator. Two wildcards are available for pattern matching: the percent (%) sign and the underscore (_) . The underscore matches a single character. The percent sign matches an arbitrary number (including zero) of characters. It functions as a truncation operator if placed at both ends of a string pattern.
For example, the following select statement returns a set of customer objects whose addresses contain the substring CALIF:
SELECT c FROM Customer c WHERE c.address LIKE %CALIF%;The Java Blend tool does not recognize the question mark (?) or the asterisk (*) as a wildcard.
You can specify a range in the WHERE clause, using the BETWEEN and AND keywords, as follows:
expression BETWEEN value AND valueSpecifying a range selects objects that fall within the bottom and top limits of the range. For example, the following WHERE clause selects employees whose salaries are between $50,000 and $90,000 inclusive:
WHERE e.salary BETWEEN 50000 AND 90000This clause is equivalent to:
WHERE 50000 <= e.salary AND e.salary <= 90000The BETWEEN expression is not part of the ODMG OQL definition.
Place query results in sorted order using the ORDER BY keyword. This keyword always returns a LIST. You can sort data in ascending order, by specifying ASC, or in descending order, with the keyword DESC. If you do not specify whether the order should be ascending or descending, OQL uses the previous ordering specification within the ORDER BY clause. If no previous ordering specification is given, the default is ascending. For example, the following statement:
SELECT e FROM Employees e ORDER BY e.salary DESC, e.name ASC, e.departno;returns employees sorted in descending order by salary, then in ascending order by name and department number. Keep in mind that ordering of elements must be done on values whose types allow order-based comparison. Ordering can be done on the following types:
Table 5-1 shows the format of the time pattern strings that the Java Blend tool passes to the class java.text.SimpleDateFormat. Refer to the Java 2 Platform documentation for a description of that class and of codes used in Table 5-1.
When processing time pattern strings, the Java Blend tool uses lenient parsing; calls to the method SimpleDateFormat.isLenient would return TRUE.
Table 5-1 Time Pattern Strings Passed by the Java Blend Tool
| Data type | Time Pattern String |
| Date | yyyy-MM-dd |
| Time | HH:mm:ss[.SSS] |
| Timestamp | yyyy-MM-dd HH:mm:ss[.SSS |
A parameterized query is one that, once coded, cannot change dynamically in the programonly the values of the parameters that are passed in to the query can change. A parameterized query requires a set of predefined parameters (or no parameters).
Suppose you want to have an application select customers by customer identification numbers so it can retrieve one customer at a time. To accomplish this, you write a query that accepts an identification number as a parameter. Later, the application simply instantiates this query object, and when it runs the query, it passes the value for which the query is to search.
To use a parameter in a query, you just specify the parameterpreceded by the dollar ($) sign and the type of the parameter within parentheses. In this example, the customer identification number is an integer value. You specify the name of the parameter, id, preceded by the dollar sign and the parameter type, int, within parentheses: $(int)id. Specifying the parameter type makes it possible to check the type at query development and query runtime.
To illustrate, you could start with the generated query to get all customers, OQLGetAllCustomer.oql. Then modify that query to create another query to get only the customer whose customer identification number matched a specified value. To achieve this objective, add the following WHERE clause to the select statement, as shown:
import mydemo.Customer; SELECT o FROM Customer o WHERE o.custId = $(int)id;The Customer class declares the custId field. The syntax $(int)id indicates that id is a parameter of type int.
You can also pass a collection to a query as a parameter in the FROM clause. To do this, you use the $(DCollection) syntax in front of the parameter name for the collection. For example, you might write a query to select from a collection of customers whose sales region is the East coast. The parameter name in this example is called CustomersReg:
import com.sun.javablend.DCollection; import mydemo.Customer; SELECT c FROM $(DCollection)CustomersReg c WHERE ((Customer)c).SalesRegion = East Coast;You must cast the contents of the collection to the proper type. In this example, the result c is cast to Customer type. You can also use parameters in the SELECT clause. In general, though, this is useful only with nested queries.
When a statement includes a subquery, the subquery evaluates first. The outer SELECT statement then acts on the results of the inner statement. If the statement contains many nested SELECT statements, OQL evaluates the innermost statement first, then the statement that immediately encloses the innermost one, and so on.
For example, you could expand the statement:
SELECT c.name FROM Customer c;to nest another SELECT statement that checks for customers whose orders are greater than a certain quantity. The nested SELECT statement, instead of allowing the first SELECT clause to return all customer names, limits the selection to only those customers with orders greater than the specified quantity. Whereas the previous query returned a collection of names, this query returns a collection of constructed STRUCT statements. There is one STRUCT for each customer name. It includes the name of the customer and then a collection of the quantities for each order for that customer.
For example, the statement:
SELECT STRUCT (name : c.name, order_amt : (SELECT o.qty FROM o in c.getOrders() WHERE o.qty > 100)) FROM Customer c;returns a collection of STRUCT statementsone STRUCT per customer. Each STRUCT contains a collection of order quantities for that customer, as follows:
<John, <102, 155, 200>> <Kenneth, <300>> <Mary, <1110, 410>>You could write the query to return the same data, but grouped differently. You could write it as follows:
SELECT STRUCT (name : c.name, order_amt : o.qty) FROM Customer c, c.getOrders o WHERE o.qty > 100;Written this way, the query returns a collection of STRUCTs where each STRUCT includes the customer name and one order quantity. If a particular customer has more than one qualifying order, another STRUCT is in place for that customer for each order quantity. Thus, the query might return the following:
<John, 102> <John, 155> <John, 200> <Kenneth, 300> <Mary, 1110> <Mary, 410>A nested SELECT statement, such as the previous example, accomplishes a join operation. More typically, you use the FROM clause for joins.
The COUNT keyword returns the frequency with which an element occurs within a collection. The COUNT keyword extracts an integer that represents the number of occurrences of an element. For example, the following query determines the number of times a particular customer occurs in a customer table:
COUNT(SELECT c FROM Customers c WHERE c.custName = "Watson")The MAX keyword calculates the maximum value in a collection. For example, the following query determines the maximum salary of all employees:
MAX(SELECT e.salary FROM Employees e)Replacing the keyword MAX by MIN the query would calculate the minimum salary. The keyword AVG returns the average salary. The keyword SUM calculates the sum of all salaries.
SELECT partition FROM Employee e GROUP BY department : e.deptnoA GROUP BY query builds the cartesian product by the FROM clause, and filters its result by retaining only those elements that satisfy the WHERE clause. It then evaluates the partition attributes for all elements of the Cartesian product. All elements that match the same partition attribute values belong to the same partition. The result of this operation is a set of struct statements, where each struct consists of the partition attributes followed by the bag of elements that match this particular valued partition. This struct field is conventionally called partition. Finally the SELECT clause is applied to this partitioned set. In the example above, the result after the group operation is of type:
set<struct(department : int, partition : bag<struct(e: Employee)>)>.The SELECT clause extracts the partition field, so the query returns a set of
struct(e:Employee) values.You can also add a HAVING clause to the query in order to filter specific partitions that match the predicate specified after the keyword HAVING. The following query returns the department number together with the average salary of all employees of a partition, if the average salary is more than 30000:
SELECT department, AVG(SELECT x.e.salary FROM partition x) FROM Employee e GROUP BY department : e.deptno HAVING AVG(SELECT x.e.salary FROM partition x) > 30000
The outermost operation of the query determines whether the result is a collection or a single element. If the outermost operation is a SELECT-FROM-WHERE statement, it always returns (you always get back) a collection. The form of the query determines the type of the collectionthat is, whether it is a list, a set, or a bag. If the query includes an ORDER BY clause, you get back a LIST. If the query includes a DISTINCT clause (without an ORDER BY clause), then you get back a SET. Otherwise, you get back a BAG.
While you might construct the SELECT-FROM-WHERE statement to return only a single element, you still get back a collectionin this case, a collection of one element. For example, the following statement returns a collection of one customer element because the id parameter happens to be unique over all customer records:
SELECT c FROM Customer c WHERE c.id = 10031;Queries that return a single element have an operation other than a SELECT-FROM- WHERE statement as the outermost operation. These queries might have the keyword COUNT or ELEMENT as the outermost operation.
The COUNT keyword, when it appears as the outermost operation, evaluates an expression and returns a single int value. For example, the following query returns an integer that represents the number of persistent objects of class Person:
COUNT(SELECT p FROM Person p);
symbol : expressionThe syntax expression expression describes a set of phrases named by the non-terminal symbol symbol. The following symbols are used for the syntax expressions:
root : {importClause} query [;]
importClause : IMPORT qualifiedName [AS identifier] ;
qualifiedName : identifier{. identifier}
query : selectExpr
| expr
selectExpr : SELECT [DISTINCT] projectionAttributes fromClause
[whereClause] [groupClause] [orderClause]
projectionAttributes : projection{, projection} | *
projection : field
| expr [AS identifier]
fromClause : FROM iteratorDef {, iteratorDef}
iteratorDef : expr [AS] identifier
| identifier IN expr
whereClause : WHERE expr
groupClause : GROUP BY partitionAttributes {havingClause}
partitionAttributes : projection {, projection}
havingClause : HAVING expr
orderClause : ORDER BY sortCriteria
sortCriteria : sortCriterion {, sortCriterion}
sortCriterion : expr [ordering]
ordering : ASC | DESC
expr : orExpr
orExpr : andExpr {OR andExpr}
andExpr : quantifierExpr {AND quantifierExpr}
quantifierExpr : FOR ALL inClause : rangeExpr
| EXISTS inClause : rangeExpr
inClause : identifier IN expr
rangeExpr : equalityExpr {BETWEEN equalityExpr AND equalityExpr}
equalityExpr : relationalExpr {equalityOp [compositePredicate] relationalExpr}
| relationalExpr {LIKE relationalExpr}
equalityOp : =
| !=
relationalExpr : additiveExpr {relationalOp [compositePredicate] additiveExpr}
relationalOp : <
| <=
| >
| >=
compositePredicate : SOME
| ANY
| ALL
additiveExpr : multiplicativeExpr {+ multiplicativeExpr}
| multiplicativeExpr {- multiplicativeExpr}
| multiplicativeExpr {UNION multiplicativeExpr}
| multiplicativeExpr {EXCEPT multiplicativeExpr}
| multiplicativeExpr {|| multiplicativeExpr}
multiplicativeExpr : castExpr {* castExpr}
| castExpr {/ castExpr}
| castExpr {(% | MOD) castExpr}
| castExpr {INTERSECT castExpr}
castExpr : ( type ) castExpr
| inExpr
inExpr : unaryExpr {IN unaryExpr}
unaryExpr : + unaryExpr
| - unaryExpr
| ABS unaryExpr
| NOT unaryExpr
| postfixExpr
postfixExpr : primaryExpr {[ index ]}
| primaryExpr {(. | ->) identifier [argList]}
index : expr {, expr}
| expr : expr
argList : ( [valueList] )
primaryExpr : conversionExpr
| collectionExpr
| aggregateExpr
| undefinedExpr
| collectionConstruction
| structConstruction
| identifier [argList]
| queryParam
| literal
| ( query )
conversionExpr : LISTTOSET ( query )
| PICK ( query )
| ELEMENT ( query )
| DISTINCT ( query )
| FLATTEN ( query )
collectionExpr : FIRST ( query )
| LAST ( query )
| HEAD ( query )
| TAIL ( query )
| UNIQUE ( query )
| EXISTS ( query )
| OCCURRENCE ( expr , expr )
aggregateExpr : SUM ( query )
| MIN ( query )
| MAX ( query )
| AVG ( query )
| COUNT ( (query | *) )
undefinedExpr : IS_UNDEFINED ( query )
| IS_DEFINED ( query )
structConstruction : STRUCT ( fieldList )
fieldList : field {, field}
field : identifier : expr
collectionConstruction : ARRAY ( [valueList] )
| SET ( [valueList] )
| BAG ( [valueList] )
| LIST ( [valueList] )
valueList : expr {, expr}
queryParam : $ [( type )] (identifier | integer-literal)
type : qualifiedName
| BOOLEAN
| BYTE
| SHORT
| INT
| LONG
| FLOAT
| DOUBLE
| CHAR
| STRING
identifier : letter {letter| digit | _ }
literal : integer-literal
| long-literal
| float-literal
| double-literal
| char-literal
| string-literal
| boolean-literal
| object-literal
| undefined-literal
| date-literal
| time-literal
| timestamp-literal
integer-literal : digit {digit}
long-literal : digit {digit} (l | L)
float-literal : digit {digit} . digit {digit}
[(E | e) [+ | -]digit {digit}] (f | F)
double-literal : digit {digit} . digit {digit}
[(E | e) [+ | -]digit {digit}] [d | D]
char-literal : 'character'
string-literal : "{character}"
boolean-literal : TRUE | FALSE
object-literal : NIL | NULL
undefined-literal : UNDEFINED
date-literal : DATE ' integer-literal - integer-literal -integer-literal '
time-literal : TIME ' integer-literal : integer-literal :double-literal '
timestamp-literal : TIMESTAMP ' integer-literal - integer-literal -integer-literal integer-literal : integer-literal: double-literal'
character : letter
| digit
| special-character
letter : A | B | ... | Z |
a | b | ... | z
digit : 0 | 1 | ... | 9