Artículo
· 14 mar, 2024 Lectura de 7 min

Consultas como %Query o consultas basadas en ObjectScript

En este tutorial, me gustaría hablar sobre las Consultas de Clase (Class Queries). Para ser más precisos, sobre las Consultas basadas en código escrito por el usuario:

Mucha gente descarta este tipo de consulta simplemente porque no se sienten muy cómodos escribiendo mucho código ObjectScript para los métodos o no ven cómo pueden utilizarlo en sus aplicaciones relacionales. Pero para ser honesto, para mí - ¡es uno de los inventos más geniales para el modelo relacional en IRIS! Permite exponer cualquier información que se quiera (no limitada a las tablas de la base de datos) como un conjunto de resultados relacionales para un cliente. Así, básicamente, se puede envolver cualquier dato almacenado en la base de datos y más allá de ella en una "tabla virtual ordenada" que el usuario puede consultar. Y desde el punto de vista de un usuario, será el mismo conjunto de resultados que si hubiera consultado una tabla o una vista.

Estos son algunos ejemplos de lo que se puede utilizar como datos para la consulta:

  • Indicadores del sistema de IRIS y del Sistema Operativo local (si IRIS tiene acceso a ellos)
  • Resultados de consultas externas, como REST o SOAP (por ejemplo, si no se quieren enviar solicitudes directamente desde un cliente por motivos de seguridad o por cualquier otro motivo)
  • Resultados de Cursores embebidos o Sentencias simples
  • Cualquier dato ad hoc que cada uno pueda componer por sí mismo
  • Prácticamente cualquier otra cosa que se os ocurra y sea posible obtener utilizando ObjectScript

Veamos ahora cómo se hace.

Si estáis utilizando Studio - es muy amigable y sencillo.

En la clase cls, hay que ir al menú Class – Add – Query:

O en la barra de herramientas “Members”, hacer clic en el icono Query:

Y se abrirá el Asistente para realizar una nueva Query. Os indicará los pasos para crear los métodos necesarios para que la consulta funcione.

Vamos a ver este proceso desde el ejemplo más sencillo que sería envolver en un método de tipo query el resultado de un Cursor Embebido. En este caso, realmente estaríamos escribiendo una consulta a una tabla, pero es sólo para hacerlo más fácil. Más adelante veremos ejemplos de uso de %Query para devolver datos de otras fuentes.

Digamos que tenemos una clase Sample.Human que extiende %Persistent y %Populate. Este último lo utilizaré para ingresar datos porque puedo hacerlo:

Class Sample.Human Extends (%Persistent, %Populate)
{

Property Name As %Name;
Property DoB As %Date(MAXVAL = 61727, MINVAL = 32507);
Property Age As %Integer [ Calculated, SqlComputeCode = {set {Age} = $h - {DoB} \ 365.25}, SqlComputed ];
}

Cuando empezamos a crear la Query, el primer paso del asistente es definir un nombre y un tipo:

El siguiente paso es definir los parámetros de entrada si los hay. Para añadir un nuevo parámetro, hay que hacer clic en el botón superior de la columna derecha de los botones:

El siguiente paso es una parte muy importante - hay que definir los nombres y tipos o columnas en un conjunto de resultados resultante. Cuando estáis creando una Query basada en SQL este paso lo hace por vosotros el sistema automáticamente - simplemente mira las columnas que incluisteis en la consulta y toma sus nombres y tipos de datos. Como en la consulta basada en ObjectScript puede que no haya ningún campo, tenéis que indicarle vosotros mismos al sistema lo que debe esperar.

Y terminamos. Como resultado para este Asistente, obtendréis 3 nuevos ClassMethods y una Query en vuestra clase:

/// Get all the names and ages of people whose age is greater or equal than nput parameter
Query GetAllOlderThan(Age As %Integer = 65) As %Query(ROWSPEC = "Name:%Name,Age:%Integer")
{
     //no code here. it'll be ignored
}

ClassMethod GetAllOlderThanExecute(ByRef qHandle As %Binary, Age As %Integer = 65) As %Status
{
               Quit $$$OK
}

ClassMethod GetAllOlderThanClose(ByRef qHandle As %Binary) As %Status [ PlaceAfter = GetAllOlderThanExecute ]
{
               Quit $$$OK
}

ClassMethod GetAllOlderThanFetch(ByRef qHandle As %Binary, ByRef Row As %List, ByRef AtEnd As %Integer = 0) As %Status [ PlaceAfter = GetAllOlderThanExecute ]
{
               Quit $$$OK
}

}

La Query sólo le dice al sistema que hay una consulta en una clase que puede ser llamada por un nombre con un parámetro y que devolverá 2 columnas en el resultado. Cualquier código que se introduzca en el bloque de código de Query será ignorado.

Toda la codificación se realiza en los ClassMethods. Ellos son responsables de rellenar nuestro conjunto de resultados "virtual" (Execute - llamado cuando se ejecuta una sentencia SQL), devolver la siguiente fila (Fetch) y limpiar al terminar (Close). Tienen un parámetro en común – ByRef qHandle As %Binary en el que los datos se almacenan y se transmiten entre métodos. Como se puede ver, se trata de datos binarios por lo que se puede poner cualquier cosa dentro (el sentido común es el límite). En Execute también hay un parámetro de entrada de la consulta: en este caso, es Edad As %Integer = 65. Y en Fetch hay dos parámetros adicionales:

  • ByRef Row As %Listes una %List que contiene la fila actual del conjunto de resultados “virtual” con el número y valores de las  columnas descritas en la ROWSPEC de la Consulta (Name:%Name, Age:%Integer).
  • ByRef AtEnd As %Integer = 0 – este es un indicador que señala si la fila actual es la última fila (1) o no (0).

Si estáis trabajando en VSCode, tendréis que escribir todas las firmas vosotros mismos y tratar de no cometer errores 😊

Ahora que sabemos qué parámetro es responsable de qué, echemos un vistazo al código:

/// Get all the names and ages of people whose age is greater or equal than nput parameter
Query GetAllOlderThan(Age As %Integer = 65) As %Query(ROWSPEC = "Name:%Name,Age:%Integer") [ SqlProc ]
{
}

ClassMethod GetAllOlderThanExecute(ByRef qHandle As %Binary, Age As %Integer = 65) As %Status
{
     &sql(DECLARE C CURSOR FOR
        SELECT Name, Age
          FROM Sample.Human 
         WHERE Age >= :Age
     )
     &sql(OPEN C)
    Quit $$$OK
}

ClassMethod GetAllOlderThanClose(ByRef qHandle As %Binary) As %Status [ PlaceAfter = GetAllOlderThanExecute ]
{
    &sql(CLOSE C)
    Quit $$$OK
}

ClassMethod GetAllOlderThanFetch(ByRef qHandle As %Binary, ByRef Row As %List, ByRef AtEnd As %Integer = 0) As %Status [ PlaceAfter = GetAllOlderThanExecute ]
{
    &sql(FETCH C INTO :Name, :Age)
    If (SQLCODE'=0) {
        Set AtEnd = 1
        Set Row = ""
        Quit $$$OK
    }
    Set Row = $Lb(Name, Age)
    Quit $$$OK
}

Este código declara y abre un Cursor embebido que selecciona los nombres y las edades de las personas mayores de Age en Execute, después obtiene el resultado del cursor y forma una lista en Fetch y la cierra en Close.

Podemos llamarlo mediante el código:

 SET tStatement = ##class(%SQL.Statement).%New()
 SET rstatus = tStatement.%PrepareClassQuery("Sample.Human","GetAllOlderThan")
 SET rset = tStatement.%Execute()
 DO rset.%Display()

Muy bonito y limpio. Y probablemente no es lo que estabais buscando.

Como ejemplo de uso sin datos almacenados en la base de datos, digamos que por alguna razón no tenemos acceso a las tablas reales, pero necesitamos comprobar que nuestra aplicación funciona como debería. Necesitamos que la consulta devuelva algunos datos de prueba. Podemos reescribir este ejemplo para que los datos se generen automáticamente durante la obtención de una nueva fila.

Obviamente, no necesitamos guardar datos en la memoria, por lo que no es necesario poblar la variable qHandle en Execute - podemos crear datos dentro de Fetch. Y en qHandle almacenaremos el tamaño del conjunto de resultados a devolver (un número aleatorio inferior a 200, por ejemplo) y el número de la fila actual que incrementaremos en <Fetch. Al final, obtendremos el siguiente código:

Query GetAllOlderThan(Age As %Integer = 65) As %Query(ROWSPEC = "Name:%Name,Age:%Integer") [ SqlProc ]
{
}

ClassMethod GetAllOlderThanExecute(ByRef qHandle As %Binary, Age As %Integer = 65) As %Status
{
    set qHandle = $lb($random(200), 0, Age)
    Quit $$$OK
}

ClassMethod GetAllOlderThanClose(ByRef qHandle As %Binary) As %Status [ PlaceAfter = GetAllOlderThanExecute ]
{
    set qHandle = ""
    Quit $$$OK
}

ClassMethod GetAllOlderThanFetch(ByRef qHandle As %Binary, ByRef Row As %List, ByRef AtEnd As %Integer = 0) As %Status [ PlaceAfter = GetAllOlderThanExecute ]
{
	if $ListGet(qHandle, 2) = $ListGet(qHandle, 1)
	{
		Set Row = ""
		set AtEnd = 1
	} else
	{
    	Set Row = $Lb(##class(%PopulateUtils).Name(), ##class(%PopulateUtils).Integer($ListGet(qHandle, 3), 90))
    	set $list(qHandle, 2) = $list(qHandle, 2) + 1
	}
    Quit $$$OK
}

}

Como se puede ver, estoy generando datos ad hoc. Esto significa que podéis obtener vuestros datos de donde sea, envolverlos en un conjunto de resultados y hacerlos accesibles desde vuestra aplicación que utilice ODBC/JDBC fuera de la base de datos IRIS. Lo que a su vez significa que podéis utilizar el acceso relacional habitual para obtener datos no relacionales si conseguís estructurarlos.

Comentarios (0)0
Inicie sesión o regístrese para continuar