Desarrollo con XQuery: un mejor lenguaje de programación para el programador de bases de datos

Aproveche los beneficios de XQuery con rapidez de desarrollo y facilidad de mantenimiento

La mayoría de los programadores piensa que el lenguaje XQuery se desarrolló para satisfacer el nicho del mercado: Consultas de datos y lenguaje de transformación diseñado para manejar los datos XML. En el caso de las bases de datos relacionales, la práctica predominante es la utilización de SQL para datos que no sean XML y XQuery para datos XML. Este artículo justifica que las construcciones de programación de potencia eficiente en el lenguaje XQuery hacen que sea un mejor lenguaje de programación que SQL y que esta mejora en la expresividad y facilidad de uso es suficiente como para garantizar el diseño de las bases de datos con un creciente énfasis en los tipos de datos XML.

Kenneth Stephen, Software Engineer, IBM

Photo of Kenneth StephenKenneth Stephen es un arquitecto de aplicación que tiene 20 años de experiencia en el diseño e implementación de aplicaciones en plataformas que van de PC al sistema principal. Tiene una vasta experiencia en el diseño e implementación de aplicaciones mediante el uso de tecnologías XML, entre ellas XSLT y XQuery. Kenneth trabaja en la actualidad en IBM Software Services for WebSphere.



31-07-2012

Visión general

SQL se inventó para que los programadores puedan resumir algoritmos de código que se supone utilizan, manipulan o transforman los datos y para que no se pierda la implementación del algoritmo en los detalles de llegar a los datos o continuar en ellos. En esta meta, SQL ha sido tremendamente exitoso. Permite la creación de aplicaciones OLTP, sistemas de informe, depósitos de datos y análisis comerciales muy complejos. SQL y el modelo relacional del diseño de base de datos están entrelazados y el poder y flexibilidad de SQL hizo del modelo relacional el tipo de modelo de datos dominante de las últimas décadas.

Acrónimos de uso frecuente

  • OLTP: Online transaction processing
  • RDBMS: Relational database management systems
  • SQL: Structured Query Language

Perdida entre esta historia de éxitos se encuentra la historia de las bases de datos jerárquicas. Estas bases de datos tienen los datos estructurados en los bosques de datos—donde los tipos de informes pueden tener relaciones subordinadas entre ellos. Los lenguajes de programación utilizados con estas bases de datos no tenían tanto éxito como SQL en permitirles a los programadores abstraer los mecanismos de accesos y persistencia de datos y esta limitación llevo a su decadencia.

Los documentos XML representan las relaciones entre las entidades mediante una jerarquía. XQuery se usa para trabajar con datos XML en un documento o más. Y XQuery le permite abstraer los mecanismos de acceso y persistencia de datos de la misma forma que SQL, al mismo tiempo que proporciona posibilidades de programación que son mejores a las de SQL. La tecnología ha creado un círculo en término de cómo trabajar con los datos a un alto nivel. XQuery tiene algunos aspectos interesantes que lo hacen más atractivo para el programador. En este artículo, exploro esos beneficios.

Trabajar con datos jerárquicos

Primero, observe algunas situaciones en las que XQuery tenga una ventaja obvia sobre SQL. En un RDBMS, es normal encontrar relaciones subordinadas representadas en dos tablas separadas de una base de datos. Por ejemplo, una orden de compra en los que se compran varios ítems puede estar representada en la base de datos por una tabla purchase_order y una tabla con los ítems. (La tabla purchase_order incluye tanto los números de orden como los números del cliente. La tabla de ítems incluye los números de orden y los números de ítems). Esta tabla de ítems también tiene una relación de código externo en la tabla purchase_order. SQL maneja muy bien este tipo de relación. Consulte Figura 1 y el Listado 1.

Figura 1. Diseño de la tabla subordinada para la orden de compra
Diseño de la tabla subordinada para la orden de compra
Listado 1. Argumento SQL para encontrar todos los ítems en una orden de compra dada
select item_no, item_desc 
from purchase_order po, items i 
where ord_no = 'A12345' 
and po.ord_no = i.ord_no

SQL muestra sus vulnerabilidades cuando los datos tienen una cadena de relaciones que son de una longitud impredecible. Por ejemplo, un gerente puede tener varios empleados, a su vez, tener también muchos empleados y así sucesivamente. En este caso, representar a los datos en diferentes tablas subordinadas no es práctico. Por un lado, debe diseñar las tablas según la máxima longitud de la relación—si la cadena de administración alcanza al menos seis niveles de altura, se deben diseñar seis tablas. Esta práctica no es buena. Lo peor es que para llegar a todos los empleados en el nivel n desde un gerente en el nivel x, debe hacer una unión de tablas desde el nivel x al n. Este enfoque será costoso en cuanto a los recursos utilizados.

La solución que generalmente se adopta es tener una tabla única donde las filas de la tabla tienen una relación subordinada entre sí como en la Figura 2. Las columnas para el ID del empleado, ID del gerente y el nombre del empleado indica las relaciones. En este ejemplo, Jack Brown es gerente de dos empleados John Silver y Ron McDonald. John Silver es gerente de Jon Carino. Desafortunadamente, los datos de consulta como este en XML, generan consultas que son difíciles de escribir y mantener. Consulte el Listado 2 para el código para crear un argumento SQL que se utiliza para encontrar empleados que se reporten directa o indirectamente a un gerente.

Figura 2. Diseño de tabla simple para datos de empleados con filas interrelacionadas
Diseño de tabla simple para datos de empleados con filas interrelacionadas
Listado 2. Argumento SQL para encontrar empleados que se reporten directa o indirectamente a un gerente
with reporting_to(emp_id, emp_name) as 
( 
     select emp_id, emp_name 
     from employees 
     where emp_name = 'John Silver' 

     union all 

     select direct_reports.emp_id, direct_reports.emp_name 
     from employees direct_reports, reporting_to 
     where reporting_to.emp_id = direct_reports.mgr_id 
) 
select emp_id, emp_name 
from reporting_to

Como puede ver, esta consulta utiliza recursividad y requiere un esfuerzo significativo para poder comprenderlo. Este problema se resuelve mucho mejor utilizando los tipos de datos XML y XQuery. En este caso, representa a toda la organización en un documento XML simple en una fila de la tabla. Los listados 3 y 4 muestran la implementación resultante y usted puede juzgar cuál es pragmáticamente más fácil de construir y mantener.

Listado 3. Representación XML de datos de empleados
<?xml version="1.0"?> 
<org> 
     <employee id="0001"> 
          <name>Jack Brown</name> 
          <employee id="0002"> 
               <name>John Silver</name> 
               <employee id="0004"> 
                         <name>John Silver</name> 
               </employee> 
          </employee> 
          <employee id="0003"> 
               <name>Ron McDonald</name> 
          </employee> 
     </employee>
</org>
Listado 4. Encontrar la lista de empleados cuando se utiliza un tipo de datos XML
select emp.emp_id, emp.emp_name 
from employees, xmltable( 
     '$ORG/org//employee[name = "John Silver"]/descendant-or-self::employee' 
     columns 
          emp_id char(4) path '@id', 
          emp_name varchar(254) path 'name/text()' 
) emp

También observe que los datos jerárquicos son más comunes de lo que piensa. Por ejemplo, en el ejemplo anterior, las órdenes de compra no pueden contener otras órdenes de compra, por lo tanto en apariencia, parecería que este ejemplo nunca sería jerárquico. Una orden de compra puede fallar, es decir de una lista de 20 ítems comprados, 2 podrían no estar disponibles o que no haya una cantidad suficiente. Por lo tanto, la orden de compra existente se cierra con los ítems disponibles que se suministrarán y se genera una nueva orden de compra para los ítems que no están disponibles. En dicha situación, es ideal que la nueva orden de compra tenga un enlace a la vieja. Es posible tener una cadena de órdenes de compra cuyos vínculos de relación se parecen a los datos de empleados que acabamos de analizar.

Trabajar con datos no jerárquicos

Los datos no jerárquicos representan a una gran cantidad de datos modelados en un mundo relacional. Es importante que cualquier lenguaje de programación que se utilice para acceder a una base de datos o para escribir en una base de datos pueda manejar muy bien estas entidades de datos. En el siguiente ejemplo, describo lo bien que XQuery maneja esos datos y, desde el punto de vista de un programador, cómo proporciona una solución superior a SQL.

Estudio de caso 1: Encontrar la mejor coincidencia y utilizarla

Este ejemplo se encarga del caso en el que las tareas de programación se organizan en competiciones ("eventos"). Para cada uno de esos eventos, el creador o administrador del evento le asigna una cierta cantidad de "puntos". Se proporciona una tabla de búsqueda de precios básicos, a cada precio básico se le proporciona un número entero denominado "nivel". El problema es encontrar el precio básico que tenga la mejor coincidencia con los valores "básicos" del evento y utilizar los valores como el "precio" del nivel (cuánto obtiene el competidor que gana el concurso). Si dos niveles tienen una coincidencia muy cercana, se debe utilizar el nivel más alto para determinar el precio básico que debe aplicarse.

Los listados 5 y 6 muestran el diseño de base de datos estándar y una muestra de los datos.

Listado 5. Diseño de la base de datos con tipos de datos que no son XML
[db2pe@lc4eb4168274532 code]$ db2 describe table pricing 
                                Data type                     Column 
Column name                     schema    Data type name      Length     Scale Nulls 
------------------------------- --------- ------------------- ---------- ----- ------ 
EVENT_ID                        SYSIBM    INTEGER                      4     0 No 
POINTS                          SYSIBM    INTEGER                      4     0 Yes 
PRICE                           SYSIBM    INTEGER                      4     0 Yes 

     3 record(s) selected. 

[db2pe@lc4eb4168274532 code]$ db2 describe table pricing_tier 
                                Data type                     Column 
Column name                     schema    Data type name      Length     Scale Nulls 
------------------------------- --------- ------------------- ---------- ----- ------ 
LEVEL                           SYSIBM    INTEGER                      4     0 No 
PAYMENT                         SYSIBM    INTEGER                      4     0 Yes 

     2 record(s) selected.
Listado 6. Datos de muestra de las tablas
[db2pe@lc4eb4168274532 code]$ db2 "select * from pricing fetch first 5 rows only" 

EVENT_ID    POINTS      PRICE 
---------- ---------- ----------- 
      10472         640           0 
      10471         220           0 
      10470         190           0 
      10469         180           0 
      10466         780           0 

     5 record(s) selected. 

[db2pe@lc4eb4168274532 code]$ db2 "select * from pricing_tier" 

LEVEL       PAYMENT 
----------- ----------- 
          0          60 
          1         120 
          2         240 
          3         360 
          4         480 
          5         600 
          6         720 
          7         840 

     8 record(s) selected.

el Listado 7 muestra el argumento actualizado SQL requerido.

Listado 7. Actualizar el argumento para actualizar la tabla de precios con el SQL estándar
update pricing o 
    set (price) = ( 
        select payment 
        from ( 
            select pricing_info.event_id event_id , max(level) matched_level 
            -- "min_values" will have the smallest difference between the current 
            -- price and all the pre-defined price points for each event. "pricing_info" 
            -- will contain an index of all the price point differences tabulated 
            -- by the price point level. A join of these two tables by event_id 
            -- and price point difference, should get you the price point level that 
            -- you are seeking for each event. 
            from ( 
                select event_id, min(absdiff) 
                from ( 
                    -- For each event_id, calculate the absolute difference 
                    -- between the existing price and the price points 
                    select event_id, abs(points - payment) absdiff, level 
                    from pricing, pricing_tier 
                ) 
                group by event_id 
            ) as min_values(event_id, closest_match), ( 
                -- For each event_id, calculate the absolute difference 
                -- between the existing price and the price points 
                select event_id, abs(points - payment) absdiff, level 
                from pricing, pricing_tier 
            ) as pricing_info(event_id, absdiff, level)
            where min_values.event_id = pricing_info.event_id 
            and closest_match = absdiff 
            group by pricing_info.event_id 
        )x , pricing_tier y 
        where x.matched_level = y.level 
        and x.event_id = o.event_id 
    )

Ahora observe una implementación utilizando los tipos de datos XML. La tabla pricing_tier y los detalles del evento pueden estar representados con los documentos XML en los listados 8 y 9.

Listado 8. Información de precios en documento XML
<pricing> 
     <pricingtier level="0"><price>60</price></pricingtier> 
     <pricingtier level="1"><price>120</price></pricingtier> 
     <pricingtier level="2"><price>240</price></pricingtier> 
     <pricingtier level="3"><price>360</price></pricingtier> 
     <pricingtier level="4"><price>480</price></pricingtier> 
     <pricingtier level="5"><price>600</price></pricingtier> 
     <pricingtier level="6"><price>720</price></pricingtier> 
     <pricingtier level="7"><price>840</price></pricingtier> 
</pricing>
Listado 9. Detalles del evento en documento XML
<event id="9083" eventstate="Cancelled: Client request"> 
     <title>UCD research 5</title> 
     <points>170</points> 
</event>

el Listado 10 muestra el diseño de tabla utilizado.

Listado 10. Diseño de la tabla de base de datos con tipos de datos XML
[db2pe@lc4eb4168274532 code]$ db2 describe table events 
                                Data type                     Column 
Column name                     schema    Data type name      Length     Scale Nulls 
------------------------------- --------- ------------------- ---------- ----- ------ 
EVENT_ID                        SYSIBM    INTEGER                      4     0 No 
EVENT                           SYSIBM    XML                          0     0 No 

     2 record(s) selected. 

[db2pe@lc4eb4168274532 code]$ db2 describe table pricing 
                                Data type                     Column 
Column name                     schema    Data type name      Length     Scale Nulls 
------------------------------- --------- ------------------- ---------- ----- ------ 
PRICING                         SYSIBM    XML                          0     0 Yes 

     1 record(s) selected.

Este diseño asume que la información de precios se inserta en el documento como un elemento par de los elementos "básicos". el Listado 11 muestra el argumento de actualización que hace esto.

Listado 11. Argumento de actualización con tipos de datos XML y XQuery
update events o 
   set (event) = ( 
      select xmlquery(' 
         (: 
          : First build a series of "pair" elements which map a pricing level number 
          : to a value that is the absolute value of the difference between the 
          : event points and the price point. Once you have this sequence, sort by 
          : the difference and pick the lowest value. This results in you picking the 
          : closest price match. Because you break ties for the closest value by picking 
          : the higher level, you add a second sort key (level) in descending order.
          :) 
         let $closestMatch := 
            ( 
               for $pair in ( for $p in $PRICING/pricing/pricingtier 
                  let $lp := xs:int($EVENT/event/points) 
                  let $absdiff := abs($lp - xs:int($p/price)) 
                  return 
                     <pair> 
                        <level>{$p/@level}</level> 
                        <diff>{$absdiff}</diff> 
                     </pair> 
               ) 
               order by xs:int($pair/diff/text()), 
                  xs:int($pair/level/text()) descending 
               return $pair 
            )[1] 
         return 
            transform 
               copy $e := $EVENT 
            modify 
               do insert 
               <pricing>{ 
                  $PRICING/pricing/pricingtier[@level = $closestMatch/level/@level]/ 
                     price/text() 
               }</pricing> after $e/event/points 
            return $e 
         ') 
         from events a, (select pricing from pricing fetch first row only) b 
         where a.event_id = o.event_id 
      )

Lo principal que se debe observar aquí es que el estilo de programación se parece a la programación procesal—en que los cálculos intermedios se realizan y los resultados se asignan a variables, que luego vuelven a utilizarse en otro lugar. Incluso están anidados para los bucles, que viene naturalmente a un programador procesal. El argumento SQL en Listado 7 es idéntico en funciones y metodología, pero es más difícil de seguir ya que el estilo de programación está lejos de quitarse del estilo procesal normal. El estilo XQuery es bastante fácil de construir y mantener, lo que lleva a mejorar la productividad del programador.

Estudio de caso 2: Encontrar eventos relacionados en datos temporales

Los datos temporales son una representación del estado de una entidad como un aspecto o más del mismo que varían a través del tiempo. Hay muchas maneras de capturar este estado cambiante. Con el fin de simplificar, debe enfocarse en el cambio en una variable simple en el siguiente ejemplo. Observe una operación de datos que sea común en el mundo de data mining: A un alto nivel, intenta identificar un grupo de entidades cuyos estados cambiantes tengan ciertas características de interés. Este conjunto puede ser, por ejemplo, un cliente que compra detergente después de comprar sopa. En este caso, observará la extensión de un ejemplo anterior. Busca empleados que no tengan más de un ascenso desde el período del 2000 al 2009. Consulte el Listado 12.

Listado 12. Estructura de tabla que captura el historial de ascensos de un empleado
[db2pe@lc4eb4168274532 code]$ db2 describe table pay_history 

                                Data type                     Column 
Column name                     schema    Data type name      Length     Scale Nulls 
------------------------------- --------- ------------------- ---------- ----- ------ 
EMP_ID                          SYSIBM    CHARACTER                    4     0 No 
START_TIME                      SYSIBM    TIMESTAMP                   10     6 No 
END_TIME                        SYSIBM    TIMESTAMP                   10     6 Yes 
JOB_LEVEL                       SYSIBM    INTEGER                      4     0 No 

     4 record(s) selected.

el Listado 13 muestra el argumento SQL que necesita.

Listado 13. Seleccione un argumento para identificar empleados con más de un ascenso en un período específico
select emp_id, count(emp_id) 
from pay_history 
where start_time > '2000-01-01-00.00.00.000000' 
and end_time < '2010-01-01-00.00.00.000000' 
group by emp_id 
having count(emp_id) > 1

el Listado 14 muestra el diseño de tabla basada en tipos de datos XML.

Listado 14. Estructura de tabla que captura el historial de ascensos de un empleado usando tipos de datos XML
[db2pe@lc4eb4168274532 code]$ db2 describe table pay_history 
                                Data type                     Column 
Column name                     schema    Data type name      Length     Scale Nulls 
------------------------------- --------- ------------------- ---------- ----- ------ 
EMP_ID                          SYSIBM    CHARACTER                    4     0 No 
HISTORY                         SYSIBM    XML                          0     0 No 

     2 record(s) selected.

el Listado 15 muestra los datos XML que representan el historial de trabajo

Listado 15. Estructura de tabla que captura el historial de ascensos de un empleado usando tipos de datos XML
[db2pe@lc4eb4168274532 code]$ db2 describe table pay_history 
                                Data type                     Column 
Column name                     schema    Data type name      Length     Scale Nulls 
------------------------------- --------- ------------------- ---------- ----- ------ 
EMP_ID                          SYSIBM    CHARACTER                    4     0 No 
HISTORY                         SYSIBM    XML                          0     0 No 

     2 record(s) selected.

el Listado 16 muestra el argumento selección basado en XQuery equivalente al argumento selección en el Listado 13Listado 13.

Listado 16. Estructura de tabla que captura el historial de ascensos de un empleado usando tipos de datos XML
<employee id="0001"> 
     <pay startDate="2001-11-23T00:00:00.000000Z" endDate="2002-10-07T00:00:00.000000Z" 
          level="4">70500</pay> 
     <pay startDate="2002-10-07T00:00:00.000000Z" endDate="2005-06-18T00:00:00.000000Z" 
          level="5">81500</pay> 
     <pay startDate="2005-06-18T00:00:00.000000Z" endDate="2007-06-01T00:00:00.000000Z" 
          level="6">96700</pay> 
     <pay startDate="2007-06-01T00:00:00.000000Z" level="7">120000</pay> 
</employee>

No parece haber ninguna mejora en el formulario XQuery. Consideremos un requerimiento adicional. Los empleados pueden bajar o subir de categoría. Cada historial de trabajo puede representar un ascenso o descenso en el puesto de trabajo. Si solo se enfocó en los ascensos y excluyó los descensos, el argumento XQuery cambia al código en el Listado 17.

Listado 17. El XQuery que solo se enfoca en ascenso para procesa el historial de trabajo
select emp_id 
from pay_history 
where xmlexists(' 
   let $i := 0 
   (: 
    : You have to work around the fact that the DB2 pureXML implementation of XQuery 
    : does not support the preceding-sibling axis. To do this, iterate through the 
    : "pay" elements for a given employee, and tack on the position of the element. 
    : Later on, use this position to determine the previous "pay" element. 
    :) 
   let $pairs := ( 
      for $jobChanges in $HISTORY/employee/pay[@startDate gt 
         "2000-01-01T00:00:00.000000Z" and @endDate lt "2010-01-01T00:00:00.000000Z"] 
      let $i := $i + 1 
      return 
         <pair><position>{$i}</position>{$jobChanges}</pair> 
   ) 
   let $numPromotions := count( 
      for $p in $pairs 
      let $currentPos := xs:int($p/position) 
      return 
         (: If this is the first "pay" element, its not a demotion :) 
         if($currentPos eq 1)then 
         ( 
            $p/pay 
         )else( 
            if($pairs[$currentPos - 1]/data(@level) lt $p/data(@level))then 
            ( 
               $p/pay 
            )else() 
         ) 
   ) 
   return 
      if($numPromotions gt 1)then 
      ( 
         true() 
      )else() 
')

El equivalente al argumento SQL, utilizando tipos de datos que no sean XML, ahora requiere una auto-unión para implementarse. Incluso después será bastante complejo y difícil de construir. Una mitigación sería agregar una columna "número de fila del historial de trabajo" en la tabla pay_history y luego permitir que en esta columna se realice la auto-unión. Este enfoque se deja como ejercicio para el lector.

Resumen

Las ideas transmitidas en este artículo no suponen ser una enumeración de lo que es posible hacer con XQuery. En realidad, los ejemplos desean ilustrar cómo las habilidades eficientes proporcionadas por el lenguaje le permiten desarrollar aplicaciones de forma más fácil y hacer que su mantenimiento sea fácil. Esta es una buena razón, según este autor, para impulsar la adopción creciente de tipos de datos XML en las bases de datos.

Recursos

Aprender

Obtener los productos y tecnologías

Comentar

Comentarios

developerWorks: Ingrese

Los campos obligatorios están marcados con un asterisco (*).


¿Necesita un IBM ID?
¿Olvidó su IBM ID?


¿Olvidó su Password?
Cambie su Password

Al hacer clic en Enviar, usted está de acuerdo con los términos y condiciones de developerWorks.

 


La primera vez que inicie sesión en developerWorks, se creará un perfil para usted. La información en su propio perfil (nombre, país/región y nombre de la empresa) se muestra al público y acompañará a cualquier contenido que publique, a menos que opte por la opción de ocultar el nombre de su empresa. Puede actualizar su cuenta de IBM en cualquier momento.

Toda la información enviada es segura.

Elija su nombre para mostrar



La primera vez que inicia sesión en developerWorks se crea un perfil para usted, teniendo que elegir un nombre para mostrar en el mismo. Este nombre acompañará el contenido que usted publique en developerWorks.

Por favor elija un nombre de 3 - 31 caracteres. Su nombre de usuario debe ser único en la comunidad developerWorks y debe ser distinto a su dirección de email por motivos de privacidad.

Los campos obligatorios están marcados con un asterisco (*).

(Por favor elija un nombre de 3 - 31 caracteres.)

Al hacer clic en Enviar, usted está de acuerdo con los términos y condiciones de developerWorks.

 


Toda la información enviada es segura.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=90
Zone=
ArticleID=827933
ArticleTitle=Desarrollo con XQuery: un mejor lenguaje de programación para el programador de bases de datos
publish-date=07312012