Previous     Contents     Index     Next     
iPlanet Application Server 6.5 Programmer's Guide (C++)



Chapter 6   Querying a Database


This chapter describes queries, which are statements that specify a set of data to be retrieved from a database.

The following topics are included in this chapter:



Introduction to Queries

The data that a query retrieves from a database is called a result set.

Typically, the results of a query are displayed in a report. Queries can also be used to dynamically populate forms. For example, you can dynamically populate a list box with selections, such as city names, based on one of the user's previous selections, such as a state name.


Types of Queries

iPlanet Application Server applications can contain two types of queries:

  • Flat queries provide tabular result sets.

  • Hierarchical queries combine the result sets from several flat queries in a tree structure.

For more information, see



Using Flat Queries



A flat query is the simplest type of query. It is called flat because its result set is not divided into levels or groups, but is simply a raw listing of data values in a tabular format. Every row contains values from the same set of tables and columns in a single database. Relational database users know this type of query as a simple SELECT statement.

You can use an individual flat query to retrieve a flat result set. You can also place one or more flat queries inside a hierarchical query when you want to merge the query's result set with a template to create dynamic output, such as a report.

A flat query is an instance of the IGXQuery interface, and its result set is an instance of the IGXResultSet interface. Flat queries are handled by the Data Access Engine service of iPlanet Application Server.

For more information about the Data Access Engine, see the Administration and Deployment Guide.


Writing Flat Queries

You can use iPlanet Application Builder to create queries quickly, without writing code. For more information, see the .

Alternatively, this section describes how to write queries programmatically if you prefer to write the code yourself. You can use any of the following techniques to write a flat query programmatically:

  • Use a series of method calls to write the query.

  • Write your own query file.

For more information, see

  • Write a SQL SELECT statement and pass it to the SetSQL( ) method. Use this technique only if you are very familiar with SQL syntax.

For more information, see


Writing a Flat Query with Method Calls

You can specify the clauses of a flat query by calling a series of methods designed for that purpose. The query-writing methods are easy to understand if you are familiar with Structured Query Language (SQL), which is a commonly used language for accessing information in relational database management systems. The method calls correspond very closely to the SELECT, FROM, WHERE, ORDER BY, GROUP BY, and HAVING clauses of a SQL SELECT statement.

When using method calls to write a flat query, you can specify aliases or parameters. An alias provides a more meaningful alternate name for a table, column, or field, and is especially useful in the case of a calculated field. Parameters provide a technique for dynamically modifying the query itself at runtime.

For more information, see and

To write a flat query using method calls:

  1. Instantiate the flat query. For example:

       IGXQuery *pQuery=NULL;

       hr = CreateQuery(&pQuery);

  2. Select tables or views. This step corresponds to the FROM clause in SQL. For example:

       pQuery->SetTables("OBAccountType, OBAccount, OBCustomer");

    For more information, see

  3. Select columns. This step corresponds to the SELECT clause in SQL. For example:

       pQuery->SetFields("OBAccountType.acctDesc as

          OBAccountType_acctDesc, OBAccount.acctNum as

          OBAccount_acctNum, OBAccount.balance as

          OBAccount_balance, OBCustomer.custName as cust");

    For more information, see


Optional Steps

  1. Put conditions on row retrieval. This step corresponds to the WHERE clause in SQL. For example:

       char tmpStr[300];

       sprintf(tmpStr, "OBAccountType.acctType = OBAccount.acctType

          and OBCustomer.ssn = OBAccount.ssn and (OBCustomer.ssn =

          '%s')", pSsn);

       pQuery->SetWhere(tmpStr);

    For more information, see

  2. Specify row sorting in the result set. This step corresponds to the ORDER BY clause in SQL. For example:

       pQuery->SetOrderBy("OBAccount.acctNum asc");

    For more information, see

  3. Summarize the data by creating aggregate rows. This step corresponds to the GROUP BY clause in SQL. For example:

       pQuery->SetGroupBy("cust");

    For more information, see

  4. Put conditions on aggregate rows. This step corresponds to the HAVING clause in SQL. For example:

       pQuery->SetHaving("OBAccount_balance > 100");

    For more information, see


Example Query Using Methods to Set Clauses
The following code shows a flat query that retrieves product information. The code first instantiates the query, then sets up the query clauses.

IGXQuery *qry;

HRESULT hr = CreateQuery(&qry);

qry->SetTables("invoices, products");

qry->SetFields("invID, invDate, invProd");

qry->SetWhere("invProd=prodName and prodPrice > 10");

qry->SetOrderBy("invProd");


Specifying Tables

To specify which database tables or views contain the data you want to retrieve, use the SetTables( ) method. Every query must contain a call to SetTables( ). For example, the following code specifies two tables, invoices and customers.

pQry->SetTables("invoices, customers");

If you call SetTables( ) more than once in the same query, each subsequent call replaces the settings specified in the previous call. To add more tables to a query, list all of them in a single SetTables( ) call.

You can use the same table several times in a query by using aliases. For example, you might want to use a table containing customer data to obtain both a Bill To address and a Ship To address. Specify an alias name each time you repeat the same table name. For example:

pQry->SetTables("customers, customers as cust2");

The information in the SetTables( ) call corresponds to the FROM clause of a SQL SELECT statement. For more information about the FROM clause, refer to your SQL documentation.


Specifying Columns and Computed Fields

To specify which database columns contain the data you want to retrieve, use the SetFields( ) method. This method call includes a comma-separated list of the columns and computed fields you want to appear in the query's result set. Any column name you use in this method's parameter list must belong to a column in one of the tables you specified in the SetTables( ) call. If any of the columns contains a BLOB data type, that column must come last in the list.

If the query does not include a call to SetFields( ), then the result set includes all fields in the tables listed in the SetTables( ) call. If you call SetFields( ) more than once in the same query, each subsequent call replaces the settings specified in the previous call. To add more columns and computed fields to a query, list all of them in a single SetFields( ) call.

The information in the SetFields( ) call corresponds to the SELECT clause of a SQL SELECT statement. For more information about the SELECT clause, refer to your SQL documentation.


Specifying Columns
To retrieve data from a column in the database, list the column name in the SetFields( ) method call. For example, the following code lists three column names.

pQry->SetFields("invID, invDate, invProd");

You can include columns from more than one table in a single SetFields( ) call. For example, the following code lists columns from two tables, invoices and customers.

pQry->SetFields("invoices.invDate, customers.custName");

To specify that you want to retrieve all columns from all the tables listed in the SetTables( ) call, use the asterisk (*). For example:

pQry->SetFields("*");

You can specify an alias name for a column. The alias name becomes the name for the corresponding column in the result set. For more information, see


Specifying Computed Fields
A computed field is a field in a result set that contains the result of an expression, rather than a value taken directly from a database column. You can specify two types of computed fields:

  • Use a mathematical expression to combine the values from several database columns. You can use any mathematical expression allowed by SQL syntax. For example, the following code specifies a computed field that multiplies the values in the ProdPrice and ProdQty columns.

       pQry->SetFields("ProdID, ProdPrice * ProdQty as Total");

  • Use an aggregate function to summarize the values for a particular column over a group of rows. If you are planning to use a GROUP BY clause in the query, you typically specify one or more computed fields using aggregate functions. The aggregate functions available depend on your database server, but those typically supported are Min( ), Max( ), Count( ), Avg( ), Sum( ), First( ), and Last( ). For example, the following code uses the Sum( ) function to create a computed column with the total of all values in the salary field.

       pQryCTY->SetFields("city, Sum(salary) as TotalSalaries");

For more information about the GROUP BY clause and for an example of how aggregate functions work, see

In the previous examples, the computed fields are given the aliases Total and TotalSalaries. It is advisable to specify an alias name for every computed field. This ensures that the result set contains a meaningful name for the computed field. However, if you do not specify an alias, you must refer to the field by its ordinal number (position) when subsequently processing data in the result set.

For more information, see


Specifying Conditions on Row Retrieval

To specify conditions that must be met by the rows in the database, use the SetWhere( ) method. For example, rather than retrieving information about all customers, the following code retrieves only information about customers in a certain city.

pQry->SetWhere("City='Bombay'");

If you call SetWhere( ) more than once in the same query, each subsequent call replaces the settings specified in the previous call. To add more conditions to a query, list all of them in a single SetWhere( ) call, separating each condition with AND or OR. For example:

pQry->SetWhere("City='Bombay' AND Sector='Northwest'");

The information in the SetWhere( ) call corresponds to the WHERE clause of a SQL SELECT statement. Use the SQL WHERE syntax to specify conditions. The method call can include any valid SQL syntax, including nested SELECT statements. For more information about the WHERE clause, refer to your SQL documentation.


Sorting Data

To change the order of the rows retrieved by the query, use the SetOrderBy( ) method. In most reports, the data is organized in a particular sequence. This sequence is determined by a sort key, which is a group of one or more column names. Use SetOrderBy( ) to list the columns and computed fields that you want to use as a sort key.

For example, in a sales report, the data might be sorted alphabetically by city. Within each city, the local customers might be sorted by customer ID. For each customer, in turn, the invoices might be sorted by invoice number. The following code shows how to specify the sort key for this sales report.

pQry->SetOrderBy("CityName,CustomerID,InvoiceNum");

The sorted data would look something like the following:

Atlanta, Customer01, Inv01

Atlanta, Customer01, Inv02

Atlanta, Customer04, Inv16

Bombay, Customer03, Inv20

Bombay, Customer03, Inv23

Bombay, Customer10, Inv05

Any column name or alias you use in the parameter list of SetOrderBy( ) must belong to one of the columns or computed fields you specified in the SetFields( ) call.

You can sort any column in either ascending (1-100, A-Z) or descending (100-1, Z-A) order. Ascending order is the default. To sort in descending order, add the keyword Desc after the column name. For example, the following code sorts rows from a weather database so that the data for the hottest days appears first.

pQry->SetOrderBy("Temperature Desc");

If you do not call SetOrderBy( ), the order of the returned rows is unpredictable. If you call SetOrderBy( ) more than once in the same query, each subsequent call replaces the settings specified in the previous call. To add more columns to a sort key, list all of them in a comma-separated list in a single SetOrderBy( ) call.

The information in the SetOrderBy( ) call corresponds to the ORDER BY clause of a SQL SELECT statement. For more information about the ORDER BY clause, refer to your SQL documentation.

You can use parameters in this method call to allow parts of the clause to be set when the query runs. For more information, see


Summarizing Data

Data can be summarized into aggregate rows. An aggregate row is a single row in a result set that combines the data from a group of database rows. These rows have one or more column values in common.

For example, suppose a customer places several orders over a period of months. In the database, the customer orders table contains one row for each of these orders. In each of these rows, the column CustName has the same value. You can summarize all the rows for a particular customer and create one aggregate row that contains the total number and average dollar amount of all that customer's orders.


Example Rows Before Summarizing




CustName

OrderID

OrderTotal

Customer1  

Order01  

200  

Customer1  

Order20  

400  

Customer2  

Order55  

100  

Customer2  

Order60  

300  

Customer2  

Order75  

200  


Example Code to Summarize These Rows
pQry->SetFields("CustName, Count(OrderID) as Orders,

   Avg(OrderTotal) as AverageAmt");

pQry->SetGroupBy("CustName");


Example Aggregate Rows After Summarizing




CustName

Orders

AverageAmt

Customer1  

2  

300  

Customer2  

3  

200  

You can also use aggregate rows to eliminate duplicates. If you do not include any computed fields when you set up the aggregate row, the row will not contain summary data, such as the number of orders and average amount in the previous example. Instead, the row contains only the values in the columns that are the same in several rows, such as the customer names in the previous example.

To summarize rows

  1. In the query's SetFields( ) call, specify one or more columns to define the groups of rows. These are the columns that have common values in several rows, such as CustName in the example earlier in this section. For more information, see

  2. In the same SetFields( ) call, you typically specify one or more computed fields using aggregate functions, such as Orders and AverageAmt in the example earlier in this section. This step is optional if you only want to eliminate duplicates, not create summary data. For more information about computed fields, see

  3. Call the SetGroupBy( ) method, and list all the column names you specified in step 1. That is, list the column names from the database table, but not the computed field aliases.

If you call SetGroupBy( ) more than once in the same query, each subsequent call replaces the settings specified in the previous call. To add more columns to the GROUP BY clause, list all of them in a comma-separated list in a single SetGroupBy( ) call.

The information in the SetGroupBy( ) call corresponds to the GROUP BY clause of a SQL SELECT statement. For more information about the GROUP BY clause, refer to your SQL documentation.

You can use parameters in this method call to allow parts of the clause to be set when the query runs. For more information, see


Example
The following code constructs a query with aggregate rows to show the number of invoices submitted by each sales representative. The query returns one aggregate row for each sales representative. The code then constructs a segment of HTML to display the aggregate rows.

// Open connection

IGXValList *conn_params;

conn_params = GXCreateValList();

conn_params->SetValString("DSN", "salesDB");

conn_params->SetValString("DB", "salesDB");

conn_params->SetValString("USER", "steve");

conn_params->SetValString("PSWD", "pass7878");

IGXDataConn *conn = NULL;

HRESULT hr;

hr = CreateDataConn(0, GX_DA_DRIVER_ODBC, conn_params, NULL,

   &conn);

// Write query

IGXQuery *qry;

CreateQuery(&qry);

qry->SetTables("invoices");

// Use COUNT() aggregate expression in SetFields()

// and GroupBy() to create aggregate rows

qry->SetFields("salesRep, COUNT(invoiceID) as numInvoices");

qry->SetGroupBy("salesRep");

// Run query

IGXResultSet *rs = NULL;

hr = conn->ExecuteQuery(0, qry, NULL, NULL, &rs);

// Construct report

StreamResult("Sales Report<p>");

ULONG col_salesRep;

rs->GetColumnOrdinal("salesRep", &col_salesRep);

ULONG col_numInvoices;

rs->GetColumnOrdinal("numInvoices", &col_numInvoices);

do

{

   char buffer[512];

   rs->GetValueString(col_salesRep, buffer, sizeof(buffer));

   StreamResult(buffer);

   StreamResult(" ");

   int numInvoices;

   rs->GetValueInt(col_numInvoices, &numInvoices);

   sprintf(buffer, "%d", numInvoices);

   StreamResult(buffer);

   StreamResult("<br>");

} while (rs->FetchNext() == NOERROR);

// Release resources

rs->Release();

qry->Release();

conn->Release();

conn_params->Release();


Specifying Conditions on Aggregate Rows

You can impose two kinds of conditions as part of a query:

  • Conditions on the rows retrieved from the database. These conditions are specified in the SetWhere( ) method. See

  • Conditions on aggregate rows. These conditions are specified in the SetHaving( ) method, which is described in this section.

When a query runs, it follows the steps described in the following illustration and list.

  1. The query first retrieves rows from the database as specified in the SELECT and FROM clauses. Only rows that meet the conditions in the WHERE clause are retrieved at this point.

  2. If the query includes a GROUP BY clause, the query's result set contains aggregate rows. Each aggregate row summarizes the data in several of the rows from the database. For information about how to create aggregate rows, see

  3. The query finally applies the conditions in the HAVING clause to the aggregate rows.

To specify conditions on aggregate rows, use the SetHaving( ) method. This method can refer to any fields in the aggregate rows to set up criteria. Before you call SetHaving( ), you must call SetGroupBy( ) to create the aggregate rows. For example:

pQry->SetFields("CustName, Avg(OrderTotal) as AverageAmt");

pQry->SetGroupBy("CustName");

pQry->SetHaving("AverageAmt > 1000");

If you call SetHaving( ) more than once in the same query, each subsequent call replaces the settings specified in the previous call. To add more conditions on aggregate rows, list all of them in a comma-separated list in a single SetHaving( ) call.

The information in the SetHaving( ) call corresponds to the HAVING clause of a SQL SELECT statement. Use the SQL HAVING syntax to specify conditions. The method call can include any valid SQL syntax, including nested SELECT statements. For more information about the HAVING clause, refer to your SQL documentation.


Using Aliases in a Query

An alias is an alternate name. You can specify aliases when writing a flat query. To specify an alias, use the following syntax:

sourceName as aliasName

You can define two types of aliases:

  • A table alias, which is specified in the SetTables( ) call, provides an alternate name for a database table. This technique is useful when you want to use the same table more than once in a query, or when you need to refer to two tables that have the same name but belong to different users within a database. You can use a table alias elsewhere in the same query to refer to the table.

  • A field alias, which is specified in the SetFields( ) call, provides an alternate name for a database column or computed field in the result set. You can not use these aliases elsewhere in the same query. However, field aliases are useful in other ways. For example, if you do not use an alias for a computed field, you must refer to the field by ordinal number, which is less convenient. You can also use a field alias in the join expression of a hierarchical query.


Example
The following code gives aliases to two tables owned by different database users.

pQry->SetTables("jim.accounts as Jim, ann.accounts as Ann");

The following code specifies that you can use prodID, which is an alias, instead of using the longer field name invoiceProductID.

pQry->SetFields("invoiceProductID as prodID");

Aliases are especially useful when you specify a formula or expression in the SetFields( ) method. For example, in the following code, Total is an alias.

pQry->SetFields("invoiceID,

   invoiceCount * prodPrice as Total");

Each row in the result set from this query contains two fields, invoiceID and Total. The Total field contains the result of the computation invoiceCount * prodPrice.


Running Flat Queries

To run a flat query, call the ExecuteQuery( ) method in the IGXDataConn interface. You can run a flat query multiple times. Each time you call ExecuteQuery( ), the query returns a new result set object, which might contain different data if the contents of the database have changed.

To run a flat query

  1. Open a connection to a data source. For example:

       IGXValList *conn_params;

       conn_params = GXCreateValList();

       conn_params->SetValString("DSN", "salesDB");

       conn_params->SetValString("DB", "salesDB");

       conn_params->SetValString("USER", "steve");

       conn_params->SetValString("PSWD", "pass7878");

       IGXDataConn *conn = NULL;

       HRESULT hr;

       hr = CreateDataConn(0, GX_DA_DRIVER_ODBC, conn_params, NULL,

          &conn);

  2. Declare a variable to hold the output result set object. For example:

       IGXResultSet *rs = NULL;

  3. Call the ExecuteQuery( )method in the IGXDataConn interface. For example:

       hr = conn->ExecuteQuery(0, qry, NULL, NULL, &rs);


Example
The following code opens a connection, writes a query, and runs the query, with the results being retrieved in a result set called rs.

IGXValList *conn_params;

conn_params = GXCreateValList();

conn_params->SetValString("DSN", "salesDB");

conn_params->SetValString("DB", "salesDB");

conn_params->SetValString("USER", "steve");

conn_params->SetValString("PSWD", "pass7878");

IGXDataConn *conn = NULL;

HRESULT hr;

hr = CreateDataConn(0, GX_DA_DRIVER_ODBC, conn_params, NULL,

   &conn);

IGXQuery *qry;

CreateQuery(&qry);

qry->SetTables("author");

qry->SetFields("first_name, last_name");

IGXResultSet *rs = NULL;

hr = conn->ExecuteQuery(0, qry, NULL, NULL, &rs);

// Use resultset here. When done, release the objects

rs->Close(0);

rs->Release();

qry->Release();

conn->Release();


Getting Data From a Flat Query's Result Set

When a flat query runs, it returns rows of data in an instance of IGXResultSet interface. In order to use the data from a flat query, you need to iterate through the rows in the result set and retrieve data from each row. To move from row to row, use the FetchNext( ) method. To retrieve data from a particular row, use the GetValue**( ) methods in the IGXResultSet interface.

When you are finished using a result set, release it by calling Release( ). This method releases the database connection so that it is available for use by other application code. Do not release the result set or close the database connection until you are finished using the result set. Just because the query has run and returned a result set interface, that doesn't mean all the data is there. Typically the result set is buffered, and live database cursors may still be open. Therefore, when you reach the last row in the buffer, the result set object still needs the connection to get the next batch of rows into the buffer.


Example
The following code, from the OBShowTransferPage AppLogic in the Online Bank sample application, copies data from rows in a result set into a template map.

char pAcctDesc[200];

char pAcctNum[200];

// Pull the column ordinals for the account description and

// accout num

ULONG acctDescCol=0;

pRset->GetColumnOrdinal("OBAccountType_acctDesc",

   &acctDescCol);

ULONG acctNumCol=0;

pRset->GetColumnOrdinal("OBAccount_acctNum", &acctNumCol);

char tmpStr[300];

do {

   pRset->GetValueString(acctDescCol, pAcctDesc, 200);

   pRset->GetValueString(acctNumCol, pAcctNum, 200);

   sprintf(tmpStr, "acctDesc=%s;acctNum=%s", pAcctDesc,

      pAcctNum);

   pAcctsTempDB->RowAppend(tmpStr);

   pAcctsTempDB2->RowAppend(tmpStr);

} while(pRset->FetchNext()==GXE_SUCCESS);



Using Hierarchical Queries



A hierarchical query is a query that combines one or more flat queries to generate a result set with multiple nested levels of data. A hierarchical query returns data similarly to a database join or nested query, although its output is actually a collection of separate, flat results sets that are related to each other in specified ways. The hierarchical query object in an iPlanet Application Server application is designed to be merged with a template by the Template Engine to produce dynamic output.

Each of the flat queries in a hierarchical query retrieves a different set of data and can even use a different database connection. The flat queries are related to each other in a series of nested levels that compose the hierarchical query.

The outer level of information is called the parent level of information, and the query that retrieves this information is called the parent query. The inner level of information is called the child level of information, and the query that retrieves this information is called the child query. This parent-child relationship can be repeated for any number of levels, as shown in the following illustration:

The parent level of information determines the grouping of information in its child levels. Each child query is run multiple times, once for each row in the parent query's result set, as shown in the following illustration.

Hierarchical queries are used in conjunction with HTML templates to create reports. If the desired output is a tabular report, the hierarchical query contains only one flat query. If the desired output is a grouped report, the hierarchical query contains two or more flat queries. For more information, see

A hierarchical query is an instance of the IGXHierQuery interface, and its result set is an instance of the IGXHierResultSet interface.


Example
The following report shows the results of a hierarchical query that contains two flat queries, one for city data and one for employee data. The outer-level rows show city summary data, taken from the result set of the parent query. The inner-level rows show individual employee data. Each set of employee data represents the result set of the child query being run once for the corresponding row from the city query.

Berkeley 1000

Isaac 300

Ken 700

Paris 600

Steve 300

Tim 300

The two levels in this example, without data, can be abstractly pictured as follows.

Parent query:

   city

SUM(salary)

Child query:

   name

salary


Writing Hierarchical Queries

You can use iPlanet Application Builder to create queries quickly, without coding. For more information, see User's Guide. This section describes how to write hierarchical queries programmatically. Use these techniques if you prefer to write the code yourself.

You can use two techniques to write a hierarchical query programmatically:

  • Use a series of method calls to write the query. This section describes how to write a hierarchical query this way.

  • Write a query file.

    For more information, see

To write a hierarchical query using a series of method calls

  1. Write the flat queries that you plan to use in the hierarchical query. For every level of data in a hierarchical query, you need to define one flat query. Each query can have a different database connection.

    For more information, see

  2. Instantiate a hierarchical query object. For example:

       IGXHierQuery *pHq=NULL;

       hr=CreateHierQuery(&pHq);

  3. Construct the hierarchical query by using the AddQuery( ) method to place the flat queries in relation to each other. Each query you add, except the first, is a child query. There is no practical limit to the number of nested parent-child levels. For example, in the following code, SelCustAccts is the parent and SelAcctTrans is the child:

       pHq->AddQuery(pQuery, pConn, "SelCustAccts", "", "");

       pHq->AddQuery(pQ, pConn, "SelAcctTrans", "SelCustAccts",

          "SelAcctTrans.OBAccount.acctNum =

          'SelCustAccts.OBAccount_acctNum'");

In the AddQuery( ) call, you list the flat query object that you are adding, its database connection, and its name. In the case of a child query, you also specify the name of its parent query and a join expression that shows how the two queries are related. In this example, the two queries are joined on the acctNum fields. For more information, see

Typically, each parent query has a single child query, so each AddQuery( ) call represents a new level of nesting. For an example of this type of hierarchical query, see

A parent query can also have several child queries, as shown in the following illustration:

In a multi-child hierarchical query, several AddQuery( ) calls refer to the same parent query. The result is parallel subreports whose results are displayed one after the other. For an example of this type of hierarchical query, see


Joins in Hierarchical Queries

In an iPlanet Application Server application, join syntax is used to connect the flat queries that make up a hierarchical query. The join is specified for every flat query in the hierarchical query except the first, which is the outermost parent query. You specify the join in the last parameter of the AddQuery( ) method.

When you write a join expression, you are specifying a relationship between a field in the child query and a field in the parent query. A join expression uses the following syntax:

"child.table.col = [']parent.colOrAlias[']"

The single quotes in the parent portion of the syntax are required only if the field is a String or Date/Time data type. For example:

hq->AddQuery(qryEMP, conn, "EMP", "CTY",

   "EMP.employees.city = 'CTY.city'");

The following illustration shows an example of joins in a three-level hierarchical query:

Avoid queries with more than two or three joins, as this will degrade performance. To improve performance, consider denormalizing the database. Denormalization results in duplicate data in the database, but simplifies queries and improves performance.


Multi-Field Joins

A join expression can set up relationships among multiple fields. This is useful when you cannot set up a unique relationship using a single field.

For example, suppose your database contains information about products from several vendors. Each product has an ID number which is unique for that vendor, but which might be the same as an ID number used by a different vendor for a totally different product. The database might contain the following rows:




VendorID

ProductID

Description

Vendor01  

prod1111  

Sweater  

Vendor02  

prod1111  

Hard disk  

In this case, you need to use both the vendor name and product ID to make a unique join expression.


Sample Hierarchical Query with Multi-field Join
Suppose you have a database with two tables, one for authors (first_name, last_name) and one for books (name, author_first, author_last). You want to construct a report that shows the titles of novels grouped by author. For example:

Bill Smith

No Road Ahead

Sandra Smith

Sunshine in May

James Worthington

King of Hearts

After the Supper

You need a hierarchical query object with two query levels, one for author information and the other for book information. The relationship between the two queries involves multiple fields. Both the first name and last name of the author must be matched, because two authors might have the same last name. The following code shows how to construct a hierarchical query to produce this report:

IGXQuery *qryAuth;

CreateQuery(&qryAuth);

qryAuth->SetTables("author");

qryAuth->SetFields("first_name, last_name");

IGXQuery *qryBook;

CreateQuery(&qryBook);

qryBook->SetTables("book");

qryBook->SetFields("name, author_first, author_last");

IGXHierQuery *hqry;

CreateHierQuery(&hqry);

hqry->AddQuery(qryAuth, conn, "AUTHOR", "", "");

hqry->AddQuery(qryBook, conn, "BOOK", "AUTHOR",

   "BOOK.author_firstname = 'AUTHOR.first_name'

   and

   BOOK.author_lastname = 'AUTHOR.last_name'");


Example Two-Level Hierarchical Query

The following report shows salary figures for employees in various cities. The total salary amount for each city is shown as summary data next to each city name:

Berkeley 100

Isaac 30

Ken 70

Paris 60

Steve 30

Kim 30

The following code produces the report above:

// Begin by defining two flat queries, one for city

// information and the other for employee information.

// The example assumes the database contains an employeee

// table with columns called name, salary, and city.

IGXQuery *qryCTY;

CreateQuery(&qryCTY);

qryCTY->SetTables("employee");

qryCTY->SetFields("city, SUM(salary) as salarysum");

qryCTY->SetGroupBy("city");

IGXQuery *qryEMP;

CreateQuery(&qryEMP);

qryEMP->SetTables("employee");

qryEMP->SetFields("name, salary, city");

// Next, the code instantiates a hierarchical query object.

IGXHierQuery *hqry;

CreateHierQuery(&hqry);

// The following code adds the first flat query to the

// hierarchical query. The flat query is given the name CTY.

hqry->AddQuery(qryCTY, conn, "CTY", "", "");

// The following code adds the second flat query. This flat

// query is given the name EMP in the third parameter to

// AddQuery(). The next parameter specifies that the EMP

// query is a child of the CTY query. That is, results from

// the EMP query are grouped and nested within results from

// the CTY query. The last parameter specifies that the two

// flat queries are joined by their city fields.

hqry->AddQuery(qryEMP, conn, "EMP", "CTY",

   "EMP.employees.city = 'CTY.city'");


Sample Multi-Child Hierarchical Query

In the following code, the CITY query has two child queries, OFFICE and EMP:

hqry->AddQuery(qryCTY, conn, "CITY", "", "");

hqry->AddQuery(qryOFC, conn, "OFFICE", "CITY",

   "OFFICE.cities.city = 'CITY.city'");

hqry->AddQuery(qryEMP, conn, "EMP", "CITY",

   "EMP.employee.city = 'CITY.city'");

This query can be used to generate the following type of report. Under each row from the CITY query, the results of the OFFICE query are printed, followed by the results of the EMP query. The two child queries, OFFICE and EMP, are parallel, not grouped or joined to each other. Therefore, the report shows all the offices in a city, followed by all the employees in that city, regardless of which office they work in.

San Francisco

Financial District Office

Haight Office

Anderson, M

Chen, S

Myers, P

San Jose

Santa Clara Street Office

Bellows, R

Franklin, M


Running Hierarchical Queries

Typically, hierarchical queries are constructed in order to be merged with templates, and they are run automatically when you call EvalTemplate( ) or EvalOutput( ) to merge the data. You can also run a hierarchical query in a standalone fashion by calling the Execute( ) method in the IGXHierQuery interface. When you call a hierarchical query in this manner, a hierarchical result set is returned.

To run a hierarchical query without merging it with a template

  1. Declare a variable to hold the output result set object. For example:

       IGXHierResultSet *rs = NULL;

  2. Call the . For example:

       HRESULT hr = hqry->Execute(0, 0, NULL, &rs);


Getting Data From a Hierarchical Query's Result Set

To use the data in a hierarchical query's result set, you typically call EvalOutput( ) or EvalTemplate( ) to merge the data with a template. However, when you do not wish to return results to the client but instead need to access data in a hierarchical result set programmatically, you can use the methods in the IGXHierResultSet interface.

When you are finished using a result set, release it by calling Release( ). This method releases the database connection so that it is available for use by other application code. Do not release the result set or close the database connection until you are finished using the result set. Just because the query has run and returned a result set interface, that doesn't mean all the data is there. Typically the result set is buffered, and live database cursors may still be open. Therefore, when you reach the last row in the buffer, the result set object still needs the connection to get the next batch of rows into the buffer.


Example
The following code example shows processing a hierarchical result set and retrieving fixed length values in the result set rows:

STDMETHODIMP

TestGxq::TestResultSetProcessing(IGXHierQuery *pHierQuery)

{

   HRESULT hr = GXE_SUCCESS;

   char cityName[300];

   cityName[0] = '\0';

   double population = 0;

   ULONG rowNumber = 0;

   IGXHierResultSet *hrs = NULL;

   hr = pHierQuery->Execute(0, 0, NULL, &hrs);

   if ((hr == GXE_SUCCESS) && hrs &&

   (hrs->GetRowNumber("cityQuery", &rowNumber)==GXE_SUCCESS)

   &&

   rowNumber) {

      hrs->GetValue("cityQuery", "NAME", cityName, 300);

      hrs->GetValue("cityQuery", "POP", (LPSTR) &population,

         sizeof(population));

   }

   cout << "City Name: " << cityName << endl;

   cout << "Population: " << population << endl;

   return hr;

}



Buffering Result Sets From Queries



You can retrieve result sets from flat or hierarchical queries into a memory buffer. This technique offers the following advantages:

  • Backward (as well as forward) movement through the rows.

  • Multiple passes through the rows, such as to perform two-pass calculations.

The buffer exists only within the scope of the code that created it. For example, if an AppLogic uses a buffer, the buffer lasts only as long as the AppLogic runs, and is deallocated when the AppLogic returns.

To buffer a result set, you pass a buffering flag and a set of buffer parameters when you run a query using any of the following techniques:

  • When calling the ExecuteQuery( ) method in the IGXDataConn interface to run a flat query.

  • When calling the Execute( ) method in the IGXPreparedQuery interface to run a prepared query.

  • When calling the Execute( ) method in the IGXHierQuery interface to run a hierarchical query.

Avoid buffering too many rows, because buffering can use large amounts of virtual memory. To keep control of the buffer size, use buffer parameters, as described in the next section.


Setting Buffer Parameters

You can customize buffering by setting the following optional parameters:

  • Initial number of rows in buffer (RS_INIT_ROWS). The default is 10.

  • Maximum number of rows in buffer (RS_MAX_ROWS). The default is 100.

  • Maximum size of the buffer in bytes (RS_MAX_SIZE). The default is 12,800.

By specifying a maximum number of rows or byte size for the buffer, you conserve memory space. However, this does not limit your access to the data returned by the query.

For example, you might have a maximum buffer size of 100 rows, but the query might actually return 200 rows. The buffer stores 100 rows to begin with, but if you attempt to access the 101st row by calling FetchNext( ), iPlanet Application Server adds another row to the buffer and returns an informational message to let you know that the buffer has been exceeded.

The same holds true if you call MoveTo( ) and specify any row number greater than the buffer size. iPlanet Application Server adds the necessary number of rows to the buffer.

If you specify both RS_MAX_ROWS and RS_MAX_SIZE, the actual limit is the smaller of the two settings, that is, whichever is exceeded first.

To buffer a result set

  1. Define an IGXValList object that contains an item with the name RS_BUFFERING and the value TRUE. For example:

       IGXValList *props;

       props = GXCreateValList();

       props->SetValString("RS_BUFFERING", "TRUE");

  2. To set the initial size of the buffer, add an item with the name RS_INIT_ROWS and a value that indicates the initial number of rows. For example:

       props->SetValInt("RS_INIT_ROWS", 50);

  3. To set the maximum number of rows you want to include in the buffer, add an item with the name RS_MAX_ROWS. For example:

       props->SetValInt("RS_MAX_ROWS", 100);

  4. To set the maximum size of the buffer in bytes, use an item named RS_MAX_SIZE. For example:

       props->SetValInt("RS_MAX_SIZE", 500000);

  5. Declare a variable to hold the results of the query. For example, the following code is for a flat query:

       IGXResultSet *prs;

  6. Pass the IGXValList object when you run the query. In addition, pass GX_DA_RS_BUFFERING as the flags parameter. The exact text to use for this flag varies depending on the type of query you are running. For example, the following code runs a flat query with buffering:

       HRESULT hr = conn->ExecuteQuery(GX_DA_RS_BUFFERING, qry,

          null, props, &prs);

    The following code runs a prepared query with buffering:

       HRESULT hr = pqry->Execute(GX_DA_RS_BUFFERING, cmdIn,

          null, null, &prs);


Example
The following code shows how to define properties for buffering the result set of a flat query.

IGXValList *conn_params;

conn_params = GXCreateValList();

conn_params->SetValString("DSN", "salesDB");

conn_params->SetValString("DB", "salesDB");

conn_params->SetValString("USER", "steve");

conn_params->SetValString("PSWD", "pass7878");

IGXDataConn *conn = NULL;

HRESULT hr;

hr = CreateDataConn(0, GX_DA_DRIVER_ODBC, conn_params, NULL,

   &conn);

IGXValList *props;

props = GXCreateValList();

// Turn on result set buffer.

props->SetValString("RS_BUFFERING", "TRUE");

// Specify the maximum number of rows to buffer.

props->SetValInt("RS_MAX_ROWS", 50);

IGXQuery *qry;

CreateQuery(&qry);

// . . . define query properties . . .

// Execute query with result set buffer.

IGXResultSet *rs = NULL;

hr = conn->ExecuteQuery(GX_DA_RS_BUFFERING, qry, NULL,

   props, &rs);

// Use resultset here. When done, release objects.

rs->Close(0);

rs->Release();

qry->Release();

props->Release();

conn->Release();

conn_params->Release();



Creating Database Reports



A report is a formatted presentation of data. In an iPlanet Application Server application, a report is an HTML page presented to the user in response to a request for information. In order to create a report, an iPlanet Application Server application combines the following elements:

  • A hierarchical result set, which comes from either a hierarchical query or an instance of the GXTemplateDataBasic class.

  • An HTML template to format and present the data. The HTML template can include images, applets, input fields, buttons, hyperlinks, and any other HTML features. The template also contains special GX markup tags that specify where to merge data from the hierarchical query.

  • An AppLogic object that specifies the hierarchical query and runs the report. When the AppLogic runs the report, it specifies which HTML template to use.

The following illustration and list summarize the typical sequence of events in an iPlanet Application Server reporting application.

  1. A user requests a report by submitting a form in a Web browser. The request is routed through the Web server to iPlanet Application Server.

  2. iPlanet Application Server checks to see whether the report has already been created and cached as a result of a previous request for the same report. If so, iPlanet Application Server returns the cached report, which is routed back through the Web server to the user's Web browser.

  3. If the report is not cached, iPlanet Application Server runs the AppLogic identified in the request.

  4. The AppLogic calls iPlanet Application Server's Template Engine service, which runs the query (unless you are using a GXTemplateDataBasic object), merges the data with the specified HTML template, and streams the resulting dynamically-generated HTML page to the user's Web browser.


Types of Reports

iPlanet Application Server applications can create both tabular and grouped reports. Tabular reports, sometimes called listings, simply print all the records retrieved from the database. Grouped reports can show the records in logical groups, such as sales grouped by geographic region, with summary data for each group.

Tabular reports and grouped reports both obtain their data from hierarchical queries, but the hierarchical query for a tabular report contains only a single flat query with no join expression. The hierarchical query object is used for tabular reports, even though the data in tabular reports does not appear to be hierarchical. This is because the EvalTemplate( ) method, which you use to run reports, requires a hierarchical query as a parameter.


Sample Tabular Report




Isaac  

300  

Berkeley  

Julie  

300  

Paris  

Ken  

700  

Berkeley  

Steve  

200  

Paris  


Sample Grouped Report




Berkeley  

 

1000  

 

Isaac  

300  

 

Ken  

700  

Paris  

 

500  

 

Julie  

300  

 

Steve  

200  


Creating Tabular Reports

To create a tabular report, use the following techniques:

  • Write a flat query.

For more information, see

  • Place the flat query in a hierarchical query object, and do not specify a join expression.

For more information about how to construct a hierarchical query, see

  • Write an HTML template that uses a single level of GX tile and cell markup tags. These tags refer to the fields in the hierarchical query's result set, and when the report runs they are dynamically replaced with values in the result set.

    For more information, see

  • To add standard headers and footers to the report, you can use the GX include tag in the HTML template.

    For more information, see


Creating Grouped Reports

To create a grouped report, you need:

  • A hierarchical query or a GXTemplateDataBasic object. Both of these provide data in a hierarchical result set.

  • An HTML template. The HTML template contains text and tags that format the data from the hierarchical result set.

For example, in a report that shows sales generated by employees in various cities, one query is required for city data and another query is required for employee data. The two queries are combined into one hierarchical query, and the results are merged with the HTML template to create the output report.

To create a grouped report, use the following techniques:

  • Write a hierarchical query or a GXTemplateDataBasic object.

    For more information, see or

  • Write an HTML template that uses several nested levels of GX tile and cell markup tags. These tags refer to fields in the hierarchical result set.

    For more information, see

  • To add standard headers and footers to the report, you can use the GX include tag in the HTML template.

    For more information, see


Running Reports

Running a report means dynamically creating a new version of the report with current data.

When you run a report, iPlanet Application Server performs the following tasks:

  1. If a hierarchical query is used, iPlanet Application Server runs the report's query to get current data from a database. This step might be skipped if you are caching results, because the iPlanet Application Server will first check to see whether a cached result is available before incurring the overhead of running a query. For more information, see

  2. iPlanet Application Server then merges the data with an HTML template that specifies the report's appearance.

  3. Finally, iPlanet Application Server returns the merged HTML page to the user.

To run a report

You run both tabular and grouped reports using the same technique:

  • Call EvalTemplate( ). Pass the hierarchical query object (or GXTemplateDataBasic object) and the HTML template file name to EvalTemplate( ) as parameters.


Sample Reports

This section provides annotated examples that show how to work with reports in an iPlanet Application Server application. The examples illustrate how to perform the following tasks:

For more complete examples, see the demo applications supplied with your iPlanet Application Server package.


Sample Tabular Report

This example application retrieves data about the salaries of employees and provides the data in a tabular report. The application consists of an AppLogic called TabRept, and an HTML template called tabrept.html.


Tabrept AppLogic Output
The TabRept AppLogic creates a report that looks like the following:

Isaac 300

Ken 700

Steve 300

Vasu 300


Tabrept AppLogic Code
The following AppLogic generates the employee salary report. The example assumes the data source is an employee table which has name and salary columns.

IGXValList *conn_params;

conn_params = GXCreateValList();

conn_params->SetValString("DSN", "salesDB");

conn_params->SetValString("DB", "salesDB");

conn_params->SetValString("USER", "steve");

conn_params->SetValString("PSWD", "pass7878");

IGXDataConn *conn = NULL;

HRESULT hr;

hr = CreateDataConn(0, GX_DA_DRIVER_ODBC, conn_params, NULL,

   &conn);

// Create a flat query to retrieve employee data.

IGXQuery *qry;

CreateQuery(&qry);

qry->SetTables("emp");

qry->SetFields("name, salary");

// Create a hierarchical query to contain the employee

// query so that it can be passed to EvalTemplate().

IGXHierQuery *hqry;

CreateHierQuery(&hqry);

// The query is added to the hierarchical query under

// the name EMP.

hqry->AddQuery(qry, conn, "EMP", "", "");

// Call EvalTemplate to merge the data from the

// hierarchical query with the template and return

// the resulting report HTML page.

EvalTemplate("GXApp/EmpTrack/Templates/tabrept.html",

   hqry, NULL, NULL, NULL);

hqry->Release();

qry->Release();

conn->Release();

conn_params->Release();


HTML Template tabrept.html
The following HTML template formats the data in this example report. The template contains a tile tag to repeat a set of data for each employee. In this example, comments are provided before each GX markup tag. For information about the other tags in the template, refer to your HTML documentation.

<HTML>

<BODY>

<!-- The following GX tag sets up a loop that repeats for each employee. The type=tile attribute specifies that this is a looping marker. The id=EMP attribute specifies that the loop repeats for each row of the result set from the EMP query.

-->

%gx type=tile id=EMP%

<!-- The following GX tags display the name and salary for each employee. The type=cell attribute specifies that the body text of each marker is to be replaced with a dynamic data value. The id=EMP.name and id=EMP.salary attributes specify the fields in the result set that contain the dynamic values.

-->

%gx type=cell id=EMP.name%%/gx%

%gx type=cell id=EMP.salary%%/gx%

<BR>

%/gx%

</BODY>

</HTML>


Sample Grouped Report

This example application retrieves data about the salaries of employees in various cities and presents that data in a grouped report. The application consists of an AppLogic called DoReport and an HTML template called report.html.


DoReport AppLogic Output
The DoReport AppLogic creates a report that looks like the following:

Berkeley 1000

Isaac 300

Ken 700

Paris 600

Steve 300

Kim 300


DoReport AppLogic Code
The following AppLogic generates the city-employee summary report. The example assumes the data source is an employee table which has name, city, and salary columns.

IGXValList *conn_params;

conn_params = GXCreateValList();

conn_params->SetValString("DSN", "emp");

conn_params->SetValString("DB", "emp");

conn_params->SetValString("USER", "steve");

conn_params->SetValString("PSWD", "pass7878");

IGXDataConn *conn = NULL;

HRESULT hr;

hr = CreateDataConn(0, GX_DA_DRIVER_ODBC, conn_params, NULL,

   &conn);

// Begin by defining two flat queries, one for city

// information and the other for employee information.

// The example assumes the database contains an employeee

// table with columns called name, salary, and city.

IGXQuery *qryCTY;

CreateQuery(&qryCTY);

qryCTY->SetTables("employee");

qryCTY->SetFields("city, SUM(salary) as salarysum");

qryCTY->SetGroupBy("city");

IGXQuery *qryEMP;

CreateQuery(&qryEMP);

qryEMP->SetTables("employee");

qryEMP->SetFields("name, salary, city");

// Next, the code instantiates a hierarchical query object.

IGXHierQuery *hqry;

CreateHierQuery(&hqry);

// The following code adds the first flat query to the

// hierarchical query. The flat query is given the name CTY.

hqry->AddQuery(qryCTY, conn, "CTY", "", "");

// The following code adds the second flat query. This flat

// query is given the name EMP in the third parameter to

// AddQuery(). The next parameter specifies that the EMP

// query is a child of the CTY query. That is, results from

// the EMP query are grouped and nested within results from

// the CTY query. The last parameter specifies that the two

// flat queries are joined by their city fields.

hqry->AddQuery(qryEMP, conn, "EMP", "CTY",

   "EMP.employees.city = 'CTY.city'");

// Call EvalTemplate to merge the data from the

// hierarchical query with the template and return

// the resulting report HTML page.

EvalTemplate("GXApp/EmpTrack/Templates/report.html",

   hqry, NULL, NULL, NULL);

hqry->Release();

qryCTY->Release();

qryEMP->Release();

conn->Release();

conn_params->Release();


HTML Template report.html
The following HTML template, report.html, formats the data in this example report. The template contains two nested tile tags, one to loop over the cities and the other to loop over the employees within each city. In this example, comments are provided before each GX markup tag. For information about the other tags in the template, refer to your HTML documentation.

<HTML>

<BODY>

<!-- The following GX tag sets up a loop that repeats for each city. The type=tile attribute specifies that this is a looping marker. The id=CTY attribute specifies that the loop repeats for each row of the result set from the CTY query.

-->

%gx type=tile id=CTY%

<!-- The following GX tags display the name and total salary figure for each city. The type=cell attribute specifies that the body text of each marker is to be replaced with a dynamic data value. The id=CTY.city and id=CTY.sumsalary attributes specify the fields in the result set that contain the dynamic values.

-->

%gx type=cell id=CTY.city%%/gx%

%gx type=cell id=CTY.sumsalary%%/gx%

<BR>

<!-- The following GX tags set up a loop that prints the name and salary of each employee. The tags are similar to those used to print the city data.

-->

%gx type=tile id=EMP%

%gx type=cell id=EMP.name%%/gx%

%gx type=cell id=EMP.salary%%/gx%

<BR>

%/gx%

%/gx%

</BODY>

</HTML>


Sample Three-Level Grouped Report

This example retrieves data about the populations of cities from various continents. The report in this example has three levels of nested data. The application consists of one AppLogic called Cities, and an HTML template called cityrept.html.


Cities AppLogic Output
The Cities AppLogic creates a report that looks like the following:

Asia

China

Bejing 300

Shanghai 700

Japan

Tokyo 250

Osaka 250

Europe

France

Paris 300

Nice 300

Spain

Madrid 200


Cities AppLogic Code
To generate the three-level hierarchical report, the AppLogic uses a hierarchical query that consists of three flat queries: one each for continent, country, and city information.

Abstractly, without data, the hierarchical query for this example can be pictured as follows.

continent

country

city population

The following example code assumes that the database contains the following tables:

  • The continents table has a field called name.

  • The countries table has name and continent fields.

  • The cities table has city and pop fields.

    IGXValList *conn_params;

    conn_params = GXCreateValList();

    conn_params->SetValString("DSN", "geo");

    conn_params->SetValString("DB", "geo");

    conn_params->SetValString("USER", "steve");

    conn_params->SetValString("PSWD", "pass7878");

    IGXDataConn *conn = NULL;

    HRESULT hr;

    hr = CreateDataConn(0, GX_DA_DRIVER_ODBC, conn_params, NULL,

       &conn);

    // Specify the three flat queries.

    //

    IGXQuery *qryCONTINENT;

    CreateQuery(&qryCONTINENT);

    qryCONTINENT->SetTables("continents");

    qryCONTINENT->SetFields("name");

    IGXQuery *qryCOUNTRY;

    CreateQuery(&qryCOUNTRY);

    qryCOUNTRY->SetTables("countries");

    qryCOUNTRY->SetFields("name");

    IGXQuery *qryCTY;

    CreateQuery(&qryCTY);

    qryCTY->SetTables("cities");

    qryCTY->SetFields("city, pop");

    // Next, the code instantiates a hierarchical query object.

    IGXHierQuery *hqry;

    CreateHierQuery(&hqry);

    // Add the first flat query to the hierarchical query,

    // and name the query CONTINENT.

    hqry->AddQuery(qryCONTINENT, conn, "CONTINENT", "", "");

    // Add the second flat query to the hierarchical query,

    // and name the query COUNTRY. The fourth paramter

    // specifies that the COUNTRY query's parent query is

    // CONTINENT so that the data for each country is nested

    // within the data for the corresponding continent.

    // The last parameter is a join expression. This expression

    // specifies that the result sets of the COUNTRY and

    // CONTINENT queries are joined on the continent name.

    // This means that the value of the continent field in

    // the COUNTRY query's result set matches the value of the

    // name field in the result set of the parent query,

    // CONTINENT.

    hqry->AddQuery(qryCOUNTRY, conn, "COUNTRY", "CONTINENT",

       "COUNTRY.countries.continent = 'CONTINENT.name'");

    // Add the second flat query to the hierarchical query,

    // and name the query CITY. The fourth parameter specifies

    // that the CITY query's parent query is COUNTRY. The result

    // sets of the CITY and COUNTRY queries are joined on the

    // country name. This means that the value of the country

    // field in the CITY query's result set matches the value of

    // the name field in the result set of the parent query,

    // COUNTRY.

    hqry->AddQuery(qryCTY, conn, "CITY", "COUNTRY",

       "CITY.cities.country = 'COUNTRY.name'");

    // Call EvalTemplate to merge the data from the

    // hierarchical query with the template and return

    // the resulting report HTML page.

    EvalTemplate("GXApp/EmpTrack/Templates/cityrept.html",

       hqry, NULL, NULL, NULL);

    hqry->Release();

    qryCTY->Release();

    qryCOUNTRY->Release();

    qryCONTINENT->Release();

    conn->Release();

    conn_params->Release();


HTML Template cityrept.html
The following HTML template, cityrept.html, formats the data in this example report. The template contains three nested tile tags: one to loop over the continents, another to loop over the countries, and a third to loop over the cities within each country. In this example, comments are provided before each GX markup tag. For information about the other tags in the template, refer to your HTML documentation.

<HTML>

<BODY>

<!-- The following GX tag sets up a loop that repeats for each continent. The type=tile attribute specifies that this is a looping marker. The id=CONTINENT attribute specifies that the loop repeats for each row of the result set from the CONTINENT query.

-->

%gx type=tile id=CONTINENT%

<!-- The following GX tag displays the name of each continent. The type=cell attribute specifies that the body text of each marker is to be replaced with a dynamic data value. The id=CONTINENT.name attribute specifies the field in the result set that contains the dynamic value.

-->

%gx type=cell id=CONTINENT.name%%/gx%

<BR>

<!-- The following GX tags sets up a loop that repeats for each country within each continent. The tags are similar to those used for the CONTINENT query.

-->

%gx type=tile id=COUNTRY%

%gx type=cell id=COUNTRY.name%%/gx%

<BR>

<!-- The following GX tags sets up a loop that repeats for each city within each country. The tags are similar to those used for the CONTINENT and COUNTRY queries.

-->

%gx type=tile id=CITY%

%gx type=cell id=CITY.city%%/gx%

%gx type=cell id=CITY.pop%%/gx%

<BR>

%/gx%

%/gx%

%/gx%

</BODY>

</HTML>



Working with Query Files



A query file is a file is a file with a .gxq extension that contains the specifications for one or more queries. Each query in the file can be flat or hierarchical. Query files are generated automatically when you use iPlanet Application Builder to build queries.

You can also write query files yourself using any text editor. This technique is useful for running SQL SELECT statements that you already have on hand before you begin programming for iPlanet Application Server. You can also write new queries in a query file if you prefer this to using the iPlanet Application Builder or the query-writing method calls provided in the iPlanet Application Server Foundation Class Library.


Writing a Flat Query in a Query File

The specification for a flat query in a query file begins with the following lines:

/* optional comments */

query queryName using (driverCode, DSN, UserName) is

After these lines, write a SQL SELECT statement (compliant with ANSI SQL89). The statement can include parameters, but do not type any statement terminators. These characters vary depending on your database. For example, the SQL Server statement terminator is GO.


Example
The following lines, from the SelCustTrans.gxq file in the Online Bank sample application, specify a flat query named SelCustAccts.

query SelCustAccts using (ODBC, ksample, kdemo) is

select OBAccountType.acctDesc as OBAccountType_acctDesc

   /* DATATYPE_STRING */,

OBAccount.acctNum as OBAccount_acctNum

   /* DATATYPE_STRING */,

OBAccount.balance as OBAccount_balance /* DATATYPE_LONG */

from OBAccount /* (157, 2) */,

OBAccountType /* (329, 19) */,

OBCustomer /* (15, 5) */

where OBAccountType.acctType = OBAccount.acctType

and OBCustomer.ssn = OBAccount.ssn

and (OBCustomer.userName = ':userName'

      /* DATATYPE_STRING */ )

order by OBAccount.acctNum asc


Running a Flat Query in a Query File

To run a flat query in a query file, you specify the file name and query name, load the file, then run it like any other flat query.

To run a flat query in a query file

  1. Declare a variable of type IGXQuery. For example:

       IGXQuery *qry;

  2. If the query requires parameters, set up an IGXValList object with the parameter values to be passed to LoadQuery( ).

    For more information, see

  3. Load the query file into the query object by calling LoadQuery( ). For example:

       LoadQuery("queryFile", query1, 0, params, &qry);

  4. Open a connection to the data source that corresponds to the query you want to run.

  5. Declare a variable to reference the output from the query. For example:

       IGXResultSet *rs;

  6. Call the ExecuteQuery( ) method in the IGXDataConn interface. For example:

       HR = conn->ExecuteQuery(0, qry, NULL, NULL, &rs);


Writing a Hierarchical Query In a Query File

The specification for a hierarchical query in a query file contains several SQL SELECT statements (compliant with ANSI SQL89) with the following additions:

  • Each flat query is preceded by the following line:

       query queryName using (driverCode, DSN, UserName) is

  • For a child query, append the following line after the SQL SELECT statement:

       join currentQueryName to parent parentName where

          currentQueryName.table.column =

          [']parentName.colOrAlias[']

    For more information about the syntax in the where clause, see

In a query file, do not type any statement terminators. These characters vary depending on your database. For example, the SQL Server statement terminator is GO.


Example
The following lines, from the SelCustTrans.gxq file in the Online Bank sample application, specify a hierarchical query.

/* SelCustAccts: */

query SelCustAccts using (ODBC, ksample, kdemo) is

select OBAccountType.acctDesc as OBAccountType_acctDesc,

   OBAccount.acctNum as OBAccount_acctNum ,

   OBAccount.balance as OBAccount_balance

from OBAccount, OBAccountType, OBCustomer

where OBAccountType.acctType = OBAccount.acctType

   and OBCustomer.ssn = OBAccount.ssn

   and (OBCustomer.userName = ':userName' )

order by OBAccount.acctNum asc

/* SelAcctTrans: */

query SelAcctTrans using (ODBC, ksample, kdemo) is

select OBAccount.acctNum as OBAccount_acctNum,

   OBTransaction.postDate as OBTransaction_postDate,

   OBTransactionType.transDesc as

   OBTransactionType_transDesc,

   OBTransaction.amount as OBTransaction_amount

from OBAccount ,

   OBTransaction,

   OBTransactionType

where OBTransactionType.transType = OBTransaction.transType

   and OBAccount.acctNum = OBTransaction.acctNum

order by OBTransaction.postDate desc

/* Join expression */

join SelAcctTrans to parent SelCustAccts

   where SelAcctTrans.OBAccount.acctNum =

   'SelCustAccts.OBAccount_acctNum'


Running a Hierarchical Query in a Query File

To run a hierarchical query in a query file, you specify a set of database connections that are needed by the queries in the file. To do so, you use an instance of the IGXDataConnSet interface. Then you load the file and run it like any other hierarchical query.

To run a hierarchical query file

  1. Declare a pointer variable of type IGXHierQuery. For example:

       IGXHierQuery *hqry;

  2. Declare a pointer variable of type IGXDataConnSet and create a connection set for the connections used by all the queries in the file. For example:

       IGXDataConnSet *connSet;

       CreateDataConnSet(0, &connSet);

  3. Populate the connection set by calling the AddConn( ) method from the IGXDataConnSet interface. Each call to AddConn( ) specifies a query name and the connection to be used for that query. For example:

       connSet->AddConn("COUNTIES", conn1);

       connSet->AddConn("STATES", conn2);

  4. If the hierarchical query requires parameters, set up an IGXValList object with the parameter values to be passed to LoadHierQuery( ).

    For more information, see

  5. Load the query file into the hierarchical query object by calling LoadHierQuery( ). For example:

       LoadHierQuery("queryFile", connSet, 0, params, &hqry);

  6. Declare a variable to reference the output from the query. For example:

       IGXHierResultSet *hrs;

  7. Run the hierarchical query by calling Execute( ) on the hierarchical query object. For example:

       hr = hqry->Execute(0, 0, NULL, &hrs);

    Alternatively, if you want to send the output to the end user, call EvalOutput( ) or EvalTemplate( ). For example:

       EvalOutput("templateReport", hqry, NULL, NULL, NULL);


Example
The following code shows how to use a query file to run a hierarchical query.

IGXValList *conn_params;

conn_params = GXCreateValList();

conn_params->SetValString("DSN", "geo");

conn_params->SetValString("DB", "geo");

conn_params->SetValString("USER", "steve");

conn_params->SetValString("PSWD", "pass7878");

IGXDataConn *conn = NULL;

HRESULT hr;

hr = CreateDataConn(0, GX_DA_DRIVER_ODBC, conn_params, NULL,

   &conn);

IGXDataConnSet *connSet;

CreateDataConnSet(&connSet);

connSet->AddConn("COUNTIES", conn);

connSet->AddConn("STATES", conn);

IGXValList *params;

params = GXCreateValList();

params->SetValString("pop", "100000");

IGXHierQuery *hqry = NULL;

hr = LoadHierQuery("file.gxq", connSet, 0, params, &hqry);

// Call EvalTemplate to merge the data from the

// hierarchical query with the template and return

// the resulting report HTML page.

EvalTemplate("states_report.html", hqry, NULL, NULL, NULL);

hqry->Release();



Running Asynchronous Queries



You can run queries asynchronously so that your application can do other work while the database server is processing the query. Your application can detect when the query is finished so it can process the result set. The iPlanet Application Server supports asynchronous queries through the IGXOrder interface.

To run asynchronous queries

  1. Create the query or queries that you want to run asynchronously.

  2. Execute each query, passing the GX_DA_EXEC_ASYNC constant to ExecuteQuery( ). For example:

       IGXResultSet *rs0;

       hr = conn0->ExecuteQuery(GX_DA_EXEC_ASYNC,

             qry0, NULL, NULL, &rs0);

       IGXResultSet *rs1;

       hr = conn1->ExecuteQuery(GX_DA_EXEC_ASYNC,

             qry1, NULL, NULL, &rs1);

  3. Declare and allocate an array of IGXOrder pointers, with one array element for each query. For example:

       IGXOrder *orders[2];

  4. Call GetOrder( ) in the IGXResultSet interface for each result set and assign the result of each call to an element in the array. For example:

       hr = rs0->GetOrder(&orders[0]);

       hr = rs1->GetOrder(&orders[1]);

  5. Call GXWaitForOrder( ) to wait for an order to come back, indicating that one of the queries has finished its result set. For example:

       hr = GXWaitForOrder(orders, 2, &nOrder, m_pContext, 7200);

  6. Process the result set. If desired, use the array index to determine which result set is finished and perform processing that is specific to that query. For example:

       if (nOrder == 0) {

          // . . . process result set rs0 . . .

  7. }Release the resources used. For example:

       orders[0]->Release();

  8. Reset the array element that was just processed to null. For example:

       orders[0] = NULL;


Example
The following code uses IGXOrder to track the progress of several asynchronous flat query commands.

HRESULT hr;

// Define the flat queries

IGXResultSet *rs0;

hr = conn0->ExecuteQuery(GX_DA_EXEC_ASYNC,

      qry0, NULL, NULL, &rs0);

IGXResultSet *rs1;

hr = conn1->ExecuteQuery(GX_DA_EXEC_ASYNC,

      qry1, NULL, NULL, &rs1);

IGXOrder orders[2];

hr = rs0->GetOrder(&orders[0]);

hr = rs1->GetOrder(&orders[1]);

while(orders[0] != NULL || orders[1] != NULL) {

   int nOrder;

   hr = GXWaitForOrder(orders, 2, &nOrder, m_pContext, 7200);

   if (nOrder == 0) {

      // . . . process result set rs0 . . .

   }

   else if (nOrder == 1) {

      // . . . process result set rs1 . . .

   }

   else

      //. . . break & return error condition on waitOrders()

orders[0]->Release();

orders[1]->Release();

}


Previous     Contents     Index     Next     
Copyright © 2002 Sun Microsystems, Inc. All rights reserved.

Last Updated March 05, 2002