Se pueden utilizar conjuntos de registros jerárquicos como alternativa a la sintaxis de JOIN y GROUP BY cuando necesite tener acceso a información de resumen y de elementos primarios y secundarios.
Los conjuntos de registros jerárquicos se utilizan en muchos productos: los productos XBASE usan el comando SET RELATION, Access usa "tablas virtuales segmentadas" internamente en los informes con niveles de agrupación, y así sucesivamente. Las jerarquías permiten crear uno o varios conjuntos de registros, definir agrupamientos y especificar cálculos totales sobre conjuntos de registros secundarios. Aunque puede implementar una funcionalidad similar mediante código, de esta manera desplaza la mayor parte del trabajo rutinario del desarrollador al sistema.
Los conjuntos de registros jerárquicos están disponibles a través del proveedor de MSDataShape, que implementa el motor de cursor del cliente.
Los conjuntos de registros jerárquicos se diferencian de las instrucciones SQL JOIN y GROUP BY en que con un JOIN, tanto los campos de tabla primarios como los secundarios se representan en el mismo conjunto de registros. Mediante una jerarquía, el conjunto de registros contiene sólo campos de la tabla primaria. Además, contiene un campo extra que representa los datos secundarios relacionados, que puede asignar a un segundo conjunto de registros variable y que se puede recorrer.
Cuando realiza funciones agregadas mediante GROUP BY y los operadores agregados, sólo aparecen los valores agregados en el conjunto de registros.
En los conjuntos de registros jerárquicos, los valores agregados se representan en el conjunto de registros primario y los registros detallados aparecen en el conjunto de registros secundario.
Puede crear tres tipos de instrucciones SHAPE, cada uno con sus ventajas e inconvenientes. Elija el que mejor se ajuste a las necesidades de la aplicación y el entorno en el que ésta se ejecuta. Son los siguientes:
Los dos primeros son similares en que producen una jerarquía que, de otra manera, se representaría mediante una instrucción SQL JOIN. Se diferencian en que todos los registros primarios y secundarios se leen en una caché local antes de que continúe cualquier proceso en la jerarquía basada en relaciones. Este tipo de jerarquía tiene una sobrecarga inicial alta al recuperar los registros, pero que disminuye tras la recuperación inicial.
Inicialmente, las jerarquías basadas en parámetros sólo leen los registros primarios y buscan los registros secundarios cuando se requiere. Aunque se reduce la sobrecarga inicial, debe realizar una nueva consulta secundaria para cada registro primario al que se tiene acceso y debe mantener la conexión al origen de datos durante el tiempo que esté abierto el conjunto de registros.
La jerarquía basada en grupos equivale a producir una instrucción SQL agregada junto a una instrucción SQL detallada o realizar funciones agregadas en datos sin normalizar. No puede actualizar las columnas resumidas ni las calculadas porque pueden provenir de más de un registro. Como en las jerarquías basadas en relaciones, todos los registros se deben leer en primer lugar.
La disponibilidad de los conjuntos de registros jerárquicos se consigue mediante la cláusula SHAPE. En primer lugar se proporciona la sintaxis simplificada y a continuación los ejemplos con diagramas. Como la sintaxis de SHAPE puede ser muy compleja, al final del artículo se incluye la parte más formal de la cláusula para que pueda extender los ejemplos. También puede utilizar el programa que se incluye al final del artículo para probar sus propias instrucciones SHAPE.
Un comando SHAPE define la estructura de un RECORDSET jerárquico y los comandos necesarios para poblarlo de datos.
Una parte del comando SHAPE es una consulta emitida al proveedor de datos subyacente que devuelve un objeto RECORDSET. La sintaxis de la consulta depende de los requisitos del proveedor de datos principal. Normalmente éste será SQL, aunque ADO no necesita el uso de ningún lenguaje de consulta en particular. Podría utilizar una cláusula JOIN de SQL para relacionar dos tablas, sin embargo, un Recordset jerárquico representará la información de forma más eficaz. Cada fila de un Recordset creado mediante un JOIN repite la información de forma redundante de una de las tablas. Un Recordset jerárquico sólo tiene un Recordset primario para cada uno de los objetos Recordset secundarios.
Un proveedor de origen de datos OLE debe proporcionar datos que se pasarán a otro proveedor, que llevará a cabo la formación de los datos. El proveedor de origen de datos se especifica en la cadena de conexión del objeto CONNECTION como "Shape Provider = OrigenDeDatos". El proveedor que suministra el soporte de formación de datos se especifica en la propiedad PROVIDER del objeto CONNECTION como "MSDataShape".
ADO 2.0 presenta la función cursor jerárquico, que permite definir un objeto RECORDSET secundario como el valor de un campo en un RECORDSET principal.
Esta es una forma sencilla de ver los cursores jerárquicos. Imagine un control visual, como el cuadro de dialogo "Abrir Archivo", que muestra los archivos y subdirectorios de una forma jerárquica. Piense en cada directorio como un objeto RECORDSET, en cada archivo dentro de un directorio como un objeto FIELD y en cada subdirectorio dentro de un directorio como un objeto FIELD cuyo valor es otro RECORDSET.
ADO 2.0 presenta además una nueva sintaxis de lenguaje de manipulación de datos de tipo SHAPE, que permite realizar consultas que den como resultado un RECORDSET jerárquico. Un comando de lenguaje SHQPE se emite como se haría con cualquier cadena de comando ADO.
El lenguaje SHAPE está incorporado en ADO Client Cursor Engine (Motor de cursores clientes de ADO). El proceso de crearlo se denomina Formación de datos.
El lenguaje SHAPE permite crear objetos jerárquicos de dos formas. La primera agrega un RECORDSET de nivel inferior al RECORDSET de nivel superior y la segunda calcula una operación agregada a un RECORDSET secundario y genera un RECORDSET principal.
Se pueden anidar los objetos RECORDSET jerárquicos a cualquier profundidad que se requiera (esto es, crear objetos RECORDSET secundarios dentro de objetos RECORDSET secundarios, y así sucesivamente).
Se puede tener acceso al RECORDSET jerárquico resultante con un programa o mediante un control visual adecuado.
El lenguaje SHAPE es relativamente difícil de escribir. Por ello, Microsoft proporciona una herramienta visual que genera los comandos: "El diseñador de entorno de datos" y otra herramienta visual para mostrarlos: "El Flexgrid".
El comando APPEND de SHAPE asigna un RECORDSET secundario a la propiedad VALUE de objetos FIELD en un RECORDSET principal.
Sintaxis:
| Parte | Descripción |
| Comando Primario Comando Secundario | Un comando de consulta que devuelve un objeto RECORDSET. El comando se emite al proveedor principal de datos y su sintaxis depende de los requisitos de ese proveedor. Normalmente éste será SQL, aunque ADO no necesita el uso de ningún lenguaje de consulta en particular. |
| Columna del comando primario | Una columna del RECORDSET devuelto por el comando primario. |
| Columna del comando secundario | Una columna del RECORDSET devuelto por el comando secundario |
| Alias de tabla | Un alias utilizado para referirse al RECORDSET devuelto |
El motor del cursor del cliente emitirá el comando primario al proveedor, que devolverá un RECORDSET principal. Posteriormente se emite el comando secundario, que devuelve un RECORDSET secundario.
Por ejemplo, el comando primario podría devolver en conjunto de clientes de una compañía de la base de datos de clientes y el secundario los perdidos que ha realizado cada uno de ellos.
Los objetos RECORDSET secundarios y primarios deben tener una columna en común. A las columnas se les asigna un nombre en la cláusula RELATE, primero la columna del primario y después la del secundario. Las columnas pueden tener nombres distintos en los respectos objetos RECORDSET, pero deben referirse a la misma información para especificar una relación con sentido.
El motor del cursor cliente crea internamente una nueva columna y la agrega literalmente al RECORDSET principal. Los valores de los campos en la nueva columna son referencias a las filas del RECORDSET secundario que satisfacen la cláusula RELATE.
Notas:
Sintaxis:
Se obtiene como resultado un conjunto de registros primario que contienen todos los campos de la tabla Clientes y un campo llamado rsOrders. rsOrders proporciona una referencia al conjunto de registros secundario y contiene todos los campos de la tabla Pedidos.
Sintaxis:
Esto da como resultado la misma jerarquía que la jerarquía de relación simple.
Este ejemplo muestra una jerarquía de tercer nivel de customers, orders y order details:
Sintaxis:
Que da como resultado:
Este ejemplo muestra una jerarquía con un conjunto de registros primario y dos secundarios, uno de los cuales está parametrizado:
Sintaxis:
Que da como resultado:
Sintaxis:
Que da como resultado:
Sintaxis:
Que da como resultado:
Nota: La cláusula interna SHAPE en este ejemplo es idéntica a la instrucción utilizada en el ejemplo Jerarquía con elementos agregados.
Sintaxis:
Que da como resultado:
Sintaxis:
Que da como resultado:
Sintaxis:
Que da como resultado:
Observe la ausencia de la cláusula "BY" en el resumen externo. Esto define el Total absoluto, ya que el conjunto de filas primario contiene un único registro con el total absoluto y un puntero al conjunto de registros secundario.
Este ejemplo muestra una jerarquía que contiene un conjunto de filas primario, dos secundarios (uno de los cuales está parametrizado) y un detalle de grupo.
Sintaxis:
Que da como resultado:
Sintaxis:
Que da como resultado:
| 1 | Grupo 1 |
| 2 | Grupo 2 |
| 3 | Grupo 3 |
| 4 | Grupo 4 |
| 1 | 1 | SubGrupo 1.1 |
| 2 | 1 | SubGrupo 1.2 |
| 3 | 1 | SubGrupo 1.3 |
| 4 | 2 | SubGrupo 2.1 |
| 5 | 2 | SubGrupo 2.2 |
| 6 | 2 | SubGrupo 2.3 |
| 7 | 3 | SubGrupo 3.1 |
| 8 | 3 | SubGrupo 3.2 |
| 9 | 3 | SubGrupo 3.3 |
| 10 | 4 | SubGrupo 4.1 |
| 11 | 4 | SubGrupo 4.2 |
| 12 | 4 | SubGrupo 4.3 |
| 1 | 1 | Concepto 1 |
| 2 | 1 | Concepto 2 |
| 3 | 2 | Concepto 3 |
| 4 | 2 | Concepto 4 |
| 5 | 3 | Concepto 5 |
| 6 | 3 | Concepto 6 |
| 7 | 4 | Concepto 7 |
| 8 | 4 | Concepto 8 |
| 9 | 5 | Concepto 9 |
| 10 | 5 | Concepto 10 |
| 11 | 6 | Concepto 11 |
| 12 | 6 | Concepto 12 |
| 13 | 7 | Concepto 13 |
| 14 | 7 | Concepto 14 |
| 15 | 8 | Concepto 15 |
| 16 | 8 | Concepto 16 |
| 17 | 9 | Concepto 17 |
| 18 | 9 | Concepto 18 |
| 19 | 10 | Concepto 19 |
| 20 | 10 | Concepto 20 |
| 21 | 11 | Concepto 21 |
| 22 | 11 | Concepto 22 |
| 23 | 12 | Concepto 23 |
| 24 | 12 | Concepto 24 |
| 1 | 1 | 10 |
| 2 | 1 | 20 |
| 3 | 2 | 30 |
| 4 | 2 | 40 |
| 5 | 3 | 50 |
| 6 | 3 | 60 |
| 7 | 4 | 70 |
| 8 | 4 | 80 |
| 9 | 5 | 90 |
| 10 | 5 | 100 |
| 11 | 6 | 110 |
| 12 | 6 | 120 |
| 13 | 7 | 130 |
| 14 | 7 | 140 |
| 15 | 8 | 150 |
| 16 | 8 | 160 |
| 17 | 9 | 170 |
| 18 | 9 | 180 |
| 19 | 10 | 190 |
| 20 | 10 | 200 |
| 21 | 11 | 210 |
| 22 | 11 | 220 |
| 23 | 12 | 230 |
| 24 | 12 | 240 |
| 25 | 13 | 250 |
| 26 | 13 | 260 |
| 27 | 14 | 270 |
| 28 | 14 | 280 |
| 29 | 15 | 290 |
| 30 | 15 | 300 |
| 31 | 16 | 310 |
| 32 | 16 | 320 |
| 33 | 17 | 330 |
| 34 | 17 | 340 |
| 35 | 18 | 350 |
| 36 | 18 | 360 |
| 37 | 19 | 370 |
| 38 | 19 | 380 |
| 39 | 20 | 390 |
| 40 | 20 | 400 |
| 41 | 21 | 410 |
| 42 | 21 | 420 |
| 43 | 22 | 430 |
| 44 | 22 | 440 |
| 45 | 23 | 450 |
| 46 | 23 | 460 |
| 47 | 24 | 470 |
| 48 | 24 | 480 |

Un comando COMPUTE de SHAPE ejecuta una función agregada en las filas del RECORDSET secundario para generar un RECORDSET principal, y a continuación, asigna el RECORDSET secundario a la propiedad VALUE de los objetos FIELD en el RECORDSET principal generado.
Sintaxis:
| Parte | Descripción |
| Comando secundario | Un comando de consulta que devuelve un objeto RECORDSET. El comando se emite al proveedor de datos principal, y su sintaxis depende de los requisitos de ese proveedor. Normalmente será SQL, aunque ADO no necesita el uso de ningún lenguaje de consulta en particular. |
| Alias de tabla | Un alias utilizado para hacer referencia al RECORDSET devuelto por el comando secundario. |
| Función de agregado | Una lista de los campos en los que opera una función agregada. Se puede hacer referencia al RECORDSET mediante su alias de tabla en la función agregada. También se puede crear una columna para utilizarla como se desee, con la operación NEW. |
| Lista de campos de grupo | Una lista de columnas que especifica el orden de las filas en el RECORDSET de nivel inferior. Si la cláusula BY no se especifica, entonces sólo se devolverá el resultado de la función agregada. Si se especifica la cláusula BY, el RECORDSET secundario se agregará al RECORDSET principal generado. |
El motor de cursor cliente emitirá el comando secundario al proveedor, que devolverá un RECORDSET secundario.
La cláusula COMPUTE especifica una operación agregada para que se ejecute en las columnas especificadas (lista de campos del comando agregado) del RECORDSET secundario, como sumar todos los valores en una columna, o encontrar el valor máximo de una columna. La operación de agregación crea un RECORDSET principal.
Si no existe una cláusula BY, entonces el comando SHAPE concluye. Si existe una cláusula BY, el RECORDSET secundario se agregará al RECORDSET principal. Las filas del RECORDSET secundario serán dispuestas en grupos según se especifica en la lista de campos de grupo.
| Funciones de agregado | Descripción |
| SUM(<alias>.<nombre de campo>) | Calcula la suma de todos los valores en el campo especificado. |
| AVG(<alias>.<nombre de campo>) | Calcula la media de todos los valores del campo especificado. |
| MAX(<alias>.<nombre de campo>) | Calcula el valor máximo del campo especificado. |
| MIN(<alias>.<nombre de campo>) | Calcula el valor mínimo del campo especificado. |
| COUNT((<alias>[.<nombre de campo>]) | Cuenta el número de filas en el campo especificado. |
| STDEV(<alias>.<nombre de campo>) | Calcula la desviación en el campo especificado. |
| ANY(<alias>.<nombre de campo>) | El valor de una columna (donde el valor de la columna es el mismo en todas las filas) |
| CALC(expresión) | Calcula el valor de una expresión arbitraria, pero sólo en la fila actual. |
| NEW(tipo de campo [(ancho|escala[,precisión])] | Añade una columna vacía del tipo especificado al RECORDSET. |
Tomando como referencia las tablas del apartado anterior se describirán unos ejemplos para mostrar el cálculo de la suma de los importes en diferentes niveles, el resultado será asignado a un control MSHFlexGrid de Microsoft.
Supongamos una tabla llamada "demografía" con el siguiente contenido:
| Estado | Ciudad | Población |
| WA | Seattle | 700.000 |
| OR | Medford | 200.000 |
| OR | Portland | 600.000 |
| CA | Los Angeles | 900.000 |
| CA | San Diego | 400.000 |
| WA | Tacoma | 500.000 |
| OR | Corvallis | 300.000 |
Si emitimos la sentencia:
Obtenemos el siguiente resultado:
| Población | Estado | Ciudad | Población | |
| - | 3.600.000 | WA | Seatle | 700.000 |
| OR | Medford | 200.000 | ||
| OR | Portland | 600.000 | ||
| CA | Los Angeles | 900.000 | ||
| CA | San Diego | 400.000 | ||
| WA | Tacoma | 500.000 | ||
| OR | Corvallis | 300.000 |
El RECORDSET obtenido está formado por uno principal que contiene la población total y uno secundario con el total por ciudades.
Si emitimos esta otra sentencia:
Obtenemos el siguiente resultado:
| Población | Estado | Estado | Ciudad | Población | |
| - | 1.300.000 | CA | CA | Los Angeles | 900.000 |
| CA | San Diego | 400.000 | |||
| - | 1.100.000 | OR | OR | Medford | 200.000 |
| OR | Portland | 600.000 | |||
| OR | Corvallis | 300.000 | |||
| - | 1.200.000 | WA | WA | Seatle | 700.000 |
| WA | Tacoma | 500.000 |
En este caso el RECORDSET primario está formado por tres filas que contienen el nombre y el total de la población del estado, el secundario está formado por el detalle de la población.
| <comandoShape> | ::= | SHAPE <expTabla> [AS <alias>] [<acciónShape>] |
| <acciónShape> | ::= | APPEND <listaCampoAlias> | COMPUTE <listaCampoAlias> [BY <listaCampo>] | BY <listaCampo> |
| <expTabla> | ::= | {<instrucciónSQLNativa>} | ( <comandoShape> ) |
| <listaCampoAlias> | ::= | <campoAlias> [, <campoAlias...] |
| <campoAlias> | ::= | <expCampo> [AS <alias>] |
| <expCampo> | ::= | ( <expRelación> ) | <expCalculado> |
| <expRelación> | ::= | <expTabla> [AS <alias>] RELATE <listaCondRelación> |
| <listaCondRelación> | ::= | <conRelación> [, <condRelación>...] |
| <condRelación> | ::= | <nombreCampo> TO <refSecundaria> |
| <refSecundaria> | ::= | <nombreCampo> | PARAMETER <refParam> |
| <refParam> | ::= | <nombre> | <número> |
| <listaCampo> | ::= | <nombreCampo [, <nombreArchivado>] |
| <expCalculado> | ::= | SUM (<nombreCampoCalificado>) | AVG (<nombreCampoCalificado>) | MIN (<nombreCampoCalificado>) | MAX (<nombreCampoCalificado>) | COUNT (<alias>) | SDEV (<nombreCampoCalificado>) | ANY (<nombreCampoCalificado>) | CALC (<expresión>) |
| <nombreCampoCalificado> | ::= | <alias>.<nombreCampo> | <nombreCampo> |
| <alias> | ::= | <nombreCitado> |
| <nombreCampo> | ::= | <nombreCitado> |
| <nombreCitado> | ::= | "<cadena>" | '<cadena>' | <nombre> |
| <nombre> | ::= | alfabético [ alfabético | dígito | _ | # ...] |
| <número> | ::= | dígito [dígito...] |
| <cadena> | ::= | CarácterUnicode [carácerUnicode...] |
| <expresión> | ::= | una expresión reconocida por el servicio Jet Expression cuyos operandos son otras columnas no calculadas en la misma fila. |
El siguiente código de programa de VBA le permite escribir su propio comando SHAPE y mostrar la jerarquía de campo o indicar la ubicación del error de sintaxis.
NOTA: Si escribe incorrectamente los nombres de tabla o de campo cuando utilice el controlador ODBC o los proveedores de JOLT de Access 97, obtendrá el siguiente mensaje:
Con otros proveedores puede obtener un mensaje diferente.