Article ID: 308045 - Last Review: July 14, 2004 - Revision: 4.3 How To Use the ADO SHAPE Command with a DataReader in Visual Basic .NETThis article was previously published under Q308045 On This PageSUMMARY This article describes the ActiveX Data Objects (ADO) SHAPE
command syntax that produces hierarchical recordsets and explains how to
traverse hierarchical recordsets. Sample code is also provided. You can use hierarchical recordsets as an alternative to JOIN and GROUP BY syntax when you need to access parent-child and summary data. Hierarchical recordsets are used in many products: Xbase products use the SET RELATION command, Microsoft Access uses segmented virtual tables internally for reports with grouping levels, and so forth. Hierarchical recordsets are available through the MSDataShape provider, which the client cursor engine implements. Hierarchies enable you to build one or several recordsets, define groupings, and specify aggregate calculations over child recordsets. Although you can implement similar functionality through code, this functionality shifts much of the mundane work from the developer to the system. Difference Between Hierarchical Recordsets and SQL JOIN and GROUP BY StatementsHierarchical recordsets differ from SQL JOIN and GROUP BY statements. In a SQL JOIN statement, both the parent table fields and child table fields are represented in the same recordset. In a hierarchical recordset, the recordset contains only fields from the parent table. In addition, the recordset contains an extra field that represents the related child data, which you can assign to a second recordset variable and traverse.When use GROUP BY and aggregate operators to perform aggregate functions, only aggregate values appear in the recordset. In hierarchical recordsets, the aggregate values are represented in the parent recordset, and the detail records are represented in the child recordset. Different Types of ShapesYou can create three types of shapes:
Relation-Based HierarchyBoth the relation-based and parameter-based hierarchies produce a hierarchy that is otherwise represented by a SQL JOIN statement. However, these hierarchies differ in how they read the parent and child records. In the relation-based hierarchy, all of the parent and child records are read into a local cache before any processing continues. As a result, the relation-based hierarchy has a high initial overhead when you retrieve the records. However, the overhead is low after the initial retrieval.Parameter-Based HierarchyInitially, parameter-based hierarchies only read the parent records and fetch the child records on demand. Although the initial overhead is reduced, you must issue a new child query for each parent record that you access, and you must maintain the connection to the data source for as long as the recordset is open.Group-Based HierarchyThe group-based hierarchy is equivalent to producing an aggregate SQL statement that is joined to a detail SQL statement. The group-based hierarchy is also equivalent to performing aggregate functions on non-normalized data. You cannot update the summary columns and calculated columns because they may be derived from more than one record. Like relation-based hierarchies, all records must be read up front.Simplified SyntaxThe SHAPE clause makes the hierarchical recordsets available. This section provides the simplified syntax. Because the SHAPE syntax can become quite complex, the formal grammar for the SHAPE clause is provided at the end of the article to allow you to extend the examples. You can also use the program at the end of this article to test your own SHAPE statements.Notes
ExamplesThis section provides examples with diagrams. These examples use tables from the Northwind sample database.Simple Relation Hierarchy
SHAPE {select * from customers}
APPEND ({select * from orders} AS rsOrders
RELATE customerid TO customerid)
Customers.*
rsOrders
|
+----Orders.*
Parameterized Hierarchy
SHAPE {select * from customers}
APPEND ({select * from orders where customerid = ?} AS rsOrders
RELATE customerid TO PARAMETER 0)
Compound Relation HierarchyThis sample illustrates a three-level hierarchy of customers, orders, and order details:
SHAPE {SELECT * from customers}
APPEND ((SHAPE {select * from orders}
APPEND ({select * from [order details]} AS rsDetails
RELATE orderid TO orderid)) AS rsOrders
RELATE customerid TO customerid)
Customers.*
rsOrders
|
+----Orders.*
rsDetails
|
+----[Order Details].*
Multiple Relation HierarchyThis sample illustrates a hierarchy that involves a parent recordset and two child recordsets, one of which is parameterized:
SHAPE {SELECT * FROM customers}
APPEND ({SELECT *
FROM orders
WHERE orderdate < #1/1/1998# AND customerid = ?}
RELATE customerid TO PARAMETER 0) AS rsOldOrders,
({SELECT *
FROM orders
WHERE orderdate >= #1/1/1998#}
RELATE customerid TO customerid) AS rsRecentOrders
Customers.*
rsOldOrders
|
+----Orders.*
rsRecentOrders
|
+----Orders.*
Hierarchy with Aggregate
SHAPE (select * from orders}
APPEND ({select od.orderid, od.UnitPrice * od.quantity as ExtendedPrice
from [order details] As od}
RELATE orderid TO orderid) As rsDetails,
SUM(ExtendedPrice) AS OrderTotal
Orders.*
rsDetails
|
+----orderid
ExtendedPrice
OrderTotal
Group Hierarchy
SHAPE {select customers.customerid AS cust_id, orders.*
from customers inner join orders
on customers.customerid = orders.customerid} AS rsOrders
COMPUTE rsOrders BY cust_id
rsOrders
|
+----cust_id
Orders.*
cust_id
Group Hierarchy with AggregateNOTE: The inner SHAPE clause in this example is identical to the statement that is used in the Hierarchy with Aggregate example.
SHAPE
(SHAPE {select customers.*, orders.orderid, orders.orderdate
from customers inner join orders
on customers.customerid = orders.customerid}
APPEND ({select od.orderid,
od.unitprice * od.quantity as ExtendedPrice
from [order details] as od} AS rsDetails
RELATE orderid TO orderid),
SUM(rsDetails.ExtendedPrice) AS OrderTotal) AS rsOrders
COMPUTE rsOrders,
SUM(rsOrders.OrderTotal) AS CustTotal,
ANY(rsOrders.contactname) AS Contact
BY customerid
rsOrders
|
+----Customers.*
orderid
orderdate
rsDetails
|
+----orderid
ExtendedPrice
OrderTotal
CustomerTotal
Contact
customerid
Multiple Groupings
SHAPE
(SHAPE {select customers.*,
od.unitprice * od.quantity as ExtendedPrice
from (customers inner join orders
on customers.customerid = orders.customerid) inner join
[order details] as od on orders.orderid = od.orderid}
AS rsDetail
COMPUTE ANY(rsDetail.contactname) AS Contact,
ANY(rsDetail.region) AS Region,
SUM(rsDetail.ExtendedPrice) AS CustTotal,
rsDetail
BY customerid) AS rsCustSummary
COMPUTE rsCustSummary
BY Region
rsCustSummary
|
+-----Contact
Region
CustTotal
rsDetail
|
+----Customers.*
ExtendedPrice
customerid
Region
Grand Total
SHAPE
(SHAPE {select customers.*,
od.unitprice * od.quantity as ExtendedPrice
from (customers inner join orders
on customers.customerid = orders.customerid) inner join
[order details] as od on orders.orderid = od.orderid}
AS rsDetail
COMPUTE ANY(rsDetail.contactname) AS Contact,
SUM(rsDetail.ExtendedPrice) AS CustTotal,
rsDetail
BY customerid) AS rsCustSummary
COMPUTE SUM(rsCustSummary.CustTotal) As GrandTotal,
rsCustSummary
GrandTotal
rsCustSummary
|
+-----Contact
CustTotal
rsDetail
|
+----Customers.*
ExtendedPrice
customerid
Complex HierarchyThis example illustrates a hierarchy that contains one parent rowset, two child rowsets (one of which is parameterized), and a group detail.
SHAPE {select customers.* from customers} AS rsDetail
COMPUTE rsDetail,
ANY(rsDetail.companyname) AS Company,
({select * from orders where customerid = ?}
RELATE customerid TO PARAMETER 0) AS rsOrders,
COUNT(rsOrders.orderid) AS OrderCount
BY customerid
rsDetail
|
+----Customers.*
Company
rsOrders
|
+----Orders.*
OrderCount
customerid
Grouped Parent Related to Grouped Child
SHAPE
(SHAPE {select * from customers}
APPEND ((SHAPE {select orders.*, year(orderdate) as OrderYear,
month(orderdate) as OrderMonth
from orders} AS rsOrders
COMPUTE rsOrders
BY customerid, OrderYear, OrderMonth)
RELATE customerid TO customerid) AS rsOrdByMonth )
AS rsCustomers
COMPUTE rsCustomers
BY region
rsCustomers
|
+-----customers.*
rsOrdByMonth
|
+-----rsOrders
|
+---- Orders.*
customerid
OrderYear
OrderMonth
region
SHAPE Clause Formal Grammar
<shape-command> ::= SHAPE <table-exp> [AS <alias>]
[<shape_action>]
<shape-action> ::= APPEND <aliased-field-list>
| COMPUTE <aliased-field-list>
[BY <field-list>]
| BY <field-list>
<table-exp> ::= {<native-sql-statement>}
| ( <shape-command> )
<aliased-field-list> ::= <aliased-field> [, <aliased-field...]
<aliased-field> ::= <field-exp> [AS <alias>]
<field-exp> ::= ( <relation-exp> ) | <calculated-exp>
<relation_exp> ::= <table-exp> [AS <alias>] RELATE
<relation-cond-list>
<relation-cond-list> ::= <relation-cond> [, <relation-cond>...]
<relation-cond> ::= <field-name> TO <child-ref>
<child-ref> ::= <field-name> | PARAMETER <param-ref>
<param-ref> ::= <name> | <number>
<field-list> ::= <field-name [, <filed-name>]
<calculated-exp> ::= SUM (<qualified-field-name>)
| AVG (<qualified-field-name>)
| MIN (<qualified-field-name>)
| MAX (<qualified-field-name>)
| COUNT (<alias>)
| SDEV (<qualified-field-name>)
| ANY (<qualified-field-name>)
| CALC (<expression>)
<qualified-field-name>::= <alias>.<field-name> | <field-name>
<alias> ::= <quoted-name>
<field-name> ::= <quoted-name>
<quoted-name> ::= "<string>" | '<string>' | <name>
<name> ::= alpha [ alpha | digit | _ | # ...]
<number> ::= digit [digit...]
<string> ::= unicode-char [unicode-char...]
<expression> ::= an expression recognized by the Jet
Expression service whose operands are
other non-CALC columns in the same row.
Visual Basic .NET SHAPE Test ProgramThe following Visual Basic .NET code enables you to type your own SHAPE command and display the field hierarchy or indicate the location of the syntax error. This sample uses the ADO.NET DataReader object to retrieve the hierarchical data.Important: You must use the OLE DB managed provider with the MSDataShape provider. You cannot use the SQL or ODBC managed providers.
| Article Translations
|
Back to the top
