Bases de Datos en Visual Basic
Bases de Datos en Visual Basic
Bases de Datos en Visual Basic
12
Objetos DAO
ACCESO A BASES DE DATOS SIN UTILIZAR EL CONTROL DATA
En el capítulo anterior hemos visto los controles capaces de acceder a un Base de Datos,
enlazados mediante un control Data. Se comenzó a exponer que no es necesario usar un
control Data para acceder a leer datos, añadir registros o cambiar su contenido. Y es más.
Comenzaremos ahora a ver que el control Data pese a que puede evitarnos gran cantidad de
líneas de código, nos hace perder el control respecto al programa. Es normal. El control Data
se ha desarrollado para realizar un trabajo muy estándar. Si nuestra aplicación se separa un
poco de lo normal, lo mas probable es que necesitemos realizar las operaciones mediante
código. Esto no quiere decir que el Data haya que dejarlo en desuso. Será necesario en
aquellas aplicaciones en las que se va a usar un control DBGrid, pues como se recordará,
mediante el uso de un control Data metemos en memoria RAM todo el contenido de la base de
datos relativo al Recordset que hemos creado. El control Data será necesario en aquellas
aplicaciones donde utilicemos un DBGrid, un DBList o un DBCombo. Veremos mas
adelante que también será necesario cuando queramos guardar imágenes en una Base de
Datos.
El control Data también permite consultas más rápidas a la BD. El hecho de guardar el
contenido completo del Recordset en la memoria hace que cualquier consulta sea más rápida.
Eso sí, estamos empleando mucha más memoria RAM.
Pero en este capítulo vamos a ver como se pueden manejar bases de datos utilizando otros
objetos de acceso a datos. Concretamente los objetos DAO.- (Data Access Objet).
Los objetos DAO utilizan el Motor de Bases de Datos Jet de Microsoft y trabajan
directamente sobre el fichero que contiene la base de datos. Existen otros objetos de acceso a
datos, como ha podido ver en el capítulo anterior, que no trabajan directamente sobre el
fichero, sino sobre una conexión ODBC que enlaza con la base de datos. Son los objetos RDO
y ADO, cuyo estudio se realizará en capítulos posteriores. Estos últimos tipos son mas
modernos, pero no tienen algunas prestaciones que tienen los DAO, debido precisamente a
que no trabajan directamente sobre el fichero. Centrémonos sobre los objetos DAO
Estos objetos, pese a que no tienen representación en la interface gráfica, son objetos Visual
Basic como los demás, y nos podremos referir a ellos por su nombre como hacíamos con
todos los controles. Eso sí, debemos declararlos como se declaran las variables, y siguen
siendo válidos los criterios de declaración de variables en cuanto al ámbito de aplicación. Si
declaramos un DAO en un procedimiento, no nos podremos referir a él fuera de ese
procedimiento. Si queremos que sea válido en toda la aplicación deberemos declararlo en la
sección de declaraciones de un módulo, o en la sección de declaraciones de un formulario si
fuese suficiente ese ámbito para nuestra aplicación.
La primera sorpresa suele ocurrir a la hora de declarar un objeto. Por ejemplo, para declarar
un objeto tipo DataBase debemos hacerlo con la siguiente declaración:
Lo que le está ocurriendo es que su programa no conoce el tipo de variable DataBase. Para
que la conozca, debe agregarle una referencia. Vaya a Proyecto | Referencias … de la barra
de menú y seleccione Microsoft DAO 3.51 Objet Library Haga click en Aceptar y ya tiene
agregada esa referencia a su programa. Agregar una referencia significa que le ha dicho a su
programa que puede hacer uso de una colección de DLLs donde podrá encontrar la definición
del objeto o la variable que no encuentra. Ahora ya no le dará el error anterior, pues en esa
Verá que hay mas referencias parecidas a Microsoft DAO 3.51. (Las versiones 2.0, 2.1,
2.5/3.5, y si ha instalado Access2000 tendrá la 3.6) Existen tantas versiones distintas como
versiones de Access. En realidad esta DLL no es mas que un componente de Access que
podemos usar, al igual que lo hace Access, para gestionar una base de datos. Debe elegir la
versión mas alta, pero con cuidado. La versión 3.5 corresponde a la versión de Access 97.
Cuando cree con su programa una base de datos con esta versión, no podrá abrirla con
Access 2.0 No se olvide de la teoría de la compatibilidad de Microsoft, que dice que cualquier
versión que Microsoft considere obsoleta no debe reconocer los datos guardados por versiones
más modernas del mismo programa. Piense si es posible que alguien que vaya a abrir una
base de datos guardada con su aplicación dispone de una versión anterior de Access. Por
ejemplo, cuando se está escribiendo este libro, está recién aparecido Access 2000. ¿Cree que
es oportuno en estos momentos, en los que todavía se está introduciendo en muchos usuarios
Access 97 usar una DLL que nos cree bases de datos que solamente puedan ser abiertas por
Access 2000?.
La versión Microsoft DAO 3.6 corresponde a Access 2000. Si ha instalado este programa le
aparecerá esa referencia en la lista de referencias. No se sorprenda si antes de instalar
Access 2000 no tenía esa referencia y después de la instalación sí. Si crea una BD con la
versión 3.6 no podrá abrirla con Access 97 ¡Esta es la compatibilidad hacia delante de
Microsoft!
Hay que señalar que las colecciones de estos objetos DAO son a su vez objetos de acceso a
datos. Esto hay que explicarlo un poco mejor. Por ejemplo, un objeto Database es un objeto
DAO que representa una base de datos abierta. Al objeto que agrupa a todas las bases de
datos abiertas en ese momento le llamamos Objeto Databases. Si tenemos dos bases de
datos abiertas en un determinado momento, el objeto Databases contendrá dos elementos.
Todos los objetos DAO excepto el dbEngine tienen colecciones. Es lógico. El dbEngine, como
verá mas adelante, es precisamente el Motor de Bases de Datos Jet y solamente existe uno.
Comenzaremos precisamente por él la explicación de los objetos DAO.
El dbEngine es el motor Jet. Y como vimos en el capítulo anterior, la versión 3.5 puede
trabajar directamente sobre el fichero de la base de datos o a través de una conexión ODBC.
En el primer caso decimos que está trabajando en el espacio de trabajo Microsoft Jet Si le
hacemos trabajar a través de ODBC decimos que estamos trabajando en el espacio de
trabajo ODBCDirect
Los objetos que emplea en cada uno de los espacios de trabajo pueden verse en las figuras
20.1 y 20.2. Puede observarse que tiene muchos mas objetos en el espacio de trabajo
Microsoft Jet que en el de ODBCDirect. Es lógico. Con el primero nos permite CREAR bases
de datos, y por lo tanto necesita tener objetos tales como el Tabledef, Index o Relation. Lo
verá un poco mas adelante.
En esta figura pueden verse los objetos y sus colecciones. Las colecciones llevan el nombre
en plural. Parece un poco complicado, y posiblemente lo será. Lo cierto es que para el trabajo
que se hace normalmente con bases de datos este diagrama queda bastante reducido. No se
extrañe tampoco de ver que objetos como el Fields y el Field existen en varios niveles. Lo
verá mucho mejor mas adelante.
Este modelo parece un poco mas asequible. La razón es que no hace todo lo que hace el
espacio de trabajo Microsoft Jet.
Objeto Workspace
Un objeto Workspace define una sesión de trabajo para un usuario específico. Una sesión de
trabajo es precisamente eso, una sesión de trabajo en el más puro estilo informático. Pueden
existir varias sesiones de trabajo, pero en la mayoría de los casos eso no es lo normal. Será
necesario crear varias sesiones cuando necesitemos imponer restricciones de acceso a una
base de datos, cuando tengamos que usar Transacciones (*) en un sistema multiusuario, y en
algún caso más. Las sesiones de trabajo tienen dueño (Usuario) y una palabra clave para
acceder a ellas.
Pero lo normal es tener solamente una sesión abierta. Y visual Basic nos facilita este caso
abriendo una sesión de trabajo automáticamente. Cuando se inicia Visual Basic, se crea un
Workspace con palabra clave y nombre de usuario Admin. Este Workspace es precisamente
el que ocupa el número cero de la colección de Workspaces. Es decir, es el Workspaces(0).
No se preocupe de que ahora mismo no lo entienda. Ya lo entenderá. Pero hay que exponerlo
ahora. El objeto Workspace contiene:
Al objeto Workspace se le puede dar un nombre definido por el usuario. Ese nombre habrá que
declararlo como nombre de una variable objeto Workspace :
La declaración de la variable Objeto tiene las mismas características que cualquier variable en
cuanto al ámbito en el que se puede usar. Para que pueda usarse en toda la aplicación
deberemos declararla en un Módulo con la sentencia Public
Una vez declarado el nombre del objeto Workspace, hay que crearlo. En realidad, y tal como
citábamos mas atrás, cada vez que se inicia una sesión de Visual Basic, se crea
automáticamente un Workspace. El número 0 A este Workspace no podemos ponerle ningún
tipo de palabra de acceso, ya que se la ha puesto VB : Admin. Utilicemos este Workspace, el
número 0 de la colección Workspaces, para comenzar a trabajar. Tiempo tendremos mas
adelante de ver como se crea un Workspace. Para hacer que Misesion sea ese Workspace
que crea automáticamente VB basta con ejecutar la siguiente línea de código
Pero si no queremos aprovechar este Workspace creado automáticamente por Visual Basic, y
queremos usar otro, usemos el método CreateWorkspace. Se verá al final del capítulo.
Logicamente el término Aquí .... alguna cosa va a depender de cada método y de lo que Vd.
quiera hacer, pero la estructura Set DAOInferior = DAOSuperior.Método ( - - - - - - - - - - )
se mantendrá en todas las operaciones de creación y manipulación de objetos DAO. Esta
sintaxis es tan simple que un profesor de Visual Basic decía que es como un “juego de
niños” No lo olvide y se le quitará el miedo al manejo de bases de datos mediante código.
Posiblemente hasta ahora le haya parecido muy difícil y haya optado por usar el control Data
para todas sus aplicaciones. Si recuerda este Juego de niños verá que es más sencillo crear
objetos DAO que poner un control Data en un formulario.
Un objeto Database puede crearse, bien porque hemos creado una base de datos mediante el
procedimiento CreateDataBase, o porque hemos abierto una base de datos existente
mediante el procedimiento OpenDatabase. En cualquiera de los dos casos, el objeto
Database existe hasta que lo cerremos (mediante el método Close) o hasta que cerremos la
aplicación.
Al objeto Database se le debe dar un nombre definido por el usuario. Eso sí, hay que
declararlo como una variable objeto Database
El nombre que le demos al objeto DataBase no tiene nada que ver con el nombre del fichero
que alberga esa base de datos. El objeto DataBase es la base de datos que creamos en la
memoria RAM del ordenador.
podremos usar el método del Workspace CreateDatabase para crear ese objeto Database.
Recuerde que el objeto Database NO es el fichero que va a contener la base de datos sino que
es una estructura de base de datos que está de momento en la memoria RAM del ordenador.
El fichero se creará posteriormente cuando cerremos el objeto Database. El nombre del objeto
Database tampoco tiene porque coincidir con el nombre del fichero que se va a crear. En el
ejemplo que veremos mas adelante, el nombre del objeto Database es MiBaseDatos y el
nombre del fichero es MiBase.Mdb.
En nuestro caso el Objeto DAO superior es el Workspace Misesion, y los parámetros que hay
que pasar en este caso son:
Vayamos al ejemplo:
MiBaseDatos es el nombre del objeto Database por el cual nos referiremos a esa base de
datos, NO el nombre del archivo con el que quedará guardada en el disco.
Misesion es el nombre del objeto Workspace existente que contendrá la base de datos. Si se
omite este argumento, se utilizará el objeto Workspace predeterminado - Workspaces(0) – e
incluso, si no se pone nada, usará ese Workspace predeterminado.
escenario es una expresión de cadena utilizada para especificar dos cosas: el orden alfabético
que se va a usar en esta base de datos, (Obligatorio) que denominaremos inf_local y el
Password o palabra clave que quiere usar para restringir su uso. Este Password es opcional.
Debe especificar el argumento inf_local o se producirá un error. Consulte la tabla de
constantes para inf_local incluida más adelante en este tema.
Este parámetro es obligatorio. Piense que una vez creada la Base de Datos, alguna vez le
pedirá que le obtenga un Recordset con los datos ordenados alfabéticamente ( acuérdese de
la sentencia SQL ORDER BY Nombredelcampo )
Si desea introducir una palabra clave para restringir el acceso a la base de datos, debe
indicarlo a continuación. Deberá concatenarlo con la inf_local mediante el signo & y separarlo
mediante punto y coma. Hay que poner pwd antes de la contraseña
Constante Descripción
(Advertencia sobre las bases de datos codificadas. No piense que al codificar la base de
datos va a mantener sus datos confidenciales en secreto. Access se los guardará codificados,
pero se los va a presentar de forma clara. Utilice para ello el Password, ya que no podrá abrir
la base si no se conoce ese Password. Pero tampoco se fíe mucho. Existen infinidad de
craqueadores de contraseñas de Access. El Password vale para proteger la BD de usuarios
no “piratas”. No emplee este procedimiento en aplicaciones en las que necesite verdadera
confidencialidad.
Estos objetos que se pueden añadir son : Objetos TableDef, TableDefs, Field, Fields,
QueryDef, QueryDefs. Para hacer las cosas poco a poco nos fijaremos solamente en las
tablas y los campos. (Objetos Tabledef y Field)
El Objeto TableDef es una tabla de una base de datos ACCESS. El Objeto TableDefs es la
colección que contiene todas las tablas de la base de datos
Un objeto Field representa un campo dentro de una tabla Access. La colección Fields
contiene todos los campos de una tabla. Verá mas adelante que también hay objetos Field en
otros objetos DAO (Index, QueryDef, Recordset, Relation), pero de momento vamos a fijarnos
solamente en las tablas.
El objeto Tabledef debe crearlo un objeto DataBase. Puede ser perfectamente el objeto
Database creado anteriormente. Pero seguramente lo ha cerrado (cerrando el programa,
simplemente) para poder ver con Access la base de datos que acaba de crear. Puede volver a
crear el objeto DataBase abriendo la base de datos. Aunque se le explicará mas tarde, le
adelanto el método para abrir una base de datos existente en el disco:
Declaremos ahora el nombre que quiere ponerle al objeto Tabledef que va a crear: (MiTabla1)
Si acude a la información de VB para ver los parámetros que hay que pasar en el método
CreateTableDef verá que son muchos: ([nombre[, atributos[, origen[, conexión]]]]) Se explicará
para que sirven todos ellos, pero de momento nos quedamos únicamente con el primero:
Nombre, que es el nombre que podrá ver en Access como nombre de la tabla que va a crear.
De momento solamente hemos creado uno o varios objetos Tabledef. Pero como siempre,
vacíos. Una tabla tiene campos. Un objeto Tabledef tiene Fields. Debemos crear objetos
Field (Campos) para poder meterlos en los objetos Tabledef que acabamos de crear.
El objeto Field debe crearlo el objeto DAO superior a él: el Tabledef. Previamente
declararemos los nombres de los Objetos Field a introducir.
Ejemplos
Set MiCampo11 = Mitabla1.CreateField (“ID_Alumno”, dbText, 8)
Set MiCampo12 = Mitabla1.CreateField (“NombreAlumno”, dbText, 20)
Set MiCampo13 = Mitabla1.CreateField (“Apellidos”, dbText, 25)
Set MiCampo14 = Mitabla1.CreateField (“Edad”, dbInteger)
Set MiCampo15 = Mitabla1.CreateField (“Fecha_Ingreso”, dbDate)
Se crean todos los campos que se quieren introducir en las tablas. Observe que cada objeto
Field debe ser creado por el objeto Tabledef que lo va a contener. (MiTabla1 crea todos sus
campos, MiTabla2 los suyos, etc)
Ya están todos los campos creados, pero todavía no están metidos en las tablas. Tenemos
que añadirlos a la colección de campos de la tabla que los creó. Esa colección de campos es
el Objeto Fields de la tabla. Se añade mediante el método Append.
MiTabla1.Fields.Append Micampo11
MiTabla1.Fields.Append Micampo12
MiTabla1.Fields.Append Micampo13
MiTabla1.Fields.Append Micampo14
MiTabla1.Fields.Append Micampo15
MiTabla2.Fields.Append Micampo21
Ya tenemos campos formando parte de la colección Fields de las tablas. Ahora debemos
añadir las tablas a la colección de tablas de la base de datos. Esa colección de tablas es el
objeto Tabledefs de la base de datos, es decir, del objeto DataBase. Lo haremos también
mediante el método Append
MiBaseDatos.TableDefs.Append Mitabla1
MiBaseDatos.Tabledefs.Append Mitabla2
MiBaseDatos.Close
Ya tenemos la base de datos creada en el disco de la misma forma que lo hubiera hecho
Access. Pero puede que le falte algo respecto a una base creada directamente con Access: los
Indices y las Relaciones.
Un índice es una marca que le podemos poner a cada uno de los registros en un campo. Esa
marca puede servir por ejemplo, para ordenar los registros de la BD por ese campo. Puede
servir también para evitar que dos registros tengan el mismo valor para un determinado
campo. En el ejemplo que estamos preparando, el Campo11 (ID_Alumno) queremos que sea
un índice, y además que no se pueda repetir el mismo valor para dos registros distintos, de
forma que no puedan existir dos registros con el mismo valor en ese campo.
Una Relación es una correspondencia entre un campo de una tabla y otro campo de
características similares en otra tabla. Esto nos lleva al concepto de Base de Datos Relacional
que seguramente ya sabe de que se trata. En Access se puede establecer una relación de una
forma muy sencilla. En Visual Basic también. Pero antes de seguir reflexionemos y
recordemos lo que hemos hecho hasta ahora.
Observe que, cada vez que creamos un objetos (DataBase, Tabledef, Field) usamos el
mencionado “juego de niños”. El método correspondiente para crear un objeto DAO pertenece
Hemos visto que después de crear un objeto, debemos añadirlo a la colección a la que debe
pertenecer con el método Append. El procedimiento es siempre el mismo :
Sigamos ahora perfeccionando la base de datos. Vamos a ver como se crea un Indice.
Primer debemos crear el Objeto Index. Se hace mediante el método del objeto Tabledef,
CreateIndex. El índice debe crearlo el Tabledef al que pertenece el campo que queremos que
sea índice. La sintaxis de CreateIndex es:
Donde NombreIndice es el nombre de una variable declarada como tipo de dato objeto Index.
NombreTabledef es el nombre de variable del objeto TableDef que se desea usar para crear el
nuevo objeto Index.
Nombre es una variable de tipo String que da un nombre único al nuevo objeto Index. Este
nombre lo puede ver si abre la base de datos con Access y en la vista de Diseño de la tabla,
abre la función Ver | Indices de la barra de menú.
En nuestro ejemplo:
Creamos el índice
Ya tenemos creado el Objeto Index. Ahora, (y aquí empieza la incongruencia citada) este
objeto Index debe crear el campo que queremos que sea índice. Pero ese campo ya debe
existir en el objeto Tabledef con el que hemos creado el índice (en este caso, en MiTabla1)
Pero un índice puede tener varios campos. Por ejemplo, piense en el código de un objeto en
un inventario. El código es el número que identifica de forma unívoca a un objeto. Un objeto
inventariable (por ejemplo una mesa) tiene un código de grupo (por ejemplo, el 123) Hay
muchas mesas dentro de un inventario, pero todas ellas tienen un número distinto (una tiene
el 001, otra el 002, etc) La combinación de código de grupo más el número del objeto dentro
de ese grupo queremos que sea un índice. Ese índice estará formado entonces por dos
campos. En este caso deberemos crear los dos campos:
Ya tenemos el campo o los campos creados por el índice. Ahora debemos añadirlo (o
añadirlos) a la colección Fields del objeto Index recién creado. Lo hacemos mediante el
método Append
MiIndice.Fields.Append MiCampo11
MiIndice.Fields.Append MiCampo11
MiIndice.Fields.Append MiCampo12
No crea que ya hemos terminado. Un objeto Index tiene propiedades. Una de ellas ya la
hemos visto sin querer, la propiedad Name (nombre del índice, en nuestro ejemplo, Indice1).
Otras propiedades son:
(Existen además las propiedades Clustered, Foreign, DistinctCount que no se explican para
no complicar mas el tema. Cuando los necesite no tendrá inconveniente en estudiarlos
partiendo de la ayuda de VB)
Para introducir el valor de una de estas propiedades se procede con la siguiente sintaxis:
MiIndice.Uniuqe = True
MiIndice.Primary = True
Ya está creado el índice y tiene ya metido uno o mas campos y todas sus propiedades. Ahora
debemos añadir ese índice a la colección Indexes del Objeto Tabledef.
MiTabla1.Indexes.Append MiIndice
Lo confieso. Cada vez que hago esto en la vida real tengo que volver a leer este procedimiento
en la Guía del Estudiante. Es complicado, pero alguna vez se terminará aprendiendo.
Ya tenemos la base de datos completamente creada. Sin embargo alguien dirá que le falta
algo: Relacionar dos tablas
Para crear una relación, usaremos un nuevo objeto DAO : El objeto Relation. Este objeto
forma parte de una colección, que es a su ves otro objeto DAO : el objeto Relations.
Para crear una relación usaremos el Método CreateRelation, que es un método del objeto
Database. (Lógico, una relación se establece entre dos tablas. Por lo tanto, la relación debe
pertenecer al objeto DAO superior jerárquicamente a las tablas: el Objeto Database. Como
para cualquier otro objeto DAO, es necesario declararlo :
Una relación se hace entre dos campos. Una relación debe tener campos. Por lo tanto,
deberemos hacer una cosa similar a la que hacíamos para el método CreateIndex, crear los
campos mediante el Objeto Relation que acabamos de crear. Pero esos campos ya deben
estar creados en las tablas que se van a relacionar.
Supongamos que queremos crear una relación en la base de datos creada en el ejemplo
anterior, y queremos relacionar el campo Campo11 que está en MiTabla1 y que lo habíamos
hecho clave primaria, con el campo Campo21 de MiTabla2. MiTabla1 y MiTabla2 son los
nombres reales de las tablas, NO los nombres de los objetos Tabledef.
El ejemplo que trae la ayuda de VB puede ser muy aclaratorio, pero le advertimos lo mismo
que para los índices, paciencia. Una vez creada la relación, podrá comprobarlo
visualizándola con el visor de relaciones del Access
Creamos el objeto Relation, que tendrá por nombre RelacionUno, pero este nombre NO debe
confundirse con el nombre del objeto DAO Relation, que es MiRelacion
MiRelación.Attributes = dbRelationUpdateCascade
MiCampo.ForeignName = "Campo21"
MiRelación.Fields.Append MiCampo
Y ahora añadimos el objeto Relation recién creado a la colección Relations del objeto
Database
MiBaseDatos.Relations.Append MiRelación
Solamente nos falta ver que valores puede tener la propiedad Attributes del objeto Relation
Ahora ya casi podemos decir que tenemos la base de datos creada. Puede que sea así o que
le falte alguna cosa. Puede faltarle una o varias consultas. Las consultas también se pueden
crear mediante objetos DAO. Precisamente con un objeto QueryDef
Antes de utilizar el método CreateQueryDef debe declarar el nombre de los objetos a crear,
declarándolos como Variables Objeto tipo QueryDef . El ámbito es igual que para cualquier
variable:
Ahora podemos utilizar el método CreateQueryDef para crear el nuevo objeto QueryDef en la
base de datos.
Donde
MiConsulta1 es una variable del tipo QueryDef que previamente se ha declarado como tal.
Será el nombre por el que llamemos al Objeto QueryDef en el código de nuestra aplicación.
MiBaseDatos es el nombre del objeto Database abierto en el que vamos a introducir el nuevo
objeto QueryDef.
Nombre es una expresión de cadena que representa el nombre de la nueva consulta que
vamos a crear. Este nombre será el que veamos al abrir la base de datos con Access en la
pestaña Consultas. Puede omitirlo a la hora de crear la consulta, pero deberá añadirselo
posteriormente.
Texto_sql es una expresión de cadena (instrucción SQL válida) que define el objeto
QueryDef.
Lógicamente una consulta nos debe suministrar una serie de datos de una o mas tablas. Esos
datos no tienen porqué ser todos los datos de las tablas. Texto_sql es precisamente el filtro de
esos datos (expresado mediante una cláusula SQL).
MiBaseDatos.QueryDefs.Append MiConsulta1
Ahora ya tenemos la base de datos creada con todas las posibilidades. Ha llegado el momento
de crear una base real para comprobar todo lo expuesto.
Otra tabla necesaria será la tabla Peliculas, donde introduciremos todos los datos relativos a
las películas (existentes o no en el videoclub), tal como título, director, artistas, resumen,
Existe otra tabla denominada Cintas, donde figurarán todas las cintas existentes en el
videoclub. Para poder relacionarla con la tabla Peliculas, le ponemos un campo llamado
ID_Pelicula que en esta tabla no será clave primaria. También le pondremos el campo Idioma,
sobre el que no haremos ningún tipo de relación. Tendrá un campo ID_Cinta que será la
combinación de varios datos, uno que nos indique la película que tiene grabada esa cinta
(Será la combinación de los campos ID_Pelicula e Idioma) y de un número secuencial que
indicará el número de la copia. Si le pone imaginación y este conjunto de datos puede meterse
en un código de barras, le facilitará la operación de alquiler y devolución. En este ejercicio
haremos que sea así, dándole a este campo un tamaño de 13 dígitos para poder meterlo en un
código EAN-13. Podemos añadirle mas campos a nivel administrativo, como fecha de alta,
fecha de baja, precio de esta copia, etc.
Existirá una tercera tabla, Alquileres, que relacionará al cliente con la cinta que ha alquilado.
Tendrá un campo llamado ID_Cliente y otro ID_Cinta. Aparte tendrá otros dos campos, fecha
de alquiler y fecha de devolución.
La base de datos deberá tener dos relaciones, una, entre el campo ID_Cliente de la tabla
Clientes y el campo ID_Clientes de la tabla alquileres (Será uno a infinito) y otra relación, entre
el campo ID_Cinta de la tabla Cintas y el campo ID_Cinta de la tabla Alquileres (Relación 1 a 1
ya que solamente existe una cinta con esa ID_Cinta). Para darle más alegría al ejercicio le
pondremos una relación entre los campos ID_Pelicula e Idioma de las tablas Peliculas y
Cintas.
(Se ha puesto el nombre de Pelicula al campo relacionado con ID_Pelicula para que se vea
que dos campos relacionados no tienen porqué tener el mismo nombre. Eso sí, deben tener las
mismas características)
Como colofón a todo esto, crearemos una consulta en la que utilizaremos todas las relaciones.
Para llevar a cabo este ejercicio se ha partido de una interface gráfica en la que pueden verse
tres botones (Borrar la base de datos, crearla y salir) y un TextBox donde se ha puesto el
nombre del fichero de la base de datos en su propiedad Text. Veamos el código de cada uno
de los botones (por orden inverso de complejidad del código)
Fig. 20.3 Interface gráfica de la parte de crear bases de datos para la aplicación del Videoclub
CODIGO
‘Se crea el Objeto DataBase (Se toma el nombre del fichero del TextBox TBNombreBase
Set MiBaseDatos = Workspaces(0).CreateDatabase(TBNombreBase, dbLangGeneral)
‘Una vez creados los campos se les ponen las peopiedades que se estime oportuno
‘En este caso, se ha puesto la propiedad AllowZeroLength (Permitir valores nulos en ese
‘campo) a lo que interesa en cada uno de los campos. Nota Tenga presente que por defecto le
‘va a dejar el campo que NO permite valores nulos, circunstancia que le va a crear problemas.
MiCampo12.AllowZeroLength = True
MiCampo13.AllowZeroLength = False
MiCampo14.AllowZeroLength = True
MiCampo15.AllowZeroLength = True
MiTabla2.Fields.Append MiCampo21
MiTabla2.Fields.Append MiCampo22
MiTabla2.Fields.Append MiCampo23
MiTabla2.Fields.Append MiCampo24
MiTabla2.Fields.Append MiCampo25
MiTabla3.Fields.Append MiCampo31
MiTabla3.Fields.Append MiCampo32
MiTabla3.Fields.Append MiCampo33
MiTabla3.Fields.Append MiCampo34
MiTabla3.Fields.Append MiCampo35
MiTabla3.Fields.Append MiCampo36
MiTabla4.Fields.Append MiCampo41
MiTabla4.Fields.Append MiCampo42
MiTabla4.Fields.Append MiCampo43
MiTabla4.Fields.Append MiCampo44
‘Se declaran las variables tipo objeto Index y tipo objeto Field para crear los índices
Dim MiIndice1 As Index, MiIndice2 As Index, MiIndice3 As Index
Dim CampoIndiceA As Field
Dim CampoIndiceB As Field
‘Se declaran los objetos Relation y un par de objetos Field para crear las relaciones. Por
‘claridad se han declarado objetos Field distintos para la creación de los índices y de
las ‘relaciones, pero podrían haber sido los mismos
Dim MiRelacion1 As Relation, MiRelacion2 As Relation, MiRelacion3 As Relation
Dim CampoRelacionA As Field
Dim CampoRelacionB As Field
‘Se crea la primera relación entre el campo ID_Clientes de la tabla Clientes (Tabla
‘primaria) y el campo ID_Cliente de la tabla Alquileres (Tabla relacionada)
Set MiRelacion1 = MiBaseDatos.CreateRelation("RelClientes", "Clientes", "Alquileres")
Set CampoRelacionA = MiRelacion1.CreateField("ID_Cliente", dbText, 8)
CampoRelacionA.ForeignName = "ID_Cliente"
MiRelacion1.Fields.Append CampoRelacionA
MiBaseDatos.Relations.Append MiRelacion1
‘Se comienza a crear una consulta (La sentencia SQL está cortada dado que no cabe en
‘una línea. El signo indica que esa línea continúa en la siguiente
Dim MiConsulta1 As QueryDef
Set MiConsulta1 = MiBaseDatos.CreateQueryDef("Pelis", "SELECT Clientes.Nombre,
Clientes.Apellidos, Clientes.Telefono, Peliculas.Titulo, Alquileres.ID_Cinta " & _
" FROM Peliculas INNER JOIN (Clientes INNER JOIN (Cintas INNER JOIN Alquileres ON
Cintas.ID_Cinta = Alquileres.ID_Cinta) " & _
End Sub
El resultado de todo esto podemos verlo si abrimos la base de datos con Access
Set MiBaseDatos =
= Workspaces(0).CreateDatabase(TBNombreBase, dbLangGeneral & ";pwd=LSB")
Cuando lleguemos a la parte de abrir bases de datos, explicaremos cómo se abre una base de
datos con contraseña. Y no crea que ha conseguido la confidencialidad total de sus datos.
Existen programas que puede bajarse de Internet que le leen la contraseña con la que ha
protegido su base. No es una protección total, pero sí suficiente para que un usuario “normal”
no se la pueda abrir.
Ha merecido la pena el trabajo. Hemos creado la base de datos haciendo click en un botón de
la aplicación. No ha sido necesario venderle al cliente Access, ni enviar una base de datos
vacía con los discos de distribución. Además hemos controlado todos los parámetros de los
campos de nuestra BD. Merece la pena crearse las bases de datos por programa.
Anexo1
Propiedades de los campos
Ha visto mas atrás que puede ser necesario cambiar las propiedades de los campos una vez
creados (Por ejemplo, MiCampo13.AllowZeroLength = False) Alargaríamos demasiado este ya
largo capítulo si se explican todos los las propiedades que puede tener un campo. Añada un
poco de esfuerzo a su estudio y vea las propiedades de los objetos Field en la ayuda. Le
reseño aquí las que he considerado mas importantes
DefaultValue Es el valor que le pone a ese campo si no introduce ninguno. Puede indicar un
valor a la hora de crear el campo:
Campo14.DefaultValue = “Madrid”
Value Es justamente el dato que almacena en ese campo. Es la propiedad por defecto del
objeto Field.
En el método CreateField debe introducir el tipo del campo que desea crear. (Como puede ver
mas atrás, con esta sintaxis p.e.: CreateField("Fecha_Alq", dbDate) Ese tipo coincide con la
propiedad Type aplicada a los campos. La propiedad Size solamente tendrá que aplicarla
cuando vaya a crear un campo tipo Texto.
Puede ver la ayuda de VB para mas detalles. Le enumero los mas usuales
Propiedad Type
Propiedad Attributes
Constante Descripción
Propiedad Size
Devuelve o establece un valor que indica el tamaño máximo, en bytes, de un objeto Field que
contiene texto o el tamaño fijo de un objeto Field que contiene texto o valores numéricos
Variable = Micampo11.Size
Para abrir una base de datos existente deberemos usar el método OpenDatabase. Pero
previamente deberemos declarar el nombre que se le va a dar a ese objeto Database
mediante la instrucción Dim si queremos que el ámbito de ese Database sea un formulario, o
Global o Public, (en la sección de declaraciones de un Módulo o Formulario) si queremos que
el ámbito sea toda la aplicación.
Por ejemplo, si queremos abrir una base de datos y poder referirnos a ella en toda la
aplicación, debemos declararla de esta forma en la sección de declaraciones de un módulo :
Método OpenDatabase
Abre la base de datos existente. La base de datos abierta se agrega automáticamente a la
colección Databases.
MiBaseDatos Variable de tipo de dato objeto Database que representa el objeto DAO
Database que se va a abrir.
Misesion Variable de tipo de dato objeto Workspace que representa el objeto
Workspace existente que va a contener a la base de datos.
nombre_bd Expresión de cadena con el nombre de un archivo (y su Path) de una base de
datos existente. Si el nombre de archivo tiene extensión, es necesario
especificarla. Si la red lo admite, también puede especificar una ruta de red,
como por ejemplo "\\MISERVID\MICOMP\MIDIR\MIBD.MDB". nombre_bd
también puede ser un origen de datos OBDC. Lo veremos en otro capítulo.
Si se refiere a una base de datos ya abierta por otro usuario con acceso
exclusivo, se producirá un error.
exclusivo Valor de tipo Boolean que es True si la base de datos se va a abrir con acceso
exclusivo (no compartido) o False si se va a abrir con acceso compartido. Si se
omite este argumento, la base se abrirá con acceso compartido.
sólo_lectura Valor de tipo Boolean que es True si la base de datos se va a abrir con acceso
de sólo lectura o False si se va a abrir con acceso de lectura/escritura. Si se
omite este argumento, la base se abrirá para lectura/escritura.
origen Expresión de cadena utilizada para abrir la base de datos. Esta cadena
constituye los argumentos de conexión ODBC. Para especificar una cadena de
origen deberá especificar también los argumentos exclusivo y sólo_lectura.
Consulte la sintaxis en la propiedad Connect.
Nota para todo este capítulo. No es necesario cambiar el nombre del Workspace. Si
Misesion = Workspaces (0), la sentencia anterior podemos ponerla también :
Ya tenemos la base de datos abierta. Pero no crea que nuestro programa ha hecho trabajo. Se
ha limitado a ver si existía el fichero indicado y a “apuntar” el nombre y path de ese fichero que
contiene la base de datos. El trabajo comienza cuando cree el recordset.
También hay una colección Recordsets. La colección Recordsets contiene todos los objetos
Recordset abiertos de un objeto Database.
Al utilizar objetos de acceso a datos, casi toda la interacción con los datos se produce a través
de objetos Recordset. Todos los objetos Recordset están formados por registros (filas) y
campos (columnas). Existen tres tipos de objetos Recordset:
Recordset de tipo tabla: Representación en código de una tabla base de datos que puede
utilizarse para agregar, modificar o eliminar registros de una sola tabla de base de datos. Un
Recordset tipo Tabla contiene todos los campos de una tabla y no puede contener campos que
no pertenezcan a esa tabla.
Recordset de tipo hoja de respuestas dinámica: Resultado de una consulta que puede tener
registros actualizables. Un Recordset de tipo hoja de respuestas dinámica es un conjunto
dinámico de registros que puede utilizarse para agregar, modificar o eliminar registros de una
o más tablas de una base de datos subyacente. Este tipo de objeto Recordset puede contener
campos de una o más tablas de una base de datos.
Como cualquier objeto DAO, debemos declararlo como variable tipo objeto.
Origen en la primera expresión es una variable de tipo String que especifica el origen de los
registros del nuevo objeto Recordset. El origen puede ser un nombre de tabla, un nombre de
consulta o una instrucción SQL que devuelva registros. En el caso de los objetos Recordset de
tipo tabla, el origen sólo puede ser un nombre de tabla.
El parámetro opciones permite especificar las características del nuevo objeto Recordset tales
como las restricciones de edición y consulta para otros usuarios. Vea la Ayuda de VB para
mayor detalle.
Si tenemos abierta una base de datos llamada MiBaseDatos, podemos crear el objeto
MiRecordset eligiendo de la tabla MiTabla de esa base de datos los campos Campo1, Campo2
y Campo3, y que sea del tipo de hoja de respuestas dinámica, de la siguiente forma :
Si deseamos que el Recordset contenga todos los campos de esa misma tabla :
Veamos lo que decíamos antes. Crear un Recordset desde otro recordset. Se puede usar
solamente para variar sus propiedades. Si desde el Recordset anterior, queremos crear un
nuevo Recordset denominado MiRecordset1, que tenga la condición de que sea solo lectura,
usaremos la sentencia :
Este nuevo Recordset contendrá los mismos campos que el Recordset origen, pero no
podremos cambiar datos en él.
Pero en la mayoría de los casos, necesitaremos crear un Recordset donde se elijan varios
campos de una o varias tablas, seleccionando de esos campos unos determinados valores.
Por ejemplo, en una base con las direcciones de los clientes, a lo mejor queremos seleccionar
todas aquellas direcciones en las cuales el código postal sea el 28700 (San Sebastián de los
Reyes). Imaginemos que hemos abierto la base de datos con el nombre CLIENTES (Recuerde
que este es el nombre del objeto DAO usado para abrir la B.D., no el nombre que pueda tener
esa B.D. en el disco), y esta base de datos tiene una tabla llamada DIRECCIONES (Este sí es
el nombre real de la tabla dentro de la B.D.) Vamos a abrir un Recordset con todos los clientes
de San Sebastián de los Reyes :
El Recordset Mirecordset2 contiene todos los campos de todos los registros de la tabla
DIRECCIONES que cumplan la condición de que el código postal (campo COD_POSTAL en el
ejemplo) sea igual a 28700.
Observe en esta y anteriores expresiones, que la sentencia SQL está entre doble comilla.
Podemos introducir cualquier sentencia SQL para determinar qué registros introducimos en el
Recordset. Por ejemplo, si queremos seleccionar todos los clientes de Madrid (su código postal
comenzará necesariamente por 28 y le seguirán tres cifras) :
Vamos a prescindir del recordset tipo Snapshot si lo que queremos es leer y escribir datos. Un
recordset Snapshot solamente sirve para realizar informes (leer) de los datos en un instante
determinado. Veamos la elección entre Dynaset y Table
Si queremos seleccionar parte de los registros de una tabla, o ver registros de varias tablas al
mismo tiempo (lo que podemos ver en Access en una consulta), debemos elegir directamente
el tipo Dynaset, ya que el tipo Table debe contener TODOS los registros de una UNICA
TABLA.
Si estamos en ese caso es el único en el que tendremos dudas respecto al tipo elegido.
Si creamos un recordset tipo Table se nos puede complicar un poco el código a la hora de
movernos a lo largo del recordset, ya que no podemos usar ciertos métodos como los Find
(FindFirst, FindLast, etc.) debiendo utilizar para realizar la misma función los métodos Move
(MoveFirst, MoveLast, MovePrevious, MoveNext), o el método Seek, un poco más complicado
y que exige un índice. (Lo que ganamos con la complicación del Seek es velocidad). Los
desplazamientos a lo largo de un recordset tipo tabla son mucho más rápidos que sobre un
recordset tipo Dynaset. Esa rapidez se nota fundamentalmente cuando va a manejar miles de
registros, en cuyo caso es indispensable usar recordsets tipo tabla y moverse sobre un índice.
Si no va a manejar miles de registros, no apreciará mucha diferencia entre uno y otro. Y si usa
Dynaset dispone de más recursos, sobre todo de búsqueda. Veremos casos de uno y otro tipo.
Sintaxis NombredeMiRecordset.RecordCount
(NombredeMiRecordset puede ser del tipo Dynaset, Snapshot Table. Pero en el caso de que
sea Dynaset se va a encontrar con una sorpresa. Si le pide ese dato a un recordset tipo
Dynaset recién creado, le devolverá el valor 1. ¡Parece que solamente tiene un registro! No es
así. Un recordset tipo Dynaset tiene la ventaja (y el inconveniente) de que solamente guarda
en memoria un registro. Por lo tanto no sabe cuantos registros tiene realmente hasta que no
los recorre todos. Para que el método RecordCount le devuelva el número de registros
existentes, tiene que acceder previamente al primero y al último. Para ello basta con ejecutar
estas dos líneas de código:
NombredeMiRecordset.MoveLast
NombredeMiRecordset.MoveFirst
Método AddNew
Sintaxis MiRecordset.AddNew
El método AddNew crea un nuevo registro donde puede introducir nuevos datos, y
posteriormente agregarlo al conjunto de registros del objeto Recordset. Este método establece
en los campos el valor Null (predeterminado para los objetos Recordset de tipo tabla) o los
valores predeterminados, si existen. El registro creado queda en la memoria, y ahí se puede
modificar simplemente asignando a cada campo el valor deseado. Para asignar un valor a un
campo simplemente tenemos que poner la expresión :
Una vez que se hayan introducido los datos en el nuevo registro, debe utilizar el método
Update para guardar los cambios y agregarlo al conjunto de registros. No se modificará la
base de datos hasta que se utilice el método Update.
El registro que era actual antes de utilizar el método AddNew continúa siéndolo después. Esto
puede comprobarlo asignando a un Label el contenido de un campo, añadir un registro con un
valor para ese campo distinto al que está presente en el Label y comprobar que el contenido
del Label no se ve afectado por haber introducido un registro nuevo. Si desea hacer que el
nuevo registro sea el actual, puede establecer en la propiedad Bookmark el marcador
identificado por el valor de la propiedad LastModified. En la práctica anterior observará tras
este proceso que se cambia el contenido del Label al nuevo valor.
Método Edit
Copia el registro actual de un objeto Recordset de tipo hoja de respuestas dinámica o tabla en
el búfer de copia para su edición.
Sintaxis MiRecordset.Edit
Una vez invocado el método Edit, los cambios efectuados en los campos del registro actual se
copian en el búfer de copia. Al terminar de realizar los cambios deseados, utilice el método
Update para guardarlos. Como en el caso del método AddNew este registro modificado está
en la memoria y es necesario introducirlo en la BD.
El registro actual después de utilizar Edit es precisamente el registro que acabamos de editar.
Para poder usar Edit debe existir un registro actual. Si no es así o si MiRecordset no se refiere
a un objeto Recordset de tipo tabla u hoja de respuestas dinámica, a un objeto Table o a un
objeto Dynaset abierto, se producirá un error.
Una vez añadido el registro, o cambiados los datos de un registro, debemos utilizar el Método
Update para guardar los datos en la BD.
Método Update
Guarda el contenido del búfer de copia en un objeto Recordset de tipo hoja de respuestas
dinámica o tabla especificado.
Es decir, mete en la Base de Datos el contenido del registro que estaba en la memoria, bien
por haber utilizado el método Update, bien por haber utilizado el método Edit.
Sintaxis MiRecordset.Update
Uso del método Edit o AddNew y desplazamiento a otro registro sin utilizar antes Update.
Uso de Edit o AddNew y utilización de nuevo de Edit o AddNew sin especificar antes Update.
Establecimiento de otro registro en la propiedad Bookmark.
Cierre del conjunto de registros indicado en MiRecordset sin utilizar antes Update.
Cada vez que se crea o edita un registro se cambia el valor de la propiedad LastModified,
que tomará el marcador de ese registro creado o editado.
Método CancelUpdate
Sintaxis recordset.CancelUpdateTipo
Método Delete
Este método elimina el registro actual de un objeto Recordset de tipo hoja de respuestas
dinámica o tabla. Para eliminar un registro, debe haber un registro actual en el Recordset
antes de utilizar Delete, pues de lo contrario se producirá un error interceptable. Una vez
eliminado, este registro sigue siendo el registro actual. Puede observar, que si a continuación
de Delete utiliza AbsolutePosition para conocer en que registro está, la respuesta será -1,
prueba de que está sobre un registro inexistente.
Propiedad Bookmark
Devuelve o establece un marcador que identifica de forma única el registro actual de un objeto
Recordset o define el registro actual de un Recordset como marcador válido.
Esto merece una pequeña aclaración. Bookmark en inglés significa ese papel que
introducimos en un libro para saber en qué página hemos dejado la lectura. En Visual Basic,
significa el registro en el que estamos actualmente (registro actual) Podemos conocer en que
registro estamos mediante la siguiente expresión:
Variable = MiRecordset.Bookmark
Pero tenemos que tener en cuenta que Variable es una variable tipo String (Sí, string, aunque
parezca que para conocer la posición de un registro debería ser un numérico, pero es así). Por
lo tanto deberíamos haber declarado la variable previamente como una variable tipo String
Pero esta propiedad sirve para colocarnos en el registro que deseemos. Eso sí, previamente
deberíamos haber obtenido el Bookmark de ese registro. Imagínese que se está moviendo a lo
largo del recordset y hemos visto un registro donde tenemos un dato importante (por ejemplo,
un máximo del valor de un campo) No sabemos si habrá otro registro que tenga un valor
mayor que este. Deberemos seguir buscando, pero antes anotamos el Bookmark de ese
registro
Variable = MiRecordset.Bookmark
Seguimos buscando moviéndonos por todo el recordset, y comprobamos que no hay otro
registro con un valor mayor. Queremos volver a aquel registro. Para ello forzamos a que el
registro cuyo Bookmark sea igual a Variable se convierta en registro actual:
MiRecordset.BookMark = Variable
La propiedad Bookmark se almacena internamente como matriz de Byte. Por esta razón, si se
intenta usar la propiedad Bookmark en una operación de comparación, se producirá un error
interceptable. Antes de tener acceso a la propiedad Bookmark, copie los valores de los
marcadores a variables cadena y efectúe las comparaciones usando dichas variables cadena.
Por ejemplo, el siguiente código compara marcadores en dos objetos Recordset:
No hay límite en el número de marcadores que pueden establecerse. Para crear un marcador
para otro registro distinto del registro actual, muévase al registro deseado y asigne el valor de
la propiedad Bookmark a una variable String que identificará el registro.
Para asegurarse de que el Recordset acepta marcadores, inspeccione el valor de su propiedad
Bookmarkable antes de usar la propiedad Bookmark. Si Bookmarkable es False, el Recordset
no acepta marcadores, y el uso de la propiedad Bookmark produce un error interceptable.
Propiedad LastModified
Sintaxis NombreRecordset.LastModified
El valor devuelto por esta propiedad es un tipo de datos Variant o String. (Similar al devuelto
por Bookmark) LastModified se puede usar para colocarse en el registro más recientemente
agregado o actualizado.
Esta propiedad puede usarse para volver al último registro que ha sido modificado. Basta para
ello igualar la propiedad Bookmark a la propiedad LastModified :
NombreRecordset.Bookmark = NombreRecordset.LastModified
Puede utilizar los métodos Move para desplazarse de un registro a otro sin aplicar una
condición.
Al abrir el conjunto de registros indicado en Recordset, el primer registro pasa a ser el registro
actual y en la propiedad BOF se establece False. Si el conjunto no contiene ningún registro, se
establecerá en BOF el valor True y no habrá registro actual.
No es posible utilizar los métodos MoveFirst ni MovePrevious en los Recordset tipo snapshot
de desplazamiento hacia delante.
Para desplazar la posición del registro actual en un objeto Recordset un número de registros
determinado hacia adelante o hacia atrás, utilice el método Move.
Método Move
Desplaza la posición del registro actual en un objeto Recordset.
Donde :
filas es un valor de tipo Long con signo que especifica el número de filas (de registros) que se
desplaza la posición. Si filas es mayor que 0, la posición se desplaza hacia adelante (hacia el
final del archivo). Si es menor que 0, la posición se desplaza hacia atrás (hacia el principio del
archivo).
Inicio (opcional) es un valor de tipo String que identifica un marcador. Si se especifica inicio,
el desplazamiento será relativo al marcador indicado. Si se omite, Move comenzará por el
registro actual. El marcador que debe utilizarse para definir el registro Inicio debe ser un
Bookmark o similar (LastModified, por ejemplo)
Recordset.FindFirst "Nombre = 'Luis' " ' Busca un nombre. Recuerde siempre las
comillas dobles para la expresión de búsqueda
y las comillas simples si se trata de un dato
string.
Observe que las fechas, aparte de ponerlas en americano, hay que presentarlas entre
almohadillas (#). Observe lo dicho mas atrás para las comillas dobles en la expresión de
búsqueda.
Método Seek
Este método solo se puede usar con recordsets tipo Tabla
Si no podemos usar los métodos Find en un recordset tipo Tabla, ¿Qué podemos hacer para
buscar un dato en un recordset de este tipo? Usar el método Seek
El método Seek busca el primer registro de un objeto Recordset indexado de tipo Table que
cumple el criterio especificado para el índice activo y lo convierte en el registro activo. Sólo
funciona en espacios de trabajo Microsoft Jet.
Donde MiRecordset es un recordset de tipo Table que tiene definido un índice en el campo
por el que se va a realizar la búsqueda. Como podemos tener varios índices en una tabla,
deberemos indicarle cual es el índice de búsqueda. Una vez que se lo indiquemos, ese índice
será el Indice activo.
clave1, clave2...clave13 Son uno o más valores que corresponden a los campos en el índice
activo del objeto Recordset. Puede utilizar un argumento de hasta 13 claves.
Antes de usar Seek se debe establecer el índice activo. Todo índice tiene un nombre.
Habíamos visto cuando creábamos un índice que debía tener un nombre. Recuerde el
ejemplo:
Puede ver el nombre de ese índice en la Fig. 20.7 En este caso habíamos creado el índice
mediante código y hemos podido controlar su nombre. Si lo hubiésemos creado directamente
en Access, el nombre que le pone por defecto es el mismo que el nombre del campo.
Ese nombre del índice es el que debemos usar para crear el índice activo. Por ejemplo, si
quisiéramos que el índice activo fuese el IndicePeliculas lo haríamos índice activo mediante
la siguiente instrucción:
MiRecordset.Index = "IndicePelículas"
A partir de ahora, el campo (o campos) de ese índice será sobre el que realizaremos la
búsqueda mediante Seek. Para encontrar el registro que tenga por valor 00000012 usaremos
la expresión
Ese registro será ahora el registro actual. Si hubiese mas de un registro con ese valor, el
registro actual será el primero que cumpla esa condición
El método Seek busca en los campos clave especificados y localiza el primer registro que
cumpla el criterio especificado por comparación y clave1. Cuando lo encuentra, convierte ese
registro en activo y la propiedad NoMatch se establece en False. Si el método Seek no
consigue localizar ninguna coincidencia, la propiedad NoMatch se establece en True y el
registro activo es indefinido.
Si comparación es igual (=),mayor o igual (>=) o mayor que (>), Seek empezará al principio
del índice y buscará hacia adelante.
Si comparación es menor que (<) o mayor o igual que (<=), Seek empezará al final del índice y
buscará hacia atrás, a menos que haya entradas de índice duplicadas al final. En tal caso,
Seek empezará en una entrada cualquiera entre las entradas duplicadas existentes al final del
índice.
Debe especificar valores para todos los campos definidos en el índice. Si utiliza Seek con un
índice de múltiples columnas y no especifica un valor de comparación para cada campo del
índice, no podrá usar el operador de igual (=) en la comparación. Esto se debe a que algunos
de los campos de criterio(clave2, clave3, etc) estarán predeterminados en Null, lo que
posiblemente no concordará. Por tanto, el operador de igual sólo funcionará correctamente si
tiene un registro que sea Null en su totalidad, excepto la clave que está buscando. Es
aconsejable usar el operador mayor que o igual en su lugar.
En el ejemplo siguiente se toman los nombres de las calles y otros datos de una tabla llamada
Calles_Nombre, cuyo campo NombreVia esta indexado. El índice tiene el mismo nombre que
el campo porque se creó directamente con Access. El procedimiento BBuscaCalle_Click busca
la primera calle cuyo nombre coincida con las letras tecleadas en el TextBox TBBuscaCalle
Método Clone
En muchas ocasiones es necesario crear un Recordset que sea copia exacta de otro. Las
ocasiones en las que es necesario hacer esto pueden ser variadas, pero vamos a destacar
una : crear un Recordset idéntico al Recordset de un control Data, para trabajarlo con código
durante una parte de la ejecución del programa. Otras aplicaciones pueden ser copiar el
Recordset de otra máquina a través de la Red de Area Local, ... y donde podamos llegar con
nuestra imaginación.
Con el método Clone puede crear múltiples Recordsets. Cada uno de ellos puede tener su
propio registro actual. El uso de Clone no modifica los datos de los registros. Puede modificar
un registro desde cualquier Recordset, bien desde el que sirvió de original, bien desde
cualquiera de sus copias, pero debe hacerlo invocando los métodos Edit - Update. Puede
compartir marcadores entre dos o más Recordsets creados de esta forma.
Puede utilizar el método Clone cuando desee realizar en un conjunto de registros una
operación que requiera varios registros actuales. Este método es más rápido y eficiente que
crear un nuevo Recordset.
Inicialmente, un Recordset creado con Clone carece de registro actual. Para hacer que un
registro sea el actual antes de utilizar el Recordset copia, puede utilizar cualquiera de los
métodos Move, Find o Seek (solo para Recordsets tipo Tabla), o establecer su propiedad
Bookmark
Método Requery
El método Requery actualiza los datos de un objeto Recordset, volviendo a ejecutar la consulta
con la que se ha creado ese Recordset. Este método debe usarse cada vez que se sospeche
que los datos de la Base de datos han cambiado, y se quieran presentar los datos
actualizados. Es un método típico de una BD que se está usando desde varios puestos a
través de una Red de Area Local.
No es posible utilizar el método Requery en objetos Recordset tipo Snapshot o en los Dynaset
que tengan la propiedad Restartable a False, ni tampoco en los objetos Recordset de tipo
Tabla.
Si los valores de las propiedades BOF y EOF del objeto Recordset son ambos True después
de utilizar el método Requery, la consulta no habrá devuelto ningún registro y el objeto
Recordset no contendrá datos.
Transacciones
Veamos estos tres métodos que, dadas sus funciones, deben estudiarse conjuntamente.
A continuación hace un proceso similar con la cuenta destino, pero en este caso, sumándole el
importe de la transferencia. No hay problemas. Pero que pasa si, una vez sacado el dinero de
la cuenta origen, no se puede ingresar en la cuenta destino, por la razón que sea (cuenta
bloqueada, no existe esa cuenta, fallo de la red de área local) Obviamente la operación no se
ha completado, y hay que devolver el dinero a la cuenta origen. Podría hacerse un apunte,
metiendo la misma cantidad de dinero que se ha extraído anteriormente, y su saldo no se verá
afectado. Pero no sé lo que pensaría el cliente cuando vea un estadillo de su cuenta, en la que
le han sacado una cantidad de dinero, aunque en el siguiente apunte se lo hayan vuelto a
introducir.
Para evitar estas situaciones usamos lo que se denomina una Transacción, que es una
combinación de estos tres métodos. Con el método BeginTrans iniciamos la Transacción. Con
CommitTrans terminamos la transacción y se guardan los cambios realizados (En ambas
cuentas a la vez, en el caso del ejemplo). Con Rollback se termina la transacción sin llegar a
guardar los cambios, quedando el Objeto Workspace afectado por las operaciones internas a
esa transacción tal y como estaba antes de comenzar dicha operación.
Dado que una transacción pertenece a un Workspace, deberemos aplicar estos métodos al
Workspace que ese usuario tenga abierto. Es decir, en un sistema con varios usuarios que
están trabajando simultáneamente sobre una Base de Datos, un determinado usuario deberá
entrar con un Workspace propio (una sesión de trabajo solo para él). En estas condiciones
podemos crear una transacción. Y aquí comenzamos a ver la necesidad de crear Workspaces
distintos para distintos usuarios. Veremos un poco más adelante como se crean los
Workspaces.
Sintaxis MiSesión.BeginTrans
MiSesión.CommitTrans
MiSesión.Rollback
Dentro de un objeto Workspace, las transacciones son siempre globales y no se limitan sólo a
la base de datos o al conjunto de registros. Si realiza operaciones en más de una base de
datos o conjunto de registros durante una transacción en un objeto Workspace, el método
Rollback deshará todas las operaciones en todos ellos. Quiere esto decir que una transacción
debe iniciarse al comenzar una determinada operación, realizar esa operación sin realizar
ninguna otra durante ese tiempo, terminar la operación y finalizar la transacción, bien con
CommitTrans o con Rollback.
Si desea tener transacciones simultáneas, lo mas indicado es crear varios objetos Workspace
para usar uno con cada transacción.
Puede anidar transacciones. Es posible tener hasta cinco niveles de transacciones abiertos a
un tiempo en un mismo objeto Workspace utilizando múltiples combinaciones anidadas de
BeginTrans y CommitTrans o Rollback. En este caso, el orden de finalización de una
transacción debe ser siempre de menor a mayor nivel jerárquico, es decir, se deberá cerrar
primero la transacción que esté mas interior dentro del anidamiento, y así sucesivamente. Si
cierra una transacción anidada mediante CommitTrans, y posteriormente cierra una
transacción que abarque a esta última con Rollback, los cambios de la primera transacción NO
quedarán guardados.
(Cuando se utilizan bases de datos SQL ODBC externas no es posible anidar las
transacciones).
Si cierra un objeto Workspace sin guardar o deshacer las transacciones pendientes, éstas se
desharán automáticamente.
El hecho de usar transacciones, aparte de lo que significa para asegurar la integridad de los
datos, ahorra accesos al disco (Importantísimo en algunas redes LAN y WAN), ya que los
cambios a introducir se van almacenando en un búfer en la memoria, y se vuelcan al disco
solamente en el momento de terminar la transacción de modo afirmativo con CommitTrans.
Tiene como cualquier objeto DAO sus propiedades y métodos. Vamos a ver alguna propiedad,
en orden ascendente de importancia práctica.
Propiedad Version
Devuelve la versión actual de DAO en uso. El tipo de datos es String (p.e., "3.5" )
Propiedad DefaultType
Establece o devuelve un valor que indica qué tipo de espacio de trabajo (Microsoft Jet u
ODBCDirect) utilizará el próximo objeto Workspace que se cree. Puede tomar los valores:
dbUseJet Crea objetos Workspace conectados al motor de base de datos Microsoft Jet.
dbUseODBC Crea objetos Workspace conectados a un origen de datos ODBC.
El DefaultUser es un tipo de datos String, que puede tener entre 1 y 20 caracteres de longitud
en espacios de trabajo Microsoft Jet y cualquier longitud en espacios de trabajo ODBCDirect.
El DefaultPassword es un tipo de datos String que puede tener hasta 14 caracteres de longitud
en bases de datos Microsoft Jet y cualquier longitud en conexiones ODBCDirect. Puede
contener cualquier carácter excepto 0 ASCII.
Para que estas propiedades tenga efecto, debe establecerla antes de llamar a cualquier
método DAO.
Donde User es la variable tipo objeto User que desea crear. Debe declararse como User)
Objeto El nombre del Workspace (o del objeto objeto Group) que quiere utilizar para crear el
nuevo objeto User.
Nombre Nombre del nuevo usuario
PID. Identificador de Usuario. Debe contener de 4 a 20 caracteres alfanuméricos
Contraseñal. Contraseña para el nuevo usuario. La contraseña puede tener hasta 14
caracteres de longitud y puede incluir cualquier carácter excepto el carácter ASCII 0
Esto merece algún comentario. El nuevo usuario debe crearlo un Workspace (o un Group,
pero de eso no nos vamos a ocupar por ahora) Lo normal es crear el nuevo usuario con el
Workspaces(0) que como sabe, lo crea automáticamente Visual Basic.
Recuerde que antes de crear un nuevo usuario, debe indicar donde está la base de datos con
la información del systema, mediante la propiedad SystemDB
DBEngine.SystemDB = "C:\Windows\System\System.Mdw")
Podemos saber cuantos usuarios tiene el Workspaces(0) y su nombre. Con las siguientes
instrucciones vamos a introducir los nombres de los usuarios en la lista ListUsers
ListUsers.Clear
For I = 0 To Workspaces(0).Users.Count - 1
NomUsuario = Workspaces(0).Users(I).Name
ListUsers.AddItem NomUsuario
Next I
La sintaxis es la siguiente:
Veamos un ejemplo de cómo crear un Workspace para el usuario Luis creado anteriormente:
Podemos ver todos los Workspaces existentes. En las siguientes instrucciones podemos ver el
código para listarlos en ListWS
ListWS.Clear
For I = 0 To DBEngine.Workspaces.Count - 1
NomWS = DBEngine.Workspaces(I).Name
ListWS.AddItem NomWS
Next I
Debe tenerse en cuenta que el objeto Workspace es un objeto que solamente existe mientras
está ejecutándose la aplicación. Cuando salimos de la aplicación, ese Workspace desaparece.
No ocurre lo mismo con el Usuario, que queda en la base de datos del sistema, es decir, es un
objeto persistente.
Las partes de código expuestas se han sacado de un ejemplo creado para ver el número y
nombre de los usuarios existentes, Workspaces y DataBases. Es un ejemplo para explicar
estos conceptos. No tiene otra finalidad.
Fig. 20.9 Aspecto de la interface gráfica del ejercicio para crear Users y Workspaces
General/Declaraciones
Option Explicit
Dim VarDrag As String
Dim NuevoWS() As Workspace
RutErr:
If Err = 3028 Then
MsgBox "No se puede abrir la base de datos del sistema. Compruebe que su Path y nombre
son correctos. Vealo en Ver Ini"
End If
End Sub
El fichero Cap20Usr.INI que debe estar necesariamente en la misma carpeta que el programa,
en el caso del PC del autor tiene esta forma
REM Visual Basic - Guía del Estudiante. Cap. 20. Creación de Usuarios y WorkSpaces
DBEngINI=C:\WinNT\System32\System.mdw
Método CompactDatabase
Sintaxis
BaseDatosNva es el nombre del fichero (con su Path completo) de la base de datos nueva,
creada al copiar la BaseDatosAnt, ya compactada. No es posible especificar en el argumento
BaseDatosNva el mismo archivo de base de datos que en BaseDatosAnt.
inf_local es una expresión de cadena utilizada para especificar el alfabeto usado a la hora de
ordenar datos de esa Base de Datos. El parámetro a introducir es el mismo que para el
argumento similar usado en la creación de la Base de Datos (dbLangGeneral para el caso de
España). Este argumento es opcional. Si se omite, la información local de BaseDatosNva será
la misma que la de BaseDatosAnt.
Opciones nos permite cambiar alguna característica de la Base de Datos. Puede elegirse
entre cifrarla o no cifrarla y cambiar la versión del motor de bases de datos que va a usar la
nueva Base de Datos.
Se puede usar una constante (solamente) de encriptación y una (solamente) de Versión. Sólo
se puede compactar BaseDatosNva con una versión igual o posterior a la de BaseDatosAnt.
Método RepairDatabase
Donde NombreBase es el nombre (Y path) del fichero que contiene la Base de Datos a
reparar. Puede especificar una ruta de red. P.e. : "\\MISERVID\ MIDIR\NombreBase.MDB".
Para poder reparar la base debe estar Cerrada. Recuerde, si está en un entorno multiusuario,
que los demás usuarios tampoco pueden tenerla abierta mientras la repara.
El método RepairDatabase también intenta validar todas las tablas del sistema y todos los
índices. Los datos que no puedan repararse se pierden. Si la base de datos no puede
repararse, se producirá un error interceptable.
Método Execute
Este Método es para el Objeto DataBase y para el Objeto QueryDef.
Ejecuta una consulta de acciones o una instrucción SQL en el objeto Database especificado.
Donde NombreQuerydef es el nombre del objeto QueryDef cuya propiedad SQL especifica la
instrucción SQL a ejecutar.
Opciones igual que para el Objeto Database.
El método Execute sólo es válido para las consultas de acciones. Si utiliza Execute con otro
tipo de consultas, se producirá un error. Debido a que las consultas de acciones no devuelven
registros, Execute no devuelve un conjunto de registros.
Ejemplo. En el siguiente ejemplo, usamos EXECUTE para cambiar el campo Nombre en una
tabla llamada CLIENTES de una Base de Datos abierta, cuyo Objeto DataBase se llama
BaseDatos. Para poder “jugar” con el nombre a cambiar y el nombre cambiado, se introduce el
nombre que queremos cambiar en TBNombre2 y el nuevo nombre en TBNombre1
MiSQL = "UPDATE CLIENTES SET NOMBRE = '" & TBNombre2 & "' WHERE NOMBRE= '" &
TBNombre1 & "'"
Dada una instrucción SQL sintácticamente correcta y teniendo los permisos adecuados, el
método Execute no fallará, aún cuando no pueda modificarse ni eliminarse una línea. Por lo
tanto, debe especificar siempre la opción dbFailOnError cuando utilice el método Execute
para ejecutar una consulta de actualización o eliminación. Esta opción generará un error
interceptable y deshará todos los cambios realizados con éxito cuando alguno de los registros
afectados se encuentre bloqueado y no pueda actualizarse o eliminarse.
Propiedad RecordsAffected
Para determinar el número de registros afectado por el último método Execute, puede utilizar
la propiedad RecordsAffected del objeto Database o Querydef. Por ejemplo, RecordsAffected
contienen el número de registros eliminados, actualizados o insertados al ejecutar una consulta
de acciones. Al utilizar el método Execute para ejecutar un objeto Querydef, en la propiedad
RecordsAffected del Querydef se establece el número de registros afectados.
Una imagen (la fotografía de una persona por ejemplo) puede guardarse en una base de datos
tipo ACCESS y presentarse en un control Picture. ¡¡ Imagine una aplicación que sea una
agenda de teléfonos y pueda insertar la foto de la persona !!
Para introducir una imagen en una BD, el campo de esa BD donde se va a introducir la imagen
debe ser LongBinary ( si esa versión de ACCESS lo tiene) u Objeto OLE.
La asociación de la Base de datos al Control Data puede hacerse, bien mediante sus
propiedades DatabaseName y RecordSource, bien creando un Recordset con código e
igualando la propiedad Recordset del Control Data a ese Recordset.