Manejadores de Bases de Datos

Anuncio
MANEJADORES DE BASE DE DATOS
IBM Informix® Dynamic Server (IDS) 9.30 proporciona fiabilidad superior, atendiendo las necesidades de
las exigentes prácticas actuales del e−business−particularmente para aplicativos que requieran transacciones
de alto desempeño.
Soporta requisitos de procesamiento de transacción online, complejos y rigurosos.
Optimiza capacidades de inteligencia del negocio competitivas
Maximiza operaciones de datos para el grupo de trabajo y para la empresa en total.
Proporciona la firmeza de una administración de base de datos comprobada, mejor de su especie.
Informix Dynamic Server con J/Foundation combina las características de IDS con un ambiente abierto,
flexible, empotrado de Java! Virtual Machine. IDS con J/Foundation permite que los desarrolladores de base
de datos escriban lógica de negocio del lado−servidor usando el lenguaje Java!. Java User Defined Routines
(UDRs) tienen completo acceso a las características de la base de datos extensible líder mundial, de la base de
datos IDS. Haciendo del IDS la plataforma ideal para el desarrollo de base de datos Java.
Además de Java UDRs, el IDS está en conformidad con el estándar SQLJ para procedimientos almacenados
en Java, permitiendo el uso de los paquetes Java estándar que se encuentran incluidos en el Java Development
Kit (JDK). Escribir UDRs en Java proporciona aplicativos mucho más flexibles que se pueden desarrollar más
rápidamente que C, y más potentes y administrables que los lenguajes de procedimientos almacenados.
Una extensión adicional de escribir UDRs en Java es escribir módulos DataBlade® en Java. Los módulos
DataBlade son colecciones de nuevas funciones del lado−servidor y tipos de datos puestos en conjunto para
extender el IBM Informix® Dynamic Server con el servidor de datos J/Foundation. El DataBlade Developer's
Kit (DBDK) ahora soporta Java y permite el desarrollo, diseminación y depuración de UDRs en Java. La
tecnología IBM Informix DataBlade es líder en la industria en extender el servidor para permitir tanto la
administración de contenido rich, cuanto la lógica de negocio.
J/Foundation está provisto con IDS en muchas de las plataformas IDS 9.30 soportadas. Las plataformas
soportadas incluyen Sun Solaris 32 bit, Microsoft Windows NT/2000, Linux, IBM AIX, SGI Irix, y Compaq
Tru 64
IBM Informix® Dynamic Server (IDS) 9.30 proporciona fiabilidad superior, atendiendo las necesidades de
las exigentes prácticas actuales del e−business−particularmente para aplicativos que requieran transacciones
de alto desempeño.
Soporta requisitos de procesamiento de transacción online, complejos y rigurosos.
Optimiza capacidades de inteligencia del negocio competitivas
Maximiza operaciones de datos para el grupo de trabajo y para la empresa en total.
Proporciona la firmeza de una administración de base de datos comprobada, mejor de su especie.
Informix Dynamic Server con J/Foundation combina las características de IDS con un ambiente abierto,
flexible, empotrado de Java! Virtual Machine. IDS con J/Foundation permite que los desarrolladores de base
1
de datos escriban lógica de negocio del lado−servidor usando el lenguaje Java!. Java User Defined Routines
(UDRs) tienen completo acceso a las características de la base de datos extensible líder mundial, de la base de
datos IDS. Haciendo del IDS la plataforma ideal para el desarrollo de base de datos Java.
Además de Java UDRs, el IDS está en conformidad con el estándar SQLJ para procedimientos almacenados
en Java, permitiendo el uso de los paquetes Java estándar que se encuentran incluidos en el Java Development
Kit (JDK). Escribir UDRs en Java proporciona aplicativos mucho más flexibles que se pueden desarrollar más
rápidamente que C, y más potentes y administrables que los lenguajes de procedimientos almacenados.
Una extensión adicional de escribir UDRs en Java es escribir módulos DataBlade® en Java. Los módulos
DataBlade son colecciones de nuevas funciones del lado−servidor y tipos de datos puestos en conjunto para
extender el IBM Informix® Dynamic Server con el servidor de datos J/Foundation. El DataBlade Developer's
Kit (DBDK) ahora soporta Java y permite el desarrollo, diseminación y depuración de UDRs en Java. La
tecnología IBM Informix DataBlade es líder en la industria en extender el servidor para permitir tanto la
administración de contenido rich, cuanto la lógica de negocio.
J/Foundation está provisto con IDS en muchas de las plataformas IDS 9.30 soportadas. Las plataformas
soportadas incluyen Sun Solaris 32 bit, Microsoft Windows NT/2000, Linux, IBM AIX, SGI Irix, y Compaq
Tru 64
Nombre del Formato:
Tipo de Formato:
Descripción del Formato:
Formato disponible en Data Junction
Enterprise:
Formato disponible en Data Junction
Profesional:
Nombre del Editor:
Más información en:
Comentarios adicionales:
Informix
Base de Datos
Base de datos comercial
Sí
No (la versión Enterprise es necesaria si se
desea leer o escribir datos en este formato)
IBM Software
Informix
Sin comentarios
Funciones Oracle
Requisitos previos: Usted necesita saber los valores de las variables de entorno siguientes:
ORACLE_HOME
Ésta es la trayectoria a su directorio de la instalación del oráculo. Se define generalmente en la escritura de la
conexión de UNIX de su usuario del oráculo y de todos los usuarios del cliente del oráculo.
ORACLE_SID
Éste es el nombre del caso de la base de datos que usted desea conectar con. También se define en el ambiente
de UNIX de su usuario del oráculo y de todos los usuarios del cliente del oráculo. Descubra los valores de
estas variables por el loggin adentro como usuario que pueda conectar con la base de datos en la pregunta con
sqlplus. Entonces mecanografíe en su aviso de la cáscara de Unix:
prompt> echo $ORACLE_HOME
/opt/oracle/oracle/8.0.3
prompt> echo $ORACLE_SID
ORACLE
2
A simple PHP script using ora_* functions
<?php
putenv("ORACLE_SID=ORACLE");
putenv("ORACLE_HOME=/opt/oracle/oracle/8.0.3");
$conn = ora_login("scott", "tiger");
$curs = ora_open($conn);
ora_commitoff($conn);
$query = sprintf(&quotselect * from cat");
/* Long version */
/*
ora_parse($curs, $query);
ora_exec($curs);
ora_fetch($curs);
*/
/* Short Version */
ora_do($conn, $query);
$ncols = ora_numcols($curs);
$nrows = ora_numrows($curs);
printf("Result size is $ncols cols by $nrows rows.
");
for ($i=0; $i<$ncols; $i++) {
printf("col[%s] = %s type[%d] = %s
",
$i, ora_columnname($curs, $i),
$i, ora_columntype($curs, $i));
}
for ($j=0; $j<$nrows; $j++) {
for ($i=0; $i<$ncols; $i++) {
$col = ora_getcolumn($curs, $i);
printf(&quotval[%d, %d] = %s * ", $j, $i, ora_getcolumn($curs, $i);
}
printf("
");
}
?>
Stevel at nettek−llc dot com
<?php
putenv(
3
"ORACLE_SID=sid1");
putenv(
"ORACLE_HOME=/u01/app/oracle/product/8.0.5");
$handle = ora_plogon(
"SCOTT@sid1", "TIGER")or die;
$cursor = ora_open($handle);
ora_commitoff($handle);
$query = "SELECT * FROM EMP";
ora_parse($cursor, $query) or die;
ora_exec($cursor);
echo "<HTML><PRE>\n";
echo "$query\n\n";
$numcols = 0;
while(ora_fetch($cursor)){
$numcols = ora_numcols($cursor);
for($column=0; $column < $numcols; $column++){
$data = trim(ora_getcolumn($cursor, $column));
if($data =="") $data = "NULL";
echo"$data\t";
}
echo "\n";
}
$numrows = ora_numrows($cursor);
echo "\nROWS RETURNED: $numrows\n";
4
echo "</PRE></HTML>\n";
ora_close($cursor);
?>
Vistas y envolturas
La implementación de VFP de las vistas le permite trabajar con ellas de modo similar a como trabaja con
tablas nativas. Como las trataré en detalle en este artículo, usarlas es clave para lograr ser independiente del
software de soporte (back−end).
El otro concepto importante es el uso de envolturas. Por envoltura me refiero a la técnica por la cual una
función o comando, como SKIP, es "envuelta" en algún código que la protege y almacenada en una función
definida por el usuario, de forma tal que cada vez que se utilice, no tenga que codificar de nuevo la
intercepción de errores necesaria. Por ejemplo, la función SafeSkip puede verse como:
Function SafeSkip
LParameters cAlias
llMoved = .F.
* Avoid that "EOF encountered" error
If !EOF(cAlias)
Skip In (cAlias)
If EOF(cAlias)
* Moved to EOF, retreat
Skip −1 In (cAlias)
Else
llMoved = .T.
Endif
Endif
Return llMoved
Mientras más fuentes de datos intente accesar, más código condicional necesitará escribir. Escribir código de
esta forma le proporcionará los siguientes beneficios:
5
• Crea un solo punto de mantenimiento si se necesita realizar algún cambio.
• El mismo código puede accesar múltiples fuentes de datos.
• Puede comenzar a codificar aplicaciones cliente/servidor utilizando software de soporte en VFP hasta
que se tome una decisión acerca de utilizar MS SQL Server, Oracle, Informix, Sybase o DB/2.
Formularios de entrada de datos basadas en vistas.
Cuando se trata de formularios de entrada de datos para una aplicación cliente/servidor, puede utilizar ya sea
SQL Pass−through (SPT), el cual es sencillamente pasar una cadena de SQL a través de un ODBC a la fuente
de datos, o vistas remotas, que significa enunciados SQL almacenados en DBC cuyas tablas temporales
resultantes se comportan muy similar a las tablas nativas de VFP. El SPT almacenará su conjunto de
resultados en un cursor local, así que Usted tendrá que hacer un ciclo sobre los controles dentro de un
formulario y añadir cada uno de ellos individualmente (por ejemplo, thisform.txtCustomer.Value =
SQLResult.customer). Claro, actualizar el servidor requeriría que Usted creara los enunciados SQL Update,
Insert, y Delete apropiados, sin mencionar la verificación de contención multiusuario que debe ser codificada
también, una tarea algo tediosa y plagada de oportunidades de error. Esta es la manera en que las cosas son
usualmente hechas en Visual Basic (así que Usted sabe que debe de haber una mejor manera con VFP <risa>).
Las vistas remotas, por otro lado, son increíblemente sencillas de usar. Todo lo que necesita hacer es crear una
vista con la misma estructura que la tabla subyacente, abrir la vista, (realizar el SQL Select), manipular la
tabla temporal local que se crea de la misma forma en que lo haría con una tabla nativa de FoxPro, luego
hacer un TableUpdate. El TableUpdate automáticamente hace unos enunciados SQL Update, SQL Insert y
SQL Delete por Usted y los pasa al administrador del controlador ODBC en tu estación de trabajo. El
administrador del controlador ODBC utiliza el controlador ODBC que Usted ha seleccionado para traducir el
SQL en la sintaxis que el software de soporte (back−end) entiende. Es así de sencillo. Yo recomiendo utilizar
las vistas remotas para los ofrmularios de entrada de datos por las siguientes razones:
• Un conjunto de código. El mismo conjunto de código puede funcionar con tablas VFP o con tablas
remotas en un SQL Server. Usted solo tiene que utilizar vistas locales o vistas remotas de acuerdo a
las tablas que se utilicen.
• Desempeño. De muchas formas, las vistas pueden ser más rápidas que el SPT debido a que
TableUpdate (una función de VFP de bajo nivel) crea automáticamente el código SPT por Usted. El
tiempo que tomaría crear las cadenas de SQL update por Usted mismo y ejecutar el código muy
probablemente sería mayor que el TableUpdate. Si Usted piensa en cuanto es lo que en realidad
TableUpdate realiza (registrar la memoria intermedia [buffer] de cambios, determinar el tipo de
actualización hecha, leer las propiedades de la vista, construir el lote de enunciados SQL, pasarlos al
administrador de manejador ODBC, regresar una bandera de éxito/fracaso y limpiar la memoria
intermedia de cambios), estoy seguro de que Usted estará de acuerdo con que puede tener mejor
desempeño que el SPT.
• Todo lo anterior se puede hacer sin un solo error de sintaxis.
• Las propiedades de las vistas ofrecen una funcionalidad adicional sobre el SPT. Por ejemplo, para
simular una cuenta de cinco BatchUpdate, se necesitaría concatenar cinco enunciados SQL con punto
y comas antes de pasarlos al ODBC.
El SPT aún tiene un muy importante lugar en la aplicación cliente/servidor; Discutiré más acerca de esto en el
Paso 3. Para mayor información sobre la utilización básica de las vistas locales y remotas, refierase a la VFP
Developer's Guide.
Todas los comandos relacionados con datos deben de pasar a través de una función.
Casi la peor cosa que puede hacer para convertir en difícil el diseño de su aplicación, es codificar
directamente (hard−code) comandos de acceso, tales como los enunciados SQL, Zap, Pack, Reindex, Seek y
6
demás. En otras palabras, ¿luce su código como el siguiente?
Function PurgeOrders
* This function purges (deletes) orders shipped
* prior to the date passed.
LParameter dShipDate
Local lcMsg
Select Count(*) as OrderCnt from Orders ;
Where ShipDate <= dShipDate Into Cursor tcPurge
If _Tally = 0
lcMsg = "There are no orders to purge."
Else
Delete from Orders Where ShipDate <= dShipDate
lcMsg = Trim(Str(_Tally)) + ;
" orders were purged from the system."
Endif
MessageBox(lcMsg)
EndFunc
Esta clase de enunciados SQL es solo buena para accesar tablas que se pueden encontrar en su ruta de acceso.
Dentro del mundo cliente/servidor puede comunicarse con las fuentes de datos por medio de un manejador de
conexión (connection handle). Para las vistas, la cláusula de conexión remota <ConnectionName |
DataSourceName> del comando Create SQL View habilita que una vista accese datos remotos. Y para el SQL
en línea, la función de FoxPro SQLExec() es utilizada para pasar enunciados SQL al servidor por medio de
ODBC. Regresa un −1 si hubo un error, 1 en caso de éxito y un 0 si una petición (query) asíncrona está
pendiente de completarse aún. Como ejemplo, aquí está como obtener un cursor de órdenes de venta de un
cliente dado utilizando SQL Pass−through:
llSuccess = SQLExec(goEnv.nHandle, ;
"Select * From ORDERS Where Customer = ?cCustomer",;
"tcOrders") > 0
Si Usted hubiese empleado una función de envoltura para el enunciado SQL Select dentro de PurgeOrders,
entonces podría condicionalmente ejecutarlo contra datos en el SQL Server, datos en el servidor de archivos o
incluso datos en la estación de trabajo local. Debe crear clases de las funciones necesarias para hacer el acceso
7
de datos independiente del software de soporte (back−end). Aquí esta la función PurgeOrders reescrita de
forma cliente/servidor:
Function PurgeOrders
* This function purges (deletes) orders shipped
* prior to the date passed.
LParameter dShipDate
Local lcSQLStr
lcSQLStr = "Select Count(*) as OrderCnt " + ;
"from Orders " + ;
"Where ShipDate <= ?dShipDate"
This.SQLExecute(lcSQLStr, "tcPurge")
If Reccount("tcPurge") = 0
lcMsg = "There are no orders to purge."
Else
lcSQLStr = "Delete from Orders ;
Where ShipDate <= ?dShipDate"
This.SQLExecute (lcSQLStr)
lcMsg = Trim(Str(Reccount("tcPurge"))) ;
+ " orders were purged from the system."
Endif
MessageBox(lcMsg)
EndFunc
Function SQLExecute
* Wrapper for VFP function SQLExec
LParameters cExecuteStr, cCursor
Local llSuccess
cCursor = Iif(PCount() = 1, "SQLResult", cCursor)
8
llSuccess = .T.
If goEnv.lRemote
llSuccess = (SQLExec(goEnv.nHandle, ;
cExecuteStr, cCursor) > 0)
Else && local, just macro expand cExecuteStr
* Add VFP "Into Cursor..." clause
If Upper(Left(cExecuteStr,6)) = "SELECT"
cExecuteStr = cExecuteStr + ;
" Into Cursor " + cCursor + " NoFilter"
Endif
* This should be error trapped to return llSuccess
&cExecuteStr
Endif
Return llSuccess
EndFunc
Notará el uso de Reccount() vs. _Tally. Los cursores SPT no actualizan _TALLY, así que tendrá que olvidarse
de utilizar esta variable tan agradable. Sin embargo, no tendrá que preocuparse de que Reccount() contenga
los registros eliminados, dado que las fuentes SQL de datos remotas no tienen una eliminación de dos etapas.
Y si el software de soporte fuese una base de datos de VFP, la cláusula NoFilter prevendría que VFP creara un
filtro en vez de una tabla temporal.
La segunda peor cosa que puede hacer para convertir su aplicación en un diseño cliente/servidor difícil es
incluir funciones de VFP en sus SQL Selects. Sin embargo, si Usted tiene una envoltura como SQLExecute,
tiene la oportunidad de leer el enunciado SQL antes de ejecutarlo y convertirlo a la sintaxis específica del
software de soporte. Por ejemplo, la función de SQL Server, Convert() se utiliza para convertir tipos de datos.
Así que, Usted probablemente podría imaginarse como se vería el código para encontrar "Str(" o "Val(" en
una cadena y hacer una StrTran() para poner la función Convert en lugar de las anteriores. Una última opción
es remover la función incluida y hacer el enunciado SQL independiente del software de soporte, luego utilizar
la función VFP específica en el cursor local.
Utilice procedimientos almacenados o peticiones parametrizadas.
En términos de velocidad de ejecución, no hay nada más rápido que un procedimiento almacenado (stored
procedure, SP). Dado que son precompilados y viven en el mismo edificio que las tablas, ¿qué podría ser más
eficiente?. La forma en que los parámetros necesitan ser pasados al software de soporte es casi siempre
diferente, así que, de nuevo, una función de envoltura se vuelve útil aquí. Por ejemplo, tome nota de la
sintaxis distinta entre VFP y el SQL Server:
9
SQLExec("usp_GetOrders('Acme')") && VFP
SQLExec("usp_GetOrders 'Acme'") && SQL Server
Todo lo que Usted necesita para resolver esto es mejorar la función de envoltura SQLExecute para manejar
procedimientos almacenados. (Le dejaré esta sencilla tarea a Usted).
Si no desea irse por la ruta de los procedimientos almacenados, siempre están las peticiones parametrizadas
almacenadas en objetos de negocios. Con las peticiones parametrizadas, no tiene que preocuparse de la
sintaxis de llamada de procedimientos almacenados específicos del software de soporte, ni tiene de
preocuparse acerca de rescribir los procedimientos almacenados para cada fuente de datos. La disminución de
desempeño que se obtiene puede bien valer el incremento en la mantenibilidad.
Usted ya ha visto como es que se ve una petición parametrizada; es solo un enunciado SPT:
llSuccess = SQLExec(goEnv.nHandle, ;
"Select * From Orders Where Customer ;
= ?cCustomer", "tcOrders") > 0
Note la sintaxis de la condición de filtrado: Customer = ?cCustomer. La sintaxis es importante debido a que:
1) la mayoría de los software de soporte la soportan; y 2) le evita tener que construir los parámetros del
enunciado SQL en una cadena de caracteres específica del software de soporte:
Case lSQLServer
"...Customer = '" + cCustomer + "' And ;
shipdate <= '" + DTOS(dShipdate) + "'"
Case VFP
"...Customer = '" + cCustomer + "' And ;
shipdate <= {" + DTOS(dShipdate) + "}"
Otro beneficio es que no se topará con el "error del apóstrofe", que ocurre cuando intenta construir un
enunciado SQL como el anterior, pero la variable tiene un apóstrofe en ella (por ejemplo, "Jim's"). Dado que
su valor es evaluado antes de que la cadena sea enviada al servidor, terminará con comillas no apareadas
correctamente y con un error de tiempo de ejecución. Como puede ver, el uso de "?" puede realmente
compactar su código, así que yo recomiendo su utilización ampliamente.
Por las anteriores razones, recomiendo SPT en vez de las vistas, siempre esté creando una petición ad hoc y/o
no intenta actualizar la fuente de datos. Muchas personas crean vistas para propósitos de reporteo, pero en
sistemas grandes, no puedo ver la razón de incurrir en la sobrecarga de utilizar vistas cuando un
procedimiento almacenado o un enunciado SPT SELECT puede ser implementado en su lugar. (A menos que
deseé exponer una vista de lado del servidor a sus usuarios). El DBC de vistas del lado del cliente se puede
volver demasiado sobrecargado con datos que no necesita mantener, dado que no hay propiedades de la vista
que realmente necesiten ser establecidas.
Encuentre maneras creativas de limitar los conjuntos de resultados.
10
Dentro del paradigma cliente/servidor, los conjuntos de registros son transferidos desde el servidor hacia la
estación de trabajo local, por tanto, deben ser lo más pequeños posible para reducir el tráfico en la red. Por lo
tanto, de manera algo diferente a como funcionan las funciones de aplicaciones de servidor de archivos VFP,
las formas se deben abrir sin datos. Esto además ayudará a mantener una carga ligera causada por las formas,
aún y si la cantidad de sus datos crecen más allá de la capacidad de bases de datos de servidores de archivos,
como VFP.
Y si piensa al respecto, tiene sentido dar a los usuarios solo aquello que buscan. ¿Por qué presentar al usuario
una tabla que tiene 10,000 ordenes de venta abiertas cuando solo puede trabajar con un cliente a la vez?
Piense que hemos programado así, durante tanto tiempo, debido a que las herramientas de búsqueda
incremental nos dotaron de capacidades de navegación rápida, aún entre grandes cantidades de datos. Si bien
es cierto que esto es aceptable en sistemas pequeños, la carga se volvería insoportable en una red y su
aplicación no escalaría al mismo paso que los datos y los usuarios lo hicieran.
Vistas parametrizadas.
Así que si su forma se abre sin datos en el mundo cliente/servidor, ¿como le hacemos para obtener datos?
Necesita proporcionarle a sus usuarios una herramienta para introducir criterios de filtrado. Estos valores
servirán como parámetros de la porción Where del enunciado SQL de la vista. Usted puede proveer esta
funcionalidad de muchas formas diferentes, puede elegir entre muy sencillas hasta muy complejas. Una
implementación sencilla es tener una forma adherida a una vista parametrizada. Cuando un usuario entra en el
modo Find, Usted puede proveer una caja de texto para introducir el valor del parámetro. Por ejemplo:
* Create this view when you create the form.
Create SQL View vOrders As Select * From Orders
Where Customer = ?cCustomer
* Open the view (with no data) in the Data Environment
* or Load when you run the form.
Use vOrders NoData
* Get the customer parameter in Find Mode
cCustomer = thisform.txtCustomer.Value
Requery() && retrieve orders for cCustomer
Una segunda técnica que proporcionaría mucha más flexibilidad es proporcionar una herramienta
Query−By−Form (QBF). El QBF significa que en el modo Find, Usted puede proveer a los usuarios una
manera para que introduzcan criterios de filtrado en cualquier (o solo la mayoría) de los controles de una
forma. Luego construya una cadena Where SQL de acuerdo y utilícela de una de dos maneras:
1. Construya un enunciado SPT para crear un conjunto de registros de los campos importantes y el ID único.
Construya una vista que esté parametrizada por el ID único. Los usuarios pueden navegar el cursor para
encontrar los datos que necesitan y Usted hacer de nuevo la petición de la vista basada en la ID única.
2. Puede también utilizar la cadena SQL generada por la QBF para recrear la definición de vista remota en
tiempo real. Dado que su DBC de las vistas está en la estación de trabajo, no hay problemas de contención
11
aquí. (Más de esto en el Paso 5) .
Sugerencia:
Existen al menos dos herramientas de otras compañías de software disponibles para implementar
Query−By−Form en VFP:
• QBF Builder
• Stonefield Query
Manejando los enlaces entre controles y a tablas de búsqueda
Este concepto de limitar el conjunto de resultados debería ser aplicado también a los controles. ¿ Qué de
bueno tiene limitar las ordenes de venta a un cliente si se van a llenar varias casillas de selección (combo
boxes) en la forma con datos de una tabla de búsqueda (look up)?. La lista de selección (list box) de varios
miles de registros no tiene lugar en una aplicación cliente/servidor. Hay al menos dos formas de evitar este
problema. Una es no utilizar esta clase de controles en tablas grandes y en su lugar utilizar una caja de texto.
Luego solo lanzar una forma separada con una tabla cuando el usuario seleccione una tecla de método
abreviado (hotkey) definida.
Si Usted insiste en utilizar controles con registros múltiples, puede intentar con una segunda técnica. No llene
el control hasta que el evento GotFocus sea disparado. Esta técnica de enlace tardío (late binding) le evitará la
sobrecarga de llenar controles no utilizados. Puede utilizar una combinación de cualquiera de estas técnicas
dependiendo del tamaño anticipado de la tabla de búsqueda y solo controlarla con una propiedad y/o tabla de
opciones.
La VFP Developer's Guide le ofrece una sugerencia adicional: Hacer copias de las tablas de búsqueda
frecuentemente utilizadas a cada estación de trabajo. Como sea, el saber cuando mantener una versión local en
sincronía con la versión del servidor me suena como una pesadilla. Por lo tanto, recomiendo una de las
opciones anteriores.
Formularios Uno a Muchos
Los formularios uno a muchos deberían ser manejados como sigue. Cualquiera que sea la técnica que Usted
decida utilizar para formularios de tabla única debe ser utilizada para la porción del encabezado de un
formulario uno a muchos. Las vistas de las tablas hijas, deben de ser parametrizada con el ID único de la vista
madre. Así que cuando Usted navegue de un encabezado a otro, a la vista hija solo necesita ser pedida de
nuevo. Al utilizar este método, solo el hijo del encabezado actual necesita ser adquirido localmente.
Mantenga las vistas en su propio DBC para tratar software de soporte independientemente.
Si, en su diseño de marco de trabajo, las vistas son mantenidas en sus propios DBC, esto hará la transición a
otro software de soporte más sencilla. En otras palabras, cuando Usted desea que su aplicación accese a
fuentes de datos diferentes, desea mantener la mayor cantidad de piezas de su marco de trabajo intactas.
Mantener las vistas en DBCs separados de las tablas (en un escenario donde VFP es el software de soporte)
hará el acceso las fuentes de datos remotas una cuestión de accionar un interruptor. La implementación podría
ser como sigue:
• Cree un directorio llamado \VFPViews, y almacene un DBC de vistas locales en él. Nombre el DBC
como AppViews.DBC (\VFPViews\AppViews.DBC).
• Cree un directorio separado para cada fuente de datos que Usted desea accesar, manteniendo el mismo
nombre del DBC de la vista. En otras palabras el DBC \SSViews\AppViews.DBC contendría vistas
12
que accesan un SQL Server. Dado que frecuentemente existen varios lugares en su código donde se
referirá a las vistas de la base de datos, esto evitará que tenga que escribir código condicional para
cada software de soporte.
• En un archivo de inicialización de su elección, almacene la información tal como el nombre DSN del
ODBC, la ruta de directorios donde están almacenadas las vistas, una bandera indicando si accesa
datos locales o remotos y si son locales, la ruta de directorios de donde están almacenadas las tablas
de VFP.
• Establezca la ruta con Set Path To... al directorio de vistas apropiado al iniciar la aplicación.
• Si es un software de soporte en VFP, establezca Set Path To al directorio de red donde se almacenan
las tablas de VFP.
Aunque es cierto que podría añadir el nombre de la base de datos de vistas al archivo de inicialización,
frecuentemente esto ya esta codificado por toda la aplicación. El tener DBCs separados de las vistas también
mantendrá el tamaño de éstas manejable. Si el software de soporte fuese una base de datos de VFP,
combinarlos significaría que se necesitarían almacenar dos veces el doble del número de objetos de bases de
datos. Si tal fuera el caso, Modify Database podría tomarnos un tiempo excesivamente largo en un sistema
grande. También notará que el DBC de la vista (sin importar si contiene vistas remotas o locales) es
mantenido localmente, no en la red para ser compartido por todos los usuarios. Utilizar esta técnica brindará
mucha de la flexibilidad específica de usuario, como aprenderá luego en este artículo.
Todo este pedazo se siente como si se asumiera un almacenamiento local también, pero no está dicho
explícitamente.
Utilize Locate en vez de Seek para navegar en datos locales.
Dado que todos los datos son accesados a través de enunciados SQL en una aplicación cliente/servidor, Usted
pierde la habilidad de utilizar el mejor amigo de todo desarrollador de VFP, Seek. Enjuage esas lágrimas,
porque hay una alternativa: Locate. Dado que los conjuntos locales de resultados serán (por definición
cliente/servidor) relativamente pequeños, el comando Locate será más que suficiente para mover el puntero de
registro en un cursor local o una tabla temporal.
Aquí vienen aún mejores noticias. El conjunto de resultados de una vista es de hecho una tabla temporal. Para
ver a que me refiero. Abra cualquier vista y verifique el valor que regresa DBF(). Debe de ver algo como
C:\WINDOWS\TEMP\76633985.TMP. Así que esta "cosa" de hecho tiene una presencia en el disco. Y en
VFP, tiene una presencia en el disco que podemos indexar. Así que, siéntase libre de indexar al cursor de la
vista tanto como desee si siente la necesidad de la velocidad. Pero de todas formas, yo me quedaré con Locate
en vez de Seek. Dado que Locate es, además, optimizable con Rushmore, el utilizarlo con o sin índice no hará
que falle, uno solo será marginalmente más rápido que el otro.
Conclusión.
Bueno, solo estamos a la mitad, pero desde ahora estoy seguro de que puede ver la luz al fondo del túnel. Las
envolturas (wrappers) y las vistas son ciertamente las claves para hacer su código independiente del software
de soporte. Mantener estos conceptos en mente en todo momento le ayudará a hacer aplicaciones
cliente/servidor flexibles y escalables. El siguiente mes, cubriré seis pasos más y cerraré con un formulario de
ejemplo que puede accesar una base de datos VFP tan bien como una base de datos SQL Server. Luego, Usted
seguirá por su cuenta. Nos vemos el mes siguiente, y hasta entonces, comience a envolver.
Use claves primarias sustitutas para todos las tablas.
Incluso antes de que VFP 3.0 nos diera la capacidad de agregar claves primarias a nuestros claves, el uso de
un solo campo, o grupo de campos, para identificar de manera única una fila de la tabla ha sido sugerida por
13
muchos como una práctica sana. En aplicaciones con servidor de archivos, no obstante, en muchas situaciones
en realidad no había necesidad de hacer esto; era solamente una buena idea. Sin embargo, con vistas, las
reglas cambian.
Para utilizar vistas (views) actualizables, debe existir un clave primaria en la tabla en la cual se basa la vista
(generalmente no actualizo los datos de una vista multitabla). La razón es que debido a que la vista crea un
resultado local establecido cuando se abre (USE <NombreVista>), usted necesita algún mecanismo para poder
encontrar el registro subyacente en el servidor que está tratando de actualizar. Esto no siempre era necesario
en un modelo de servidor de archivos, donde un registro compartido puede ser directamente editado.
Para que las actualizaciones funcionen, las vistas requieren que usted establezca la propiedad KeyField en
tiempo de diseño en el Diseñador de Vistas (View Designer) o con DBSETPROP("Viewname.KeyField1",
"Field", "KeyField", .T.) o establezca la propiedad KeyFieldList en el cursor abierto en tiempo de ejecución
usando CURSORSETPROP("KeyFieldList", <lista delimitada por comas de los campos de la clave
primaria>).
En cualquier, la vista utilizará los campos de clave (key fields) para encontrar el registro en el servidor en el
que usted ha ejecutado SQL Update o SQL Delete. Las claves también serán utilizadas para detectar los
conflictos de la actualización, reportar un error si alguien ha borrado el registro que usted está intentando
actualizar o ha cambiado el clave. Aparte de ser una necesidad para las vistas actualizables, agregar claves
primarias a sus tablas también hace que solicitar un registro en particular sea más simple. Usted descubrirá
que en el mundo cliente/servidor necesitará a menudo esa capacidad.
El argumento para los claves sustitutas.
Si usted tiene campos múltiples que conforman sus claves primarias, sugiero utilizar además una clave
substituta generada por el sistema. Al tener este campo, típicamente de tipo entero, le dará el lujo de una
identificación única de la fila que le dará gusto tener. Además, para las tablas hijas Usted querrá agregar una
campo entero adicional como clave externa (foreign). Usted lo llenaría con la clave primaria sustituta del
padre (parent) antes de grabar al hijo (child).
Las ventajas de las claves sustitutas incluyen uniones (joins) más rápidas, actualizaciones más rápidas,
eliminaciones más rápidas y la recuperación simplificada de los hijos de un padre. La única desventaja está en
generarlas. Yo utilizo una tabla de sistema de números consecutivos, contiene una fila por la tabla en la
aplicación, pero esto puede causar problemas de conflicto multiusuario si no tiene cuidado. Debido a que estas
claves realmente no tienen significado (por eso también se llaman claves abstractas), usted no debe tratar de
obtener el siguiente número secuencial del servidor mientras que está a mitad de una transacción. Esto puede
crear demasiado conflicto en la tabla del sistema cuando usted necesita las claves primarias para una tabla
muy activa. Es mejor conseguir la clave exterior de la transacción y arriesgarse a perderla si una actualización
falla.
Otra idea puede ser utilizar una de las técnicas para generar una identificación única localmente, eliminando el
viaje al servidor y cualquier conflicto multiusuario posible. Igual como cuando nunca he dejado que se vaya
ningún contratista de mi casa sin haberme recomendado a un buen reparador de techos, nunca dejo que una
conversación con un desarrollador termine sin preguntar por su técnica para la generación de identificaciones
únicas. Dos interesantes que he escuchado que usan la generación del lado del cliente son:
• 1. Utilice un GUID. Son de 26 caracteres de largo, (supuestamente) únicas en todo el mundo y rápidas
para generarse. No son hermosas, pero valen la pena.
• 2. Consiga una identificación única de una tabla del sistema del lado del servidor al iniciar la
aplicación y después utilice una propiedad local de la aplicación como un contador. Concatene los dos
para generar una cadena alfanumérica única. Aunque estoy simplificando un poco, creo que esta
14
técnica es utilizada en Code Book.
Agregue una columna de tiempo a cada tabla.
Las aplicaciones cliente servidor utilizan casi exclusivamente un bloqueo optimista (optimistic locking).
Debido a eso, la verificación de conflictos multiusuario se convierte en más que un desafío de implementar.
Debe recordarse continuamente mientras codifica: "quizás no tengo la última versión de estos registros".
Aunque la clave primaria se utiliza para detectar conflictos de actualización, su capacidad se limita a detectar
si otro usuario ha suprimido el registro que usted está intentando actualizar o si ha cambiado la clave. La clave
primaria no le ayudará a determinar si otro usuario a hecho los cambios a los campos que no son parte de la
clave antes de hacer efectivos sus cambios.
Típicamente, las aplicaciones cliente/servidor utilizan una cierta forma de columna con una marca de tiempo
(timestamp), una que se modifica como parte de cada actualización, para indicar la última versión de un
registro. (Digo "una cierta forma de marca de tiempo" porque el tipo de dato puede ser casi cualquier cosa).
Esta columna entonces es revisada en el servidor durante cada actualización para determinar si está en
conflicto con la versión local de la estación de trabajo. Por ejemplo, una vista de VFP pudo generar el
siguiente estatuto SQL después de actualizar la vista del cliente:
Replace state With 'NY' For city = 'New York' In vcustomers
TableUpdate(.T., .F., "vcustomers"):
* This is what is auto−generated by the view
* and passed to the server.
* There would be one of these per updated record.
Update customers Set state = 'NY' ;
Where cust_pkey = ?vcustomers.cust_pkey and ;
timestamp = ?vcustomers.timestamp
Si este estatuto de actualización SQL falla con un error de conflicto de actualización (Update Conflict), usted
puede reportar a su usuario que alguien modificó este registro mientras lo editaba. Lo que haga después
depende de que tan amable sea. En última instancia, o debe hacer una solicitud (query) de nuevo o puede
intentar enviar un TableUpdate otra vez con el parámetro Force activado (que no se recomienda a menos que
proporcione al usuario una comparación campo por campo de los cambios).
La propiedad de la vista, WhereType, es responsable de la verificación de conflictos multiusuarios. Usted
tiene cuatro opciones para determinar qué campos son comparados cuando se genera la porción de la
verificación de conflictos del estatuto SQL. Key and Modified Fields (clave y campos modificados) y Key
and Updatable Fields (clave y campos actualizables) suenan como funciones poderosas, pero siento que en la
mayoría de los casos estas opciones son demasiado peligrosas como para implementarse. (No me gustaría que
el usuario 1 y el usuario 2 actualizaran dos columnas diferentes del mismo registro sin que cada uno conociera
primero los cambios del otro).
Esto nos deja Key Fields Only (campos de clave) y Key and Timestamp (clave y marca de tiempo). Key and
Timestamp solamente es soportada si su base de datos soporta una columna de tipo marca de tiempo
15
(timestamp). Esta columna es modificada automáticamente por el servidor en cada actualización. La
aplicación cliente no mantiene este campo, la vista solamente incluirá la columna en la verificación de
conflictos.
El SQL Server tiene soporte para una columna de marca de tiempo. De hecho, el asistente SQL Server
Upsizing Wizard tiene una opción para crear una columna de marca de tiempo para cada tabla. La columna no
es realmente de tipo de dato datetime, sino más bien una cadena única generada por el sistema. La belleza de
ella es que el servidor la maneja automáticamente y que es mucho más exacta que el tipo de dato datetime
(nunca tendrá que preocuparse por dos usuarios actualizando a la misma hora exacta).
Algunos comportamientos de los que hay que estar enterados al usar la columna de marca de tiempo
del SQL Server:
Si usted vuelve a hacer la petición (query) después grabar, la columna de marca de tiempo en el cursor de su
vista local estará actualizada con el valor actual en el servidor. Así, los intentos subsecuentes de grabar el
mismo registro fallarán con un error de conflicto de actualización (Update Conflict) cuando se comparen las
dos marcas de tiempo.
Soluciones para las limitaciones del asistente Upsizing Wizard:
El asistente de SQL Server Upsizing Wizard tiene muchos problemas que lo hacen, en mi opinión, casi
inutilizable. La mayoría de la gente que conozco ha tenido que escribir sus propias herramientas para resolver
las limitaciones. Una virtud que lo salva es que el código fuente de todos los asistentes y generadores ahora
viene con la versión 6.0 de VFP. No es realmente tan difícil modificar el código para superar sus limitaciones.
El código fuente está situado en \<localización de VFP 6.0>\Tools\Xsource\Xsource.zip.
Así que la clave y la marca de tiempo suenan excelentes para el SQL Server, pero ¿qué hay sobre los otros
sistemas de soporte (back−ends) que no soportan una columna de marca de tiempo? ¿Y cómo puede mi
prototipo con vistas locales de una base de datos de VFP funcionar con el mismo conjunto del código? Mi
solución es agregar mi propia columna de marca de tiempo y mantenerla del lado desde el cliente o desde un
disparador de actualización del lado del servidor. Entonces convierte el campo en un KeyField (campo de
clave) (véase el paso 7) y utiliza Key Fields Only WhereType. Así que su KeyFieldList podría ser cust_pkey y
timestamp. (Note que los campos en la propiedad de KeyField o KeyFieldList de una vista no tienen que ser
claves primarias o candidatas).
Usted puede utilizar una columna de tipo datetime para este propósito. Sin embargo, la columna datetime de
VFP tiene precisión de un segundo solamente, así que es posible que dos usuarios puedan actualizar el mismo
registro dentro del mismo segundo. Si usted siente que esto es un problema potencial para su aplicación,
utilice otra técnica para generar un número o cadena única. Yo utilizaba esta solución hasta que empecé a usar
SQL Server e incluí su columna de marca de tiempo. (Dado que usted no tiene ningún control sobre este
nombre de campo, puede llamar su columna de marca de tiempo en VFP algo diferente para que no haya
conflicto cuando cambie a SQL Server). Entonces puede simplemente cambiar el WhereType a Key and
Timestamp (clave y marca de tiempo) y ya está listo.
Vida sin fechas.
Era un poco inquietante, bueno, quizá era más como una sacudida repentina, descubrir que el SQL Server,
como la mayoría de las bases de datos del grandes, no soporta un tipo de dato para fechas (date). Debe utilizar
un campo de tipo datetime. Si usted está convirtiendo una aplicación existente de VFP, que utiliza campos de
fecha (date), los problemas podrían ser muy extensos. Realmente depende de cómo utiliza las fechas su
aplicación y de cuánto le importa ver 12:00:00 al final de sus campos de la fecha. He resumido los problemas
a los que me he enfrentado así como sus posibles soluciones.
16
Los controles limitan hasta los campos de la fecha.
En los casos donde es simplemente inapropiado mostrar la porción de la hora de un campo del datetime,
obviamente usted tiene que realizar alguna clase de truco para perder la hora predeterminada de 12:00:00 AM.
Tiene varias opciones. Usted podría utilizar DBSetProp para cambiar el datatype de la definición de la vista de
datetime a date. (Como se mencionaba en la parte 1, teniendo un DBC local de vistas elimina cualquier
problema de conflicto multiusuarios).
Create SQL View vOrder Remote Connection ;
Remote1 As Select * from orders
DBSetProp("vOrders.orderdate", ;
"Field", "DataType", "D(8)")
Use vOrders
Hacerlo así truncará no sólo la porción del tiempo, sino que también le proporciona un campo de tipo fecha
(date) de VFP en su vista, permitiendo a su código específico de fechas funcionar sin cambiarlo. Y
afortunadamente, cuando usted ejecuta una TableUpdate en una vista con un campo del tipo fecha en él, se le
anexa una hora predeterminada de 12:00:00:00 AM automáticamente en el SQL Server... sin error.
Si usted tiene controles que no estén enlazados a una vista sino a un cursor de solo lectura SQL Pass−through
(SPT), puede utilizar la función de conversión de tipos de datos del software de soporte (back−end) para
hacer el truncamiento. Aquí está un estatuto SQL que se podría pasarse al SQL Server que haría exactamente
eso:
SELECT orderid, ; customerid, ; CONVERT(char(10), orderdate, 101) as orderdate ; from orders
Observe que 101 refiere al argumento opcional de estilo de la función del CONVERT. 1 indica formato
americano, mm/dd/yy. Luego le suma 100 si desea el siglo incluido.
Para hacer que la función CONVERT funcione para datos locales también, usted puede crear un
procedimiento almacenado (stored procedure) llamado "Convert" en la base de datos de vistas. Haga que
acepte los tres argumentos de la función de CONVERT del SQL Server y, si se le pasa un datetime, que
regrese SubStr(TToC(pDateTimeField), 1, 10). Usted también necesitará escribir un procedimiento
almacenado Char para manejar el tipo de dato de la función del SQL Server, char(10).
Los campos datetime no pueden estar "vacíos".
Como si perder los campos de fecha no fuera ya bastante malo, usted también debe enfrentarse con el hecho
de que los campos datetime no pueden estar vacíos en la mayoría de las bases de datos. (Hay un mundo
completo de funcionalidad limitada más allá de FoxPro, ¿verdad?) Las columnas datetime deben ser llenadas
por una fecha y hora válidas o ser nulas (Null).
Si usted permite que sus campos datetime acepten Null, solucionará rápidamente el problema de la interfase
de usuario pero probablemente generará otros. Podría experimentar resultados inesperados al introducir
ciegamente Nulls. (Por ejemplo, la comparación de dos fechas donde una es nula devuelve Null, Empty
(FechaNula) devuelve .F., etcétera). Pero si usted está preparado para enfrentar estos problemas, Null
realmente es práctico al presentar datos a los usuarios gracias al comando de ambiente Set NullDisplay to de
VFP y a la propiedad NullDisplay de los controles como la caja de texto (text box).
17
Si usted no desea utilizar Nulls, VFP y el SQL Server son en realidad buenos con usted. El enviar una fecha
vacía al servidor del SQL no causa ningún error. Sin embargo, el SQL Server creará un datetime
predeterminado de 01/01/1900 12:00:000 AM. Ahora, cuando usted solicita esos datos, usted verá 01/01/1900
en el cursor local, suponiendo que usted ha utilizado alguna de las técnicas discutidas anteriormente para
truncar la hora. No creo que sea algo tan horrible como para que un usuario no deba verlo, pero si usted ha
elegido establecer Set Century Off, 01/01/00 parecerá como el 1 de enero del 2000. (Como si los problemas
con el año 2000 no fueran suficientes, ¡al cambiar a SQL Server se crearán problemas del año 1900!)
He creado tres posibles soluciones. La primera es agregar un valor predeterminado a todos los campos
datetime, uno que ponga en claro que esta es una fecha falsa, como 01/01/9999. (Observe que el tipo de dato
datetime tiene un rango de fecha del 1 de enero de 1753 al 31 de diciembre del 9999). Una vez más usted
necesitaría establecer Set Century On para distinguirlo del 1999. Una opción similar es agregar un valor
predeterminado de la columna a todos los campos datetime que sigan alguna regla de negocios. Por ejemplo,
todas las fechas de envío (Ship Date) pueden estar predeterminadamente a ocho semanas después de la fecha
de la orden (Order Date), que es un campo requerido.
La tercera opción implica más malabarismos. ¿No sería fantástico si hubiera alguna manera de borrar cada
fecha igual a {01/01/1900} en el cursor de la vista abierta, no enviar ninguna actualización al servidor y no
dejar ningún cambio pendiente? Bien, puede hacerse y es algo como esto:
Procedure OpenView
LParameters pcView
* Retrieve data form server
Use (pcView) in 0
* Prevent update statements from going to server
CursorSetProp('SendUpdates', .F., pcView)
* Strip 01/01/1900 out of this cursor
RemoveDefaultDates(pcView)
* Remove pending changes/clear the change buffer
TableUpdate(.T., .T., pcView)
* Restore update capability to view
CursorSetProp('SendUpdates', .T., pcView)
Por brevedad, no proporcionaré el código para la función RemoveDefaultDates. Pero todo lo que lo hace es un
ciclo por todas las columnas con campos del tipo fecha (date) y los substituye por {} si son iguales a
{01/01/1900}. Ahora todas las pantallas de entrada de datos se verán y funcionarán como lo hacían cuando el
software de soporte (back−end) era solo VFP.
Bien, esto funciona para las vistas, pero qué hay sobre los cursores del SPT? Solo necesita ejecutar la función
RemoveDefaultDates sobre cualquier cursor que será presentado eventualmente al usuario.
18
Diferencias entre los cursores SPT remotos y cursores SPT locales:
Los cursores del SQL Passthrough son de solo lectura cuando están creados de fuentes de datos de un servidor
de archivos, pero de son de lectura/escritura cuando están creados de una fuente de datos cliente/servidor. Así
pues, si usted está haciendo prototipos localmente, por supuesto necesitará cambiar el cursor a
lectura/escritura antes de modificarlo.
Matemáticas de fecha.
Usted necesita estar atento de cualquier matemática de fechas que pudiera tener en su aplicación. Dado que
tiene campos datetime en el servidor (y puede tenerlos en un cursor local también) donde tenía originalmente
campos date, pueden ocurrir errores en los cálculos. Por ejemplo:
?Date() + 20 && 03/08/99 + 20 = 03/28/99
?Datetime() + 20 && 03/08/99 10:35:15 PM + 20 = ;
&& 03/08/99 10:35:35 PM!
* against SQL Server:
* ExpectedDate is 14 DAYS after order date
Select orderdate, ;
Orderdate + 14 as ExpectedDate ;
From Orders
* against VFP:
* ExpectedDate is 14 SECONDS after order date
Select orderdate, ;
Orderdate + 14 as ExpectedDate ;
From Orders
Puede ver que el agregar un número a un campo datetime puede tener resultados diferentes basados en el
software de soporte (back−end). Es difícil decir si se agregarán segundos o días. Una solución podría ser
convertir el número incremental en una función. De ese modo, Usted tiene lo necesario para determinar cual
es el software de soporte y proporcionar los cálculos apropiados.
Evitar esa sensación de Empty() con los Nulls.
Si usted es como yo, quizás usted ha aprovechado la flexibilidad que proporciona la función Empty() de VFP.
Empty(eExpression) funciona para cualquier tipo de datos, excepto los objetos. Puede detectar cero, una
cadena vacía, una fecha vacía o falso lógico con esta una función sin ni siquiera verificar el tipo de dato. Mala
movida.
19
Los problemas pueden ocurrir porque después de cambiar a SQL Server, puede ser que encuentre algunos
valores inesperados de funciones y de solicitudes (queries). Puede ser que incluso estén en un tipo de datos
diferente al que usted esperaba. Cuando estos valores se evalúan con Empty(), o cualquier función en este
caso, podría causar problemas importantes. Por ejemplo:
?Empty({}) && true
?Empty({01/01/1900}) && false
?Empty(Null) && false
Usted ha visto el problema con {01/01/1900} en el paso 9, la vida sin fechas, así que sabe lo que puede
ocurrir. Borrarlos localmente parecen ser la solución más fácil. ¿Pero qué hay sobre el problema de Null? La
mayoría sabe que nulo no es vacío, pero ¿sabía que incluso si usted no tiene ninguna columna en su base de
datos que acepte Null, puede ser obtenga valores Null del servidor? Intente esto en el SQL Server:
Function GetMaxOrderQty
lcSQL = "SELECT MAX(Quantity) AS nMaxQty" +;
" FROM [Order Details] " +;
" WHERE ProductID = 99 "
SQLExec(lcSQL, "cBigOrder")
If Reccount("cBigOrder") > 0
lnRetval = 0
Else
lnRetval = cBigOrder.nMaxQty
Endif
Return lnRetval
¿Cuál es el tipo de dato del valor de retorno? Depende. Si se encontraron registros para ProductdID = 99, la
respuesta es numérica. Si no hay registros encontrados para ProductdID = 99, la respuesta es Null, a pesar de
que la columna de cantidad (Quantity) no soporta valores Null.
La razón es que en solicitudes SPT para el SQL Server, usted no obtendrá un cursor resultante sin registros
para solicitudes de agregación como esta. Usted obtendrá un registro sin importar si se encontraron
coincidencias, a menos que usted tenga una cláusula Group By una columna que no sea de agregación. Y en
las columnas de agregación tendrán un valor Null. Eso podría causarle problemas en muchos lugares si no
tiene cuidado de evitarlo.
En el ejemplo precedente, lnRetval sería nulo porque no hay ningún ProductID = 99. Para evitar obtener
valores nulos, usted tiene varias opciones. Por supuesto, encontrar todas las funciones de agregación en su
código y hacer un IsNull() el chequeo es una manera. Podría también escribir una envoltura (wrapper) para
Empty, quizás IsEmpty, que detecte Null y 01/01/1900 también. Esto se puede utilizar para verificar los
20
valores de diversos tipos de datos sin temor de encontrar resultados inesperados.
fialmente, agregando la función Count(), como en Count(*) as Cnt, a todas las solicitudes como éstas, le da
una manera común de verificar si hay un valor retorno. La columna Cnt será siempre numérica, así que ahora
puede verificar si Cnt > 0 en vez de Reccount() > 0 sin temor a los Null.
El caso para los controles no enlazados.
Ya ha visto muchos problemas al trabajo con los campos que son de diferentes tipos de datos, dependiendo
del software de soporte (back−end). Yo los veo también. Estos problemas hacen que sea muy difícil de
escribir un conjunto del código para accesar múltiples fuentes de datos. Obviamente, no todas las aplicaciones
requieren tal flexibilidad, pero para los que necesiten o quieran esta funcionalidad, los controles no enlazados
(unbound) pueden ser la respuesta.
Los controles no enlazados en un formulario no tienen tipo de dato. Usted toma el resultado de una solicitud y
esencialmente "pinta" los controles con los valores del contenido del campo. Eso le daría lo necesario para
manipular los datos de cualquier manera que necesite antes de presentarlos al usuario.
Esto proporciona gran flexibilidad, pero también una tonelada de codificación. ¿Puede usted imaginarse
cuánto código tomaría para leer los datos, pasar por todos los controles, para pintar la forma y luego pasar de
nuevo por los controles para crear el código SQL que escribirá las actualizaciones al servidor? Eso sin
mencionar el tener que agregar el código de validación de tipo de dato a nivel de cada campo que obtiene
cuando usa controles enlazados (bound). El desarrollador promedio de FoxPro esta tan acostumbrado a usar
los controles enlazados que preferiría masticar vidrio antes que escribir todo ese tedioso código. Debe haber
una manera mejor.
Si tuviera que vivir mi vida otra vez, cambiaría solamente una cosa: Habría creado otra capa de abstracción de
datos. (Bueno, dos cosas: también habría comprado acciones de Microsoft hace 10 años. ¿Quién sabía?) La
capa adicional sería implementada en la forma de un cursor de lectura/escritura con la misma estructura y
contenido del resultado de la vista.
Los controles se pueden estar todavía enlazados a una fuente de datos, mientras que al mismo tiempo usted
tiene la capacidad de manipular el cursor de cualquier manera antes de presentarlo. El proceso sería algo como
esto:
Procedure Load
* For this test, open the form with data.
Use vOrders In 0
* Get a copy of the result set.
Select * from vOrders Into Cursor cOrders1 NoFilter
* Make a read−write copy of cOrders1.
Use (DBF()) In 0 Again Alias cOrders
* Close the temporary read−only cursor.
Use In cOrders1
21
*
* Now the Init of the controls fire,
* which are each bound to cOrders.
* cOrders will be the form's master alias.
EndProc
Ahora solo edite el formulario como de costumbre. En Save, vacíe el cursor editado nuevamente dentro de la
vista y ejecute una TableUpdate en ella. Todo el marco de trabajo basado en vistas discutido anteriormente
sería aplicable, simplemente está usando una capa adicional más. Y con conjuntos de resultado pequeños, un
requisito cliente/servidor, junto con el veloz motor de datos de VFP, el desempeño no debe ser un problema.
Al darse usted mismo esta oportunidad de manipular los datos antes de presentarlos, obtiene lo mejor de los
controles enlazados y no enlazados. Esto hará posible la adición de nuevas funciones donde los datos
subyacentes no coinciden con lo que ve el usuario, por ejemplo, de diferentes monedas, multilingüe y las
representaciones carácter de datos como 4 DOZ (4 dozen, cuatro docenas).
Creando un formulario que tenga acceso a múltiples software de soporte
He proporcionado un formulario que es indicativo de cómo Usted puede diseñar un formulario que necesite
tener acceso a fuentes de datos múltiples. Es tosco y sucio, con todo el código de nivel de instancia, para
facilitar el aprendizaje, pero siempre podrá mejorarlo una vez que comprenda el diseño. También por
simplicidad, he creado solamente un campo de filtro y no he implementado Query by Form.
Antes de que usted pueda ejecutar la forma, haga lo siguiente: extraiga los archivos y las carpetas, que están
en el archivo o código, en cualquier directorio. Usted encontrará el formulario Orders, los gráficos BMP para
completar el formulario y main.prg. El subdirectorio AppData contiene una copia de la base de datos
Northwind Traders en formato de VFP. (Utilicé el asistente de transformación de datos del SQL Server para
hacer esto). El directorio SSViews contiene una vista remota, vOrders, en un DBC llamado AppViews. De
manera similar, el directorio VFPViews contiene una vista local, vOrders, en un DBC llamado AppViews.
(Revise el paso 5 en la primera parte del artículo para más información sobre la estructura de archivos y
directorios).
Ejecute la versión 6.0 de VFP y fije el valor por defecto al directorio donde extrajo los archivos. Tenga el
SQL Server 7.0 instalado y ejecutándose. En el panel de control, cree un DSN ODBC de usuario que conecte
con la base de datos Northwind Traders y llámelo NORTHWIND_SS7.
Para ejecutar el formulario contra el SQL Server, ejecute: MAIN("NORTHWIND_SS7"). Para ejecutar el
formulario contra VFP como software de soporte, ejecute: MAIN("NORTHWIND_VFP"). Como usted puede
ver en la figura 1, la barra del título indica la fuente de datos. Elija la Find (encontrar), introduzca un cliente
como "VINET" y después elija Retrieve (extraer). Los cinco pedidos para este cliente serán extraídos del
servidor.
Formulario de ejemplo.
No se emocione mucho buscando código mágino, porque realmente no hay ninguno. De hecho, no sólo es
simple, sino casi idéntico al código que usted vería en un formulario enlazado a tablas intermedias.
22
Las únicas diferencias son la adición de la cláusula NoData del comando del USE en Load, la configuración
de la variable privada cCustomer que sirve como el parámetro de la vista y la llamada subsiguiente de la
función de Requery que extraerá los datos para el cliente apropiado.
Éstos son los únicos dos procedimientos que pudieran generar algún interés remoto:
PROCEDURE Load
* Important for local views
Set Exclusive Off
* Need this for table buffering
Set MultiLocks On
Set Data To AppViews
* No data on load, please
Use vOrders NoData
* Set optimistic table buffering
CursorSetProp("Buffering", 5)
ENDPROC
PROCEDURE cmdfind.Click
PRIVATE cCustomerID
cCustomerID = ""
IF Thisform.PendingChanges()
WITH Thisform
.Lockscreen = .T.
IF Not Thisform.lFindMode
thisform.lFindMode = .T.
* Change to find mode
this.Caption = "\<Retrieve"
.SetAll("Enabled", .F., "textbox")
.SetAll("Enabled", .F., "_commandbutton")
23
.txtCustomerID.Enabled = .T.
.txtCustomerID.Setfocus()
ELSE && In find mode, so Retrieve
.SetAll("Enabled", .T., "textbox")
.SetAll("Enabled", .T., "_commandbutton")
this.Caption = "\<Find"
.txtCustomerID.Enabled = .F.
cCustomerID = .txtCustomerID.Value
Requery()
thisform.lFindMode = .F.
ENDIF
.cmdfind.Enabled = .T.
.txtOrderID.Enabled = .F.
.Refresh()
.Lockscreen = .F.
ENDWITH
ENDIF
ENDPROC
SQL
SQL se ha establecido claramente como el lenguaje estándar de base de datos relaciónales. Hay numerosas
versiones de SQL.
La versión original se desarrollo en el laboratorio de investigación de San Jose de IBM. Este lenguaje,
originalmente denominado Sequel se implemento como parte del proyecto System R , a principios de 1970.
El lenguaje Sequel ha evolucionado desde entonces y su nombre ha pasado a ser SQL (Structured Query
Lenguaje) lenguaje estructurado de consultas. Actualmente, numerosos productos son compatibles con el
lenguaje SQL.
• En 1986, ANSI(Instituto Nacional Americano de Normalización) e ISO(Organización Internacional
de Normalización) publicaron una norma SQL, denominada SQL−86.
• En 1987, IBM publico su propia norma de SQL corporativo, interfaz de base de datos para
(Arquitecturas de aplicación a sistemas) SAA−SQL.
24
• En 1989 se público una norma extendida para SQL denominada SQL−89, y actualmente los sistemas
de base de datos son normalmente compatibles al menos con las características de SQL−89. La
versión actual de la norma SQLANSI/ISO es la norma SQL−92
En este apartado presenta una versión general de SQL basada en las normas SQL−89 y SQL−92. se debe tener
en cuenta que algunas implementaciones de SQL pueden ser compatibles solo con SQL−89, no siendo con
SQL−92.
SQL incorporado permite el acceso a una base de datos desde un programa escrito en un lenguaje anfitrión,
pero no proporciona ninguna asistencia en la presentación de los resultados al usuario o en la generación de
informes.
La mayoría de los productos comerciales relacionados con base de datos proporcionan un lenguaje a la hora
de crear la interfaz de usuario y dar formato a los datos para la generación de Informes.
Estos lenguajes especiales se denominan lenguajes de cuarta generación.
Conclusión.
Espero que usted no deje que ninguno de estos problemas lo disuada de migrar a SQL Server. Sí, es mucho
trabajo. Pero es más aburrido que complicado. Espero que este artículo le ahorre mucho tiempo al informarle
de muchos de los problemas antes de que usted incluso comience.
Respecto a tener un conjunto del código que tenga acceso a una base de datos de VFP así como bases de datos
cliente/servidor, no la recomiendo para el largo plazo. Hay un demasiado código condicional requerido y
duplicación de las herramientas que se deben escribir para asegurarlo. Al final, usted terminará probablemente
con un sistema que será ineficaz con todos el software de soporte, aunque siempre podrá superar la situación
agregando más hardware. Yo usaría vistas locales solamente si planeara definitivamente cambiar al servidor
del SQL, Oracle, etcétera. Después de la etapa de prototipos, recomiendo que usted migre gradualmente su
código para trabajar remotamente solamente.
La capacidad del SQL Server 7.0 para trabajar sobre los Windows 95/98 y NT y muchas características
nuevas hacen la migración de VFP un proceso mucho más sencillo. Puede servir fácilmente como su
solamente software de soporte, mientras que usa el motor de los datos de VFP para procesar los datos
extraídos del servidor. Juntos, hacen a un equipo perfecto.
25
Descargar