Desarrollo en x++, tips, trucos, tutoriales, opinión, mantenimiento de datos.

11 octubre 2005

Comparar registros

Estaba intentando crear un método para comparar dos registros de una misma tabla cuya única diferencia sería el campo que sirve de índice.
El asunto no es fácil, una opción sería meter todas las columnas en un contenedor (cada línea del contenedor correspondería a una columna) y luego hacer lo mismo con el otro registro en un segundo contenedor, luego quedaría comparar campo a campo los contenedores.
No sirve hacer la comparación directa tipo if(registro1 == registro2), porque compara los tipos de registro, no los datos que contienen.
Una desventaja para la opción anteriormente propuesta (la de entrar los datos en masa en dos contenedores y compararlos) es que si modificamos la tabla, por ejemplo agregándole una nueva columna, hay que modificar también el método de comparación para que entre esa nueva columna en el contenedor.
La opción más elegante entonces sería recorrer automáticamente uno a uno los campos de cada registro e ir comparándolos. ¿Pero cómo saber cuantos registros hay en una tabla y como ir recorriéndolos? Con los objetos dictTable y dictField.
El objeto dicttable contiene información de una tabla específica y el dictfield, de un campo particular de la tabla.
dictTable.fieldcnt() nos devuelve la cantidad de campos que contiene la tabla (esto incluye los campos de sistema, como lo son el recId o el dataAreaId) y dictTable.fieldCnt2Id(n) nos devuelve el 'n'ésimo campo de la tabla (un fieldId, es decir un identificador único de un campo).
En cuanto a dictfield, podemos obtener propiedades específicas de un campo particular, como el nombre del campo con dictField.name().
Entonces lo que se puede hacer es ir recorriendo uno a uno cada campo de una tabla específica e ir comparándolos como por ejemplo, comparar que dos registros de la inventtable, para ver si tenemos dos artículos con datos iguales pero con diferente código (campo ItemId):
Para ello primero debemos asignar nuestro dictTable a la tabla de donde sacaremos los datos a comparar:

dicttable d = new dicttable(tablenum(InventTable));

Y luego declaramos las tablas con que haremos la consulta y el dictfield para saber si el campo con el que estemos trabajando nos sirve o no:

InventTable Item1, Item2;
dictfield f;

Buscamos los dos campos a comparar, por ejemplo los artículos de código '5001' y '5002'

Item1 = InventTable::find('5001');
Item2 = InventTable::find('5002');

La cantidad de columnas o campos que contiene la InventTable la podemos conocer con el metodo fieldcnt() de la dicttable, por lo que para recorrer todos los campos hacemos:

for(i=0; i<= d.fieldCnt(); i++)
{
}

Dentro del for podemos obtener los datos del 'i'ésimo campo de la InventTable con nombreTabla.(dictTable.fieldCnt2Id(i)). Por ejemplo si hacemos Item1.(d.fieldCnt2Id(1)) obtendremos el valor '5001' ya que el primer campo es el ItemId.
Comparar los campos entonces se haría dentro del bucle de la siguiente forma:

if (Item1.(d.fieldCnt2Id(i)) != Item2.(d.fieldCnt2Id(i)))
    info ("Campos diferentes");

Pero antes, no nos interesa comparar los registros que sean de sistema. Puesto que son registros diferentes, tendrán recId diferentes, fechas de creación diferentes, etc. Así que antes, verificamos que el campo que vayamos a comparar no sea de sistema, para ello utilizaremos el dictField. Primero asignamos el dictField a la tabla y el campo en el que nos encontramos:

f = new dictfield(tablenum(InventTable), d.fieldCnt2Id(i));

El método dictField.isSystem() nos devuelve un booleano que indica si se trata de un campo de sistema o no. Pero aún queda otra cosa, tampoco queremos comparar los ItemId tal como nos lo planteamos en un principio. Para saber cuando estaremos en el campo 'ItemId' utilizaremos el método dictField.name().
Así entonces la comparación nos queda:

for(i=0; i<= d.fieldCnt(); i++)
{
    f = new dictfield(tablenum(InventTable), d.fieldCnt2Id(i));
    if (f.isSystem() == false && f.name() != 'ItemId')
    {
        if (Item1.(d.fieldCnt2Id(i)) != Item2.(d.fieldCnt2Id(i)))
        info ("Campos diferentes");
    }
}

Con dictTable y dictField podemos controlar y acceder a los datos de todos los campos de una tabla sin tener que saber cuantos campos tiene la tabla o el nombre de éstos.

03 octubre 2005

Tablas Temporales

En desarrollo en AXAPTA, me he encontrado con algunos problemas para guardar registros de tablas en variables y manipularlos sin alterar las tablas.
Una opción es utilizar los contenedores (tipo de datos container), pero si se quiere trabajar con datos que tienen la misma estructura que una tabla, mejor usar tablas temporales.
Puedo crear tablas temporales en el AOT de la misma manera que creo una tabla corriente, sólo que en la propiedad 'Temporary', le pongo 'Yes'. Así luego defino los campos con los que quiero trabajar.
Alimentar esta tabla se hace de la misma manera que se llena cualquier tabla, con insert(), igualmente podemos usar delete() o update(). Hay que recordar que las tablas temporales siempre están vacías en un comienzo, así que no importa cuantos datos pongamos dentro, ésta se va a vaciar una vez que dejemos de usarla. Las tablas temporales no se guardan en la base de datos, sino que en el sistema de archivos.
Si quiero crear una tabla temporal dinámicamente en código con la misma estructura que una tabla existente, puedo utilizar el método setTmp(), sólo tengo que crear una variable del tipo de la tabla y llamar a ese método, luego queda llenar la tabla temporal con los datos que me interesan puesto que la tabla temporal siempre se crea vacía.
Por ejemplo, si quiero una tabla temporal con los datos de la inventTable, filtrado sólo con los artículos del tipo Lista de Materiales, el código sería:

inventTable _inventTable; // La tabla de donde obtendremos los datos
inventTable TmpTable; // La tabla temporal
;

TmpTable.setTmp(); // La definimos como temporal

// Filtramos sólo las líneas que sean Listas de Materiales
while select _inventTable where _inventTable.itemType == itemType::BOM
{
// Llenamos la tabla temporal
TmpTable.data(_inventTable.data());
TmpTable.doInsert();
}

Con eso ya podemos trabajar directamente con TmpTable. Si hacems un 'select TmpTable' sólo obtendremos los datos que hayamos entrado en la tabla en lugar de la inventTable completa.