INSTITUTO SUPERIOR TECNOLÓGICO NORBERT WIENER Manual del Alumno ASIGNATURA: Lenguaje de Programación II (Visual Basic II) PROGRAMA: S3C LIMA-PERU 2 Manual del Alumno Índice General Pag N° 1. Introducción y base de datos....................................................................................... 2 2. Control Ado ............................................................................................................... 4 3. El Control Ado Data Control 6.0 (OLEDB)................ .............................................. 8 4. El Control Ado Data Control 6.0 (OLEDB)(continuación)....................................... 44 5. El Control Ado Data Control 6.0 (OLEDB)(continuación)....................................... 50 6. Controles Enlazados .................................................................................................. 56 7. Uso de Múltiples Tablas ............................................................................................ 57 8. Uso de Múltiples Tablas (continuación)..................................................................... 58 9. El Data Environment ..................................................................................................63 10. Visual Basic – SQL ................................................................................................... 69 11. Controles no Enlazados ..............................................................................................70 12. Controles no Enlazados y Módulo de Clase................................................................78 13. Controles Active X......................................................................................................86 14. Funciones Api’s.........................................................................................................91 3 Manual del Alumno INTRODUCCION Y BASE DE DATOS Acceso a Datos: Los datos se consideran uno de los activos más valiosos de la empresa moderna. Por tal razon, es importante que la misma cuente con una estrategia apropiada para administrarlos adecuadamente; esto es, almacenarlos, recuperarlos y procesarlos de tal forma que sean importantes para la toma de decisiones. Visual Basic provee soporte a datos de muchas maneras. Se puede utilizar DAO (Data Access Objects) para manipular datos provenientes de Microsoft Jet o Access o Excel, ODBC (Open Database Conectivity), ISAM y tecnologís RDO (Remote Data Object) y ADO (Actives Data Object). Los Objetos de acceso a datos (DAO) y el control Data utilizan el motor de base de datos Microsoft Jet para tener acceso a las bases de datos. El motor de base de datos Jet puede obtener acceso a tres tipos de bases de datos: ® Bases de datos Microsoft Jet: Es un motor utilizado por Microsoft Access y Visual Basic. Este motor permite crear y manipular datos directamente. ® Bases de datos del Método de acceso secuencial indizado (ISAM, Indexed Sequential Access Method): Dada la existencia de formatos conocidos y extendidos mundialmente, como por ejemplo, Btrieve, dBase, Microsoft Visual FoxPro, Paradox, entre otros, DAO permite la manipulación de datos de éstos formatos. ® Bases de datos compatibles con Open Database Connectivity (ODBC, Conectividad abierta de bases de datos): No solo las bases de datos de escritorio o denominadas “desktop” tienen soporte por parte de DAO. Las bases de datos del estilo cliente/servidor que se ajustan al estándar ODBC, tal como SQL Server, Oracle u otros a los cuales se diseñó su apropiado “provider” se le da soporte. Visual Basic también provee otros métodos de acceso a datos, tal y como: ® Control de origen de datos remotos En la edición empresarial de Visual Basic se dispone de un control de origen de datos remotos, que soportan ODBC, tal como Microsoft SQL Server y Oracle. 4 Manual del Alumno ® Bibliotecas ODBC Le permiten llamar directamente a la interfaz de programación de aplicaciones (API) de ODBC. Está disponible como un producto independiente. ® Bibliotecas SQL para Visual Basic (VBSQL) Proporcionan un vínculo directo a Microsoft SQL Server. Están disponibles como productos independientes. Descripción de una Base de Datos: Hoy día la mayoría de sistemas informáticos que administran la información de una empresa utilizan un esquema relacional. Esto es, relacionan logicamente la información utilizando tablas y entidades que conforman llaves de enlace. Un esquema relacional presenta los datos como un grupo de tablas que se relaciona logicamente entre si. Por ejemplo, Visual Basic provee la base de datos Biblio.Mdb y en ella radican varias tablas de datos (tales como author y titles) que se relacionan entre si gracias a la duplicacion que existe en ellas del ID u otros campos. Se puede observar el diagrama de base de datos denominada Students And Classes elaborada mediante asistente en Visual Foxpro, versión 6.0. Observense los campos, índices y relaciones contenidos: Elementos de una tabla: La base de datos Students and classes contiene varias tablas que agrupan información, como studentes, assigments, results, entre otras. En una base de datos, las filas de tablas se denominan registros o tuplas y las columnas campos. Los registros pertenecen a la descripción de una entidad, por ejemplo, un cliente, un proveedor, un empleado, un producto, entre otros. Por otro lado, el campo 5 Manual del Alumno es parte del registro como puede ser la cedula, el nombre, la existencia, la direccion, el telefono, entre otros. Asimismo, existen índices (que pueden ser de diferentes tipos) y reglas (reglas de validación y de integridad referencial). Clave principal: Cada tabla contenida en una base de datos posee una clave principal. Esta es un campo, o la combinación de varios campos, que es exclusiva en cada fila de la tabla. Esto permite identificar de forma uniequivocamente un registro particular, permitiendo que no existan registros duplicados o accesar más rapidamente a un registro o a una colección de ellos. En el caso de la base de la base de datos Students and classes, se tiene una tabla denominada students, donde la clave primaria es studentid, dado que la identificación de todos y cada uno de los estudiantes es única en la colección de registros. Asimismo, en la tabla results se tiene una clave por el mismo campo, siendo una llave que se puede repetir tantas veces como sea necesaria, dado que un estudiante puede registrar todos los resultados obtenidas por materia. En este caso se determina una clave externa, dado que la misma no es propia de la tabla sino de otra que es students. Registros: Un registro contiene información referente a una entrada a una tabla. Es preferible que una tabla no contenga duplicados. En este caso es una tabla maestra. En el caso de la tabla Students, es necesario que cada estudiante tenga su propio Id y no exista otro similar en la tabla. Sin embargo, en registros históricos se repetirá tantas veces una llave como sea necesario. Campos: Un campo de una tabla identifica una parte de un registro. Por ejemplo, la tabla students contiene los campos studentid, lastname y firstname, entre otros. Indices: Los indices de una tabla son listas ordenadas en las que se puede localizar más rápidamente un determinado registro o conjunto de ellos. Se crean en base a un campo de la tabla, tomando como requerimiento cuál de ellos representa una entidad fuerte dentro de la colección o cuál es el adecuado para ordenar un conjunto de registros. Conectividad a datos en Visual Basic: Existen dos métodos para abrir una base de datos en Visual Basic: utilizando el Data Control (el cual es un objeto provisto por Visual Basic) o mediante el método OpenDatabase. El control data es un objeto provisto por Visual Basic para la manipulación de datos en una o varias tablas. El control data implementa este acceso mediante la utilización de un motor de bases de datos tal como Microsoft Jet. Existen métodos apropiados para la manipulación de datos y se provee de una herramienta de generación automática de formularios que se encarguen de dicha manipulacion, sin necesidad de escribir mucho código. 6 Manual del Alumno Para construir una aplicación de bases de datos, utilizando el control data, se recomienda seguir los siguientes pasos: 1. Agregar un control data al formulario y configurar las propiedades Databasename, connect y recordsource, principalmente. Lo anterior para establecer la base de datos a la que se va a conectar el formulario, el tipo de conexión (por defecto es Microsoft Access) y el origen de datos (la tabla de la base de datos abierta). 2. Agregar controles tales como text box o label para que contengan los datos del recordsource. Aqui hay que configurar datasource y datafield como las propiedades que se conectarán a los datos del data control. Usar controles enlazados a datos: Cuando se utiliza un data control en un formulario generalmente es necesario diseñar controles que permitan contener los datos cargados en dicho data control. Estos controles pueden ser CheckBox, Image, Label, PictureBox, TextBox, ListBox, ComboBox y los contenedores OLE. Propiedades y Métodos del Control Data: Además de las propiedades Databasename y RecordSource el data control posee otras propiedades y métodos, tales como: ® Propiedad Connect ® Propiedad Exclusive ® Propiedad ReadOnly ® Propiedad Recordset ® Propiedades BOFAction y EOFAction ® Método Refresh La Propiedad connect especifica el tipo de base de datos que se abrirá. Puede incluir argumentos tales como Id de usuario y la contraseña. La propiedad Exclusive determina si la base de datos será abierta en forma exclusiva o no. Si el valor de esta propiedad es True la base de datos se abrirá exclusivamente y ninguna aplicación podrá utilizarla hasta que se cierre. La propiedad ReadOnly determina si la base de datos se abrirá para solo lectura o no. Si la propiedad es True no se podrán modificar los datos obtenidos. El objeto Recordset contiene los registros devueltos para el data control, basado en una apertura de una tabla o producto de la ejecución de una instruccion SQL. Un recordset tiene propieades y metodos que se pueden utilizar para trabajar con los registros obtenidos. Las propiedaes BOFAction y EOFAction determinan que acción realizar cuando las propiedades BOF o EOF del Recordset son True. Por ejemplo, si la propiead EOFAction del control data es vbAddNew y utiliza el control data para situarse trás el último registro del recordset, el control data ejecutará automáticamente el metodo AddNew para incluir un nuevo registro. 7 Manual del Alumno El método Refresh actualiza un objeto recordset. Si se cambia la propiedad RecordSource del data control, se deberá llamar a este método para actual el conjunto de registros en el data. Lo siguiente es un ejemplo de lo anterior: Data1.RecordSource = "SELECT * FROM Empleados " & _ "WHERE [IdEmpleado] = " & txtEmpID.text Data1.Refresh Recordset: Es el conjunto de registros contenidos en el control data. Un Recordset se almacena en la memoria, transfiriendo los datos contenidos al disco, si fuera necesario. Para manipular un Recordset debe utilizarse la propiedad del mismo nombre del data control. Para determinar los límites de un Recordset se utilizan las propiedades EOF y BOF. Cuando se desplace al último registro (EOF) o al inicio del archivo (BOF), se ejecutará la acción indicada por el valor de la propiedad BOFAction o EOFAction. Seguidamente se muestra como Microsoft grafica estas propiedades: Asimismo, se muestra como utilizar un objeto Recordset de un determinado control Data: Data1.Recordset.MoveNext 'Va al registro siguiente. If Data1.Recordset.EOF Then Data1.Recordset.MoveLast End If El ejemplo anterior muestra como desplazarse al registro siguiente en el Recordset y posteriormente validar si se ha llegado al final del mismo. Se pueden utilizar otros métodos y propiedades del objeto Recordset para recuperar, agregar, modificar o eliminar registros de este objeto. Las propiedades BOF y EOF del objeto Recordset indican si el registro actual está al inicio o al final del Recordset, respectivamente. Si lo anterior es verdadero, se asignará True a la propiedad respectiva. Si ambas son verdaderas es que no existen datos en el Recordset. 8 Manual del Alumno Método AddNew: El metodo AddNew de un objeto Recordset se utiliza para agregar un nuevo registro. Cuando se ejecuta este metodo, Visual Basic elimina los controles enlazados y asigna a la propiedad EditMode del control data el valor dbEditAdd. Para actualizar un registro en el Recordset luego de utilizar AddNew debe utilizarse el método UpdateRecord o Update. Lo siguiente muestra este aspecto: Sub cmdAdd_Click () Data1.Recordset.AddNew End Sub Metodo UpdateRecord: El método UpdateRecord se utiliza para guardar el registro actual en una base de datos. Es decir, posterior al metodo AddNew, tal y como se muestra a continuación: Sub cmdUpdate_Click () Data1.UpdateRecord End Sub Metodo CancelUpdate: Se utiliza para cancelar un método AddNew o Edit y actualizar los controles enlazados al control data. Es decir, restaura los datos originales del Recordset. A continuación se muestra el uso de este método: Sub cmdCancel_Click () Data1.CancelUpdate End Sub Metodo CancelUpdate: Este método se utiliza para eliminar un registro de una tabla. El registro eliminado continuará siendo el actual hasta tanto el usuario no realice ninguna otra acción, tal y como se muestra en el codigo siguiente: Sub cmdDelete_Click () Data1.Recordset.Delete Data1.Recordset.MoveNext If Data1.Recordset.EOF Then Data1.Recordset.MoveLast End If End Sub 9 Manual del Alumno Usando Objetos Recordset Un recordset es un objeto que presenta los registros de una tabla o el resultado de una consulta. Se pueden usar objetos para manipular los datos de una base de datos a un nivel de registro. Se puede usar objetos “Field” para manipular los datos de un archivo a un nivel de registro. Los cuatro tipos de recordset son: Table, Dynaset, Snapshot y Forward-Only Table: Este Recordset puede ser creado para una tabla Microsoft Access, pero no soporta ODBC (Open Database Connectivity) o tablas ligadas. Es decir, funciona para una sola tabla de Microsoft Access. Cuando se crea una tabla, el motor de bases de datos JET abre dicha tabla y subsecuentemente se produce la manipulacion de los datos, operando directamente sobre la misma. Una ventaja importante de este tipo de recordset es que se puede utilizar un indice. Esto hace más eficiente la ordenacion de registros mediante un índice, mejorando significativamente las opciones de búsqueda. Para localizar un determinado registro se puede utilizar el metodo SEEK y no FIND, dado que este ultimo es mucho más lento. Dynaset:Este recordset puede crearse sobre una o varias tablas, mediante consultas. Esto es mediante un conjunto de referencias a registros en una o más tablas. Con un dynaset, se pueden extraer y actualizar datos desde una o varias tablas, incluyendo las ligas que puedan existir con otras bases de datos. La actualizacion heterogenea es una caracteristica importante de los dynaset. Uno de los más importantes beneficios es que los cambios realizados sobre una tabla se realizan tambien en otra que se encuentra ligada a ésta. Asimismo, los cambios hechos por otros usuarios se reflejan en el dynaset. El dynaset es el más flexible y poderoso de los demás tipos de recordset, sin embargo es mucho más lento que, por ejemplo, el tipo table. Snapshot:Este recordset es una copia estática de un conjunto de registros, capturados en el momento de crearse el recordset. Este puede contener campos de una o más tablas de una base de datos. Este recordset es actualizable. La principal ventaja de un snapshot es que éste crea menos “overhead” en el procesamiento que los demás recordset. Además se pueden ejecutar consultas y retornar los valores más rápidamente, especialmente cuando se trabaja con ODBC. Se debe considerar que para archivos MDB, OLE y archivos memo son direccionados por punteros en la consulta. 10 Manual del Alumno Forward-Only: Este tipo de recordset (muchas veces denominado forward-scrolling snapshot o forward-only snapshot) provee un subconjunto de capacidades de un snapshot. Con este objeto se puede mover solo en dirección adelante a traves de registros. Solo se soporta los metodos Move o Move Next. La ventaja de este tipo de recordset es que usualmente provee la mayor velocidad entre los demas recordset. Sin embargo, ofrece la menor funcionalidad que los demas. Un Snapshot almacena una copia de todos los registros (excepto objetos OLE y campos memo). Un Dynaset almacena justamente la llave primaria para cada registro, copiando la totalidad de registros solo cuando es necesario para edicion o propositos de despliegue. Desde un snapshot almacena una copia completa de todos los registros en una tabla, éste puede rendir más lentamente que un dynaset si el numero de registros es muy cuantioso. Para determinar cuando usar un dynaset o un snapshot se pueden abrir ámbos, ejecutar la misma consulta y comparar los tiempos de respuesta. El tipo de recordset que se use depende de lo que el usuario debe hacer con los datos. Puede que solo desee realizar una vista o que además deba actualizarlos. Por ejemplo, si el usuario desea ordenar los datos o trabajar con indices, usese un tipo table, dado que estos estan indexados y son más rapidos de localizar. Otro caso es que si el usuario desea actualizar un conjunto de registros seleccionados por una consulta, debe usarse el dynaset. Finalmente, si la tabla que se desea consultar pocas veces está disponible y se desea realizar una busqueda en un conjunto de registros determinados puede usarse un tipo forward-only. Creando una variable objeto: Para crear una variable objeto, debe usarse el método OpenRecordset. Primero debe declararse el tipo de variable y el grupo de variables para el objeto, retornado por el metodo OpenRecordset. Puede usarse el método OpenRecordset con Database, TableDef, QueryDef, y objetos existentes. La sintaxis del método OpenRecordset es: Set variable = database.OpenRecordset (source [, type [, options [, lockedits ]]]) La sintaxis del método OpenRecordset para todos los tipos de objetos es: Set variable = object.OpenRecordset ([type [, options [, lockedits ]]]) 11 Manual del Alumno La argumento variable es el nombre del nuevo objeto. El argumento database es el nombre del objeto de base de datos abierto. El argumento Object es el TableDef, QueryDef, o el objeto existente. El argumento source especifica el origen de los registros para el nuevo objeto. El valor del origen es el valor del objeto DAO. Cuando se crea un nuevo objeto desde un objeto Database el argumento source es un TableDef o QueryDef en la base de datos o un retorno válido de una consulta SQL o sentencia. Cuando se crea un objeto de este tipo el mismo provee el origen de datos para el nuevo objeto. El argumento type es una constante intrinseca que especifica la clase de objeto que se desea crear. Se pueden utilizar las siguientes constantes: dbOpenTable dbOpenDynaset dbOpenSnapshot dbOpenForwardOnly El tipo de constante dbOpenForwardOnly reemplaza al tipo de constante dbForwardOnly que estaba contenida en versión posterior de DAO. Es decir, esta constante se mantiene por compatibilidad con la version anterior de DAO. La siguiente sección muestra los tipos, opciones y argumentos de “lockedits” en detalle: Tipos por defecto: Porque DAO automaticamente escoge el tipo por defecto, dependienteo del origen de datos y como se realiza la apertura, no se necesita especificar el tipo. Sin embargo, se puede especificar un tipo diferente usando el argumento type en el metodo OpenRecordset. La lista siguiente describe los tipos disponibles y los tipos por defecto, dependiendo de cómo se abra el objeto: Usando el método OpenRecordset con un objeto Database Set rstNew = dbs.OpenRecordset("Data Source") Si el recurso de datos es una tabla local en la base de datos, los cuatro tipos estan disponibles y el tipo table está por defecto. Si el origen de datos es diferente, solo Dynaset y Snapshot estan disponibles. Dynaset es el default. Usando el método OpenRecordset con un objeto TableDef: 12 Manual del Alumno Set rstNew = tdfTableData.OpenRecordset Si tdfTableData se refiere a una tabla de Microsoft Access (*.mdb) o ISAM abierta directamente, entonces los cuatro tipos estan disponibles. Si tdfTableData es un base de datos abierta mediante ODBC o es una tabla ligada en una tabla externa, solo los tipos dynaset y snapshot estan disponibles. Usando el método OpenRecordset con un objeto QueryDef: Set rstNew = qdfQueryData.OpenRecordset Solo los objetos dynaset y snapshot estan disponibles. Usando el método OpenRecordset con un objeto existente: Set rstNew = rstExisting.OpenRecordset Solo los objetos dynaset y snapshot estan disponibles. Opciones OpenRecordset: Con el argumento Options del metodo OpenRecordset se puede especificar un numero de otras caracteristicas para un objeto. Se pueden utilizar las siguientes constantes: DbAppendOnly: Los usuarios pueden agregar nuevos registros, pero no se puede editar o eliminar. Esto es util en aplicaciones que capturan y archivan muchos datos (solo dynaset). DbReadOnly: No se pueden hacer cambios. Este argumento es proveido solo por compatibilidad de versiones anteriores. Se debe utilizar la constante dbReadOnly en el argumento lockedits para ello. DbSeeChanges: Si otro usuario realiza cambios en los datos en los registros (con edit y update) se produce un error en tiempo de ejecucion. Esto es util en aplicaciones donde multiples usuarios realizan, simultaneamente, operaciones de lectura/escritura sobre los mismos datos (solo dynaset y table). DbDenyWrite: Cuando se usa con dynaset o snapshot, esta opcion previene a otros usuarios de inserciones o modificaciones de registros. Cuando una tabla está en uso otros usuarios no pueden abrir ningun otro tipo de recordset. DbDenyRead: Otros usuarios no pueden leer datos en la tabla (solo tabla) DbForwardOnly: Esta opcion crea un recordset forward-only o snapshot. Este es proveido solo por compatibilidad con versiones anteriores. Use la constante dbOpenForwardOnly en el argumento type. DbSQLPassThrough: Cuando el argumento source es una sentencia SQL, use esta constante para pasar las sentencias SQL a una base conectada via ODBC, para procesamiento. Si la usa con un dynaset, los datos no son actualizables (dynaset y snapshot solamente). dbConsistent (Default): Solo consiste en actualizaciones (solo dynaset). Se puede usar esta constante con la constante dbInconsistent. 13 Manual del Alumno DbInconsistent: Son permitidas actualizaciones inconsistentes. Esta es la contraria de dbConsistent (solo dynaset). No se puede usar esta constante con la constante dbConsistent. Con el argumento lockedits del metodo OpenRecordset se puede controlar como bloquear un objeto. Se pueden utilizar las siguientes constantes: DbReadOnly: No se pueden realizar cambios. Esta constante reemplaza a la constante dbReadOnly que fue usada en el argumento options en versiones anteriores de DAO. dbPessimistic (Default): Microsoft Jet usa bloqueo pesimista para determinar como los cambios pueden ser hechos en un ambiente multiusuario. DbOptimistic: Microsoft Jet usa bloqueo optimista para determinar como los cambios pueden ser hechos en un ambiente multiusuario. El valor por defecto es dbPessimistic. El solo efecto de usar dbPessimistic o dbOptimistic es para configurar el valor de los objetos con la propiedad LockEdits. 14 Manual del Alumno Creando un Objeto desde un Formulario: Usted puede crear un objeto Recordset basado en un formulario de Microsoft Access. Para hacer esto, use la propiedad RecordsetClone del formulario. Este crea un Recordset tipo dynaset que se refiere a la misma consulta o “data” del fomulario. Si el formulario está basado en una consulta, hay que referirse a la propiedad RecordsetClone. Esto es el equivalente a crear un dynaset con la misma consulta. Se puede utilizar la propiedad RecordsetClone cuando necesite aplicar un metodo provisto de acceso a todos los metodos y propiedades que se pueden utilizar con un dynaset. La sintaxis para la propiedad RecordsetClone es: Set variable = form.RecordsetClone El argumento variable es el nombre de un objeto Recordset existente. El argumento form es el nombre de un formulario Microsoft Access. El siguiente ejemplo muestra como asignar a un objeto Recordset los registros en el formulario “Orders”: Dim rstOrders As Recordset Set rstOrders = Forms!Orders.RecordsetClone Este codigo siempre crea el tipo de Recordset siendeo cloneado (el tipo de Recordset basado en el formulario); otros tipos no están disponibles. Creando un Objeto desde una Tabla: El metodo que se use para crear un objeto Recordset desde una tabla depende de si la tabla es local a la base de datos actual o si está ligada a otra tabla en otra base de datos. La siguiente discusion explica las diferencias y provee de ejemplos para cada tipo de tabla: Creando un Recordset desde una tabla en una base de datos local Microsoft Access: El siguiente ejemplo usa el metodo OpenRecordset para crear una tabla tipo Recordset: Objeto para una tabla en la base de datos actual: Dim dbs As Database, rstCustomers As Recordset Set dbs = CurrentDb Set rstCustomers = dbs.OpenRecordset("Customers") Notese que no es necesario usar la constante dbOpenTable para crear una tabla de tipo Recordset. Si se omite la constante type DAO selecciona el tipo de Recordset de mayor funcionalidad, dependiendo del objeto, en cuanto el Recordset creado y el origen de datos. Para cuando el tipo de tabla está disponible y se abre un Recordset, DAO usa automáticamente este tipo. 15 Manual del Alumno Creando un Recordset desde una tabla ligada a una tabla de un formato diferente: El siguiente ejemplo crea un Recordset de tipo dynaset, para una tabla Paradox® 3.X. Pero el tipo tabla no está disponible cuando se abre un Recordset desde una tabla ligada a una base de datos que no sea Microsoft Access. DAO selecciona el tipo próximo de Recordset que sea más eficiente: Dim dbs As Database Dim tdf As TableDef Dim rstTableData As Recordset ' Obtiene la base de datos activa. Set dbs = CurrentDb Set tdf = dbs.CreateTableDef("PDXAuthor") ' Conecta a la base de datos la tabla Paradox Author. ' C:\PDX\Publish.tdf.Connect = "Paradox 3.X;DATABASE=C:\PDX\Publish" tdf.SourceTableName = "Author" ' Liga la tabla. dbs.TableDefs.Append tdf ' Crea un Recordset tipo dynaset-type para la tabla. Set rstTableData = tdf.OpenRecordset() Se puede abrir directamente una tabla Paradox, abriendo inicialmente la base de datos Paradox. Usando un Indice en un Recordset de tipo tabla: Se pueden ordenar registros en un Recordset de tipo “table” configurando la propiedad Index. Cualquier objeto Index en la colección Indexes para los objetos tipo Recordset pueden ser especificados en la propiedad Index. El ejemplo siguiente crea un Recordset de tipo “table” basado en la tabla Customer y utilizando un indice existente llamado City: Dim dbs As Database, rstTableData As Recordset Set dbs = CurrentDb Set rstTableData = dbs.OpenRecordset("Customers", dbOpenTable) ' Mueve el puntero al primer registro de la tabla rstTableData.MoveFirst ' El primer registro sin indice MsgBox rstTableData!CompanyName rstTableData.Index = "City" ' Selecciona el indice City. rstTableData.MoveFirst ‘ Mueve el puntero al primer registro. MsgBox rstTableData!CompanyName rstTableData.Close Si se utiliza la propiedad Index para un indice que no existe, un error de ejecucion capturable ocurre. Si se desea ordenar registros de acuerdo a un índice que no existe, se 16 Manual del Alumno puede crear el indice primero o crear un Recordset de tipo dynaset o snapshot, usando una consulta que retorne registros en un orden establecido. Es importante utilizar la propiedad Index antes de usar el metodo Seek. Creando un objeto Recordset desde una consulta: Se puede crear un objeto Recordset basado en una consulta almacenada o no. En el siguiente ejemplo se muestra la lista de productos en una consulta almacenada en la base de datos activa: Dim dbs As Database, rstProducts As Recordset Set dbs = CurrentDb Set rstProducts = dbs.OpenRecordset("Current Product List") Si la consulta almacenada no existe, el metodo OpenRecordset tambien acepta una cadena SQL. El ejemplo anterior puede ser reescrito tal como sigue: Dim dbs As Database, rstProducts As Recordset Dim strQuerySQL As String Set dbs = CurrentDb strQuerySQL = "SELECT * FROM Products WHERE Discontinued = No " _ & "ORDER BY ProductName;" Set rstProducts = dbs.OpenRecordset(strQuerySQL) La desventaja de esta última es que la consulta basada en una cadena debe ser compilada cada vez que se ejecuta, mientras que una almacenada solo lo hace la primera vez. Esto mejora el rendimiento. Cuando se crea un objeto Recordset, usando una cadena SQL o una consulta almacenada, el codigo de la misma no necesita seguir ejecutandose hasta que la consulta retorna la primera fila en el Recordset. Ordenando y Filtrando Registros: Si se utiliza un Recordset de tipo “Table” y se utiliza la propiedad Index se puede estar seguro que los registros apareceran en un orden especifico. Sin embargo, usualmente se necesita extraer registros en un especifico orden en otro tipo de Recordset. Por ejemplo, se puede necesitar visualizar las facturas ordenadas por número, o clientes en orden alfabetico. Para ordenar los registros en un objeto Recordset que no es una tabla, utilice la claúsula Order By en la consulta. Tambien se puede filtrar los datos mediante la utilización de la claúsula Where El siguiente ejemplo muestra el uso de ámbos: Dim dbs As Database, rstManagers As Recordset Set dbs = CurrentDb 17 Manual del Alumno Set rstManagers = dbs.OpenRecordset("SELECT FirstName, LastName FROM " _ & "Employees WHERE Title = 'Sales Manager' ORDER BY LastName") Una desventaja de ejecutar una consulta SQL en un metodo OpenRecordset es que éste deventaja de ser precompilado cada vez que se ejecuta. Por ende, si la consulta es usada frecuentemente, se obtiene mayor rendimiento creando un procedimiento almacenado usando la misma consulta y abriendo un objeto Recordset cada vez que se requiera la consulta, tal y como se muestra a continuacion: Dim dbs As Database Dim rstSalesReps As Recordset Dim qdf As QueryDef Set dbs = CurrentDb Set qdf = dbs.CreateQueryDef("SalesRepQuery") qdf.SQL = "SELECT * FROM Employees WHERE Title = 'Sales Representative';" Set rstSalesReps = dbs.OpenRecordset("SalesRepQuery") Para una mayor flexibilidad y control en tiempo de ejecucion se pueden usar parametros para determinar criterio de orden y filtro. Recreando a consulta desde un objeto Recordset: Se puede utilizar un objeto Recordset abierto desde un QueryDef creando un objeto de este tipo. Para hacer esto, se debe utilizar el metodo CopyQueryDef. Este es usualmente utilizado en situaciones donde un la variable de un objeto Recordset creado desde un objeto QueryDef es pasado a una funcion y ésta debe recrear el SQL equivalente a la consulta y posiblemente modificarla. Modificando una Consulta desde un objeto Recordset: Se puede utilizar el metodo Requery en un Recordset de tipo dynaset o snapshot cuando se requiere ejecutar una consulta cada vez que se cambian los parametros. Esto es más conveniente que abrir un nuevo Recordset y se ejecuta más velozmente. El siguiente ejemplo crea un objeto Recordset y llama a una funcion que usa el metodo CopyQueryDef para extraer la cadena correspondiente a la consulta SQL. Este entonces dispone al usuario para que pueda utilizar los parametros de la consulta. El codigo usa el metodo Requery para ejecutar la consulta modificada por el usuario: Sub AddQuery() Dim dbs As Database Dim qdf As QueryDef Dim rstSalesReps As Recordset Set dbs = CurrentDb Set qdf = dbs.CreateQueryDef("SalesRepQuery") qdf.SQL = "SELECT * FROM Employees WHERE Title = 'Sales Representative'" set rstSalesReps = qdf.OpenRecordset() 18 Manual del Alumno 'Llama a la funcion para agrear el constraint. AddQueryFilter rstSalesReps ' Retorna a la base de datos original. dbs.QueryDefs.Delete "SalesRepQuery" rstSalesReps.Close End Sub Function AddQueryFilter(rst As Recordset) Dim qdf As QueryDef Dim strNewFilter As String, strRightSQL As String Set qdf = rst.CopyQueryDef '"LastName LIKE 'D*'". strNewFilter = InputBox("Digite nuevo criterio : ") strRightSQL = Right(qdf.SQL, 1) ' Quita caracteres desde el final de la consulta, como sea necesario. Do While strRightSQL = " " Or strRightSQL = ";" Or strRightSQL = vbCR Or _ strRightSQL = vbLF qdf.SQL = Left(qdf.SQL, Len(qdf.SQL) - 1) trRightSQL = Right(qdf.SQL, 1) Loop qdf.SQL = qdf.SQL & " AND " & strNewFilter & ";" rst.Requery qdf ' Reconsulta el Recordset. rst.MoveLast ' llena el Recordset. ' "Lastname LIKE 'D*'" podría retornar 2 registros. MsgBox "Número de Registros encontrados = " & rst.RecordCount End Function Para usar el metodo Requery, la propiedad Restartable del objeto Recordset debe estar en True. La propiedad Restartable es simpre True cuando el Recordset es creado desde una consulta. No se puede reiniciar consultas pass-through. Se puede o no tener disponibilidad para reiniciar consultas con tablas ligadas en otro formato de base de datos, sino es la nativa Microsoft Access. Para determinar cuando un objeto puede reejecutar una consulta chequee la propiedad Restartable. Ordenamiento DAO y propiedades de filtro. Otra manera de ordenar y filtrar objetos Recordset está para configurar las propiedades de ordenamiento y filtrado del objeto DAO. Sin embargo, este es usualmente más lento que incluir un criterio de ordenamiento o filtrado en la consulta original o cambiando los parametros y ejecutando este de nuevo con el metodo Requery. Las propiedades de ordenamiento y filtrado de DAO son usualmente utilizadas cuando se necesitar utilizar un conjunto de resultados para un usuario, pero el origen de los datos no está disponible para una nueva consulta. Por ejemplo, cuando un objeto Recordset tiene mas de 100 registros. Usar el metodo CopyQueryDef es preferible para cuando esa cantidad es inferior. 19 Manual del Alumno Moviendose a traves de objetos Recordset: Un objeto Recordset usualmente tiene una posición actual, a menudo es un registro. Cuando se refiere a un campo en el RecordSet, se obtienen valores desde el registro en la posicion actual. Sin embargo la posicion actual puede tambien ser la siguiente o la anterior. En ciertos casos, la posicion actual es indefinida. Se puede utilizar los siguientes métodos Move para movilizarse a traves de los registros de un Recordset: MoveFirst mueve al primer registro. MoveLast mueve al último registro. MoveNext mueve al registro próximo. MovePrevious mueve al registro anterior. Move [n] mueve n registros hacia delante. Se pueden usar cualquiera de estos métodos en un Recordset de tipo Table, Dynaset o Snapshot. En el tipo Forward-Only solo se puede usar el MoveNext y se pueden especificar la cantidad de registros que se desea desplazar. El siguiente ejemplo abre un objeto Recordset utilizando la tabla “Employees”, la cual contiene todos los registros que tienen un valor Null en el campo ReportsToField. La función entonces actualiza los registros para indicar que éstos son empleados temporales. Para cada registro en el Recordset, el ejemplo cambia los campos Title y Notes y salva los cambios con el método Update. Este usa el método MoveNext para moverse al registro próximo: Function UpdateEmployees() Dim dbs As Database, rstEmployees As Recordset, strQuery As String Dim intI As Integer On Error GoTo ErrorHandler Set dbs = CurrentDb ' Abre un Recordset con todos los registros desde la tabla Employees ' que tienen un valor Null en el camop ReportsTo. strQuery = "SELECT * FROM Employees WHERE ReportsTo IS NULL;" Set rstEmployees = dbs.OpenRecordset(strQuery, dbOpenDynaset) ' Si el Recordset está vacio, sale. If rstEmployees.EOF Then Exit Function intI = 1 With rstEmployees Do Until .EOF .Edit ![ReportsTo] = 5 ![Title] = "Temporary" ![Notes] = rstEmployees![Notes] & "Temp #" & intI 20 Manual del Alumno .Update .MoveNext intI = intI + 1 Loop .Close End With ErrorHandler: Select Case Err Case 0 Exit Function Case Else MsgBox "Error " & Err & ": " & Error, vbOKOnly, "ERROR" Exit Function End Select End Function Cabe aclarar que este ejemplo es meramente ilustrativo, dado que el método Update funcionaría mejor utilizando una sentencia SQL. Detectando los límites de un objeto Recordset: En un objeto Recordset si se intenta mover a una posición que no es la correcta, se produce un error de ejecución. Por ejemplo, si se intenta mover al próximo registro estando en el último o al anterior estando en el primero. La propiedad EOF indica el fin del archivo y BOF el inicio del mismo. Se puede acceder a estas propiedades para determinar el inicio o final del archivo y asi controlar el movimiento del puntero. En ambos casos se pretende obtener si la propiedad es False o True. El siguiente ejemplo muestra como utilizar las propiedades BOF y EOF para detectar el inicio y final de un Recordset. Este fragmento de codigo crea un Recordset de tipo table basada en la tabla Orders. Se realizan movimientos a traves de los registros, primero desde el inicio del Recordset hasta el final de la tabla y luego hacia el final. Dim dbs As Database, rstOrders As Recordset Set dbs = CurrentDb Set rstOrders = dbs.OpenRecordset("Orders", dbOpenTable) Do Until rstOrders.EOF . . ' Manipulación de los datos. . rstOrders.MoveNext ' Mueve el puntero al próximo registro. Loop rstOrders.MoveLast ' Mientras sea inicio del archivo ' Mueve el puntero al último registro. 21 Manual del Alumno Do Until rstOrders.BOF . . ' Manipulación de los datos. ' Mueve el puntero al registro anterior. rstOrders.MovePrevious Loop rstOrders.Close ' Cierra el Recordset. Las propiedades BOF y EOF tienen las siguientes características: Si el Recordset no contiene registros cuando se abre éste, tanto BOF como EOF son True. Cuando BOF o EOF son True, la propiedad True se mantienen hasta que exista un movimiento hasta un registro existente, asignando False a BOF o EOF. Cuando BOF o EOF es False, y solo el registro en el Recordset es eliminado, la propiedad devuelve False hasta que se desplaze el puntero hasta otro registro. En el momento en que se crea o abre un Recordset que contiene más de un registro, el primero de ellos es el registro actual y tanto BOF y EOF son False. Si el primer registro en el registro actual cuando use el método MovePrevious, BOF es puesto en True. Si se utiliza MovePrevious mientras BOF es True, un error en tiempo de ejecución ocurre. Similarmente, moviéndose al último registro en el Recordset se cambia el valor de la propiedad EOF a True. Si se utiliza el método MoveNext mientras EOF es True, un error en tiempo de ejecución se presenta. Contando el número de registros en un objeto Recordset: Si puede conocer el número de registros en un objeto Recordset. Por ejemplo, si se necesita crear un formulario que muestre cuantos registros existen en cada tabla de la base de datos o los cambios presentados cuando se realizan inclusiones. La propiedad RecordCount contiene el número de registros en un Recordset tipo tabla o el total de registros accesados en un dynaset o snapshot. Un objeto Recordset sin registros posee la propiedad RecordCount en 0. El siguiente ejemplo crea un Recordset tipo snapshot y determina el número de registros en el mismo: Function RecCount(strSQL As String) As Long Dim rstCount As Recordset, dbs As Database On Error GoTo ErrorHandler Set dbs = CurrentDb Set rstCount = dbs.OpenRecordset(strSQL) If rstCount.EOF Then 22 Manual del Alumno rstCount.Close RecCount = 0 Exit Function Else rstCount.MoveLast RecCount = rstCount.RecordCount rstCount.Close Exit Function End If ErrorHandler: Select Case Err Case 0 Exit Function Case Else MsgBox "Error:" & Err & ":" & Error, vbOKOnly, "ERROR" Exit Function End Select End Function Cuando se eliminan registros en un Recordset tipo dynaset, el valor de RecordCount decrece. Sin embargo, en un ambiente multiusuario, los registros eliminados por otros usuarios no son reflejados en el valor del RecordCount hasta que el registro acutal es posicionado sobre un registro eliminado. Al mismo tiempo la configuración de la propiedad RecordCount es actualizada. Utilizando el método Requery sobre un Recordset, seguido por un metodo MoveLast, configuran la propiedad RecordCount al número total de registros en el Recordset. Un Recordset tipo snapshot es estático y el valor RecordCount de este no se cambia cuando se agregan o eliminan registros. Buscando la posición actual en un objeto Recordset: Dos propiedades están disponibles para indicar la posición actual de un registro: AbsolutePosition y PercentPosition. El valor de la propiedad AbsolutePosition es la posición del registro actual relativo a 0. Sin embargo, no se piense como un número de registro; si el registro actual está indefinido, la propiedad AbsolutePosition retorna –1. En consecuencia, esto no significa que el registro tendrá la misma posicion absoluta si el objeto Recordset es recreado, porque el orden de los registros individuales en un Recordset utilizando SQL que incluyan la claúsula Order By cambia dichas posiciones. La propiedad PercentPosition muestra la posicion actual expresada como un porcentaje del total de registros indicados en la propiedad RecordCount. Para asegurarse la correcta asignación del porcentaje referido utilicese los metodos MoveLast y MoveFirst antes de abrir el Recordset. 23 Manual del Alumno La propiedad PercentPosition es solamente una aproximacion y no debería ser utilizada como un parametro critico. Esta propiedad es mejor para manejar un indicador que marque el progreso en una operación de registros procesados. El siguiente ejemplo abre un objeto Recordset en una tabla llamada Employees. El procedimiento entonces permite moverse a traves de la tabla y usa la funcion SysCmd para desplegar una barra de progreso, mostrando el porcentaje que ha sido procesada en la tabla. Si la fecha de nacimiento del empleado es posterior al 1 Enero de 1993, el texto “Senior Staff” es agregado al campo Notes. Function PercentPos() Dim dbs As Database, strMsg As String, rstEmployees As Recordset, intRet% Dim intCount As Integer, strQuery As String, sngPercent As Single Dim varReturn As Variant Dim lngEmpID() As Long On Error GoTo ErrorHandler strQuery = "SELECT * FROM Employees;" Set dbs = CurrentDb Set rstEmployees = dbs.OpenRecordset(strQuery, dbOpenDynaset) With rstEmployees If .EOF Then ' Si no hay registros, sale. Exit Function Else strMsg = "Procesando tabla Employees ..." intRet = SysCmd(acSysCmdInitMeter, strMsg, 100) End If Do Until .EOF If !HireDate < #1/1/93# Then .Edit !Notes = !Notes & ";" & "Senior Staff" .Update End If If .PercentPosition <> 0 Then intRet = SysCmd(acSysCmdUpdateMeter, .PercentPosition) End If .MoveNext Loop .Close End With intRet = SysCmd(acSysCmdRemoveMeter) ErrorHandler: Select Case Err Case 0 24 Manual del Alumno Exit Function Case Else MsgBox "Error " & Err & ": " & Error, vbOKOnly, "ERROR" ' limpia la métrica de progreso. varReturn = SysCmd(acSysCmdSetStatus, " ") Exit Function End Select End Function Buscando un registro específico: Muchas veces se desea encontrar un registro particular de un empleado, por ejemplo, basados en el código del mismo, o todos los detalles que se especifican para una determinada orden. En estos casos, se puede realizar una búsqueda utilizando los métodos Find y Seek. Se puede utilizar el metodo Seek con tipos de Recordset Tables y el método Find con dynaset y snapshot. Forward-Only no soporta el uso de Seek o cualquiera de los Find. Buscando un registro en un Recordset tipo Table: Se puede utilizar el metodo Seek para localizar registros en un Recordset tipo Table. Cuando se usa el método Seek para localizar un registro, el motor de base de datos Microsoft Jet usa los indices de la tabla, definidas por la propiedad Index. Si se utiliza el método Seek en un Recordset tipo table, sin haber habilitado el indice primero, ocurre un error en tiempo de ejecucion. La sintaxis para el método Seek es el siguiente: table.Seek comparison, key1, key2 ... El argumento table es el Recordset tipo table utilizado. El argumento comparación es una cadena que determina la clase de comparacion que esta siendo aplicada. La siguiente tabla lista las cadenas de comparacion que se pueden utilizar con el método Seek. Cadena de comparación Description "=" ">=" ">" "<=" "<" Igual al valor especificado Mayor o igual al valor especificado Mayor al valor especificado Menor o igual al valor especificado Menor al valor especificado 25 Manual del Alumno El argumento Keyn son una serie de uno o más valores que corresponden al campo o campos que inicializan el indice actual del Recordset. Microsoft Jet compara estos valores con los valores en los campos correspondientes del Recordset. El ejemplo siguiente abre un Recordset tipo table llamada Employees y utiliza el metodo Seek para localizar el record conteniendo el valor de IngEmpID en el campo EmployeeID. Este returna la fecha de nacimiento del empleado referido: Function intGetHireDate(lngEmpID As Long, varHireDate As Variant) As Integer Dim rstEmployees As Recordset, dbs As Database Const conFilePath$ = "C:\Program Files\Microsoft _ Office\Office\Samples\" On Error GoTo ErrorHandler Set dbs = OpenDatabase(conFilePath & "Northwind") Set rstEmployees = dbs.OpenRecordset("Employees", dbOpenTable) rstEmployees.Index = "PrimaryKey" 'Nombre del indice para Employee ID. rstEmployees.Seek "=", lngEmpID If rstEmployees.NoMatch Then varHireDate = Null 'La constante conErrNoMatch, conSuccess, y conFailed están definidos 'en el nivel de módulo como una constante publica con valores enteros 'entre -32,761, 0, y -32,737 respectivamente. intGetHireDate = conErrNoMatch Exit Function Else varHireDate = rstEmployees!HireDate intGetHireDate = conSuccess Exit Function End If ErrorHandler: Select Case Err Case 0 Exit Function Case Else varHireDate = Null intGetHireDate = conFailed MsgBox "Error " & Err & ": " & Error, vbOKOnly, "ERROR" Exit Function End Select End Function El metodo Seek siempre inicia buscando registros desde el principio del Recordset. Se puede utilizar la propiedad NoMatch en el Recordset para comprobar si el registro buscado no fue encontrado, mediante examinación del valor True o False. El siguiente ejemplo ilustra como se puede crear una función que utilice el método Seek para localizar un registro utilizando un indice de multiples campos: Function GetFirstPrice(lngOrderID As Long, lngProductID As Long) As Variant Dim dbs As Database, rstOrderDetail As Recordset 26 Manual del Alumno On Error GoTo ErrorHandler Set dbs = CurrentDb Set rstOrderDetail = dbs.OpenRecordset("Order Details", dbOpenTable) rstOrderDetail.Index = "PrimaryKey" rstOrderDetail.Seek "=", lngOrderID, lngProductID If rstOrderDetail.NoMatch Then GetFirstPrice = Null MsgBox "No se encontró el registro..." Else GetFirstPrice = rstOrderDetail!UnitPrice End If rstOrderDetail.Close ErrorHandler: Select Case Err Case 0 Exit Function Case Else MsgBox "Error " & Err & ": " & Error, vbOKOnly, "ERROR" Exit Function End Select End Function En este ejemplo, la llave primaria de la tabla consiste en dos campos: OrderID y ProductID. Cuando llama a la función GetFirstPrice con una combinación válida de campos para OrderID y ProductID, la función retorna el precio unitario desde el registro encontrado. Si no se encuentra la combinación de valores de campo la función retorna un valor Null. Buscando un registro en tipos de Recordset Dynaset- o Snapshot: Se puede utilizar el método Find para localizar un registro en un Recordset tipo Dynaset o Snapshot. DAO provee cuatro metodos para lo anterior: FindFirst : busca el primer registro que satisface el criterio de búsqueda. FindLast : busca el último registro que satisface el criterio de búsqueda. FindNext: busca el próximo que satisface el criterio de búsqueda. FindPrevious : busca el registro que satisface el criterio de búsqueda. Nota: Para localizar un registro en un Recordset tipo table, debe utilizarse el método Seek, descrito anteriormente. Cuando se utilice el metodo Find desde especificarse el criterio de busqueda; tipicamente es una expresion con un nombre de campo con un valor especifico. Se pueden localizar los registros buscados en orden último, anterior o siguiente, utilizando los métodos FindLast, FindPrevious y FindNext. 27 Manual del Alumno DAO almacena True en la propiedad NoMatch cuando un metodo Find falla y el registro actual yace indefinido. Para retornar al registro actual debe utilizarse el metodo bookmark. El ejemplo siguiente muestra como se puede utilizar el método FindNext para encontrar todas las ordenes en la tabla Orders que tiene los registros que no corresponden en la tabla Order Details y agrega los valores en el campo OrderID al arreglo IngOrderID(): Function FindEx(lngOrderID() As Long) Dim dbs As Database, rstOrders As Recordset Dim strQuery As String, rstOrderDetails As Recordset Dim intIndex As Integer On Error GoTo ErrorHandler Set dbs = CurrentDb 'Abre un recordset con los registros de las tablas 'orders y Order Details. 'Si éstas no contienen registros sale. strQuery = "SELECT * FROM Orders ORDER BY OrderID;" Set rstOrders = dbs.OpenRecordset(strQuery, dbOpenSnapshot) If rstOrders.EOF Then Exit Function strQuery = "SELECT * FROM [Order Details] ORDER BY OrderID;" Set rstOrderDetails = dbs.OpenRecordset(strQuery, dbOpenSnapshot) ' Para el primer registro en Orders, encuentra el primer registro. ' en OrderDetails. Si no no encuentra, redimensiona el arreglo de order IDs ' y agrega el order ID al arreglo. rstOrderDetails.FindFirst "OrderID = " & rstOrders![OrderID] If rstOrderDetails.NoMatch Then ReDim Preserve lngOrderID(1 To intIndex) lngOrderID(intIndex) = rstOrders![OrderID] End If ' La primera búsqueda ha sido exitosa, entonces usa FindNext para encontrar ' próximo registro que satisfaga el criterio de búsqueda. intIndex = 0 Do Until rstOrders.EOF rstOrderDetails.FindNext "OrderID = " & rstOrders![OrderID] If rstOrderDetails.NoMatch Then intIndex = intIndex + 1 ReDim Preserve lngOrderID(1 To intIndex) lngOrderID(intIndex) = rstOrders![OrderID] End If rstOrders.MoveNext Loop ErrorHandler: Select Case Err Case 0 Exit Function 28 Manual del Alumno Case Else MsgBox "Error " & Err & ": " & Error, vbOKOnly, "ERROR" Exit Function End Select End Function Si la búsqueda de registros es frecuente en un Recordset tipo dynaset, se pueden encontrar éstos facilmente creando una tabla indexada temporal y usar el método Seek. Marcando la posición de un registro con Bookmarks: Un bookmark es un sistema generado que identifica univocamente a cada registro. La propiedad bookmark de DAO en un Recordset cambia cada vez que se mueve a un nuevo registro. Para identificar un registro, se marca el registro actual con Bookmark en una variable tipo Variant. Para retornar a éste registro, asignele al bookmark el valor de esta variable. El siguiente ejemplo ilustra como se puede usar bookmark para salvar la posicion actual del registro. Se pueden realizar otras operaciones sobre el recordset y posteriormente regresar al registro marcado. Function BookMarkEx() As Integer Dim dbs As Database, rstProducts As Recordset Dim vBookMark As Variant, sngRevenue As Single Dim strQuery As String, rstCategories As Recordset, strCriteria As String On Error GoTo ErrHandler BookMarkEx = 0 strQuery = "SELECT * FROM Products WHERE UnitsOnOrder >= 40 _ ORDER BY " _ & "CategoryID, UnitsOnOrder DESC;" Set dbs = CurrentDb Set rstProducts = dbs.OpenRecordset(strQuery, dbOpenSnapshot) Set rstCategories = dbs.OpenRecordset("SELECT CategoryID FROM " _ & "Categories ORDER BY CategoryID;", dbOpenSnapshot) If rstProducts.NoMatch Then Exit Function ' Por cada categoría encontrada el producto genera el ingreso mínimo ' y el producto de mayor precio. Do Until rstCategories.EOF strCriteria = "CategoryID = " & rstCategories![CategoryID] rstProducts.FindFirst strCriteria sngRevenue = rstProducts![UnitPrice] * rstProducts![UnitsOnOrder] If Not rstProducts.NoMatch Then 'Inicializa el bookmark en el primer registro conteniendo la 29 Manual del Alumno 'CategoryID. vBookMark = rstProducts.Bookmark 'Buscar el producto generando el de mayor precio Do While rstProducts![CategoryID] = rstCategories![CategoryID] If rstProducts![UnitPrice] * rstProducts![UnitsOnOrder] > sngRevenue Then sngRevenue = rstProducts![UnitPrice] * _ rstProducts![UnitsOnOrder] End If rstProducts.MoveNext Loop ' Se mueve al primer registro que contiene el campo CategoryID. rstProducts.Bookmark = vBookMark sngRevenue = rstProducts![UnitPrice] * _ rstProducts![UnitsOnOrder] ' Encuentra el producto, generando el de menor precio/ingreso. Do While rstProducts![CategoryID] = rstCategories![CategoryID] If rstProducts![UnitPrice] * rstProducts![UnitsOnOrder] < sngRevenue Then sngRevenue = rstProducts![UnitPrice] * rstProducts![UnitsOnOrder] End If rstProducts.MoveNext Loop End If rstCategories.MoveNext Loop ' Error Handler. ErrHandler: Select Case Err Case 0 Exit Function Case Else MsgBox "Error " & Err & ": " & Error, vbOKOnly, "ERROR" Exit Function End Select End Function Un “bookmark” es particularmente útil si un método falla, por que la posición actual del registro se pierde. La propiedad LastModified del objeto Recordset clarifica el como usar el bookmark. Esta propiedad retorna el bookmark del ultimo registro en el Recordset a ser añadido o modificado. Para usar este, se debe configurar la propiedad Bookmark de DAO para que sea igual a la propiedad LastModified, tal y como se muestra a continuación: RstClientes.BookMark = rstClientes.LastModified 30 Manual del Alumno Lo anterior mueve el registro actual hasta el último que fue agregado o modificado. Esto es particularmente útil cuando se agregan nuevos registros, por que después de hacerlo el registro actual es el que se añadió. Con la propiedad LastModified, se puede mover al registro recién agregado. Cuando usted cierra un objeto Recordset, cualquier bookmarks que haya salvado se invalida. No se puede usar un bookmark de un Recordset en otro. Sin embargo, puede usarse como en el siguiente ejemplo: Dim dbs As Database Dim rstOriginal As Recordset, rstDuplicate As Recordset Dim strPlaceholder As String Set dbs = CurrentDb ' Crea el primer Recordset. Set rstOriginal = dbs.OpenRecordset("Orders", dbOpenDynaset) ' Guarda la posición del registro actual strPlaceholder = rstOriginal.Bookmark ' Crea un Recordset duplicado. Set rstDuplicate = rstOriginal.Clone() ' Se desplaza al mismo registro. rstDuplicate.Bookmark = strPlaceholder rstOriginal.Close Que objetos Recordset no soportan Bookmarks? Los Recordset de tipo Dynaset basados en ciertas tablas ligadas, como tablas Paradox que no tienen llaves primarias no soportan bookmarks. Se puede determinar si un objeto Recordset soporta o no bookmarks chequeando el valor de la propiedad bookmarkable, tal y como se muestra en el ejemplo siguiente: If rstLinkedTable.Bookmarkable Then MsgBox "Esta tabla soporta bookmarks." Else MsgBox "Esta tabla no soporta bookmarks." End If Cambiando datos: Despues que se crea un Recordset tipo table o dynaset, se pueden modificar, eliminar o agregar nuevos registros. Lo anterior no se puede realizar si el Recordset es snapshot, o forward-only. Usando consultas parametrizadas: 31 Manual del Alumno Una consulta parametrizada es aquella que cuando se ejecuta despliega una caja de diálogo que le indica al usuario que debe digitar información, como por ejemplo un criterio para la búsqueda de registros o valores para insertar en un campo. Se pueden utilizar procedimientos almacenados para brindar procesos de mantenimiento de bases de datos o para recuperar consultas de uso intensivo. Para crear un procedimiento o consulta almacenada deben realizarse los siguientes pasos: Cree una consulta almacenada, especificando los parámetros que el usuario necesita proveer. Cuando se abre un Recordset utilizando esta consulta, la aplicación abre una caja de diálogo que solicita al usuario los valores para los parámetros establecidos en la consulta. El siguiente ejemplo toma dos cadenas que representan fechas y crea una consulta parametrizada que retorna todos aquellos registros en la tabla Orders cuyo campo Order Date esté entre las dos fechas. Este añade todos los valores del campo OrderId retraido en la consulta y los almacena en un arreglo: Function OrdersFromTo(strDateFrom As Variant, strDateTo As Variant, _ lngOrderIDs() As Long) Dim dbs As Database, rstOrders As Recordset Dim qdf As QueryDef, strSQL As String, intI As Integer On Error GoTo ErrorHandler Set dbs = CurrentDb strSQL = "PARAMETERS [DateFrom] DateTime, [DateTo] DateTime; " strSQL = strSQL & "SELECT * FROM Orders WHERE OrderDate BETWEEN " strSQL = strSQL & "[DateFrom] AND [DateTo];" ' Crea una consulta almacenada parametrizada. Set qdf = dbs.CreateQueryDef("", strSQL) ' configura los parámetros de la consulta. qdf.Parameters("DateFrom") = strDateFrom qdf.Parameters("DateTo") = strDateTo 'Abre un Recordset forward-only snapshot. Set rstOrders = qdf.OpenRecordset(dbOpenSnapshot, dbForwardOnly) ' Carga en un arreglo todos los registros del campo OrderIDs ' obtenidos en la consulta intI = 1 While rstOrders.EOF = False ReDim lngOrderIDs(1 To intI) 32 Manual del Alumno lngOrderIDs(intI) = rstOrders!OrderID intI = intI + 1 rstOrders.MoveNext Wend ErrorHandler: Select Case Err Case 0 Exit Function Case Else MsgBox "Error " & Err & ": " & Error, vbOKOnly, "ERROR" Exit Function End Select End Function Para más información veáse en la ayuda: “parameter queries.” Marcando cambios Bulk: Muchos de los cambios que se pueden realizar en una base de datos pueden eficientizarse utilizando consultas definidas. El ejemplo siguiente crea un objeto QueryDef para actualizar la tabla Employees: Dim dbs As Database, qdfChangeTitles As QueryDef Set dbs = CurrentDb Set qdfChangeTitles = dbs.CreateQueryDef("") qdfChangeTitles.SQL = "UPDATE Employees SET Title = 'Account Executive' " _ & "WHERE Title = 'Sales Representative';" qdfChangeTitles.Execute dbFailOnError ' llama a la consulta. Se puede reemplazar toda la cadena SQL en este ejmplo con una consulta almacenada parametrizable. El siguiente ejemplo muestra como el código anterior puede ser reescrito como una consulta almacenada y no como consulta definida: Dim dbs As Database, qdfChangeTitles As QueryDef Dim strSQLUpdate As String, strOld As String Dim strNew As String Set dbs = CurrentDb strSQLUpdate = "PARAMETERS [Old Title] Text, [New Title] Text; " _ & "UPDATE Employees SET Title = [New Title] WHERE Title = [Old Title];" ' Crea el objeto QueryDef. Set qdfChangeTitles = dbs.CreateQueryDef("", strSQLUpdate) 33 Manual del Alumno ' Solicita al usuario el valor anterior. strOld = InputBox("Digite el puesto anterior") ' Solicita al usuario el nuevo valor. strNew = InputBox("Digite el nuevo puesto") ' Configura los parametros. qdfChangeTitles.Parameters("Old Title") = strOld qdfChangeTitles.Parameters("New Title") = strNew ' Invoca la consulta. qdfChangeTitles.Execute Nota: una consulta de eliminación en más eficiente que el código que busca ciclicamente a través de un conjunto de registros, especialmente con bases de datos creadas con Access para Windows 95 o superior. Modificando un registro existente: Se pueden modificar registros existentes en un Recordset tipo table o dynaset usando los métodos Edit y Update. Para modificar un registro existente en un Recordset table o dynaset: 1. 2. 3. 4. Localizar el registro que se quiere modificar. Use el método Edit para preparar el registro actual para edición. Realice los cambios necesarios al registro. Use el método Update para salvar los cambios hechos. El siguiente ejemplo ilustra como cambiar el título del puesto para todos los representantes de ventas contenidos en una tabla llamada Employees. Dim dbs As Database, rstEmployees As Recordset Set dbs = CurrentDb Set rstEmployees = dbs.OpenRecordset("Employees") rstEmployees.MoveFirst Do Until rstEmployees.EOF If rstEmployees!Title = "Sales Representative" Then rstEmployees.Edit rstEmployees!Title = "Account Executive" rstEmployees.Update End If rstEmployees.MoveNext 34 Manual del Alumno Loop rstEmployees.Close Es importante notar que si se utiliza el método Edit antes que se realicen los cambios ocurre un error de ejecución. Si se edita el registro actual y entonces se mueve el puntero de registros o se cierra el Recordset sin haber primeramente ejecutado un metodo Update, los cambios realizados no surtirán efecto. Se puede finalizar el método Edit y cualquier otra transacción pendiente sin salvar los cambios utilizando el método CancelUpdate. Actualizaciones inconsistentes: Un Recordset tipo dynaset puede estar basados en una consulta de múltiples tablas con una relación tipo uno a muchos. Por ejemp.lo, supongase que se desea crear una consulta de múltiples tablas que combinan campos de las tablas Orders y Order Details. Generalmente hablando no se pueden cambiar valores en la tabla Orders por que están subordinados a los valores Order Details. Los cambios en estos casos tienen que realizarse en ámbas tablas para que no se presenten inconsistencias. En tal caso puede utilizarse la constante dbInconsistent del método OpenRecordset para crear un dynaset inconsistente. Por ejemplo: Set rstTotalSales = dbs.OpenRecordset("Sales Totals" ,, _ dbInconsistent) Cuando se actualiza un dynaset inconsiste, se puede facilmente destruir la integridad referencial de los datos en el dynaset. Por ende, debe procurarse realizar los cambios en todas aquellas tablas que mantengan esta integridad referencial. La constante dbInconsisten está disponible solo para los objetos Recordset de tipo dynaset. Este es ignorado para los de tipo table, snapshot y forward-only. Elimimando un registro existente: Se puede eliminar un registro existente en un Recordset de tipo table o dynaset utilizando el método Delete. No se pueden eliminar registros de un snapshot. El ejemplo siguiente muestra como eliminar todos los registros duplicados en la tabla Shippers: Function DeleteDuplicateShippers() As Integer Dim rstShippers As Recordset, strQuery$, dbs As Database, strName$ On Error GoTo ErrorHandler strQuery = "SELECT * FROM Shippers ORDER BY CompanyName;" Set dbs = CurrentDb Set rstShippers = dbs.OpenRecordset(strQuery, dbOpenDynaset) ' Si no hay registros en la tabla Shippers table, sale. 35 Manual del Alumno If rstShippers.EOF Then Exit Function strName = rstShippers![CompanyName] rstShippers.MoveNext Do Until rstShippers.EOF If rstShippers![CompanyName] = strName Then rstShippers.Delete Else strName = rstShippers![CompanyName] End If rstShippers.MoveNext Loop ErrorHandler: Select Case Err Case 0 ' La constante conSuccess y conFailed son definidas en ' el nivel de módulo como constante pública con valor entero. ' con valores de 0 y -32,737 respectivamente. DeleteDuplicateShippers = conSuccess Exit Function Case Else MsgBox "Error " & Err & ": " & Error, vbOKOnly, "ERROR" DeleteDuplicateShippers = conFailed Exit Function End Select End Function Cuando se usa el metodo Delete, Microsoft Jet inmediatamente elimina el registro actual (después de ejecutar la búsqueda, por supuesto), sin presentar ninguna advertencia al respecto. Si se elimina el registro actual el motor no provee un desplazamiento automático al siguiente o al registro anterior en el Recordset, por tanto debe utilizarse el método MoveNext para realizarlo. Si se intenta accesar un registro previamente eliminado en el Recordset tipo table se puede presentar una condición de error: 3167 “Registro eliminado”. En un dynaset se puede obtener el error 3021 “No registro actual”. Agregando un nuevo registro: Se puede agregar un nuevo registro a un Recordset tipo table o dynaset utilizando el método AddNew. Para agregar un nuevo registro a un Recordset table o dynaset: 1. Use el método AddNew para crear un nuevo registro. 2. Asigne valores para cada campo del registro. 3. Use el método Update para guardar los datos o CancelUpdate para cancelar. 36 Manual del Alumno El siguiente ejemplo agrega un nuevo registro en el Recordset tipo tabla de nombre rstShippers: Dim dbs As Database, rstShippers As Recordset Set dbs = CurrentDb Set rstShippers = dbs.OpenRecordset("Shippers") rstShippers.AddNew rstShippers!CompanyName = "Global Parcel Service" . . ' Set remaining fields. . rstShippers.Update rstShippers.Close Cuando se utiliza el método AddNew, Microsoft Jet prepara un nuevo registro en blanco y lo habilita como el registro actual. Si se añade un registro a un Recordset de tipo dynaset este nuevo registro aparece al final del Recordset. Para forzarlo a que aparezca en la posición adecuada de un ordenamiento se debe recrear el Recordset mediante el método Requery. Si se agrega un registro a un Recordset tipo table, el registro aparece posicionado de acuerdo al indice actual, o al final de la tabla si no hay un índice abierto. Transacciones Microsoft Jet: Una transacción es un conjunto de operaciones realizadas que son tratadas como una sola unidad de trabajo. El trabajo en una transacción pueden ser completadas como un todo; si una parte de la misma falla toda la transacción falla. Las transacciones ofrecen al desarrollador de software la posibilidad para reforzar la integridad de la informacion. Cuando múltiples tablas son involucradas en procesos de actualización, por ejemplo, pueden surgir fallas que pueden crear inconsistencias peligrosas de información. El ejemplo más común de transacciones son las que atañen a la automatización bancaria de algunos procesos, Automated Teller Machine (ATM). El proceso de dispensar dinero y el débito y crédito de cuentas son consideradas una unidad lógica de trabajo y que son tratadas como una transacción: el dinero no puede ser dispensado sin que el sistema esté disponible para realizar el débito de la cuenta. Utilizando Transacciones en aplicaciones: Microsoft Jet soporta transacciones a traves de DAO: BeginTrans, CommitTrans y Rollback. La sintaxis básica de estos métodos se muestran en la siguiente tabla: Método Workspace.BeginTrans Workspace.CommitTrans Operación Inicia la transacción Después de la transacción escribe y actualiza permanente a los objetos relacionados en la base de datos. 37 Manual del Alumno Workspace.Rollback Cancela las transacciones, por error o por decisión del usuario. El siguiente ejemplo cambia los títulos de oficio de todos los representantes de ventas en la tabla Employees de la base de datos Northwind. Después del método BeginTrans inicia una transacción que realiza los cambios respectivos en la tabla Employees, el método CommitTrans guarda los cambios realizados. Se puede utilizar el método Rollback para cancelar los cambios realizados con el Update y restaurar la tabla a su estado original. Sub ChangeTitle() Dim dbsSales As Database Dim rstEmp As Recordset Dim wrkCurrent As Workspace Set wrkCurrent = DBEngine.Workspaces(0) Set dbsSales = OpenDatabase("Northwind.mdb") Set rstEmp = dbsSales.OpenRecordset("Employees", dbOpenTable) wrkCurrent.BeginTrans Do Until rstEmp.EOF If rstEmp!Title = "Sales Representative" Then rstEmp.Edit rstEmp!Title = "Sales Associate" rstEmp.Update End If rstEmp.MoveNext Loop If MsgBox("Guarda todos los cambios?", vbQuestion + vbYesNo) = vbYes Then wrkCurrent.CommitTrans Else wrkCurrent.Rollback End If rstEmp.Close dbsSales.Close End Sub Cuando se utilizan transacciones, todas las bases de datos y los objetos Recordset en el Workspace especificado son afectados. Esto es, las transacciones afectan al workspace global no a una base de datos o recordset específico. Asimismo, los métodos asociados tienen su incidencia a traves de todo el workspace: CommitTrans y Rollback. Nota: Se pueden utilizar los métodos BeginTrans, CommitTrans y Rollback con el objeto DBEngine. En este caso, la transacción es aplicada al workspace por defecto, como: DBEngine.Workspaces(0). Manejando Transacciones: Microsoft Jet utiliza algoritmos sofisticados para proveer transacciones seguras y confiables. 38 Manual del Alumno Tamaño de transacciones: El tamaño de las transacciones que se pueden realizar con Visual Basic, utilizando el motor Jet, está limitada únicamente por la cantidad física de espacio en el disco duro en que se realicen. Esto es, Microsoft Jet puede almacenar una cantidad de transacciones tan grande como lo sea el medio de almacenamiento donde se realice. Si por ejemplo, en la ejecución de una transacción se agota el espacio físico en disco se produce un error interceptable (2004) y reacciona de acuerdo al código de la aplicación. El caso es que si la aplicación realiza el commit después de ocurrido este error, es factible que Microsoft Jet no pueda regenerar la base de datos por tanto no se puede determinar el número de registros afectados (olvidemos el dbrecordaffected), poniendo a esta en un estado inconsistente. Si por el contrario, se realiza un rollback de los cambios la base de datos queda en su estado original. Niveles Transaccionales: Se pueden tener hasta 5 niveles de transacciones activas al mismo tiempo. Esto es, se pueden crear hasta 5 niveles transaccionales en una misma ejecución. Si se desea tener transacciones con “overlapping”, “nonnested scopes” se pueden abrir objetos workspace adicionales y manejar dichas transaccionales en dichos workspaces. Cuando una transacción es “Rolled Back” por el motor de bases de datos Jet: Si se cierra un objeto Workspace, cualquier transacción es automaticamente “Rolled Back”. Microsoft Jet nunca realiza un Commit automáticamente sin haberla iniciado. Asimismo, cuando se cierra una base de datos, variables atinentes o transacciones sin committ sufren el mismo efecto. Transacciones en origenes de datos externos: Las transacciones no son soportadas en origenes de datos externas ( a excepción de datos mediante ODBC). Por ejemplo, si la base de datos tiene ligas a tablas de FoxPro® o dBASE®, las transacciones a estos objetos son ignorados. Para determinar o no si una base de datos u objeto Recordset soporta transacciones se puede chequear el valor de la propiedad Transactions. Un valor True indica que si lo soporta. Transacciones y rendimiento: En versiones anteriores de Microsoft Access era generalmente recomendado que se usaran transacciones como un factor de mejoramiento en el rendimiento de aplicaciones. Hoy día todas las transacciones para DAO (agregación, modificación y eliminación) son mejoradas en su rendimiento internamente y automaticamente. En muchas situaciones, este soporte automático provee a las aplicaciones el mejor rendimiento posible. Sin embargo, muchas veces se debe recurrir a configurar algunas variables en el registro de Windows para mejorar en este aspecto. 39 Manual del Alumno Extrayendo Datos desde un registro: Después de que se ha localizado un registro particular, se puede necesitar extraer datos para usar en un aplicación, sea quiza para modificarlo. Copiando un campo simple: Se puede copiar un campo simple de un registro a una variable del tipo apropiado. El ejemplo siguiente extrae tres campos del primer registro del objeto Recordset: Dim dbs As Database, rstEmployees As Recordset Dim strFirstName As String, strLastName As String Dim strTitle As String Set dbs = CurrentDb Set rstEmployees = dbs.OpenRecordset("Employees") rstEmployees.MoveFirst strFirstName = rstEmployees!FirstName strLastName = rstEmployees!LastName strTitle = rstEmployees!Title rstEmployees.Close Copiando registros completos en un arreglo: Para copiar uno o más registros completos, se puede crear un array bidimensional y copiar los registros en él. Se incrementa el primer parámetro por cada campo y el segundo por cada registro. Una via rápida para hacer esto es con el método GetRows. El método GetRows retorna un arreglo bidimensional. El primer parámetro identifica el campo y el segundo el número de fila, tal y como se muestra en el ejemplo siguiente: varRecords(intField, intRecord) El siguiente ejemplo utiliza una sentencia SQL para extraer tres campos desde una tabla llamada Employees y los coloca en un objeto Recordset. Este entonces utiliza el método GetRows para extraer los primeros tres registros del Recordset y almacena los registros seleccionados en un arreglo bidimensional. Imprime entonces cada registro, campo por campo, usando los dos arreglos indexados para seleccionar campos especificos y registros. Para aclarar este punto se ilustra como los arreglos indexados son utilizados. El ejemplo utiliza una sentencia separada para identificar e imprimir cada campo de cada registro. En la práctica, es mejor utilizar dos ciclos, uno en el otro, y proveer variables enteras para los indices que navegan en ambas dimensiones del arreglo: 40 Manual del Alumno Sub GetRowsTest() Dim dbs As Database Dim rstEmployees As Recordset Dim varRecords As Variant Dim intNumReturned As Integer Dim intNumColumns As Integer Dim intColumn As Integer, intRow As Integer Set dbs = CurrentDb Set rstEmployees = dbs.OpenRecordset("SELECT FirstName, LastName, Title" _ & " FROM Employees", dbOpenSnapshot) varRecords = rstEmployees.GetRows(3) intNumReturned = UBound(varRecords, 2) + 1 intNumColumns = UBound(varRecords, 1) + 1 For intRow = 0 To intNumReturned - 1 For intColumn = 0 To intNumColumns - 1 Debug.Print varRecords(intColumn, intRow) Next intColumn Next intRow rstEmployees.Close End Sub Se puede utilizar subsecuentes llamadas al método GetRows si hay más registros disponibles. Usando Consultas sumarizadas: Se puede sumarizar datos utilizando las claúsulas GROUP BY y HAVING. GROUP BY organiza los datos dentro de grupos; HAVING configura condiciones de grupos para incluir en el resultado. Estas claúsulas pueden estar juntas, HAVING usada sin GROUP BY puede producir resultados confusos. Usando Eventos del Control Data: Tres eventos se pueden utilizar en aplicaciones de bases de datos, utilizando Visual Basic: Validate, Error y Reposition. Evento Validate: Este evento se utiliza para comprobar los datos del RecordSet antes de guardar el registro en la base de datos. Este evento se produce antes de que Visual Basic escriba los datos en la base de datos. Es útil para solicitar al usuario confirme los cambios a realizar en la base de datos. La sintaxis de este evento es la siguiente: Private Sub Data1_Validate (index As Integer, action As Integer, save As Integer) El argumento action indica la operación que provocó el evento Validate. Este evento es producto de realizar las siguientes operaciones: MoveFirst, MovePrevious, MoveNext, 41 Manual del Alumno MoveLast, AddNew, Update, Delete, Find, establecer la propiedad BookMark, cerrar la base de datos, descargar el formulario. Para cancelar cualquiera de estas acciones, se debe asignarsele vbDataActionCancel al argumento action. El argumento save indica si va a guardarse o no el registro. Si save es True, los datos enlazados han cambiado. Para cancelar la acción de guardar puede asignarse False al argumento save. El código siguiente solicita al usuario que confirme los cambios realizados en la base de datos. Si el usuario indica que no, los cambios se cancelarán automáticamente: Private Sub Data1_Validate (Action As Integer, Save As Integer) Dim Respuesta As Integer If Save = True Then Respuesta = MsgBox ("¿Guardar cambios?", vbYesNo) If Respuesta = vbNo Then Save = False Data1.UpdateControls ' Actualiza campos. End If End If End Sub Evento Reposition: El evento Reposition se utiliza para modificar la apariencia de un formulario o realizar una acción necesaria cuando se desplace a un nuevo registro. Esto se realiza cuando Visual Basic se desplaza a otro registro o cuando se abre la base de datos. Se puede variar la apariencia de un formulario mostrando información sobre cuántos registros se encuentran en el Recordset activo. Para ello, debe utilizarse la propiedad AbsolutePosition. El código siguiente ilustra como demostrar lo anterior: Private Sub Data1_Reposition() Data1.Caption = "Número de registro " & _ Data1.Recordset.AbsolutePosition + 1 End Sub Evento Error: Este evento se ejecuta cuanto el usuario interactua con el control data y se produce un error de acceso a datos. En este caso debe agregarse una rutina personalizada de tratamiento de errores. Los valores contenidos en objetos enlazados a datos no cambian cuando se produce un error. Puede asignar el valor 0 al argumento Response para que no se muestre el mensaje de error. El siguiente ejemplo muestra lo anterior: 42 Manual del Alumno Private Sub Data1_Error(DataErr As Integer, Response As Integer) If DataErr = 3022 Then 'Error de clave duplicada MsgBox "Escriba un número de Id. de empleado único" txtEmpID.SetFocus Response = 0 Else Response = 1 'muestra el mensaje de error estándar End If End Sub Enlazando datos a Controles ActiveX: Un control ActiveX es un objeto que permite interactuar a un usuario con una aplicación. Estos objetos tienen eventos que se pueden utilizar. La extensión de estos objetos ActiveX es .OCX. Un ejemplo clásico, y muy utilizado, es el objeto Cuadrícula (DbGrid). Este objeto permite mostrar datos de una base de datos en forma de cuadrícula, con lo que le permite al usuario manipular varios registros a la vez. Este control posee la propiedad DataSource que permite guardar el nombre del origen de datos desde el cual se cargarán sus celdas. El DBGrid permite ver y modificar registros simultáneamente. Se puede utilizar la colección columns para recuperar el texto de la celda seleccionada actualmente en tiempo de ejecución mediante el siguiente código: MsgBox DBGrid1.Columns(DBGrid1.Col).Text Para modificar la información cargada en el DBGrid del objeto Recordset asociado puede utilizarse el siguiente código: Data1.Recordset.Edit Data1.Recordset.Fields("Campeon") = "L.D.A." Data1.Recordset.Update El evento BeforeUpdate se ejecuta antes de que se muevan los datos desde un DBGrid al control data. En este caso puede validarse la información respectiva y cancelarse si es necesario. Un objeto que tiene similares particularidades al DBGrid es el MSFlexGrid. Sin embargo, este control solo permite datos de captura. Puede almacenar en el datos provenientes de un data control o de una consulta mediante SQL. Muchas veces es necesario combinar celdas, tal y como se realiza en Excel. MSFlexGrid permite realizar esto asignando a la propiedad MergeCells un valor distinto de cero y posteriormente asignar las propiedades de matrices MergeRow() y MergeCol() el valor True para las filas que se deseen combinar. El siguiente ejemplo muestra como combinar las celdas de la tabla Facturas: 43 Manual del Alumno Private Sub Form_Load() MSFlexGrid1.MergeCells = flexMergeFree MSFlexGrid1.MergeCol(1) = True End Sub Un control de cuadro de lista enlazado a datos (DBList) o el cuador combinado enlazado a datos (DBCombo) se utilizan para presentar un listado de datos de un Recordset. Esto es útil cuando se desea validar datos por parte del usuario. Por ejemplo, si se desea enviar una notificación a un cliente, se puede utilizar un control DBList para presentar la lista de todos los nombres de clientes válidos y obtener asi su ID. En un DBCombo para asignar datos debe configurarse la propiedad RowSource con el nombre de un control data existente y en la propiedad ListField el nombre de un campo. Si se debe actualizar un campo de la base de datos debe establecerse las propiedades DataSource y DataField con lo requerido. Para establecer la relación entre la tabla que contiene los valores de búsqueda y la tabla que se está modificando realmente, debe establecerse la propiedad BoundColumn. DBEngine y WorkSpace: DAO es una metodología de acceso a datos que permite la manipulación de estos mediante la utilización de objetos que pertenecen a su jerarquía. Sin embargo, para poder accesar datos de una base, debe utilizarse primero el objeto DBEngine para abrir un espacio de trabajo, o sesión, denominado Workspace. El objeto DBEngine es el objeto de nivel superior dentro del modelo DAO. En este contexto se pueden utilizar los métodos RepairDatabase y CompactDataBase de este objeto para darle mantenimiento a una base de datos, tal y como se muestra en el código siguiente: MsgBox "El número de versión de DAO es " & _ DBEngine.Version DBEngine.RepairDatabase "C:\MIBD.MDB" El objeto WorkSpace define una sesión de usuario, determinando cuanto interactua su aplicación con los datos de la base de datos. Si se abre una base de datos sin definir el Workspace se utiliza DBEngine.Workspaces(0) como valor predeterminado para la sesión. En el ejemplo siguiente se crea una sesión de usuario que utiliza el motor de bases de datos de Microsoft Jet: Dim wspNew as Workspace Set wspNew = DBEngine.CreateWorkspace _ ("NewJetWorkspace", "Admin", "", dbUseJet) Usando el método OpenDatabase: 44 Manual del Alumno Con este método se puede crear un objeto tipo Database. Este método se caracteriza por manejar las siguientes tareas: el nombre de la base de datos origen, el metodo de acceso a datos, indicación del ambiente de uso (monousuario, multiusuario), indicacion de lectura/escritura en la base de datos o alguno de ellos, conexión ISAM, DAO u ODBC. La sintaxis de esta función es la siguiente: Set basededatos = espaciodetrabajo.OpenDatabase opciones, sólolectura, conexión) (nombrebasededatos, La sintaxis del método OpenDatabase consta de las siguientes partes: Argumento basededatos espaciodetrabajo nombrebasededatos Argumento opciones sólolectura conexión Descripción Una variable de objeto que representa el objeto Database que va a abrir. Opcional. Una variable de objeto que representa el objeto Workspace existente que contendrá la base de datos. Si no incluye un valor para espaciodetrabajo, OpenDatabase utiliza el espacio de trabajo predeterminado. Un tipo de datos String que es el nombre de un archivo de base de datos Microsoft Jet existente o el nombre del origen de datos (DSN) de un origen de datos ODBC existente. Consulte la propiedad Name para obtener más información acerca de este valor. Descripción Opcional. Un tipo de datos Variant que establece varias opciones para la base de datos, como se especifica en Valores. Opcional. Un valor de tipo de datos Variant (subtipo Boolean) que es True si desea abrir la base de datos con acceso de sólo lectura o False (predeterminado) si desea abrir la base de datos con acceso de lectura/escritura. Opcional. Un tipo de datos Variant (subtipo String) que especifica información variada sobre la conexión, incluyendo las contraseñas. Valores: Para los espacios de trabajo Microsoft Jet, puede utilizar los siguientes valores para el argumento opciones: Valor True False Descripción Abre la base de datos en modo exclusivo. (Predeterminado) Abre la base de datos en modo compartido. Para los espacios de trabajo ODBCDirect, el argumento opciones determina si y cuando informar al usuario acerca de establecer la conexión. Puede utilizar una de las siguientes constantes: 45 Manual del Alumno Constante dbDriverNoPrompt DbDriverPrompt DbDriverComplete Constante DbDriverCompleteRequired Descripción El Administrador del controlador de ODBC utiliza la cadena de conexión suministrada en nombrebasededatos y conexión. Si no proporciona información suficiente, se produce un error en tiempo de ejecución. El Administrador del controlador de ODBC muestra el cuadro de diálogo ODBC Data Sources, que muestra cualquier información interesante suministrada en nombrebasededatos o conexión. La cadena de conexión se realiza con el DSN que el usuario selecciona en los cuadros de diálogo o, si el usuario no especifica un DSN, se utiliza el valor predeterminado de DSN. (Predeterminado) Si los argumentos conexión y nombrebasededatos incluyen toda la información necesaria para completar la conexión, el Administrador del controlador de ODBC utiliza la cadena en conexión. De otro modo funciona como cuando especifica dbDriverPrompt. Descripción Esta opción funciona como dbDriverComplete excepto que el controlador ODBC desactiva el envío de información que no necesita para completar la conexión. Abriendo tablas ISAM: Muchas veces se necesita abrir tablas o archivos que son tipo DBF, por ejemplo. En este caso se puede utilizar la siguiente sintaxis: Set dbMydb = OpenDatabase("\\PlaniVB", False, False, "Dbase III;") Set rs = dbMydb.OpenRecordset("Planilla", dbOpenTable) rs.Index = "Cedula" Se puede observar que PlaniVB es el directorio donde está contenida la tabla que se desea abrir. Puede observarse que el comando OpenDatabase no abre ninguna base de datos sino que solo referencia el directorio en donde yace la tabla DBF. Seguidamente, se crea un Recordset, de tipo Table, para abrir la tabla DBF respectiva. En la tercera línea puede observarse como habilitar un índice “Cedula” que utiliza la tabla y que podemos utilizar en el Recordset para realizar una búsqueda indizada en la misma, tal y como se muestra en el código siguiente: rs.Seek "=", Busc Busc en este caso, es una variable que contiene el valor obtenido, por ejemplo, de un TextBox o de cualquier objeto. El índice puede crearse utilizando Visual Data de Visual Basic. Se abre la tabla y se modifica mediante la apertura en modo diseño y se crea el índice que se necesita. Visual Basic mantiene posteriormente el índice actualizado automaticamente. También puede 46 Manual del Alumno crearse inicialmente un archivo tipo texto, con cualquier editor y salvarlo con extensión INF. El archivo debe ser tal y como se muestra: Archivo INDCED.INF: NDX = CEDULA.NDX Comentarios: Cuando abre una base de datos, automáticamente se agrega a la colección Databases. Además, en un espacio de trabajo ODBCDirect, el objeto Connection correspondiente al objeto Database nuevo también se crea y anexa a la colección Connections del mismo objeto Workspace. Estas son algunas nombrebasededatos: consideraciones que se debe aplicar cuando utilice Si se hace referencia a una base de datos que ya está abierta para acceso en modo exclusivo por otro usuario, se produce un error. Si no se hace referencia a una base de datos existente o a un nombre de un origen de datos ODBC válido, se produce un error. Si es una cadena de longitud cero ("") y conexión es "ODBC;", se muestra un cuadro de diálogo que enumera todos los nombres de orígenes de datos ODBC registrados para que el usuario pueda seleccionar una base de datos. Si se está abriendo una base de datos mediante un espacio de trabajo ODBCDirect y proporciona el DSN en conexión, puede establecer nombrebasededatos a una cadena de su elección que puede utilizar para hacer referencia a esta base de datos en el código más adelante. El argumento conexión se expresa en dos partes: el tipo de base de datos, seguido por punto y coma (;) y los argumentos opcionales. Primero debe proporcionar el tipo de base de datos, como "ODBC;" o "FoxPro 2.5;". A continuación, os argumentos opcionales sin un orden concreto, separados por punto y coma. Uno de los parámetros puede ser la contraseña (si hay alguna asignada). Por ejemplo: "FoxPro 2.5; pwd=micontraseña" Utilizar el método NewPassword en un objeto Database distinto de una base de datos de ODBCDirect cambia el parámetro de contraseña que aparece en la parte ";pwd=..." de este argumento. Debe aplicar los argumentos opciones y sólolectura para proporcionar una cadena de origen. Consulte la propiedad Connect para su sintaxis. Para cerrar una base de datos y, de este modo, quitar el objeto Database de la colección Databases, utilice el método Close en el objeto . 47 Manual del Alumno Nota Cuando tiene acceso a un origen de datos ODBC conectado a Microsoft Jet, puede mejorar el rendimiento de sus aplicaciones abriendo un objeto Database conectado al origen de datos ODBC, en vez de vincular objetos TableDef individuales a tablas específicas en el origen de datos ODBC. El cuadro siguiente muestra el soporta que brinda Visual Basic a varios formatos de bases de datos: Database Format DataBaseName Microsoft Access drive:\path\file.mdb Betrieve drive:\path\file.ddf DBase III drive:\path Dbase IV drive:\path FoxPro Versión 2.5 drive:\path ODBC (SQL Server,Oracle) Registered data source Name(usualmente el Nombre del servidor) Connect (none) “Betrieve” “dBASE III” “DBASE IV” “FoxPro 2.5” “Odbc;Dsn=Server; Uid=User; Pd = Password” Desarrollo Avanzado de Bases de Datos: Exigencia de la Integridad de los datos: Este apartado no se refiere al manejo de datos con sistemas administradores de bases de datos de alto nivel como Oracle, Sybase o Microsoft SQL Server, dado que sobrepasan el alcance de este manual. Se refiere, por el contrario, a la manipulación de datos con Microsoft Access o Microsoft Jet como motor de administración de datos para aplicaciones de mediana complejidad y de uso de escritorio y no cliente servidor. Cuando se crea una aplicación de base de datos, debe asegurarse de que sólo se escriban datos válidos en la misma. Microsoft Access permite crear reglas y establecer restricciones de integridad referencial en una base de datos para ayudar a garantizar la integridad de los datos. Se recibirá un error de tiempo de ejecución si se trata de infringir estas restricciones. En este caso debe proporcionarse el código que intercepta estos errores y que permite al usuario corregir los datos y continuar. Cuando se utiliza una base de datos de Microsoft Jet, pueden definirse reglas que especifiquen los datos que son válidos en un campo o en una tabla. Estas reglas se almacenan con la base de datos y se exigirán independientemente de cómo modifique los datos. Por ejemplo puede establecerse que en una tabla determinada de una base de datos no permita valores nulos o ceros. Propiedades de las reglas: Con Visual Basic se pueden crear o ver las reglas de una base de datos. Los objetos Field y Recordset cuentan con dos propiedades relacionadas con las reglas. Estas propiedades sólo se admiten en las bases de datos de Microsoft Jet: 48 Manual del Alumno ValidationRule: Define los criterios de la regla. ValidationText : Proporciona un mensaje de error que puede mostrarse si se infringe la regla. Por ejemplo, si una tabla denominada Pedidos posee un campo Cantidad, puede escribirse código que permita mostrar las propiedades Validadtion Rule y Validation Text, tal como sigue: Sub DisplayRule() Set rstOrders = dbMydb.OpenRecordset("Detalles de pedidos") ' Imprime ">0" Debug.Print rstOrders("Cantidad").ValidationRule ' Imprime "La cantidad debe ser mayor que 0" Debug.Print rstOrders("Cantidad").ValidationText End Sub Nota Se puede establecer la propiedad ValidationRule de un objeto Field para definir una regla de forma programada. Sin embargo, aquí no se explica cómo definir una regla por programa. Para obtener más información acerca de la creación de reglas, vea la propiedad ValidationRule en la Ayuda de Visual Basic. Errores de infracción de las reglas: Si cuando se actualiza un registro se infringe una regla, se producirá un error de tiempo de ejecución. La propiedad ValidateOnSet determina cuándo se produce el error de infracción de la regla. Si ValidateOnSet es True, el error de tiempo de ejecución se produce cuando se establece el campo, como se muestra en el ejemplo siguiente: recOrders.Edit recOrders("Cantidad").ValidateOnSet = True recOrders("Cantidad").Value = 0 'SE GENERA UN ERROR recOrders.Update Si ValidateOnSet es False, el error de tiempo de ejecución se producirá cuando se ejecute el método Update, como en el siguiente ejemplo: recOrders.Edit recOrders("Cantidad").ValidateOnSet = False recOrders("Cantidad").Value = 0 recOrders.Update 'Se genera un error. Tratamiento de las infracciones de las reglas: Normalmente, si se produce una infracción de las reglas se deseará permitir al usuario corregir los datos y tratar de volver a actualizarlos. 49 Manual del Alumno Si se utiliza el control Data e infringe una regla, se producirá el evento Error. El control Data muestra la propiedad ValidationText y cancela la actualización. El usuario puede modificar los datos y hacer clic en el control Data para tratar de volver a realizar la actualización. Se puede agregar código al evento Error para realizar una acción distinta cuando se produzca el error. Por ejemplo, puede mostrarse texto en un título en lugar de un cuadro de mensajes o bien cambiar la propiedad ForeColor de los campos para indicar que se ha producido un error. Si se utilizan Objetos de acceso a datos (DAO) e infringe una regla, se recibirá un error de tiempo de ejecución. Debe interceptarse este error e informar al usuario. El siguiente fragmento de código muestra como desplegar un mensaje cuando se produzca una infracción a la regla (cantidad igual a cero, por ejemplo): Private Sub cmdUpdate_Click() On Error GoTo update_err rs.Edit rs("Cantidad") = txtQuantity.Text rs.Update Exit Sub update_err: If Err.Number = 3316 Then 'determina la infracción lblError.Caption= Err.Description rs.CancelUpdate Exit Sub End If End Sub La propiedad Description del objeto Err contiene el valor de la propiedad ValidationText. Puede utilizarse el método CancelUpdate para cancelar el método Edit o AddNew actual y para actualizar el registro actual. Pueden dejarse los datos del usuario en el formulario o bien actualizar el formulario con los datos actuales de la base de datos. Reglas de Integridad Referencial: La integridad referencial hace referencia a las restricciones que se deben poner en una base de datos para mantener las relaciones definidas entre dos tablas cuando se agregan, cambian o eliminan registros. Definir las restricciones de integridad referencial: El motor de base de datos Microsoft Jet puede exigir automáticamente la integridad referencial en las bases de datos de Microsoft Jet. 50 Manual del Alumno Por ejemplo, puede existir una tabla denominada Clientes y otra Pedidos. No se pueden incluir pedidos si no existe un cliente; eso es mantener la integridad referencial de los datos. Nota Pueden definirse las relaciones entre tablas mediante programación con Visual Basic o bien manualmente, mediante Microsoft Access. Para obtener información acerca de la creación de relaciones mediante programación, vea el método CreateRelation en la Ayuda de Visual Basic. Actualizaciones y eliminaciones en cascada: Una vez establecida la integridad referencial, pueden especificarse las opciones Actualizar en cascada los campos relacionados o Eliminar en cascada los registros relacionados. Si establece estas opciones, el motor de base de datos Microsoft Jet permitirá al usuario cambiar y eliminar registros, y cambiará o eliminará automáticamente los registros relacionados de las tablas relacionadas. Por ejemplo, suponga que se ha creado una relación entre las tablas Clientes y Pedidos. Si se selecciona la opción Eliminar en cascada los registros relacionados para la tabla Clientes, cada vez que el usuario elimine el registro de un cliente, el motor de base de datos Microsoft Jet eliminará automáticamente todos los registros relacionados con ese cliente de la tabla Pedidos. Nota El motor de base de datos Microsoft Jet no muestra ninguna advertencia cuando elimina los registros relacionados. Es muy importante que se entienda cómo están definidas las relaciones en la base de datos para evitar pérdidas de datos al eliminar registros. Tratamiento de las infracciones de integridad referencial: Si se trata de infringir las restricciones de integridad referencial, se recibirá un error de tiempo de ejecución. Debe interceptarse este error e informar al usuario. El siguiente fragmento de código muestra un mensaje de error si el usuario intenta eliminar el registro de un cliente que tiene un pedido: Private Sub cmdDelete_Click() On Error GoTo del_err rs.Delete rs.MoveNext FillFields 'procedimiento escrito por el usuario Exit Sub del_err: 'Referencia a la violación de integridad If Err.Number = 3200 Then lblError.Caption = "No se puede borrar un cliente con órdenes." Exit Sub Else 'controlar otros errores... 51 Manual del Alumno End If End Sub El registro no se elimina y no se vuelve a posicionar en el registro actual. Los cuadros de texto del formulario siguen conteniendo los datos actuales. Prevenir infracciones de integridad referencial: Para evitar infracciones de integridad referencial puede proporcionarse un cuadro de lista con entradas válidas. Si las entradas válidas son demasiado largas y no caben en un cuadro de lista, permitase que el usuario especifique criterios de búsqueda y muestrese después una lista de entradas válidas. Por ejemplo, cuando agrega un nuevo producto, se puede permitir al usuario escribir un valor para un nombre de producto y a continuación mostrar una cuadrícula con todos los productos que coincidan con ese nombre. Seguidamente, el usuario puede seleccionar un producto válido de la cuadrícula. Transacciones: Una transacción es una serie de cambios realizados en una base de datos. Pueden agruparse varios cambios de bases de datos en una transacción. Si todas las operaciones se realizan correctamente, confirmará la transacción. Si alguna de las operaciones falla, deshará la transacción. Métodos de transacciones: Los métodos de transacciones se aplican al objeto Workspace. Se Utiliza el método BeginTrans antes de actualizar el primer registro. Si alguna de las siguientes actualizaciones falla, se utiliza el método Rollback para deshacerlas todas. Se usa el método CommitTrans tras haber actualizado correctamente el último registro. El siguiente fragmento de código muestra la utilización de transacciones con el fin de agregar un entrada a la tabla Pedidos y a la tabla Detalles de pedidos: Sub cmdAddOrder_Click () On Error GoTo AddOrder_Err DBEngine.WorkSpaces(0).BeginTrans db.Execute "Insert Into Pedidos...", dbFailOnError db.Execute "Insert Into [Detalles de pedidos]..." , dbFailOnError DBEngine.WorkSpaces(0).CommitTrans lblStatus.Caption = "Actualizaciones terminadas" Exit Sub AddOrder_Err: lblStatus.Caption = Err.Description Msgbox "No se han efectuado todas las actualizaciones con éxito" DBEngine.Workspaces(0).Rollback 52 Manual del Alumno Exit Sub End Sub Utilizar transacciones para mejorar el rendimiento: Además de utilizar las transacciones para mantener la integridad de los datos, puede mejorarse el rendimiento si se incluye una instrucción Update de SQL en una transacción. Las operaciones de la transacción se almacenan en búfer y no se escriben a disco hasta que se confirma la transacción. El siguiente ejemplo incluye una instrucción Update de SQL en una transacción: DBEngine.Workspaces(0).BeginTrans stSQL = "UPDATE Productos SET [PrecioUnidad] = [PrecioUnidad] * 0.1" db.Execute strSQL, dbFailOnError DBEngine.Workspaces(0).CommitTrans Nota Es posible anidar transacciones en un objeto Workspace. Se deberá confirmar o deshacer la transacción actual antes de poder confirmar o deshacer una transacción de un nivel superior. Si se desea disponer de transacciones que se solapen pero que sean independientes entre sí, puede crearse un segundo objeto Workspace. Para obtener más información acerca de la creación de objetos Workspace, veáse el método CreateWorkspace en la Ayuda de Visual Basic. Consideraciones multiusuario: Las aplicaciones de bases de datos se vuelven más complejas si se desea permitir que varios usuarios tengan acceso a una base de datos simultáneamente. Deberán tratar diversos errores que pueden producirse cuando varios usuarios tengan acceso a los mismos datos. Por ejemplo: Dos usuarios tratan de modificar los mismos datos: El motor de base de datos Microsoft Jet bloquea automáticamente los datos para evitar que dos usuarios modifiquen un registro simultáneamente. Si los usuarios tratan de actualizar un registro bloqueado, recibirán un error de tiempo de ejecución. Alguien cambia un registro que otro usuario está viendo: Si un usuario intenta actualizar un registro y los datos se han modificado desde que se ejecutó el método Edit, el motor de base de datos Microsoft Jet devolverá un error. Alguien elimina un registro que otro usuario está viendo: Si un usuario intenta tener acceso a un registro que se ha eliminado, el motor de base de datos Microsoft Jet devolverá un error. Abrir una tabla para acceso exclusivo: 53 Manual del Alumno Para evitar los problemas que ocurren cuando se permite que varios usuarios tengan acceso a una base de datos, puede abrirse un tabla para uso exclusivo. Al abrirse una tabla de este modo se evita que otro usuario la utilice. Aunque se trata de una solución bastante restrictiva, puede resultar adecuada para tal situación. El argumento opciones para el método OpenRecordset determina cómo se abre el Recordset. En el código siguiente se abre una tabla para uso exclusivo. Puede modificarse la tabla Pedidos, pero ningún otro usuario podrá ver ni modificar los datos de dicha tabla: Set recOrders = dbMydb.OpenRecordset _ ("Pedidos", dbOpenTable, dbDenyRead + dbDenyWrite) En el código siguiente se abre una tabla para escritura en modo exclusivo. Puede verse y modificarse la tabla Pedidos; los otros usuarios pueden ver los datos de la tabla, pero no pueden modificarlos: Set recOrders = dbMydb.OpenRecordset _ ("Pedidos", dbOpenTable, dbDenyWrite) En el código siguiente se abre un Recordset en modo de sólo lectura. Pueden leerse los datos de la tabla, pero no modificarlos. Esto no afecta a los demás usuarios: Set recOrders = dbMydb.OpenRecordset _ ("Pedidos", dbOpenTable, dbReadOnly) En el código siguiente se abre un Recordset en modo anexar. Sólo pueden agregarse registros; no pueden verse ni modificar los registros existentes. Esto no afecta a los demás usuarios: Set recOrders = dbMydb.OpenRecordset _ ("Pedidos", dbOpenDynaset, dbAppendOnly) Si sólo se desea agregar registros a una tabla, debe utilizarse la opción dbAppendOnly para obtener mayor rendimiento. La opción dbDenyRead sólo está disponible en el Recordset de tipo table. Para obtener una lista de valores válidos para el argumento opciones, veáse el método OpenRecordset en la Ayuda de Visual Basic. El bloque de Microsoft Jet: Todos los sistemas de administración de bases de datos proporcionan algún tipo de mecanismo de bloqueo para evitar que dos usuarios actualicen datos simultáneamente. El motor de base de datos Microsoft Jet proporciona un bloqueo de página. Las páginas son bloques de registros de 2048 bytes (2 KB) de tamaño. Visual Basic almacena tantos registros como quepan en cada página. Cuando Visual Basic bloquea la página que contiene un registro que está modificando, todos los demás registros de esa página también quedarán bloqueados. 54 Manual del Alumno Nota La información aquí consignada es específica de las bases de datos que utilizan el motor Microsoft Jet. Cuando se tiene acceso a bases de datos a través de ODBC, el mecanismo de bloqueo está controlado por el sistema de administración de bases de datos. Bloqueo pesimista frente a bloqueo optimista: El valor de la propiedad LockEdits del objeto Recordset determina cuándo se activa un bloqueo para los registros del Recordset. Bloqueo pesimista: Si la propiedad LockEdits tiene el valor True, el bloqueo pesimista está activo. La página que contiene el registro actual se bloquea en el momento en que utiliza el método Edit. La página se desbloquea cuando utiliza el método Update. La estrategia de bloqueo predeterminada bloquea los registros durante un período de tiempo mayor, pero garantiza que después de ejecutarse el método Edit ningún otro usuario pueda modificar los datos. Bloqueo optimista: Si la propiedad LockEdits tiene el valor False, el bloqueo optimista está activo. La página que contiene el registro sólo se bloquea mientras se actualiza el registro. Los bloqueos permanecen activos durante un período de tiempo más corto y varios usuarios pueden utilizar el método Edit sin bloquear la página. No obstante, en este caso deberán tratarse los posibles errores cuando el usuario ejecute el método Update. Si se piensa que los usuarios no intentarán modificar el mismo registro a menudo, considerese la posibilidad de asignar a la propiedad LockEdits el valor False. Desbloqueo: El motor de base de datos Microsoft Jet marca una página para que se desbloquee en cuanto se complete la actualización. Sin embargo, los bloqueos se quitan realmente cuando no se produce ninguna otra acción. Puede utilizarSE el método Idle del objeto DBEngine con la opción dbFreeLocks para suspender el proceso y permitir al motor de base de datos Microsoft Jet liberar los bloqueos y seguir con las otras tareas en segundo plano, como muestra el siguiente código: DBEngine.Idle dbFreeLocks Controlar los errores de bloqueo: Cuando varios usuarios están actualizando una base de datos, deberá agregarse código que intercepte los errores siguientes: 3260: No se pudo actualizar; actualmente está bloqueado: Se produce en el método Edit cuando el registro está bloqueado. El código debe esperar unos instantes y tratar de aplicar Edit otra vez o informar al usuario del error. 55 Manual del Alumno 3186: No se pudo guardar el registro; bloqueado actualmente: Se produce en el método Update cuando el registro está bloqueado. El código debe esperar unos instantes y volver a intentarlo o informar al usuario del error. 3197: Los datos han cambiado; la operación se ha detenido: Se produce en el método Update o Edit si otro usuario cambió los datos desde la última vez que tuvo acceso a ellos. El código debe actualizar el formulario con los datos más recientes o informar al usuario del error. Para obtener una lista completa de los errores disponibles en Visual Basic, veáse Errores interceptables en la Ayuda de Visual Basic. Tratar errores en Edit El siguiente código de ejemplo muestra como tratar los errores de bloqueo que pueden ocurrir en el método Edit: Private Sub cmdEdit_Click() On Error GoTo HandleError rs.Edit ButtonEditAddMode Exit Sub HandleError: Select Case Err.Number Case 3260 'la página está bloqueada actualmente MsgBox "El registro está bloqueado actualmente. Inténtelo más tarde." Case 3197 'los datos han cambiado MsgBox "Los datos han cambiado y se van a actualizar." rs.Bookmark = rs.Bookmark 'obtener datos actualizados FillFields rs.Edit Case Else MsgBox Err.Number & ": " & Err.Description End Select End Sub Si el registro está bloqueado cuando el usuario ejecuta el método Edit, se mostrará un mensaje; si los datos han cambiado, el código actualizará el formulario con los datos más recientes. La instrucción recOrders.Bookmark = recOrders.Bookmark actualiza el registro actual del Recordset con los datos más recientes de la base de datos. Tratar errores en Update: 56 Manual del Alumno El siguiente código de ejemplo muestra como tratar los errores de bloqueo que pueden ocurrir en el método Update: Private Sub cmdSave_Click() Dim answer As Integer On Error GoTo HandleError rs.Fields("NombreCategoría") = txtCategoryName.Text rs.Fields("Descripción") = txtDescription.Text rs.Update rs.Bookmark = rs.LastModified FillFields ButtonNonEditAddMode Exit Sub HandleError: Select Case Err.Number Case 3260 MsgBox "El registro está bloqueado actualmente. " & _ "Intente guardarlo más tarde o cancele los cambios." Case 3197 answer = MsgBox("Los datos han sido modificados por otro usuario. " & _ "¿Sobrescribir cambios?", vbYesNo) If answer = vbYes Then Resume Else rs.Bookmark = rs.Bookmark 'actualizar con cambios de 'otros usuarios cmdCancel_Click End If Case Else MsgBox Err.Number & ": " & Err.Description End Select End Sub Tratar errores de los registros eliminados por otros usuarios: Cuando el Recordset de tipo dynaset se ha llenado por completo, si otro usuario elimina un registro de la base de datos, el puntero a esa entrada no se eliminará del dynaset. Sin embargo, dicho registro ya no aparecerá en la base de datos. No podrá verse y se producirá un error si se intenta tener acceso a los datos de ese registro. La forma más sencilla de tratar este error es eliminar la entrada que hace referencia al registro eliminado del dynaset. El siguiente fragmento de código muestra cómo tratar un error generado por un intento de tener acceso a un registro eliminado: 57 Manual del Alumno Private Sub cmdMoveNext_Click() On Error GoTo err_movefirst: retry_MoveNext: rs.MoveNext txtLName.Text = rs("Apellidos") 'Puede ocurrir un error Exit Sub err_Movefirst: If Err.Number = 3167 Then 'se borra el registro, 'eliminar la entrada del dynaset rs.Delete Resume retry_MoveNext Else MsgBox Err.Description Exit Sub End If End Sub Anteriormente, se dedicó gran parte a explicar el objeto Recordset y se comentó acerca de la creación y manipulación de estos. Seguidamente, se brinda un reforzamiento de conceptos al respecto. Acceso a bases de datos externas: Desde Visual Basic puede tenerse acceso a diversas bases de datos ISAM (Método de acceso secuencial indizado) como dBASE, Paradox, Btrieve y Microsoft Visual FoxPro. Visual Basic proporciona controladores para diversas bases de datos ISAM. Estos controladores se muestran en el registro. También puede utilizar Visual Basic para tener acceso a archivos de datos estándar, como archivos de texto delimitados por comas. Nota Las bases de datos que utilizan el motor de base de datos Microsoft Jet se consideran nativas de Visual Basic. Opciones de acceso: Hay dos formas de tener acceso a los datos de una base de datos externa: adjuntar la base de datos externa y abrirla directamente. Adjuntar una tabla a una base de datos de Microsoft Jet: Cuando se adjunta una base de datos externa a una base de datos de Microsoft Jet, los datos de la tabla permanecen en la base de datos externa. Sin embargo, la información de conexión y la definición de la tabla se almacenan en la base de datos de Microsoft Jet. Puede 58 Manual del Alumno utilizarse la tabla como cualquier otra tabla de bases de datos de Microsoft Jet, a excepción de que no puede crear un Recordset de tipo table en una tabla adjunta. Puede utilizarse Microsoft Access para adjuntar fácilmente una tabla a una base de datos de Microsoft Jet. También puede adjuntarse una tabla por programa mediante Visual Basic. Para obtener información acerca de cómo adjuntar tablas por programa, veáse el método CreateTableDef en la Ayuda de Visual Basic. Nota Normalmente puede tenerse acceso más rápidamente a los datos de las tablas ODBC que se adjuntan a una base de datos de Microsoft Jet que a los datos de una base de datos ODBC que haya abierto directamente. Si es posible, considerese la posibilidad de adjuntar tablas externas en lugar de abrirlas directamente. Abrir una tabla directamente: Cuando se abre directamente una tabla externa, debe especificarse la información de conexión mediante los argumentos del método OpenDatabase o la propiedad Connect del control Data. El siguiente fragmento de código muestra como utilizar los métodos OpenDatabase y OpenRecordset para abrir una tabla directamente: 59 Manual del Alumno Sub OpenTable() Dim wspCurrent As Workspace Dim dbCurrent As Database Dim rstProducts As Recordset Set wspCurrent = CreateWorkspace("New Workspace", "Admin", "", _ dbUseJet) Set dbCurrent = wspCurrent.OpenDatabase("C:\Archivos de _ programa\DevStudio\VB\Inventario.mdb") Set rstProducts = dbCurrent.OpenRecordset("Productos", dbOpenTable) rstProducts.MoveLast MsgBox "Hay " & rstProducts.RecordCount & " registros." rstProducts.Close dbCurrent.Close wspCurrent.Close End Sub Acceso a bases de datos ISAM: Para abrir una base de datos ISAM directamente, debe asignarse al argumento Database del método OpenDatabase (o a la propiedad DatabaseName del control Data) el nombre de la carpeta que contiene las tablas ISAM. Si la red lo admite, puede utilizar una ruta de acceso de red para el nombre de la base de datos, por ejemplo, “\\mi_servidor\mi_recurso”. Asignese a la propiedad Connect del objeto Database o del control Data el tipo de base de datos que se va a abrir. Para obtener una lista de valores posibles, veáse la propiedad Connect en la Ayuda de Visual Basic. En el ejemplo siguiente se abre directamente una base de datos de Microsoft Visual FoxPro: Public Sub OpenFoxProTable() Dim dbFox As Database Dim recAccounts As Recordset Set dbFox = OpenDatabase _ ("\\FoxPro\Data\AP", False, False, “FoxPro 5.0;") ‘direccionado a la red. Set recAccounts = dbFox.OpenRecordset("Cuentas") End Sub Para adjuntar una base de datos ISAM a una base de datos .mdb existente, debe crearse un nuevo objeto TableDef y establecer las propiedades Connect y SourceTableName del objeto TableDef. En el ejemplo de código siguiente se adjunta una tabla de Microsoft FoxPro a una base de datos de Microsoft Jet. Puede crearse una nueva base de datos de Microsoft Jet o bien utilizar una existente: Dim tbdAttach as TableDef Set dbMydb=OpenDatabase ("Mibd.mdb") Set tbdAttach = dbMydb.CreateTableDef ("Tabla FoxPro adjunta") 60 Manual del Alumno tbdAttach.Connect = "FoxPro 5.0;DATABASE=\\FoxPro\AP" tbdAttach.SourceTableName = "Cuentas" dbMydb.TableDefs.Append tbdAttach Cuando se haya adjuntado la tabla a una base de datos de Microsoft Jet, podrá abrirse de la forma siguiente: Set dbMydb = OpenDatabase ("Mibd.mdb") Set recAttach = dbMydb.OpenRecordset ("Tabla FoxPro adjunta") Distribuir el ejecutable: Cuando se distribuya una aplicación que tenga acceso a una base de datos ISAM, deberá proporcionarse al usuario los controladores ISAM adecuados. Trabajando con archivos de datos: Pueden crearse aplicaciones de Visual Basic que trabajen con información de archivos de datos estándar en lugar de hacerlo con una base de datos. Cuando se trabaje con información procedente de archivos de datos estándar, pueden utilizarse alguna de las siguientes opciones: Instrucciones de E/S de archivos: Puede utilizar las instrucciones estándar de acceso a archivos de Visual Basic para leer y escribir archivos. Para obtener más información acerca de la instrucción Open, vea Open en la Ayuda de Visual Basic. Importación manual a una base de datos: Puede utilizar Microsoft Access para importar manualmente un archivo de texto a una base de datos. Microsoft Access admite diversos formatos de archivo, como de longitud fija, delimitados por comas, etc. Importación mediante programación a una base de datos: Puede crear un programa que lea un archivo de texto y agregue los datos a una base de datos. 61 Manual del Alumno El siguiente código de ejemplo muestra los pasos básicos para utilizar la E/S de archivo con el fin de leer un archivo e insertar los datos en una base de datos: Private Sub Command4_Click() Dim fhandle As Integer fhandle = FreeFile() Open "newCust.txt" For Input As fhandle Do 'Leer archivo utilizando alguna instrucción Input 'Crear cadena sql 'Ejecutar instrucción Insert de SQL Loop Until EOF(fhandle) End Sub Acceso a datos mediante ODBC: La versión 3.5 de DAO presenta una nueva tecnología de conexión cliente-servidor llamada Open Database Connectivity Direct (ODBCDirect). Esta tecnología establece una conexión directamente con un origen de datos ODBC, sin cargar el motor de base de datos Microsoft Jet. Definir un espacio de trabajo ODBC: La tecnología de ODBCDirect permite tener acceso directamente a orígenes de datos ODBC mediante Objetos de acceso a datos (DAO). Pueden utilizarse las características de esta conexión cliente-servidor para tener acceso rápidamente a una base de datos, sin cargar el motor de base de datos Microsoft Jet. El primer paso para utilizar ODBCDirect consiste en definir el tipo de espacio de trabajo que utilizará. Puede hacerse de dos formas: establecer el tipo de espacio de trabajo predeterminado o definir un tipo de espacio de trabajo específico como ODBCDirect. Establecer el espacio de trabajo predeterminado: Para establecer el tipo de espacio de trabajo predeterminado como ODBCDirect, debe utilizarse la propiedad DefaultType del objeto DBEngine. Este tipo se utilizará siempre que se cree un objeto Workspace. Para definir un tipo de espacio de trabajo específico como ODBCDirect, debe asignarse a la propiedad DefaultType el valor dbUseODBC. Este valor impide que el motor de base de datos Microsoft Jet se cargue en memoria, si no ha creado ya un espacio de trabajo de base de datos de Microsoft Jet. 62 Manual del Alumno El código siguiente establece el tipo de espacio de trabajo predeterminado como ODBCDirect: DBEngine.DefaultType = dbUseODBC Crear un espacio de trabajo específico: Puede invalidarse el valor de espacio de trabajo predeterminado para un determinado espacio de trabajo si utiliza el argumento tipo del método CreateWorkspace. Conectarse a un origen de datos remoto: Conectarse con un origen de datos remoto es similar a abrir una base de datos de Microsoft Jet. Primero debe crearse un espacio de trabajo (como un espacio de trabajo de ODBCDirect) y después abrir el origen de datos. Para abrir un origen de datos remoto, debe crease un objeto Connection de la misma forma en que se crearía un objeto Database para abrir una base de datos de Microsoft Jet. Una vez creada una conexión, pueden recuperarse datos del origen de datos remoto. Declaración de la conexión: La variable de objeto Connection se refiere a su conexión. Utilicese la instrucción Set para abrir la conexión. El código siguiente abre una conexión en el espacio de trabajo predeterminado: Dim conNewConnection As Connection Set conNewConnection = OpenConnection _ ("New", dbDriverPrompt, False, _ "ODBC;DATABASE=pubs;UID=sa;PWD;DSN=SQLServer") Método OpenConnection: En la tabla siguiente se enumeran los argumentos del método OpenConnection y se describe su uso. 63 Manual del Alumno Argumento Nombre Opciones Solo_lectura Conexión Descripción Cualquier cadena, si especifica un nombre de origen de datos (DSN) ODBC registrado. Si en la declaración de OpenConnection no se incluye un DSN válido, el nombre deberá hacer referencia a un DSN válido de ODBC, que también será la propiedad Name. Establece diversas opciones para la conexión. True o False. Si es True, no se permiten modificaciones. El valor predeterminado es False. Una cadena de conexión ODBC. El argumento opciones determina si y cuándo se pedirá al usuario que establezca la conexión y si la conexión se abrirá o no de manera asíncrona. En la tabla siguiente se muestran los valores del argumento opciones y se describe su uso. Constante dbDriverNoPrompt Resultado El Administrador de controladores ODBC utiliza la cadena de conexión proporcionada en el argumento conexión El Administrador de controladores ODBC muestra el cuadro de diálogo dbDriverPrompt Seleccionar origen de datos, que presenta toda la información relevante proporcionada en el argumento conexión. La cadena de conexión está formada por el DSN seleccionado por el usuario mediante los cuadros de diálogo o bien, si el usuario no especifica ningún DSN, se usará el DSN predeterminado. Si el argumento conexión incluye toda la información necesaria para dbDriverComplete completar una conexión, el Administrador de controladores ODBC usará la cadena del argumento conexión. De lo contrario, se comportará como cuando especifica la constante predeterminada dbDriverPrompt. Es el valor predeterminado. dbDriverCompleteR Esta opción se comporta como la constante dbDriverComplete, excepto en que el controlador ODBC desactiva las peticiones de información equired innecesarias para completar la conexión Ejecuta el método de forma asíncrona. Esta constante se puede utilizar dbRunAsync con cualquiera de las demás constantes de opciones 64 Manual del Alumno Recuperar datos remotos: La recuperación de datos remotos mediante ODBCDirect es muy similar a la recuperación de datos desde una base de datos de Microsoft Jet. Después de establecer una conexión con una base de datos compatible con ODBC mediante ODBCDirect, cree un objeto Recordset; para ello, utilicese el método OpenRecordset de la misma forma que si se recuperaran registros de una base de datos de Microsoft Jet. Declaración del Recordset: El siguiente ejemplo de código crea un espacio de trabajo de ODBCDirect con una conexión al espacio de trabajo y después recupera un Recordset: Sub OpenODBCDirectRecordset() Dim wspODBC As Workspace Dim conODBC As Connection Dim rstODBC As Recordset Set wspODBC = CreateWorkspace _ ("", "sa", "", dbUseODBC) Set conODBC = wspODBC.OpenConnection _ ("NewConnection", dbDriverNoPrompt, False, _ "ODBC;DATABASE=pubs;UID=sa;PWD=;DSN=SQLServer") Set rstODBC = conODBC.OpenRecordset _ ("SELECT * FROM Autores WHERE au_lname = 'Enrique Gómez'", _ dbOpenSnapshot, 0, dbReadOnly) rstODBC.MoveLast MsgBox "Este conjunto de datos remoto contiene " & _ rstODBC.RecordCount & " registros." rstODBC.Close conODBC.Close wspODBC.Close End Sub El Recordset recuperado de un origen de datos remoto se puede controlar de la misma forma que un Recordset recuperado mediante DAO con el motor de base de datos Microsoft Jet. Método OpenRecordset con ODBCDirect: Además de las opciones disponibles en el método OpenRecordset de un objeto Database, el método OpenRecordset de un objeto Connection permite recuperar registros de una base de datos. Cuando se utiliza un espacio de trabajo de ODBCDirect están disponibles los siguientes argumentos del método OpenRecordset: Tipo: — dbOpenDynamic 65 Manual del Alumno Abre un Recordset de tipo dynamic que puede utilizar para agregar, cambiar o eliminar registros de una o varias tablas de una base de datos subyacente. Un cursor dinámico puede contener campos de una o más tablas de una base de datos. Opciones: — dbRunAsync Ejecuta una consulta asíncrona. Una consulta asíncrona es aquella en que las consultas SQL devuelven el control inmediatamente, incluso aunque los resultados no estén preparados. Esto permite que una aplicación efectúe otros procesos mientras la consulta se termina. — dbExecDirect Ejecuta una consulta; para ello elude SQLPrepare y llama directamente a SQLExecDirect. Utilice esta opción sólo cuando no vaya a abrir un Recordset basado en una consulta de parámetros. ® ediciones_bloqueo — dbOptimisticValue Utiliza la concurrencia optimista basada en valores de fila. — dbOptimisticBatch Activa la actualización optimista por lotes. Nota El espacio de trabajo predeterminado de ODBCDirect es de sólo lectura. Para que los usuarios puedan leer y escribir registros en un espacio de trabajo de ODBCDirect, debe especificarse el tipo de bloqueo dbOptimistic. Para obtener información acerca de los tipos de bloqueo, veáse el método OpenRecordset en la Ayuda de Visual Basic. 66 Manual del Alumno Problemas de rendimiento: El diseño de una aplicación afecta en gran medida a su rendimiento. Cuando se desarrollen aplicaciones de base de datos, tengase en cuenta las siguientes sugerencias: Analícese lo que se requiere: Algunas consultas SQL son más eficientes que otras. Por ejemplo, ordenar por un campo que contiene un índice es más rápido que ordenar por un campo que no contiene un índice. Para obtener más información acerca de la optimización de consultas, busque Sugerencias de rendimiento y solución de problemas en los Libros en pantalla de Visual Basic y haga clic en Sugerencias de rendimiento y solución de problemas. Solicitar únicamente los datos que necesite: No crear Recordsets de tipo dynaset y snapshot en toda una tabla. El diseño de una aplicación debe permitir al usuario especificar criterios y devolver un Recordset limitado. En lugar de seleccionar todas las columnas de un registro, deben seleccionarse únicamente las columnas necesarias. El código siguiente crea un Recordset basándose en un valor especificado por el usuario. Por ejemplo, si el usuario escribe “A”, el código creará un Recordset de todos los empleados cuyo apellido empiece por “A”: strSQL = "Select [Apellidos], [Nombre] From Empleados " & _ "Where [Apellidos] Like " & _ "'" & txtName.Text & "'" & "*" Set recEmployees = dbMydb.OpenRecordset (strSQL,dbOpenDynaset) Utilizar únicamente la funcionalidad necesaria: Si no se necesita actualizar la base de datos, ábrase de sólo lectura. Esto mejora el rendimiento, ya que el motor de base de datos Microsoft Jet no necesita hacer un seguimiento de los bloqueos. Si no se necesita actualizar un Recordset, ábrase como de sólo lectura. Si sólo se va a agregar a un Recordset, ábrase con la opción dbAppendOnly. Utilizar transacciones: Utilizar una transacción reduce el acceso a disco a una vez por transacción, no una vez por registro, lo cual puede mejorar considerablemente el rendimiento de una aplicación. Puede que se desee agrupar transacciones. Si una transacción es demasiado grande, utilizará memoria que pueden necesitar otras operaciones. Utilizar consultas almacenadas: En general, es mejor crear consultas almacenadas que utilizar SQL en el texto. Una consulta almacenada está precompilada, por lo que ahorrará tiempo de compilación durante la ejecución. Además, normalmente es más sencillo mantener consultas que están almacenadas en un archivo .mdb que las creadas mediante aplicación. Si es posible, utilice consultas almacenadas o SQL para las operaciones por lotes, como la actualización de un grupo de registros, en lugar de actualizar los registros de uno en uno con el método Update. 67 Manual del Alumno Adjuntar tablas SQL: Adjuntar tablas de bases de datos ODBC a una base de datos .mdb local. La información de conexión y de definición de datos se mantiene con el archivo .mdb y, por tanto, mejora el rendimiento. Escribir sus propias pruebas de rendimiento: Hay muchos factores que afectan al rendimiento de una aplicación. La mejor manera de averiguar si una característica mejorará el rendimiento es probarla con los datos de su entorno. Práctica No. 17 Diseñe el siguiente formulario: Escriba el siguiente codigo en las declaraciones generales del formulario: Public CodDML As Integer Public Mensaje, TituloBarra As String Function Ver_Forma2() Form2.Show End Function Function Habilita_Textos() If CodDML = 0 Then 68 Manual del Alumno Text1.Enabled = True End If Text2.Enabled = True Text3.Enabled = True Text4.Enabled = True End Function Function DesHabilita_Textos() If CodDML = 0 Then Text1.Enabled = False End If Text2.Enabled = False Text3.Enabled = False Text4.Enabled = False End Function Function Descubre_Botones() Incluir.Visible = True Anterior.Visible = True Siguiente.Visible = True Final.Visible = True Inicio.Visible = True Modificar.Visible = True Excluir.Visible = True Cerrar.Visible = True Aceptar.Visible = False Cancelar.Visible = False End Function Function Esconde_Botones() Incluir.Visible = False Modificar.Visible = False Excluir.Visible = False Anterior.Visible = False Siguiente.Visible = False Final.Visible = False Inicio.Visible = False Cerrar.Visible = False Aceptar.Visible = True Cancelar.Visible = True End Function Private Sub Aceptar_Click() Select Case CodDML Case CodDML = 0 Data1.Recordset.Update Case CodDML = 1 Data1.Recordset.Update Case CodDML = 2 Data1.Recordset.Delete End Select 69 Manual del Alumno MsgBox Mensaje, 0 + 16 + 56, TituloBarra Call Descubre_Botones Call DesHabilita_Textos End Sub Private Sub Anterior_Click() If Not Data1.Recordset.BOF Then Data1.Recordset.MovePrevious Siguiente.Enabled = True Final.Enabled = True If Data1.Recordset.BOF Then Anterior.Enabled = False Inicio.Enabled = False Data1.Recordset.MoveFirst MsgBox "Inicio de la tabla" End If End If End Sub Private Sub Buscar_Click() Call Ver_Forma2 Data1.Recordset.FindFirst "[CEDULA] = 'ABuscar'" If Data1.Recordset.NoMatch Then MsgBox "NO se encuentra" End If End Sub Private Sub Cancelar_Click() Data1.Refresh Call DesHabilita_Textos Call Descubre_Botones End Sub Private Sub Cerrar_Click() End End Sub Private Sub Excluir_Click() CodDML = 2 Call Esconde_Botones Call Habilita_Textos Mensaje = "El Registro fue Exitosamente Eliminado" TituloBarra = "Eliminado.." End Sub Private Sub Final_Click() Data1.Recordset.MoveLast Siguiente.Enabled = False Final.Enabled = False 70 Manual del Alumno Anterior.Enabled = True Inicio.Enabled = True End Sub Private Sub Incluir_Click() CodDML = 0 Call Esconde_Botones Call Habilita_Textos Mensaje = "El Registro fue Exitosamente almacenado" TituloBarra = "Inclusión de Registros.." Data1.Recordset.AddNew End Sub Private Sub Inicio_Click() Data1.Recordset.MoveFirst Anterior.Enabled = False Inicio.Enabled = False Siguiente.Enabled = True Final.Enabled = True End Sub Private Sub Modificar_Click() CodDML = 1 Call Esconde_Botones Call Habilita_Textos Data1.Recordset.Edit Mensaje = "El Registro fue Exitosamente Modificado" TituloBarra = "Modificación de Registros.." Text2.SetFocus End Sub Private Sub Siguiente_Click() If Not Data1.Recordset.EOF Then Data1.Recordset.MoveNext Anterior.Enabled = True Inicio.Enabled = True If Data1.Recordset.EOF Then Data1.Recordset.MoveLast Siguiente.Enabled = False Final.Enabled = False MsgBox "Final de la tabla" End If End If End Sub Private Sub Text1_KeyPress(Tecla As Integer) 71 Manual del Alumno If Tecla = 13 Then Text2.SetFocus End If End Sub Private Sub Text2_GotFocus() Text2.SelStart = 0 Text2.SelLength = Len(Text2.Text) End Sub Private Sub Text2_KeyPress(Tecla As Integer) If Tecla = 13 Then Text3.SetFocus End If End Sub Private Sub Text3_GotFocus() Text3.SelStart = 0 Text3.SelLength = Len(Text3.Text) End Sub Private Sub Text3_KeyPress(Tecla As Integer) If Tecla = 13 Then Text4.SetFocus End If End Sub Private Sub Text4_GotFocus() Text4.SelStart = 0 Text4.SelLength = Len(Text4.Text) End Sub Nota: La codificación anterior contiene algunos errores de forma y de fondo. Corresponderá al estudiante corregirlas y culminar este ejemplo con o sin ayuda del profesor. Asimismo, el profesor le explicará, paso a paso, la codificación del mismo. Práctica No. 18 1. Cree una tabla en Microsoft Access o utilizando el VisData de Visual Basic, denominada Nomina.Mdb y en ella una tabla del mismo nombre con los campos Id_depart(Text 15) y dep_desc(text 30). 2. Diseñe el siguiente formulario y establezca en el Data Control la base de datos Nomina y la tabla del mismo nombre. 72 Manual del Alumno 3. Escriba en cualquiera de los botones (los cuales son una matríz de controles) el siguiente código: Private Sub Barra_Click(Index As Integer) Select Case Index Case 0 If Barra(0).Caption = "Nuevo" Then Data1.Recordset.AddNew Barra(0).Caption = "Incluir" Barra(1).Caption = "Cancelar" CODIGO.Enabled = True DESCRIPCION.Enabled = True For a = 2 To 4 Barra(a).Visible = False Barra(a).Enabled = False Next CODIGO.SetFocus Else Data1.Recordset!id_depart = CODIGO.Text Data1.Recordset!dep_desc = DESCRIPCION.Text On Error GoTo RUTINA_ERROR Data1.Recordset.Update On Error GoTo 0 Barra(0).Caption = "Nuevo" Barra(1).Caption = "Editar" CODIGO.Enabled = False DESCRIPCION.Enabled = False For a = 2 To 4 Barra(a).Visible = True Barra(a).Enabled = True Next End If Barra(0).Refresh 73 Manual del Alumno Barra(1).Refresh Case 1 If Barra(1).Caption = "Cancelar" Then CODIGO.Text = "" DESCRIPCION.Text = "" Barra(0).Caption = "Nuevo" Barra(1).Caption = "Editar" For a = 2 To 4 Barra(a).Visible = True Barra(a).Enabled = True Next Else Data1.Recordset.Edit Barra(0).Caption = "Modificar" Barra(1).Caption = "Cancelar" DESCRIPCION.Enabled = True For a = 2 To 4 Barra(a).Visible = False Barra(a).Enabled = False Next DESCRIPCION.SetFocus End If Barra(0).Refresh Barra(1).Refresh Case 2 Case 3 Case 4 End End Select RUTINA_ERROR: Select Case Err.Number Case 3022 Msg = "Registro Ya Existe en la Tabla" MsgBox Msg, , "!!! PANTALLA DE ERRORES !!!" Err.Clear ' Clear Err object fields CODIGO.SetFocus End Select End Sub 74 Manual del Alumno SQL SQL es un lenguaje de programación de base de datos cuyas iniciales vienen de las palabras inglesas Structured Query Language, que se puede traducir como Lenguaje Estructurado de Búsqueda o Consulta. Este lenguaje está estrechamente relacionado con la invención de la base de datos relacional hecha por E.F. Cood a principio de 1970. El moderno SQL ha evolucionado ampliamente como un estandard en el manejo de bases de datos relacionales, y está definido por los estandares ANSI. La mayoría de las implementaciones de SQL tienen pequeñas diferencias de los estandares definidos incluyendo la version soportada por el motor de bases de datos Microsoft Jet. SQL Vrs. Navegación: El motor de base de datos Microsoft Jet provee dos metodos separados para llevar a cabo la mayoría de tareas relacionadas con las bases de datos: El modelo navegacional que consiste en ir accesando los registros de la base de datos. El modelo relacional que está basado en el lenguaje estructurado de búsqueda. Para los programadores que están familiarizados con sistemas de bases de datos basados/orientados a archivos como Dbase, Foxpro o Paradox podrían sentirse más comodos utilizando el metodo navegacional. Sin embargo, en la mayoria de los casos el equivalente utilizadndo el metodo SQL es mas eficiente y po lo tanto deberian ser tuilizados donde el rendimiento o tiempo de respuesta es importante. Adicionalmente, SQL tiene la ventaja de ser la interfase estandar de base de datos de diferentes porveedores, por lo tanto, el conocimiento de comandos SQL permiten accesar y manipular una gran variedad de productos de bases de datos. Codificando consultas con SQL: El SQL es usado para crear objetos QueryDef utilizandolo como parametro en el metodo Recordset o en la propiedad de un Data Control. Tambien puede ser utilizado con el metodo Execute para crear o manipular directamente bases de datos JET y para crear consultas por Selección o por Acción. Operaciones DML: El SQL Data Manipulation Language (Lenguaje de manipulación de datos) son utilizados para consultar, actualizar, crear o eliminar registros en las tablas de una base de datos. Esto se realiza mediante el uso de varias instrucciones, pero la mayoría de ellas se usan con la estructura general del query Select. COMANDO CREATE DESCRIPCIÓN Se usa para crear nuevas tablas, campos e 75 Manual del Alumno índices. Se usa para borrar tablas e índices. Se usa para modificar tablas, añadiendo campos o cambiando las definiciones. Se usa para crear consultas a la base de datos, según un criterio especificado. Se usa para cargar datos en la base de datos. Se usa para actualizar registros de tablas. Se usa para borrar registros de tablas. DROP ALTER SELECT INSERT UPDATE DELETE Operadores lógicos: AND OR NOT Los operadores lógicos se utilizan para conectar expresiones, usualmente en la claúsula WHERE. Ejemplo: SELECT *FROM MyTable WHERE condition1 AND condition2 Operadores de comparación: < <= > >= <> BETWEEN LIKE IN Menor que Menor o igual que Mayor que Mayor o igual que Diferente de Entre Parecido a En Ejemplo: SELECT *FROM MyTable WHERE PubId = 5 Sintaxis: SELECT SELECT [ALL] | DISTINCT | DISTINCTROW | [TOP n [PERCENT]]] {*| Table.*}| [Table.]field1 [AS alias1] [,[table.]field2[AS alias2][,...]]} FROM tableexpresion [, ...] [IN externaldatabase] [WHERE ...] 76 Manual del Alumno [GROUP BY ...] [HAVING ...] [ORDER BY ...] [WITH OWNERACCESS OPTION] SELECT field1[, field2[, ...]] INTO newtable [IN externaldatabase] FROM source INSERT INTO target [IN externaldatabase] [(field1[,field2[, ...]])] SELECT [source.]field1 [,field2[, ...] FROM tableexpresion Single record append query: INSERT INTO target [(field1[,field2[, ...]]) VALUES (value1[,value2[, ...]]) DELETE [table.*] FROM table WHERE criteria UPDATE table SET newvalue WHERE criteria; Ejemplos SQL: 1. SELECT *FROM empleados; 2. SELECT empleados.depto, supervisores.supnombre FROM empleados; INNER JOIN supervisores ; WHERE empleados.dpto = supervisores.depto; 3. SELECT BirthDate AS Birth FROM empleados; 4. SELECT COUNT(empleadosID) As HeadCount FROM empleados; 5. SELECT LastName, FirstName, Title, Salary FROM empleados; AS T1 WHERE Salary >= (SELECT Avg(Salary) FROM empleados; WHERE T1.Title = empleados.titles) ORDER BY Title; 6. SELECT LastName, Salary FROM empleados WHERE Salary > 100000; 7. SELECT * FROM Orders WHERE ShippedDate = DateValue(‘01/02/1999’); 8. INSERT INTO Customers SELECT * FROM [New Customers]; 9. INSERT INTO empleados (FirstName,LastName, Title); VALUES (‘Gómez’,’Enrique’Enrique’,’Profesor’); 10. SELECT Title, Count(Title) AS Total FROM empleados; WHERE Region = ‘Heredia’; GROUP BY Title; HAVING Count(Title) > 1; 11. SELECT Count(*) AS TotalOrders FROM orders; 12. SELECT IIF(PostalCode BETWEEN 98101 AND 99100, ‘Local’, ‘NonLocal’); FROM publishers; 13. SELECT sum(UnitPrice*Quantity) AS [Total UK Sales]; 77 Manual del Alumno FROM orders; INNER JOIN [Orders Details]; ON orders.OrderId = [Orders Details]. OrderId; WHERE (ShipCountry = ‘UK’); 14. SELECT LastName, FirstName FROM empleados; WHERE LastName LIKE ‘[A-D]*’; 15. SELECT Avg(Freight) AS [Average Freight] FROM orders WHERE Freight > 100; 16. SELECT Count (ShipCountry) AS [UK Orders]; FROM orders WHERE ShipCountry = ‘UK’; 17. SELECT DISTINCTROW CompanyName FROM customers; INNER JOIN orders; ON customer.customerID = orders.customerID; ORDER BY CompanyName; 18. UPDATE orders; SET orderAmount = orderAmount * 1.5; Freight = Freight + (Freight * .10); WHERE shipcountry = ‘UK’; 78 Manual del Alumno ACCESOS EXTERNOS: 79 Manual del Alumno 80 Manual del Alumno 81 Manual del Alumno 82 Manual del Alumno