RSS

Uso de SQLite por Alejandro Sandoval

domingo, 25 de enero de 2009

Hola Comunidad de SQLite Latino, Feliz comienzo de año 2009. hace unos dias El Sr. Alejandro Sandoval nos envio un articulo relacionado con SQLite. Agradecemos su aporte, Asi tambien todo estan invitados a participar de SQLite Latino

Espero que sea de su agradao. Hasta la proxima
Uso de SQLite 

SQLite es un pequeño 'sistema' de base de datos: es una biblioteca que sobre un archivo almacena el esquema de la base de datos y los datos propiamente tal. En términos generales presenta un buen rendimiento, y está aconsejada para quienes requieren almacenar un volumen medio de datos en sus aplicaciones, utilizando un esquema relacional.En el caso de querer incluir la API SQLite en tu proyecto de programación, puedes incluir en tu programa C/C++ a sqlite.h, compilar a sqlite.c y listo.
Dentro de lo que podemos llamar una desventaja de SQLite es que no implementa la integridad referencial, es decir, no disponemos de claves foráneas (puedes declararlas, pero el intérprete ignorará tu declaración). De todas formas, SQLite si implementa triggers, así que hay algunas formas de implementar la integridad referencial de esa forma.
¿Un ejemplo? Pero con mucho gusto... de todas maneras, el ejemplo está pensado solo para mostrar las características de SQLite, así que no esperes un buen diseño ni nada por el estilo...
Por ejemplo, imagínese una pequeña empresa de buses que requiere almacenar información acerca de sus viajes. Para ello, supongamos que tenemos la tabla buses (con información de los buses), viaje (con los datos de un viaje) y pasajero (con los datos de un pasajero). Una propuesta entonces podría ser la siguiente:

create table buses(
  id integer primary key not null autoincrement,
  matricula text not null,
  ano integer not null,
  capacidad integer not null); 

create table pasajero(
  cedula text primary key not null,
  nacimiento date not null);

create table viaje(
  id_bus integer not null,
  cedula text not null);
Bueno, hay algunas cosas por explicar de acá:

SQLite implementa el incremento automático de un campo (autoincrement), el que perfectamente pude indicarse para el primary key.
Las claves foráneas, como ya mencioné, no están implementadas aún en SQLite, así que no las agrego.
La primary key de la tabla viaje será su OID.
SQLite, para almacenar una tupla en una tabla, utiliza el sistema de OID, es decir, asigna secretamente un identificador para cada tupla, que facilita su búsqueda en el arbol B (la estructura de datos usada por SQLite para almacenar los datos en archivo). No necesito declarar el OID, pero puedo consultarlo en cualquier tabla; pronto un ejemplo de eso.
Ahora, insertemos algunos datos, para hacer las pruebas.
insert into buses values(null, 'ABCD-01', 2008, 40);
insert into buses values(null, 'JANO-05', 2007, 27);
insert into buses values(null, 'INSQ-35', 2007, 20);
insert into pasajero values('123ASD-K', 1980-05-23);
insert into pasajero values('237QSD-3', 1970-03-28);
insert into pasajero values('135QTW-3', 1989-08-05);
Con esos datos, podemos ver en acción el sistema de los OID:
sqlite>select oid,* from buses;
1|1|ABCD-01|2008|40
2|2|JANO-05|2007|27
3|3|INSQ-35|2007|20

sqlite>select oid,* from pasajero;
1|123ASD-K|1980-05-23
2|135QTW-3|1989-08-05
3|237QSD-3|1970-03-28

Puede parecer extraño lo que ocurre con la tabla buses, puesto que ya existe su primary key, y esta se autoincrementa. Bien, pues de todas formas se genera un OID... pero cuando se define una primary key autoincrementable de tipo integer, el OID tendrá el mismo valor que dicha clave. No ocurre lo mismo en la tabla pasajero, puesto que su primary key es un texto, y no un entero. Nota también que en los campos que se autoincrementan, el siguiente valor se genera insertando un null.

Gracias a esta característica es que he definido la tabla viajes sin primary key: confío en el OID (lo que no es recomendado, por cierto).

Ahora, la parte artística: ¿y las claves foraneas? Como mencioné, para eso disponemos de los triggers...

create trigger fki_viaje_buses
before insert on viaje
for each row
begin
  select raise(rollback, 'Inserción en viaje viola restricción fki_viaje_buses')
  where (select id from buses where id=new.id_bus) is null;
end;

Luego de eso, intentemos insertar un viaje para un bus no válido:

sqlite> insert into viaje values(5, '123ASD-K');

SQL error: Inserción en viaje viola restricción fki_viaje_buses

El trigger para la actualización (update) es exactamente igual, pero en el caso de la eliminación tenemos un problema: si quiero eliminar un bus ¿qué hago con los viajes? Por como se definió la base de datos, no se pueden eliminar buses o pasajeros mientras hayan viajes, así que manos al trigger:

create trigger fkd_viaje buses
before delete on buses
for each row
  begin
    select raise(rollback, 'No se puede eliminar bus')
    where (select id_bus from viaje where old.id = id_bus) is not null;
end;
Ahora, si yo he insertado los siguientes datos de viaje
insert into viaje values(1, '135QTW-3');
insert into viaje values(1, '123ASD-K');
el trigger impedirá que eliminemos un bus, lo que generaría un estado inconsistente en la base de datos

sqlite> delete from buses where id = 1;
SQL Error: No se puede eliminar bus
 
Como se puede apreciar, SQLite es una alternativa interesante cuando se necesita un acceso a datos rápido y sencillo. Aunque me parece que SQLite es muy bueno, no me atrevería aún a usarlo en un entorno más de producción... aunque en la medida que lo voy usando, más me convence.

Autor: Alejandro Sandoval
Urls : http://esteparioprogramador.bligoo.com

:::NOTA COMPLEMENTARIA:::

Basándose en el ejemplo, el análisis es el siguiente: viaje es una tabla intermedia para la relación de muchos a muchos entre buses y pasajero. Por lo tanto, y siendo los campos de la tabla viaje claves foráneas, no puedo permitir la inserción de un viaje si el bus o el pasajero no existen: si lo permitiera, no tendría integridad referencial, dejando inconsistente la base de datos.

Por esa razón, se crean los triggers, y esa es la lógica que están siguiendo en este caso. Despedacemos el primer trigger pa que quede más claro:

create trigger fki_viaje_buses : Con eso, creo el famosillo trigger, y su nombre es fki_viaje_buses

before insert on viaje : Esto nos asegura la pega: el trigger será ejecutado antes de insertar en la tabla viaje. Eso te garantiza que la inserción no generará un estado inconsistente.

for each row : Está diciendo... puro traducir: repetir el trigger para cada fila insertada. Lo que se va a ejecutar está dentro del begin - end, por si acaso.

select raise(rollback, 'Inserción en viaje viola restricción fki_viaje_buses') 
 where (select id from buses where id=new.id_bus) is null;

Aquí está el detallito: le estamos diciendo que lance un error (y ejecute un rollback) cuando se cumpla la condición del where, es decir, si no hay ninguna tupla en la tabla buses que tenga un ID igual al ID del bus que está en la nueva tupla de viaje (la variablenew se ocupa para designar a la tupla que se intenta insertar). Como ves, es exáctamente lo que definimos en palabras antes de comenzar el análisis del problema.

La actualización es la misma situación, puesto que es en el fondo una inserción. De hecho, la única diferencia entre ambos triggers son los mensajes y el nombre.

La eliminación es ligeramente diferente, y pensemos en el caso de eliminar un bus. ¿Qué haré con los viajes asociados?

Las soluciones ahí son:

  • on delete set null: poner los id_bus de viaje en null (lo que en una tabla intermedia no es factible, puesto que las FK son PK además).
  • on delete cascade: si elimino un bus, elimino todos sus viajes.
  • on delete restrict: no puedo eliminar un bus mientras hayan viajes asociados.

El trigger implementado al respecto sigue la última filosofía: no te dejará eliminar un bus mientras hayan viajes registrados con ese bus. En ese esquema, la lógica es al revés: hago el rollback si HAY viajes con el mismo bus_id que tiene old.id (como imaginarás, oldindica la tupla a eliminar).

El tema de los triggers para simular las FK no es tan complejo... pero igual hay que dedicarle su tiempo (en mi caso, significó aprender triggers primero, porque era materia nueva). Aún así, creo que no me demoré mucho en aprender lo necesario.Espero que hayas quedado más claro. Si hay más dudas, pregunta nomás: yo feliz de ayudar dentro de lo que pueda.