13 Ejemplo de aplicación SQLite

Vamos a desarrollar un programa basado en ejemplo 2 del curso de MySQL.

Recordemos el enunciado del problema:

Nuestro segundo ejemplo es más complicado. Se trata de gestionar una biblioteca, y nuestro cliente quiere tener ciertas herramientas a su disposición para controlar libros, socios y préstamos. Adicionalmente se necesita un control de los ejemplares de cada libro, su ubicación y su estado, con vistas a su retirada o restitución, para esto último necesita información sobre editoriales a las que se deben pedir los libros.

Tanto los libros como los socios estarán sujetos a ciertas categorías, de modo que según ellas cada libro podrá ser o no prestado a cada socio. Por ejemplo, si las categorías de los libros van de A a F, y la de los socios de B a F, un libro de categoría A nunca puede ser prestado a ningún socio. Estos libros sólo se pueden consultar en la biblioteca, pero no pueden salir de ella. Un libro de categoría B sólo a socios de categoría B, un libro de categoría C se podrá prestar a socios de categorías B y C, etc. Los libros de categoría F siempre pueden prestarse.

El sistema debe proporcionar también un método de búsqueda para libros por parte de los socios, por tema, autor o título. El socio sólo recibirá información sobre los libros de los que existen ejemplares, y sobre la categoría.

Además, se debe conservar un archivo histórico de préstamos, con las fechas de préstamo y devolución, así como una nota que el responsable de la biblioteca quiera hacer constar, por ejemplo, sobre el estado del ejemplar después de su devolución. Este archivo es una herramienta para la biblioteca que se puede usar para discriminar a socios "poco cuidadosos".

Los préstamos, generalmente, terminan con la devolución del libro, pero algunas veces el ejemplar se pierde o el plazo supera un periodo de tiempo establecido y se da por perdido. Estas circunstancias pueden cerrar un préstamo y provocan la baja del ejemplar (y en ocasiones la del socio :-). Nuestro archivo histórico debe contener información sobre si el libro fue devuelto o perdido.

Supondremos que vamos a desarrollar nuestro programa usando el compilador Code::Blocks, aunque el código y las acciones a tomar serán muy parecidas usando otros IDEs.

Empezaremos creando un nuevo proyecto, de tipo "Console application", en C++ y con el título "ejemplosqlite".

A continuación abrimos "Proyecto->Opciones para la construcción", vamos a la pestaña "linker settings", seleccionamos "ejemplosqlite", en el árbol de la izquierda, para que las opciones se apliquen tanto a la versión "debug" como a la "release", y agregamos la librería "sqlite".

Si la el fichero "sqlite3.dll" no está en una carpeta donde se pueda encontrar siempre (por ejemplo en system32, o cualquiera en el path), deberemos copiarla a la carpeta del proyecto. Haremos lo mismo con el fichero "sqlite3.exe", para poder consultar la base de datos desde consola, si fuera necesario.

Iremos añadiendo rutinas al programa, paso a paso, a medida que los vayamos explicando, de modo que cada fase de la aplicación pueda ser compilada y probada.

Crear la base de datos

Lo primero es crear la base de datos, si no existe, y abrirla si existe. Crear la base de datos es trivial, basta con intentar abrirla, si el fichero de base de datos no existe, se crea uno vacío. Nuestro primer programa es simple:

/*
 * Aplicación de ejemplo de uso de SQLite en C++
 * EjemploSQLite
 * Salvador Pozo, Con Clase (www.conclase.net)
 * Marzo de 2012
 * Fichero: main.cpp
 * fichero principal
 * Incluir en el enlazador la librería libsqlite.a (sqlite)
 * Facilitar el acceso a la librería de enlace dinámico sqlite3.dll
 */

#include <iostream>
#include <sqlite/sqlite3.h>

using namespace std;

int main()
{
    int rc;
    sqlite3 *db;

    // Abrir base de datos
    rc = sqlite3_open("biblioteca.db", &db);
    if(SQLITE_OK != rc) {
        cout << "Error: No se puede abrir la base de datos" << endl;
		return 1;
    }
	// Aquí ira nuestro programa...

    // Cerrar base de datos
    sqlite3_close(db);
    return 0;
}

Después de ejecutar este programa podremos ver que se ha creado el fichero de la base de datos vacío.

Verificar si existen las tablas

El segundo paso es verificar si existen las tablas. Para ello haremos una consulta en cada una se ellas, y asumiremos que si se produce un error es porque la tabla no existe. SQLite no dispone de códigos de error específicos para indicar que una tabla no existe, aunque la función sqlite3_errmsg si detecta ese error.

Para nuestra segunda versión del programa crearemos una función que verifique la existencia de las nueve tablas que componen la base de datos, y que cree aquellas que no existen.

/*
 * Aplicación de ejemplo de uso de SQLite en C++
 * EjemploSQLite
 * Salvador Pozo, Con Clase (www.conclase.net)
 * Marzo de 2012
 * Fichero: main.cpp
 * fichero principal
 * Incluir en el enlazador la librería libsqlite.a (sqlite)
 * Facilitar el acceso a la librería de enlace dinámico sqlite3.dll
 */

#include <iostream>
#include <sqlite/sqlite3.h>

const int nTablas = 9;

bool VerificarTablas(sqlite3 *);

using namespace std;

int main()
{
    int rc;
    sqlite3 *db;

    // Abrir base de datos
    rc = sqlite3_open("biblioteca.db", &db);
    if(SQLITE_OK != rc) {
        cout << "Error: No se puede abrir la base de datos" << endl;
		return 1;
    }

    if(!VerificarTablas(db)) return -1;

    // Cerrar base de datos
    sqlite3_close(db);
    return 0;
}

bool VerificarTablas(sqlite3 *db) {
    char consulta[36];
    char *tabla[] = {
        "editorial",
        "libro",
        "autor",
        "tema",
        "ejemplar",
        "socio",
        "prestamo",
        "trata_sobre",
        "escrito_por"
    };
    char *tabla[] = {
        "editorial",
        "libro",
        "autor",
        "tema",
        "ejemplar",
        "socio",
        "prestamo",
        "trata_sobre",
        "escrito_por"
    };
    char *create[] = {
        "CREATE TABLE editorial("
          "claveeditorial INTEGER PRIMARY KEY,"
          "editorial TEXT,"
          "direccion TEXT,"
          "telefono TEXT);",
        "CREATE TABLE libro("
          "clavelibro INTEGER PRIMARY KEY,"
          "titulo TEXT,"
          "idioma TEXT,"
          "formato TEXT,"
          "claveeditorial INTEGER "
          "REFERENCES editorial(claveeditorial) "
          "ON DELETE SET NULL "
          "ON UPDATE CASCADE);",
        "CREATE TABLE autor("
          "claveautor INTEGER PRIMARY KEY,"
          "autor TEXT);",
        "CREATE TABLE tema("
          "clavetema INTEGER PRIMARY KEY,"
          "tema TEXT);",
        "CREATE TABLE ejemplar("
          "clavelibro INTEGER "
          "REFERENCES libro(clavelibro) "
          "ON DELETE CASCADE "
          "ON UPDATE CASCADE,"
          "numeroorden INTEGER NOT NULL,"
          "edicion INTEGER,"
          "ubicacion TEXT,"
          "categoria TEXT,"
          "PRIMARY KEY(clavelibro,numeroorden));",
        "CREATE TABLE socio("
          "clavesocio INTEGER PRIMARY KEY,"
          "socio TEXT,"
          "direccion TEXT,"
          "telefono TEXT,"
          "categoria TEXT);",
        "CREATE TABLE prestamo("
          "clavesocio INTEGER "
          "REFERENCES socio(clavesocio) "
          "ON DELETE SET NULL "
          "ON UPDATE CASCADE,"
          "clavelibro INTEGER "
          "REFERENCES ejemplar(clavelibro) "
          "ON DELETE SET NULL "
          "ON UPDATE CASCADE,"
          "numeroorden INTEGER,"
          "fecha_prestamo DATE NOT NULL,"
          "fecha_devolucion DATE DEFAULT NULL,"
          "notas TEXT);",
        "CREATE TABLE trata_sobre("
          "clavelibro INTEGER NOT NULL "
          "REFERENCES libro(clavelibro) "
          "ON DELETE CASCADE "
          "ON UPDATE CASCADE,"
          "clavetema INTEGER NOT NULL "
          "REFERENCES tema(clavetema) "
          "ON DELETE CASCADE "
          "ON UPDATE CASCADE);",
        "CREATE TABLE escrito_por("
          "clavelibro INTEGER NOT NULL "
          "REFERENCES libro(clavelibro) "
          "ON DELETE CASCADE "
          "ON UPDATE CASCADE,"
          "claveautor INTEGER NOT NULL "
          "REFERENCES autor(claveautor) "
          "ON DELETE CASCADE "
          "ON UPDATE CASCADE);"
    };

    for(int i = 0; i < nTablas; i++) {
        sprintf(consulta, "SELECT COUNT(*) FROM %s;", tabla[i]);
        if(SQLITE_OK != sqlite3_exec(db, consulta, 0, 0, 0)) {
            cout << "La tabla " << tabla[i] << " no existe." << endl;
            if(SQLITE_OK != sqlite3_exec(db, create[i], 0, 0, 0)) {
               cout << "Error al crear la tabla " << tabla[i] << endl;
               return false;
            }
        }
    }
    return true;
}

Menús de acceso

Nuestra aplicación estará escrita para consola, de modo que necesitaremos alguna forma de comunicarnos con ella. Por comodidad, para el usuario, interactuaremos mediante un menú.

Aprovecharemos la disponibilidad de la base de datos para almacenar los menús en una tabla. Lo normal sería no mezclar los datos que maneja la aplicación con los que la hacen funcionar, pero en este caso nos limitaremos a añadir una tabla a la base de datos. Al contrario que el resto de las tablas, esta tendrá que contener información desde el principio, ya que los datos almacenados en esa tabla son imprescindibles para el funcionamiento de la aplicación.

Lo primero es diseñar un sistema de menús que nos permita hacer todas las tareas que indica el enunciado. Pero no nos interesa que todas las opciones se muestren desde el principio, ya que eso dificulta el manejo del programa. En lugar de eso crearemos una estructura en árbol para el menú, donde al activar ciertas opciones se despliegue un nuevo menú con las opciones disponibles. Por ejemplo, en un primer nivel mostraremos cinco opciones: "Ficheros maestros", "Libros", "Socios", "Préstamos" y "Salir". Si se elige "Ficheros maestros" se desplegará un segundo menú con las opciones de "Editoriales", "Autores", "Temas" y "Volver", etc.

Cada opción de menú tendrá asociado:

  • Un identificador único de opción de menú.
  • Un identificador de menú.
  • Una letra que active la opción.
  • El texto del menú.
  • Un identificador de menú hijo.
  • Un código de acción.

Aprovecharemos la estructura para almacenar los títulos de los submenús, para ello usaremos un valor especial de código de acción.

En nuestro proyecto añadiremos dos nuevos ficheros: menu.h y menu.cpp, que usaremos para declarar y definir las funciones, tipos y datos necesarios para manejar los menús.

En rigor, no necesitamos almacenar los menús en la base de datos, pero hacerlo tiene algunas ventajas, por ejemplo, permite traducir fácilmente los textos de los menús, inhibir algunas opciones, cambiar las teclas para activar ciertas opciones, etc.

El fichero menus.h queda así:

/*
 * Aplicación de ejemplo de uso de SQLite en C++
 * EjemploSQLite
 * Salvador Pozo, Con Clase (www.conclase.net)
 * Marzo de 2012
 * Fichero: menus.h
 * fichero de cabecera para implementar menús
 */

#include <sqlite/sqlite3.h>

#define TITULO              -1
#define SALIR               0
#define ABRIRMENU           1
#define NUEVAEDITORIAL      2
#define EDITAREDITORIAL     3
#define BORRAREDITORIAL     4
#define CONSULTAEDITORIAL   5
// Añadiremos más códigos de operación a medida que implementemos nuevas opciones

struct stmenu { // Estructura para inicializar tabla de menús
    int idmenu;
    char letra[2];
    char texto[64];
    int hijo;
    int accion;
};

// Prototipos:
bool IniciarMenu(sqlite3 *);
void MostrarMenu(sqlite3 *, int);
int LeerMenu(sqlite3 *, int&);

Usaremos tres funciones para manejar los menús:

IniciarMenu
Se encargará de crear la tabla "menu" y de insertar las filas necesarias. Si la tabla ya existe, no hará nada. Para obligar al programa a que genere la tabla de nuevo tendremos que borrarla manualmente desde la consola "sqlite3", con la sentencia "DROP TABLE menu;".
MostrarMenu
Evidentemente, esta función se encargará de mostrar el menú adecuado, dependiendo del nivel, que pasaremos como segundo parámetro.
LeerMenu
Leerá la respuesta del usuario, y devolverá el código de acción asociado a la elección, y si eso implica un cambio de nivel, modificará el valor del segundo parámetro. Este parámetro se pasa por referencia, a la entrada indica el nivel actual, y a la salida el nuevo nivel.

La implementación de estas funciones se hace en "menu.cpp":

/*
 * Aplicación de ejemplo de uso de SQLite en C++
 * EjemploSQLite
 * Salvador Pozo, Con Clase (www.conclase.net)
 * Marzo de 2012
 * Fichero: menus.cpp
 * fichero de implementación de menús
 */

#include <iostream>
#include "menus.h"

stmenu menu[] = {
    // Nivel, letra, texto, submenú, acción
    {1,"-","---MENU PRINCIPAL---",0,TITULO},
    {1, "1", "Maestros >", 2, ABRIRMENU},
    {1, "2", "Libros >", 3, ABRIRMENU},
    {1, "3", "Socios >", 4, ABRIRMENU},
    {1, "4", "Prestamos >", 5, ABRIRMENU},
    {1, "0", "Salir", 0, SALIR},
    {2,"-","---TABLAS MAESTRAS---",0,TITULO},
    {2, "1", "Editoriales >", 6, ABRIRMENU},
    {2, "2", "Autores >", 7, ABRIRMENU},
    {2, "3", "Temas >", 8, ABRIRMENU},
    {2, "0", "Salir <", 1, ABRIRMENU},
    {6,"-","---MENU EDITORIALES---",0,TITULO},
    {6, "1", "Nuevo", 0, NUEVAEDITORIAL},
    {6, "2", "Editar", 0, EDITAREDITORIAL},
    {6, "3", "Borrar", 0, BORRAREDITORIAL},
    {6, "4", "Consultar", 0, CONSULTAEDITORIAL},
    {6, "0", "Salir <", 2, ABRIRMENU},
    {3,"-","---MENU LIBROS---",0,TITULO},
    {3, "0", "Salir <", 1, ABRIRMENU},
    {4,"-","---MENU SOCIOS---",0,TITULO},
    {4, "0", "Salir <", 1, ABRIRMENU},
    {5,"-","---MENU PRESTAMOS---",0,TITULO},
    {5, "0", "Salir <", 1, ABRIRMENU},
    {7,"-","---MENU AUTORES---",0,TITULO},
    {7, "0", "Salir <", 2, ABRIRMENU},
    {8,"-","---MENU TEMAS---",0,TITULO},
    {8, "0", "Salir <", 2, ABRIRMENU}
};

using namespace std;

bool IniciarMenu(sqlite3 *db) {
    sqlite3_stmt *ppStmt;
    int rc;
    char consulta[100];

    if(SQLITE_OK != sqlite3_exec(db, "SELECT COUNT(*) FROM menu", 0, 0, 0)) {
        // if(SQLITE_OK != sqlite3_exec(db, "DROP TABLE menu;", 0, 0, 0)) return false;
        if(SQLITE_OK != sqlite3_exec(db, "CREATE TABLE menu(iditem INTEGER PRIMARY KEY,"
                                     "idmenu INTEGER,letra TEXT,texto TEXT,hijo INTEGER,accion INTEGER);", 0, 0, 0)) return false;
        if(SQLITE_OK != sqlite3_exec(db, "BEGIN;", 0, 0, 0)) return false;

        strcpy(consulta, "INSERT INTO menu(idmenu,letra,texto,hijo,accion) VALUES(@mid,@let,@txt,@hij,@acc);");
        rc = sqlite3_prepare_v2(db, consulta, -1, &ppStmt, NULL);
        if( rc!=SQLITE_OK ){
            cout << sqlite3_errmsg(db) << endl;
            return false;
        } else {
            for(int i = 0; i < sizeof(menu)/sizeof(stmenu); i++) {
                sqlite3_bind_int(ppStmt, sqlite3_bind_parameter_index(ppStmt, "@mid"), menu[i].idmenu);
                sqlite3_bind_text(ppStmt, sqlite3_bind_parameter_index(ppStmt, "@let"), menu[i].letra, -1, SQLITE_STATIC);
                sqlite3_bind_text(ppStmt, sqlite3_bind_parameter_index(ppStmt, "@txt"), menu[i].texto, -1, SQLITE_STATIC);
                sqlite3_bind_int(ppStmt, sqlite3_bind_parameter_index(ppStmt, "@hij"), menu[i].hijo);
                sqlite3_bind_int(ppStmt, sqlite3_bind_parameter_index(ppStmt, "@acc"), menu[i].accion);
                sqlite3_step(ppStmt);
                sqlite3_reset(ppStmt);
            }
            sqlite3_finalize(ppStmt);
        }
        if(SQLITE_OK != sqlite3_exec(db, "COMMIT;", 0, 0, 0)) return false;
    }
    return true;
}

void MostrarMenu(sqlite3 *db, int nivel) {
    sqlite3_stmt *ppStmt;
    int rc;
    char consulta[80];
    int nLineas;
    char titulo[64];

    // Contar items:
    sprintf(consulta, "SELECT COUNT(*) FROM menu WHERE idmenu=%d AND accion!=-1;", nivel);
    rc = sqlite3_prepare_v2(db, consulta, -1, &ppStmt, NULL);
    if( rc==SQLITE_OK ){
        if(SQLITE_ROW == sqlite3_step(ppStmt)) {
            nLineas = sqlite3_column_int(ppStmt, 0);
        }
        sqlite3_finalize(ppStmt);
    }

    // 24 líneas en blanco (Borrar pantalla):
    for(int i = 0; i < 24; i++) cout << endl;

    // Titulo:
    strcpy(titulo, "---MENU---"); // Titulo por defecto
    sprintf(consulta, "SELECT texto FROM menu WHERE idmenu=%d AND accion=-1;", nivel);
    rc = sqlite3_prepare_v2(db, consulta, -1, &ppStmt, NULL);
    if( rc==SQLITE_OK ){
        if(SQLITE_ROW == sqlite3_step(ppStmt)) {
            strcpy(titulo, (const char*)sqlite3_column_text(ppStmt, 0));
        }
        sqlite3_finalize(ppStmt);
    }

    cout << "\t\t" << titulo << "\n" << endl;

    strcpy(consulta, "SELECT letra,texto FROM menu WHERE idmenu=@mid AND accion!=-1;");
    rc = sqlite3_prepare_v2(db, consulta, -1, &ppStmt, NULL);
    if( rc!=SQLITE_OK ){
        cout << "Error: " << sqlite3_errmsg(db) << endl;
    } else {
        sqlite3_bind_int(ppStmt, sqlite3_bind_parameter_index(ppStmt, "@mid"), nivel);
        while(SQLITE_ROW == sqlite3_step(ppStmt)) {
            cout << sqlite3_column_int(ppStmt, 0) << ") " << sqlite3_column_text(ppStmt, 1) << endl;
        }
        sqlite3_finalize(ppStmt);
    }
    // Más líneas en blanco:
    for(int i = 0; i < (18-nLineas); i++) cout << endl;
    cout << "\tOpcion: ";
}

int LeerMenu(sqlite3 *db, int& nivel) {
    char resp[2];
    sqlite3_stmt *ppStmt;
    int rc;
    char consulta[64];
    int retval;

    cin >> resp;
    sprintf(consulta, "SELECT hijo,accion FROM menu WHERE letra='%s' AND idmenu=%d;", resp, nivel);
    rc = sqlite3_prepare_v2(db, consulta, -1, &ppStmt, NULL);
    if( rc!=SQLITE_OK ){
        cout << "Error: " << sqlite3_errmsg(db) << endl;
    } else {
        if(SQLITE_ROW == sqlite3_step(ppStmt)) {
            retval = sqlite3_column_int(ppStmt, 1);
            if(retval == ABRIRMENU) nivel = sqlite3_column_int(ppStmt, 0);
        }
        sqlite3_finalize(ppStmt);
    }
    return retval;
}

Por último, modificaremos el fichero "main.cpp" para integrar los menús:

/*
 * Aplicación de ejemplo de uso de SQLite en C++
 * EjemploSQLite
 * Salvador Pozo, Con Clase (www.conclase.net)
 * Marzo de 2012
 * Fichero: main.cpp
 * fichero principal
 * Incluir en el enlazador la librería libsqlite.a (sqlite)
 * Facilitar el acceso a la librería de enlace dinámico sqlite3.dll
 */

#include <iostream>
#include <sqlite/sqlite3.h>
#include "menus.h"

const int nTablas = 9;

bool VerificarTablas(sqlite3 *);

using namespace std;

int main()
{
    int rc;
    sqlite3 *db;
    int nivel=1;
    bool salir;

    // Abrir base de datos
    rc = sqlite3_open("biblioteca.db", &db);
    if(SQLITE_OK != rc) {
        cout << "Error: No se puede abrir la base de datos" << endl;
		return 1;
    }

    if(!VerificarTablas(db)) return -1;
    if(!IniciarMenu(db)) return -1;

    do {
        MostrarMenu(db, nivel);
        switch(LeerMenu(db, nivel)) {
            case ABRIRMENU:
               // Nada que hacer.
			   break;
            case NUEVAEDITORIAL:
            case EDITAREDITORIAL:
            case BORRAREDITORIAL:
            case CONSULTAEDITORIAL:
               cout << "No implementado" << endl;
               break;
            case SALIR:
               salir = true;
        }
    } while(!salir);

    // Cerrar base de datos
    sqlite3_close(db);
    return 0;
}
...
// El resto es igual que antes...

Las modificaciones en main se limitan a añadir una llamada a IniciarMenu, y un bucle en el que se muestra el menú, se lee la respuesta del usuario y se procesa el resultado, hasta que se elija la opción de salir.

Ahora ya podemos empezar a implementar las distintas opciones. Usaremos dos ficheros para cada submenú, uno con los prototipos de las funciones y otro con la implementación.

Procesar tabla de editoriales

Ahora que tenemos divididas las tareas, podemos empezar a codificarlas una a una. Empezaremos con las editoriales, diseñando las funciones para añadir, editar, borrar y consultar datos.

Como siempre, añadiremos dos ficheros al proyecto, uno con los prototipos de funciones y otro con la implementación.

Fichero de cabecera:

/*
 * Aplicación de ejemplo de uso de SQLite en C++
 * EjemploSQLite
 * Salvador Pozo, Con Clase (www.conclase.net)
 * Marzo de 2012
 * Fichero: editorial.h
 * fichero de cabecera para manupular datos de editoriales
 */

#ifndef __EDITORIAL_H__
#define __EDITORIAL_H__

#include <sqlite/sqlite3.h>

void NuevaEditorial(sqlite3 *);
int ListaEditoriales(sqlite3 *);
void EditarEditorial(sqlite3 *);
void BorrarEditorial(sqlite3 *);
void BuscarEditorial(sqlite3 *);
#endif

El fichero de implementación es algo más largo:

/*
 * Aplicación de ejemplo de uso de SQLite en C++
 * EjemploSQLite
 * Salvador Pozo, Con Clase (www.conclase.net)
 * Marzo de 2012
 * Fichero: editorial.cpp
 * fichero de implementación para manupular datos de editoriales
 */

#include <iostream>
#include <iomanip>
#include "editorial.h"

using namespace std;

void NuevaEditorial(sqlite3 *db) {
    char nombre[64];
    char direccion[128];
    char telefono[32];
    sqlite3_stmt *ppStmt;
    int rc;
    char consulta[1024];
    bool existe, ignorar=false;
    char nombre2[64];
    int clave;
    char resp[2];

    for(int i= 0; i < 24; i++) cout << endl;
    cout << "A continuacion se pedira el nombre, direccion y telefono de la editorial." << endl;

    cin.ignore();
    cout << "Nombre: ";
    cin.getline(nombre, 64);
    cout << "Direccion: ";
    cin.getline(direccion,128);
    cout << "Telefono: ";
    cin.getline(telefono,32);

    // Verificar si el nombre existe ya:
    sprintf(consulta, "SELECT claveeditorial,editorial FROM editorial WHERE editorial LIKE '%s';", nombre);
    rc = sqlite3_prepare_v2(db, consulta, -1, &ppStmt, NULL);
    existe=false;
    if( rc!=SQLITE_OK ){
        cout << "Error: " << sqlite3_errmsg(db) << endl;
    } else {
        if(SQLITE_ROW == sqlite3_step(ppStmt)) {
            existe = true;
            clave = sqlite3_column_int(ppStmt, 0);
            strncpy(nombre2, (const char*)sqlite3_column_text(ppStmt, 1), 64);
            nombre2[63]=0;
        }
        sqlite3_finalize(ppStmt);
    }

    if(!existe) {
        sprintf(consulta, "INSERT INTO editorial(editorial,direccion,telefono) VALUES('%s','%s','%s');",
                nombre, direccion, telefono);
    } else {
        cout << "Ya existe una editorial con el nombre " << nombre2 << " (s)obrescribir, insert(a)r o (i)gnorar: " << endl;
        cin >> resp;
        switch(resp[0]) {
            case 's':
                sprintf(consulta, "UPDATE editorial SET editorial='%s',direccion='%s',telefono='%s' WHERE claveeditorial=%d;",
                        nombre, direccion, telefono, clave);
                break;
            case 'a':
                sprintf(consulta, "INSERT INTO editorial(editorial,direccion,telefono) VALUES('%s','%s','%s');",
                        nombre, direccion, telefono);
                break;
            case 'i':
            default:
                ignorar=true;
        }
    }
    if(!ignorar) {
        if(SQLITE_OK != sqlite3_exec(db, consulta, 0, 0, 0)) {
            cout << "Error: " << sqlite3_errmsg(db) << endl;
        }
        else cout << "Editorial insertada" << endl;
    }
    cin.ignore();
}

int ListaEditoriales(sqlite3 *db) {
    sqlite3_stmt *ppStmt;
    int rc;
    char consulta[1024];
    int desplazamiento=0;
    char resp[10];
    bool salir=false;
    bool ultima;
    int fila=0;
    int i;

    // Mostrar una lista, teniendo en cuenta que puede haber más de las que caben en una pantalla.
    do {
        cout << "Elegir editorial" << endl << endl;
        sprintf(consulta, "SELECT claveeditorial,editorial FROM editorial ORDER BY nombre LIMIT 20 OFFSET %d;", desplazamiento);
        rc = sqlite3_prepare_v2(db, consulta, -1, &ppStmt, NULL);
        if( rc!=SQLITE_OK ){
            cout << "Error: " << sqlite3_errmsg(db) << endl;
        } else {
            i = 0;
            while(SQLITE_ROW == sqlite3_step(ppStmt)) {
                cout << sqlite3_column_int(ppStmt, 0) << ") " <<
                    sqlite3_column_text(ppStmt, 1) << endl;
                i++;
            }
            sqlite3_finalize(ppStmt);
        }
        ultima = (i < 20);
        while(i < 20) { cout << endl; i++; }
        cout << "\n" << "(n) editar, (s)ig pagina, (a)nt pagina, (x)salir" << endl;
        cin >> resp;
        switch(resp[0]) {
            case 's':
                if(!ultima) desplazamiento+=20;
                break;
            case 'a':
                if(desplazamiento > 0) desplazamiento-=20;
                break;
            case 'x':
                salir=true;
                break;
            default:
                if(isdigit(resp[0])) {
                    fila = atoi(resp);
                    salir=true;
                }
                break;
        }
    } while(!salir);

    return fila;
}

void EditarEditorial(sqlite3 *db) {
    sqlite3_stmt *ppStmt;
    int rc;
    char consulta[1024];
    char nombre[64];
    char direccion[128];
    char telefono[32];
    int i;
    int fila;
    bool salir=true;

    fila = ListaEditoriales(db);

    // Editar:
    for(i = 0; i < 22; i++) cout << endl;
    sprintf(consulta, "SELECT editorial,direccion,telefono FROM editorial WHERE claveeditorial='%d';", fila);
    rc = sqlite3_prepare_v2(db, consulta, -1, &ppStmt, NULL);
    if( rc!=SQLITE_OK ){
        cout << "Error: " << sqlite3_errmsg(db) << endl;
    } else {
        i = 0;
        if(SQLITE_ROW == sqlite3_step(ppStmt)) {
            cout << "Nombre:    " << sqlite3_column_text(ppStmt, 0) << endl;
            cout << "Direccion: " << sqlite3_column_text(ppStmt, 1) << endl;
            cout << "Telefono:  " << sqlite3_column_text(ppStmt, 2) << endl;
            cout << "Dejar en blanco los campos que no se quieren modifiar" << endl;
            salir=false;
        }
        sqlite3_finalize(ppStmt);
    }
    if(!salir){
        cin.ignore();
        cout << "Nombre: ";
        cin.getline(nombre, 64);
        cout << "Direccion: ";
        cin.getline(direccion,128);
        cout << "Telefono: ";
        cin.getline(telefono,32);
        if(strlen(nombre)>0) {
            sprintf(consulta, "UPDATE editorial SET editorial='%s' WHERE claveeditorial=%d;", nombre, fila);
            if(SQLITE_OK != sqlite3_exec(db, consulta, 0, 0, 0)) {
                cout << "Error: " << sqlite3_errmsg(db) << endl;
            }
        }
        if(strlen(direccion)>0) {
            sprintf(consulta, "UPDATE editorial SET direccion='%s' WHERE claveeditorial=%d;", direccion, fila);
            if(SQLITE_OK != sqlite3_exec(db, consulta, 0, 0, 0)) {
                cout << "Error: " << sqlite3_errmsg(db) << endl;
            }
        }
        if(strlen(telefono)>0) {
            sprintf(consulta, "UPDATE editorial SET telefono='%s' WHERE claveeditorial=%d;", telefono, fila);
            if(SQLITE_OK != sqlite3_exec(db, consulta, 0, 0, 0)) {
                cout << "Error: " << sqlite3_errmsg(db) << endl;
            }
        }
        cout << "Editorial modificada" << endl;
        cin.ignore();
    }
}

void BorrarEditorial(sqlite3 *db) {
    sqlite3_stmt *ppStmt;
    int rc;
    char consulta[1024];
    int fila;
    char resp[2];
    int i;
    bool salir=true;

    fila = ListaEditoriales(db);
    cout << "Borrar: " << fila << endl;

    for(i = 0; i < 22; i++) cout << endl;
    sprintf(consulta, "SELECT editorial,direccion,telefono FROM editorial WHERE claveeditorial='%d';", fila);
    rc = sqlite3_prepare_v2(db, consulta, -1, &ppStmt, NULL);
    if( rc!=SQLITE_OK ){
        cout << "Error: " << sqlite3_errmsg(db) << endl;
    } else {
        i = 0;
        if(SQLITE_ROW == sqlite3_step(ppStmt)) {
            cout << "Nombre:    " << sqlite3_column_text(ppStmt, 0) << endl;
            cout << "Direccion: " << sqlite3_column_text(ppStmt, 1) << endl;
            cout << "Telefono:  " << sqlite3_column_text(ppStmt, 2) << endl;
            salir=false;
        }
        sqlite3_finalize(ppStmt);
    }
    if(!salir){
        cin.ignore();
        cout << "Borrar este registro? (s/n)" << endl;
        cin >> resp;
        if(resp[0] == 's' || resp[0] == 'S') {
            sprintf(consulta, "DELETE FROM editorial WHERE claveeditorial=%d;", fila);
            if(SQLITE_OK != sqlite3_exec(db, consulta, 0, 0, 0)) {
                cout << "Error: " << sqlite3_errmsg(db) << endl;
            }
        }
    }
    cout << "Editorial borrada" << endl;
    cin.ignore();
}

void BuscarEditorial(sqlite3 *db) {
    sqlite3_stmt *ppStmt;
    int rc;
    char consulta[1024];
    char nombre[64];
    char direccion[128];
    char telefono[32];
    int i;

    for(i = 0; i < 22; i++) cout << endl;
    // Búsqueda de editoriales por nombre, direccion o telefono:
    cout << "Introducir cadenas de busqueda, _ para comodin de caracter, % para comodin de cadena" << endl;
    cout << "Dejar en blanco para ignorar el campo en la busqueda" << endl;
    cin.ignore();
    cout << "Nombre: ";
    cin.getline(nombre, 64);
    cout << "Direccion: ";
    cin.getline(direccion,128);
    cout << "Telefono: ";
    cin.getline(telefono,32);

    if(strlen(nombre) == 0) strcpy(nombre, "%");
    if(strlen(direccion) == 0) strcpy(direccion, "%");
    if(strlen(telefono) == 0) strcpy(telefono, "%");
    sprintf(consulta, "SELECT editorial,direccion,telefono FROM editorial "
            "WHERE editorial LIKE '%s' AND direccion LIKE '%s' AND telefono LIKE '%s';",
            nombre, direccion, telefono);
    rc = sqlite3_prepare_v2(db, consulta, -1, &ppStmt, NULL);
    if( rc!=SQLITE_OK ){
        cout << "Error: " << sqlite3_errmsg(db) << endl;
    } else {
        i = 0;
        while(SQLITE_ROW == sqlite3_step(ppStmt)) {
            cout.setf(ios::left);
            cout.width(64);
            cout << sqlite3_column_text(ppStmt, 0) << endl;
            cout << sqlite3_column_text(ppStmt, 1) << " ";
            cout.width(12);
            cout << sqlite3_column_text(ppStmt, 2) << endl;
            i +=2;
            if(!(i % 22)) {
                cout << "Pulsa return";
                cin.ignore();
                cin.get();
            }
        }
        sqlite3_finalize(ppStmt);
    }
    cin.ignore();
    cin.get();
}

Por último, modificamos el fichero "main.cpp", añadiendo un include para el nuevo fichero de cabecera, y modificando el bucle de procesamiento de menú:

/*
 * Aplicación de ejemplo de uso de SQLite en C++
 * EjemploSQLite
 * Salvador Pozo, Con Clase (www.conclase.net)
 * Marzo de 2012
 * Fichero: main.cpp
 * fichero principal
 * Incluir en el enlazador la librería libsqlite.a (sqlite)
 * Facilitar el acceso a la librería de enlace dinámico sqlite3.dll
 */

#include <iostream>
#include <sqlite/sqlite3.h>
#include "menus.h"
#include "editorial.h"

const int nTablas = 9;

bool VerificarTablas(sqlite3 *);

using namespace std;

int main()
{
    int rc;
    sqlite3 *db;
    int nivel=1;
    bool salir;

    // Abrir base de datos
    rc = sqlite3_open("biblioteca.db", &db);
    if(SQLITE_OK != rc) {
        cout << "Error: No se puede abrir la base de datos" << endl;
		return 1;
    }

    if(!VerificarTablas(db)) return -1;
    if(!IniciarMenu(db)) return -1;

    do {
        MostrarMenu(db, nivel);
        switch(LeerMenu(db, nivel)) {
            case ABRIRMENU:
               // Nada que hacer.
               break;
            case NUEVAEDITORIAL:
               NuevaEditorial(db);
               break;
            case EDITAREDITORIAL:
               EditarEditorial(db);
               break;
            case BORRAREDITORIAL:
               BorrarEditorial(db);
               break;
            case CONSULTAEDITORIAL:
               BuscarEditorial(db);
               break;
            case SALIR:
               salir = true;
        }
    } while(!salir);

    // Cerrar base de datos
    sqlite3_close(db);
    return 0;
}
...

Procesar tablas de autores y temas

Las funciones para el tratamiento de las tablas de autores y temas son similares, y se pueden adaptar de la de editoriales con muy pocos cambios.

Fichero 'autor.h':

/*
 * Aplicación de ejemplo de uso de SQLite en C++
 * EjemploSQLite
 * Salvador Pozo, Con Clase (www.conclase.net)
 * Marzo de 2012
 * Fichero: autor.h
 * fichero de cabecera para manupular datos de autores
 */

#ifndef __AUTOR_H__
#define __AUTOR_H__

#include <sqlite/sqlite3.h>

void NuevoAutor(sqlite3 *);
int ListaAutores(sqlite3 *);
void EditarAutor(sqlite3 *);
void BorrarAutor(sqlite3 *);
void BuscarAutor(sqlite3 *);
#endif

Fichero 'tema.h':

/*
 * Aplicación de ejemplo de uso de SQLite en C++
 * EjemploSQLite
 * Salvador Pozo, Con Clase (www.conclase.net)
 * Marzo de 2012
 * Fichero: tema.h
 * fichero de cabecera para manupular datos de temas
 */

#ifndef __TEMA_H__
#define __TEMA_H__

#include <sqlite/sqlite3.h>

void NuevoTema(sqlite3 *);
int ListaTemnas(sqlite3 *);
void EditarTema(sqlite3 *);
void BorrarTema(sqlite3 *);
void BuscarTema(sqlite3 *);
#endif

Fichero 'autor.cpp':

/*
 * Aplicación de ejemplo de uso de SQLite en C++
 * EjemploSQLite
 * Salvador Pozo, Con Clase (www.conclase.net)
 * Marzo de 2012
 * Fichero: autor.cpp
 * fichero de implementación para manupular datos de autores
 */

#include <iostream>
#include <iomanip>
#include "autor.h"

using namespace std;

void NuevoAutor(sqlite3 *db) {
    char nombre[64];
    sqlite3_stmt *ppStmt;
    int rc;
    char consulta[1024];
    bool existe, ignorar=false;
    char nombre2[64];
    int clave;
    char resp[2];

    for(int i= 0; i < 24; i++) cout << endl;
    cout << "A continuacion se pedira el nombre, del autor." << endl;

    cin.ignore();
    cout << "Nombre: ";
    cin.getline(nombre, 64);

    // Verificar si el nombre existe ya:
    sprintf(consulta, "SELECT claveautor,autor FROM autor WHERE autor LIKE '%s';", nombre);
    rc = sqlite3_prepare_v2(db, consulta, -1, &ppStmt, NULL);
    existe=false;
    if( rc!=SQLITE_OK ){
        cout << "Error: " << sqlite3_errmsg(db) << endl;
    } else {
        if(SQLITE_ROW == sqlite3_step(ppStmt)) {
            existe = true;
            clave = sqlite3_column_int(ppStmt, 0);
            strncpy(nombre2, (const char*)sqlite3_column_text(ppStmt, 1), 64);
            nombre2[63]=0;
        }
        sqlite3_finalize(ppStmt);
    }

    if(!existe) {
        sprintf(consulta, "INSERT INTO autor(autor) VALUES('%s');", nombre);
    } else {
        cout << "Ya existe un autor con el nombre " << nombre2 << " (s)obrescribir, insert(a)r o (i)gnorar: " << endl;
        cin >> resp;
        switch(resp[0]) {
            case 's':
                sprintf(consulta, "UPDATE autor SET autor='%s' WHERE claveautor=%d;", nombre,  clave);
                break;
            case 'a':
                sprintf(consulta, "INSERT INTO autor(autor) VALUES('%s');", nombre);
                break;
            case 'i':
            default:
                ignorar=true;
        }
    }
    if(!ignorar) {
        if(SQLITE_OK != sqlite3_exec(db, consulta, 0, 0, 0)) {
            cout << "Error: " << sqlite3_errmsg(db) << endl;
        }
        else cout << "Autor insertado" << endl;
    }
    cin.ignore();
}

int ListaAutores(sqlite3 *db) {
    sqlite3_stmt *ppStmt;
    int rc;
    char consulta[1024];
    int desplazamiento=0;
    char resp[10];
    bool salir=false;
    bool ultima;
    int fila=0;
    int i;

    // Mostrar una lista, teniendo en cuenta que puede haber más de las que caben en una pantalla.
    do {
        cout << "Elegir autor" << endl << endl;
        sprintf(consulta, "SELECT claveautor,autor FROM autor ORDER BY autor LIMIT 20 OFFSET %d;", desplazamiento);
        rc = sqlite3_prepare_v2(db, consulta, -1, &ppStmt, NULL);
        if( rc!=SQLITE_OK ){
            cout << "Error: " << sqlite3_errmsg(db) << endl;
        } else {
            i = 0;
            while(SQLITE_ROW == sqlite3_step(ppStmt)) {
                cout << sqlite3_column_int(ppStmt, 0) << ") " <<
                    sqlite3_column_text(ppStmt, 1) << endl;
                i++;
            }
            sqlite3_finalize(ppStmt);
        }
        ultima = (i < 20);
        while(i < 20) { cout << endl; i++; }
        cout << "\n" << "(n) editar, (s)ig pagina, (a)nt pagina, (x)salir" << endl;
        cin >> resp;
        switch(resp[0]) {
            case 's':
                if(!ultima) desplazamiento+=20;
                break;
            case 'a':
                if(desplazamiento > 0) desplazamiento-=20;
                break;
            case 'x':
                salir=true;
                break;
            default:
                if(isdigit(resp[0])) {
                    fila = atoi(resp);
                    salir=true;
                }
                break;
        }
    } while(!salir);

    return fila;
}

void EditarAutor(sqlite3 *db) {
    sqlite3_stmt *ppStmt;
    int rc;
    char consulta[1024];
    char nombre[64];
    int i;
    int fila;
    bool salir=true;

    fila = ListaAutores(db);

    // Editar:
    for(i = 0; i < 22; i++) cout << endl;
    sprintf(consulta, "SELECT autor FROM autor WHERE claveautor='%d';", fila);
    rc = sqlite3_prepare_v2(db, consulta, -1, &ppStmt, NULL);
    if( rc!=SQLITE_OK ){
        cout << "Error: " << sqlite3_errmsg(db) << endl;
    } else {
        i = 0;
        if(SQLITE_ROW == sqlite3_step(ppStmt)) {
            cout << "Nombre:    " << sqlite3_column_text(ppStmt, 0) << endl;
            cout << "Dejar en blanco los campos que no se quieren modifiar" << endl;
            salir=false;
        }
        sqlite3_finalize(ppStmt);
    }
    if(!salir){
        cin.ignore();
        cout << "Nombre: ";
        cin.getline(nombre, 64);
        if(strlen(nombre)>0) {
            sprintf(consulta, "UPDATE autor SET autor='%s' WHERE claveautor=%d;", nombre, fila);
            if(SQLITE_OK != sqlite3_exec(db, consulta, 0, 0, 0)) {
                cout << "Error: " << sqlite3_errmsg(db) << endl;
            }
        }
        cout << "Autor modificado" << endl;
        cin.ignore();
    }
}

void BorrarAutor(sqlite3 *db) {
    sqlite3_stmt *ppStmt;
    int rc;
    char consulta[1024];
    int fila;
    char resp[2];
    int i;
    bool salir=true;

    fila = ListaAutores(db);
    cout << "Borrar: " << fila << endl;

    for(i = 0; i < 22; i++) cout << endl;
    sprintf(consulta, "SELECT autor FROM autor WHERE claveautor='%d';", fila);
    rc = sqlite3_prepare_v2(db, consulta, -1, &ppStmt, NULL);
    if( rc!=SQLITE_OK ){
        cout << "Error: " << sqlite3_errmsg(db) << endl;
    } else {
        i = 0;
        if(SQLITE_ROW == sqlite3_step(ppStmt)) {
            cout << "Nombre:    " << sqlite3_column_text(ppStmt, 0) << endl;
            salir=false;
        }
        sqlite3_finalize(ppStmt);
    }
    if(!salir){
        cin.ignore();
        cout << "Borrar este registro? (s/n)" << endl;
        cin >> resp;
        if(resp[0] == 's' || resp[0] == 'S') {
            sprintf(consulta, "DELETE FROM autor WHERE claveautor=%d;", fila);
            if(SQLITE_OK != sqlite3_exec(db, consulta, 0, 0, 0)) {
                cout << "Error: " << sqlite3_errmsg(db) << endl;
            }
        }
    }
    cout << "Autor borrado" << endl;
    cin.ignore();
}

void BuscarAutor(sqlite3 *db) {
    sqlite3_stmt *ppStmt;
    int rc;
    char consulta[1024];
    char nombre[64];
    int i;

    for(i = 0; i < 22; i++) cout << endl;
    // Búsqueda de editoriales por nombre, direccion o telefono:
    cout << "Introducir cadenas de busqueda, _ para comodin de caracter, % para comodin de cadena" << endl;
    cout << "Dejar en blanco para ignorar el campo en la busqueda" << endl;
    cin.ignore();
    cout << "Nombre: ";
    cin.getline(nombre, 64);

    if(strlen(nombre) == 0) strcpy(nombre, "%");
    sprintf(consulta, "SELECT autor FROM autor WHERE autor LIKE '%s';", nombre);
    rc = sqlite3_prepare_v2(db, consulta, -1, &ppStmt, NULL);
    if( rc!=SQLITE_OK ){
        cout << "Error: " << sqlite3_errmsg(db) << endl;
    } else {
        i = 0;
        while(SQLITE_ROW == sqlite3_step(ppStmt)) {
            cout.setf(ios::left);
            cout.width(64);
            cout << sqlite3_column_text(ppStmt, 0) << endl;
            i++;
            if(!(i % 22)) {
                cout << "Pulsa return";
                cin.ignore();
                cin.get();
            }
        }
        sqlite3_finalize(ppStmt);
    }
    cin.ignore();
    cin.get();
}

Fichero 'tema.cpp':

/*
 * Aplicación de ejemplo de uso de SQLite en C++
 * EjemploSQLite
 * Salvador Pozo, Con Clase (www.conclase.net)
 * Marzo de 2012
 * Fichero: tema.cpp
 * fichero de implementación para manupular datos de temas
 */

#include <iostream>
#include <iomanip>
#include "tema.h"

using namespace std;

void NuevoTema(sqlite3 *db) {
    char nombre[64];
    sqlite3_stmt *ppStmt;
    int rc;
    char consulta[1024];
    bool existe, ignorar=false;
    char nombre2[64];
    int clave;
    char resp[2];

    for(int i= 0; i < 24; i++) cout << endl;
    cout << "A continuacion se pedira el nombre del tema." << endl;

    cin.ignore();
    cout << "Nombre: ";
    cin.getline(nombre, 64);

    // Verificar si el nombre existe ya:
    sprintf(consulta, "SELECT clavetema,tema FROM tema WHERE tema LIKE '%s';", nombre);
    rc = sqlite3_prepare_v2(db, consulta, -1, &ppStmt, NULL);
    existe=false;
    if( rc!=SQLITE_OK ){
        cout << "Error: " << sqlite3_errmsg(db) << endl;
    } else {
        if(SQLITE_ROW == sqlite3_step(ppStmt)) {
            existe = true;
            clave = sqlite3_column_int(ppStmt, 0);
            strncpy(nombre2, (const char*)sqlite3_column_text(ppStmt, 1), 64);
            nombre2[63]=0;
        }
        sqlite3_finalize(ppStmt);
    }

    if(!existe) {
        sprintf(consulta, "INSERT INTO tema(tema) VALUES('%s');", nombre);
    } else {
        cout << "Ya existe un tema con el nombre " << nombre2 << " (s)obrescribir, insert(a)r o (i)gnorar: " << endl;
        cin >> resp;
        switch(resp[0]) {
            case 's':
                sprintf(consulta, "UPDATE tema SET tema='%s' WHERE clavetema=%d;", nombre,  clave);
                break;
            case 'a':
                sprintf(consulta, "INSERT INTO tema(tema) VALUES('%s');", nombre);
                break;
            case 'i':
            default:
                ignorar=true;
        }
    }
    if(!ignorar) {
        if(SQLITE_OK != sqlite3_exec(db, consulta, 0, 0, 0)) {
            cout << "Error: " << sqlite3_errmsg(db) << endl;
        }
        else cout << "Tema insertado" << endl;
    }
    cin.ignore();
}

int ListaTemas(sqlite3 *db) {
    sqlite3_stmt *ppStmt;
    int rc;
    char consulta[1024];
    int desplazamiento=0;
    char resp[10];
    bool salir=false;
    bool ultima;
    int fila=0;
    int i;

    // Mostrar una lista, teniendo en cuenta que puede haber más de las que caben en una pantalla.
    do {
        cout << "Elegir tema" << endl << endl;
        sprintf(consulta, "SELECT clavetema,tema FROM tema ORDER BY tema LIMIT 20 OFFSET %d;", desplazamiento);
        rc = sqlite3_prepare_v2(db, consulta, -1, &ppStmt, NULL);
        if( rc!=SQLITE_OK ){
            cout << "Error: " << sqlite3_errmsg(db) << endl;
        } else {
            i = 0;
            while(SQLITE_ROW == sqlite3_step(ppStmt)) {
                cout << sqlite3_column_int(ppStmt, 0) << ") " <<
                    sqlite3_column_text(ppStmt, 1) << endl;
                i++;
            }
            sqlite3_finalize(ppStmt);
        }
        ultima = (i < 20);
        while(i < 20) { cout << endl; i++; }
        cout << "\n" << "(n) editar, (s)ig pagina, (a)nt pagina, (x)salir" << endl;
        cin >> resp;
        switch(resp[0]) {
            case 's':
                if(!ultima) desplazamiento+=20;
                break;
            case 'a':
                if(desplazamiento > 0) desplazamiento-=20;
                break;
            case 'x':
                salir=true;
                break;
            default:
                if(isdigit(resp[0])) {
                    fila = atoi(resp);
                    salir=true;
                }
                break;
        }
    } while(!salir);

    return fila;
}

void EditarTema(sqlite3 *db) {
    sqlite3_stmt *ppStmt;
    int rc;
    char consulta[1024];
    char nombre[64];
    int i;
    int fila;
    bool salir=true;

    fila = ListaTemas(db);

    // Editar:
    for(i = 0; i < 22; i++) cout << endl;
    sprintf(consulta, "SELECT tema FROM tema WHERE clavetema='%d';", fila);
    rc = sqlite3_prepare_v2(db, consulta, -1, &ppStmt, NULL);
    if( rc!=SQLITE_OK ){
        cout << "Error: " << sqlite3_errmsg(db) << endl;
    } else {
        i = 0;
        if(SQLITE_ROW == sqlite3_step(ppStmt)) {
            cout << "Nombre:    " << sqlite3_column_text(ppStmt, 0) << endl;
            cout << "Dejar en blanco los campos que no se quieren modifiar" << endl;
            salir=false;
        }
        sqlite3_finalize(ppStmt);
    }
    if(!salir){
        cin.ignore();
        cout << "Nombre: ";
        cin.getline(nombre, 64);
        if(strlen(nombre)>0) {
            sprintf(consulta, "UPDATE tema SET tema='%s' WHERE clavetema=%d;", nombre, fila);
            if(SQLITE_OK != sqlite3_exec(db, consulta, 0, 0, 0)) {
                cout << "Error: " << sqlite3_errmsg(db) << endl;
            }
        }
        cout << "Tema modificado" << endl;
        cin.ignore();
    }
}

void BorrarTema(sqlite3 *db) {
    sqlite3_stmt *ppStmt;
    int rc;
    char consulta[1024];
    int fila;
    char resp[2];
    int i;
    bool salir=true;

    fila = ListaTemas(db);
    cout << "Borrar: " << fila << endl;

    for(i = 0; i < 22; i++) cout << endl;
    sprintf(consulta, "SELECT tema FROM tema WHERE clavetema='%d';", fila);
    rc = sqlite3_prepare_v2(db, consulta, -1, &ppStmt, NULL);
    if( rc!=SQLITE_OK ){
        cout << "Error: " << sqlite3_errmsg(db) << endl;
    } else {
        i = 0;
        if(SQLITE_ROW == sqlite3_step(ppStmt)) {
            cout << "Nombre:    " << sqlite3_column_text(ppStmt, 0) << endl;
            salir=false;
        }
        sqlite3_finalize(ppStmt);
    }
    if(!salir){
        cin.ignore();
        cout << "Borrar este registro? (s/n)" << endl;
        cin >> resp;
        if(resp[0] == 's' || resp[0] == 'S') {
            sprintf(consulta, "DELETE FROM tema WHERE clavetema=%d;", fila);
            if(SQLITE_OK != sqlite3_exec(db, consulta, 0, 0, 0)) {
                cout << "Error: " << sqlite3_errmsg(db) << endl;
            }
        }
    }
    cout << "Tema borrado" << endl;
    cin.ignore();
}

void BuscarTema(sqlite3 *db) {
    sqlite3_stmt *ppStmt;
    int rc;
    char consulta[1024];
    char nombre[64];
    int i;

    for(i = 0; i < 22; i++) cout << endl;
    // Búsqueda de editoriales por nombre, direccion o telefono:
    cout << "Introducir cadenas de busqueda, _ para comodin de caracter, % para comodin de cadena" << endl;
    cout << "Dejar en blanco para ignorar el campo en la busqueda" << endl;
    cin.ignore();
    cout << "Nombre: ";
    cin.getline(nombre, 64);

    if(strlen(nombre) == 0) strcpy(nombre, "%");
    sprintf(consulta, "SELECT tema FROM tema WHERE tema LIKE '%s';", nombre);
    rc = sqlite3_prepare_v2(db, consulta, -1, &ppStmt, NULL);
    if( rc!=SQLITE_OK ){
        cout << "Error: " << sqlite3_errmsg(db) << endl;
    } else {
        i = 0;
        while(SQLITE_ROW == sqlite3_step(ppStmt)) {
            cout.setf(ios::left);
            cout.width(64);
            cout << sqlite3_column_text(ppStmt, 0) << endl;
            i++;
            if(!(i % 22)) {
                cout << "Pulsa return";
                cin.ignore();
                cin.get();
            }
        }
        sqlite3_finalize(ppStmt);
    }
    cin.ignore();
    cin.get();
}

En 'menus.h' añadimos la definición de las macros para las nuevas opciones de menú:

#define NUEVOAUTOR          6
#define EDITARAUTOR         7
#define BORRARAUTOR         8
#define CONSULTAAUTOR       9
#define NUEVOTEMA           10
#define EDITARTEMA          11
#define BORRARTEMA          12
#define CONSULTATEMA        13

En 'menus.cpp' añadimos los valores del array de menú para las nuevas opciones:

    {7,"-","---MENU AUTORES---",0,TITULO},
    {7, "1", "Nuevo", 0, NUEVOAUTOR},
    {7, "2", "Editar", 0, EDITARAUTOR},
    {7, "3", "Borrar", 0, BORRARAUTOR},
    {7, "4", "Consultar", 0, CONSULTAAUTOR},
    {7, "0", "Salir <", 2, ABRIRMENU},
    {8,"-","---MENU TEMAS---",0,TITULO},
    {8, "1", "Nuevo", 0, NUEVOTEMA},
    {8, "2", "Editar", 0, EDITARTEMA},
    {8, "3", "Borrar", 0, BORRARTEMA},
    {8, "4", "Consultar", 0, CONSULTATEMA},
    {8, "0", "Salir <", 2, ABRIRMENU}

Finalmente, modificamos el fichero 'main.cpp' para añadir los ficheros de cabecera y los 'case' para procesar las nuevas opciones:

/*
 * Aplicación de ejemplo de uso de SQLite en C++
 * EjemploSQLite
 * Salvador Pozo, Con Clase (www.conclase.net)
 * Marzo de 2012
 * Fichero: main.cpp
 * fichero principal
 * Incluir en el enlazador la librería libsqlite.a (sqlite)
 * Facilitar el acceso a la librería de enlace dinámico sqlite3.dll
 */

#include <iostream>
#include <sqlite/sqlite3.h>
#include "menus.h"
#include "editorial.h"
#include "autor.h"
#include "tema.h"

const int nTablas = 9;

bool VerificarTablas(sqlite3 *);

using namespace std;

int main()
{
    int rc;
    sqlite3 *db;
    int nivel=1;
    bool salir;

    // Abrir base de datos
    rc = sqlite3_open("biblioteca.db", &db);
    if(SQLITE_OK != rc) {
        cout << "Error: No se puede abrir la base de datos" << endl;
		return 1;
    }

    if(!VerificarTablas(db)) return -1;
    if(!IniciarMenu(db)) return -1;

    do {
        MostrarMenu(db, nivel);
        switch(LeerMenu(db, nivel)) {
            case ABRIRMENU:
               // Nada que hacer.
               break;
            case NUEVAEDITORIAL:
               NuevaEditorial(db);
               break;
            case EDITAREDITORIAL:
               EditarEditorial(db);
               break;
            case BORRAREDITORIAL:
               BorrarEditorial(db);
               break;
            case CONSULTAEDITORIAL:
               BuscarEditorial(db);
               break;
            case NUEVOAUTOR:
               NuevoAutor(db);
               break;
            case EDITARAUTOR:
               EditarAutor(db);
               break;
            case BORRARAUTOR:
               BorrarAutor(db);
               break;
            case CONSULTAAUTOR:
               BuscarAutor(db);
               break;
            case NUEVOTEMA:
               NuevoTema(db);
               break;
            case EDITARTEMA:
               EditarTema(db);
               break;
            case BORRARTEMA:
               BorrarTema(db);
               break;
            case CONSULTATEMA:
               BuscarTema(db);
               break;
            case SALIR:
               salir = true;
        }
    } while(!salir);

    // Cerrar base de datos
    sqlite3_close(db);
    return 0;
}
...

Procesar tabla de libros

Esta tabla tiene más trabajo que hacer, ya que la tabla de libros incluye referencias a autores, temas y editoriales. Además, deberemos manejar ejemplares.

Crear, modificar o borrar libros tiene muchas más implicaciones que hacerlo con las tablas que hemos visto hasta ahora. Lamentablemente, escribir un programa como este para consola, usando sólo funciones estántar y el API de SQLite, limita mucho la facilidad de edición en cuanto a la elección de valores válidos desde listas, etc.

En un programa más elaborado crearíamos tablas auxiliares para almacenar idiomas y formatos, de modo que podamos seleccionarlos desde una lista, y minimizar los errores y facilitar las búsquedas. En este ejemplo dejaremos que el usuario escriba los valores de esos campos como quiera.

Por ello usaremos más opciones de menú. Por ejemplo, en lugar de seleccionar los autores desde una lista, añadiremos una opción de menú para añadir o eliminar autores a un libro.

Haremos lo mismo con los temas. Hay que tener en cuenta que un libro puede tener varios autores y tratar sobre varios temas.

Como en los casos anteriores, añadiremos los nuevos identificadores de menú en "menus.h":

...
#define NUEVOLIBRO          14
#define EDITARLIBRO         15
#define ANEXARAUTOR         16
#define ELIMINARAUTOR       17
#define ANEXARTEMA          18
#define ELIMINARTEMA        19
#define BORRARLIBRO         20
#define CONSULTALIBRO       21
...

También añadiremos las nuevas entradas en la tabla de menús. Para que las tenga en cuenta, hay que eliminar la tabla de menús, lo haremos desde la consola de SQLite, con la orden "DROP TABLE menu;".

    {3, "-", "---MENU LIBROS---",0,TITULO},
    {3, "1", "Nuevo", 0, NUEVOLIBRO},
    {3, "2", "Editar", 0, EDITARLIBRO},
    {3, "3", "Anexar autor a libro", 0, ANEXARAUTOR},
    {3, "4", "Eliminar autor de libro", 0, ELIMINARAUTOR},
    {3, "5", "Anexar tema a libro", 0, ANEXARTEMA},
    {3, "6", "Eliminar tema de libro", 0, ELIMINARTEMA},
    {3, "7", "Borrar", 0, BORRARLIBRO},
    {3, "8", "Consultar", 0, CONSULTALIBRO},
    {3, "9", "Ejemplares >", 9, ABRIRMENU},
    {3, "0", "Salir <", 1, ABRIRMENU},
...
    {9, "-", "---MENU EJEMPLARES---",0,TITULO},
    {9, "0", "Salir <", 3, ABRIRMENU}

En el fichero "main.cpp" añadiremos las nuevas opciones al bucle de tratamiento de menú:

            case NUEVOLIBRO:
               NuevoLibro(db);
               break;
            case EDITARLIBRO:
               EditarLibro(db);
               break;
            case ANEXARAUTOR:
               AnexarAutor(db);
               break;
            case ANEXARTEMA:
               AnexarTema(db);
               break;
            case ELIMINARAUTOR:
               EliminarAutorLibro(db);
               break;
            case ELIMINARTEMA:
               EliminarTemaLibro(db);
               break;
            case BORRARLIBRO:
               BorrarLibro(db);
               break;
            case CONSULTALIBRO:
               BuscarLibro(db);
               break;

Evitar repeticiones de autores y temas

Cuando diseñamos las tablas para este problema, no creamos una clave para las tablas "escrito_por" y "trata_sobre". Esto hace que sea posible que existan varios registros con los mismos valores de clavelibro/claveautor y clavelibro/clavetema. Es decir, que se especifique el mismo autor o tema varias veces para el mismo libro.

Podríamos hacer que el código del programa evitase esto, verificando si la fila ya existe antes de insertarla, pero es más sencillo dejar la tarea al motor de base de datos, creando un índice único para cada tabla.

Para ello modificaremos las consultas SQL en la función 'VerificarTablas' de modo que se creen esas restricciones para las tablas "escrito_por" y "trata_sobre". De modo que para que se creen las tablas con las nuevas características, eliminaremos las tablas manualmente.

Además aprovecharemos para activar el soporte para claves foráneas, que habíamos olvidado hacer:

PRAGMA foreign_keys = ON;

La función queda así:

bool VerificarTablas(sqlite3 *db) {
    char consulta[36];
    char *tabla[] = {
        "editorial",
        "libro",
        "autor",
        "tema",
        "ejemplar",
        "socio",
        "prestamo",
        "trata_sobre",
        "escrito_por"
    };
    char *create[] = {
        "CREATE TABLE editorial("
          "claveeditorial INTEGER PRIMARY KEY,"
          "editorial TEXT,"
          "direccion TEXT,"
          "telefono TEXT);",
        "CREATE TABLE libro("
          "clavelibro INTEGER PRIMARY KEY,"
          "titulo TEXT,"
          "idioma TEXT,"
          "formato TEXT,"
          "claveeditorial INTEGER "
          "REFERENCES editorial(claveeditorial) "
          "ON DELETE SET NULL "
          "ON UPDATE CASCADE);",
        "CREATE TABLE autor("
          "claveautor INTEGER PRIMARY KEY,"
          "autor TEXT);",
        "CREATE TABLE tema("
          "clavetema INTEGER PRIMARY KEY,"
          "tema TEXT);",
        "CREATE TABLE ejemplar("
          "clavelibro INTEGER "
          "REFERENCES libro(clavelibro) "
          "ON DELETE CASCADE "
          "ON UPDATE CASCADE,"
          "numeroorden INTEGER NOT NULL,"
          "edicion INTEGER,"
          "ubicacion TEXT,"
          "categoria TEXT,"
          "PRIMARY KEY(clavelibro,numeroorden));",
        "CREATE TABLE socio("
          "clavesocio INTEGER PRIMARY KEY,"
          "socio TEXT,"
          "direccion TEXT,"
          "telefono TEXT,"
          "categoria TEXT);",
        "CREATE TABLE prestamo("
          "clavesocio INTEGER "
          "REFERENCES socio(clavesocio) "
          "ON DELETE SET NULL "
          "ON UPDATE CASCADE,"
          "clavelibro INTEGER "
          "REFERENCES ejemplar(clavelibro) "
          "ON DELETE SET NULL "
          "ON UPDATE CASCADE,"
          "numeroorden INTEGER,"
          "fecha_prestamo DATE NOT NULL,"
          "fecha_devolucion DATE DEFAULT NULL,"
          "notas TEXT);",
        "CREATE TABLE trata_sobre("
          "clavelibro INTEGER NOT NULL "
          "REFERENCES libro(clavelibro) "
          "ON DELETE CASCADE "
          "ON UPDATE CASCADE,"
          "clavetema INTEGER NOT NULL "
          "REFERENCES tema(clavetema) "
          "ON DELETE CASCADE "
          "ON UPDATE CASCADE,"
		  "UNIQUE(clavelibro,clavetema));",
        "CREATE TABLE escrito_por("
          "clavelibro INTEGER NOT NULL "
          "REFERENCES libro(clavelibro) "
          "ON DELETE CASCADE "
          "ON UPDATE CASCADE,"
          "claveautor INTEGER NOT NULL "
          "REFERENCES autor(claveautor) "
          "ON DELETE CASCADE "
          "ON UPDATE CASCADE,"
		  "UNIQUE(clavelibro,claveautor));"
    };

    // Activar soporte para claves foráneas
    if(SQLITE_OK != sqlite3_exec(db, "PRAGMA foreign_keys = ON;", 0, 0, 0)) {
        cout << "Imposible activar claves foraneas" << endl;
        return false;
    }

    for(int i = 0; i < nTablas; i++) {
        sprintf(consulta, "SELECT COUNT(*) FROM %s;", tabla[i]);
        if(SQLITE_OK != sqlite3_exec(db, consulta, 0, 0, 0)) {
            cout << "La tabla " << tabla[i] << " no existe." << endl;
            if(SQLITE_OK != sqlite3_exec(db, create[i], 0, 0, 0)) {
               cout << "Error al crear la tabla " << tabla[i] << endl;
               return false;
            }
        }
    }
    return true;
}

Tratamiento de libros

Por último, añadimos los ficheros "libro.h" y "libro.cpp" al proyecto. En este caso necesitamos algunas funciones más, y el código se complica un poco:

Fichero "libro.h":

/*
 * Aplicación de ejemplo de uso de SQLite en C++
 * EjemploSQLite
 * Salvador Pozo, Con Clase (www.conclase.net)
 * Abril de 2012
 * Fichero: libro.h
 * fichero de cabecera para manupular datos de libros
 */

#ifndef __LIBRO_H__
#define __LIBRO_H__

#include <sqlite/sqlite3.h>

void NuevoLibro(sqlite3 *);
int ListaLibros(sqlite3 *);
void EditarLibro(sqlite3 *);
void BorrarLibro(sqlite3 *);
void BuscarLibro(sqlite3 *);
void AnexarAutor(sqlite3 *);
void AnexarTema(sqlite3 *);
int ListaAutoresLibro(sqlite3 *, int clavelibro);
void EliminarAutorLibro(sqlite3 *);
int ListaTemasLibro(sqlite3 *, int clavelibro);
void EliminarTemaLibro(sqlite3 *);
#endif

Fichero "libro.cpp":

/*
 * Aplicación de ejemplo de uso de SQLite en C++
 * EjemploSQLite
 * Salvador Pozo, Con Clase (www.conclase.net)
 * Abril de 2012
 * Fichero: libro.cpp
 * fichero de implementación para manipular datos de libros
 */

#include <iostream>
#include <iomanip>
#include "libro.h"
#include "editorial.h"
#include "autor.h"
#include "tema.h"

using namespace std;

void NuevoLibro(sqlite3 *db) {
    char titulo[64];
    char idioma[64];
    char formato[32];
    int editorial;
    sqlite3_stmt *ppStmt;
    int rc;
    char consulta[1024];
    bool existe, ignorar=false;
    char titulo2[64];
    int clave;
    char resp[2];

    for(int i= 0; i < 24; i++) cout << endl;
    cout << "A continuacion se pedira el titulo, idioma, formato y editorial del libro." << endl;

    cin.ignore();
    cout << "Titulo: ";
    cin.getline(titulo, 64);
    cout << "Idioma: ";
    cin.getline(idioma,64);
    cout << "Formato: ";
    cin.getline(formato,32);
    // Seleccionar editorial:
    editorial = ListaEditoriales(db);

    // Verificar si el nombre existe ya:
    sprintf(consulta, "SELECT clavelibro,titulo FROM libro WHERE titulo LIKE '%s';", titulo);
    rc = sqlite3_prepare_v2(db, consulta, -1, &ppStmt, NULL);
    existe=false;
    if( rc!=SQLITE_OK ){
        cout << "Error: " << sqlite3_errmsg(db) << endl;
    } else {
        if(SQLITE_ROW == sqlite3_step(ppStmt)) {
            existe = true;
            clave = sqlite3_column_int(ppStmt, 0);
            strncpy(titulo2, (const char*)sqlite3_column_text(ppStmt, 1), 64);
            titulo2[63]=0;
        }
        sqlite3_finalize(ppStmt);
    }

    if(!existe) {
        sprintf(consulta, "INSERT INTO libro(titulo,idioma,formato,claveeditorial) VALUES('%s','%s','%s',%d);",
                titulo, idioma, formato, editorial);
    } else {
        cout << "Ya existe un libro con el titulo " << titulo2 << " (s)obrescribir, insert(a)r o (i)gnorar: " << endl;
        cin >> resp;
        switch(resp[0]) {
            case 's':
                sprintf(consulta, "UPDATE libro SET titulo='%s',idioma='%s',formato='%s',claveeditorial=%d WHERE clavelibro=%d;",
                        titulo, idioma, formato, editorial, clave);
                break;
            case 'a':
                sprintf(consulta, "INSERT INTO libro(titulo,idioma,formato,claveeditorial) VALUES('%s','%s','%s','%d);",
                        titulo, idioma, formato, editorial);
                break;
            case 'i':
            default:
                ignorar=true;
        }
    }
    if(!ignorar) {
        if(SQLITE_OK != sqlite3_exec(db, consulta, 0, 0, 0)) {
            cout << "Error: " << sqlite3_errmsg(db) << endl;
        }
        else cout << "Libro insertado" << endl;
    }
    cin.ignore();
}

int ListaLibros(sqlite3 *db) {
    sqlite3_stmt *ppStmt;
    int rc;
    char consulta[1024];
    int desplazamiento=0;
    char resp[10];
    bool salir=false;
    bool ultima;
    int fila=0;
    int i;

    // Mostrar una lista, teniendo en cuenta que puede haber más de las que caben en una pantalla.
    do {
        cout << "Elegir libro" << endl << endl;
        sprintf(consulta, "SELECT clavelibro,titulo FROM libro ORDER BY titulo LIMIT 20 OFFSET %d;", desplazamiento);
        rc = sqlite3_prepare_v2(db, consulta, -1, &ppStmt, NULL);
        if( rc!=SQLITE_OK ){
            cout << "Error: " << sqlite3_errmsg(db) << endl;
        } else {
            i = 0;
            while(SQLITE_ROW == sqlite3_step(ppStmt)) {
                cout << sqlite3_column_int(ppStmt, 0) << ") " <<
                    sqlite3_column_text(ppStmt, 1) << endl;
                i++;
            }
            sqlite3_finalize(ppStmt);
        }
        ultima = (i < 20);
        while(i < 20) { cout << endl; i++; }
        cout << "\n" << "(n) editar, (s)ig pagina, (a)nt pagina, (x)salir" << endl;
        cin >> resp;
        switch(resp[0]) {
            case 's':
                if(!ultima) desplazamiento+=20;
                break;
            case 'a':
                if(desplazamiento > 0) desplazamiento-=20;
                break;
            case 'x':
                salir=true;
                break;
            default:
                if(isdigit(resp[0])) {
                    fila = atoi(resp);
                    salir=true;
                }
                break;
        }
    } while(!salir);

    return fila;
}

void EditarLibro(sqlite3 *db) {
    sqlite3_stmt *ppStmt;
    int rc;
    char consulta[1024];
    char titulo[64];
    char idioma[64];
    char formato[32];
    int editorial;
    int i;
    int fila;
    bool salir=true;

    fila = ListaLibros(db);

    // Editar:
    for(i = 0; i < 22; i++) cout << endl;
    sprintf(consulta, "SELECT titulo,idioma,formato,claveeditorial,editorial FROM libro NATURAL LEFT JOIN editorial WHERE clavelibro='%d';", fila);
    rc = sqlite3_prepare_v2(db, consulta, -1, &ppStmt, NULL);
    if( rc!=SQLITE_OK ){
        cout << "Error: " << sqlite3_errmsg(db) << endl;
    } else {
        i = 0;
        if(SQLITE_ROW == sqlite3_step(ppStmt)) {
            cout << "Titulo:    " << sqlite3_column_text(ppStmt, 0) << endl;
            cout << "Idioma:    " << sqlite3_column_text(ppStmt, 1) << endl;
            cout << "Formato:   " << sqlite3_column_text(ppStmt, 2) << endl;
            cout << "Editorial: " << sqlite3_column_text(ppStmt, 5) << endl;
            cout << "Dejar en blanco los campos que no se quieren modifiar" << endl;
            salir=false;
        }
        sqlite3_finalize(ppStmt);
    }
    if(!salir){
        cin.ignore();
        cout << "Titulo: ";
        cin.getline(titulo, 64);
        cout << "Idioma: ";
        cin.getline(idioma,64);
        cout << "Formato: ";
        cin.getline(formato,32);
        editorial = ListaEditoriales(db);
        if(strlen(titulo)>0) {
            sprintf(consulta, "UPDATE libro SET titulo='%s' WHERE clavelibro=%d;", titulo, fila);
            if(SQLITE_OK != sqlite3_exec(db, consulta, 0, 0, 0)) {
                cout << "Error: " << sqlite3_errmsg(db) << endl;
            }
        }
        if(strlen(idioma)>0) {
            sprintf(consulta, "UPDATE libro SET idioma='%s' WHERE clavelibro=%d;", idioma, fila);
            if(SQLITE_OK != sqlite3_exec(db, consulta, 0, 0, 0)) {
                cout << "Error: " << sqlite3_errmsg(db) << endl;
            }
        }
        if(strlen(formato)>0) {
            sprintf(consulta, "UPDATE libro SET formato='%s' WHERE clavelibro=%d;", formato, fila);
            if(SQLITE_OK != sqlite3_exec(db, consulta, 0, 0, 0)) {
                cout << "Error: " << sqlite3_errmsg(db) << endl;
            }
        }
        if(editorial) {
            sprintf(consulta, "UPDATE libro SET claveeditorial=%d WHERE clavelibro=%d;", editorial, fila);
            if(SQLITE_OK != sqlite3_exec(db, consulta, 0, 0, 0)) {
                cout << "Error: " << sqlite3_errmsg(db) << endl;
            }
        }
        cout << "Libro modificado" << endl;
        cin.ignore();
    }
}

void BorrarLibro(sqlite3 *db) {
    sqlite3_stmt *ppStmt;
    int rc;
    char consulta[1024];
    int fila;
    char resp[2];
    int i;
    bool salir=true;

    fila = ListaLibros(db);
    cout << "Borrar: " << fila << endl;

    for(i = 0; i < 22; i++) cout << endl;
    sprintf(consulta, "SELECT titulo,idioma,formato FROM editorial WHERE clavelibro='%d';", fila);
    rc = sqlite3_prepare_v2(db, consulta, -1, &ppStmt, NULL);
    if( rc!=SQLITE_OK ){
        cout << "Error: " << sqlite3_errmsg(db) << endl;
    } else {
        i = 0;
        if(SQLITE_ROW == sqlite3_step(ppStmt)) {
            cout << "Titulo:    " << sqlite3_column_text(ppStmt, 0) << endl;
            cout << "Idioma:    " << sqlite3_column_text(ppStmt, 1) << endl;
            cout << "Formato:   " << sqlite3_column_text(ppStmt, 2) << endl;
            salir=false;
        }
        sqlite3_finalize(ppStmt);
    }
    if(!salir){
        cin.ignore();
        cout << "Borrar este registro? (s/n)" << endl;
        cin >> resp;
        if(resp[0] == 's' || resp[0] == 'S') {
            sprintf(consulta, "DELETE FROM libro WHERE clavelibro=%d;", fila);
            if(SQLITE_OK != sqlite3_exec(db, consulta, 0, 0, 0)) {
                cout << "Error: " << sqlite3_errmsg(db) << endl;
            }
        }
    }
    cout << "Libro borrado" << endl;
    cin.ignore();
}

void BuscarLibro(sqlite3 *db) {
    sqlite3_stmt *ppStmt;
    int rc;
    char consulta[1024];
    char filtro[256];
    char titulo[64];
    char idioma[64];
    char formato[32];
    char editorial[32];
    char autor[64];
    char tema[32];
    int i, colautor=5, coltema=5;

    for(i = 0; i < 22; i++) cout << endl;
    // Búsqueda de editoriales por nombre, direccion o telefono:
    cout << "Introducir cadenas de busqueda, _ para comodin de caracter, % para comodin de cadena" << endl;
    cout << "Dejar en blanco para ignorar el campo en la busqueda" << endl;
    cin.ignore();
    cout << "Titulo: ";
    cin.getline(titulo, 64);
    cout << "Idioma: ";
    cin.getline(idioma,64);
    cout << "Formato: ";
    cin.getline(formato,32);
    cout << "Editorial: ";
    cin.getline(editorial,32);
    cout << "Autor: ";
    cin.getline(autor,64);
    cout << "Tema: ";
    cin.getline(tema,32);

    if(strlen(titulo) == 0) strcpy(titulo, "%");
    if(strlen(idioma) == 0) strcpy(idioma, "%");
    if(strlen(formato) == 0) strcpy(formato, "%");
    if(strlen(editorial) == 0) strcpy(editorial, "%");
    strcpy(consulta, "SELECT titulo,idioma,formato,editorial");
    if(strlen(autor) > 0) { coltema++; strcat(consulta, ",autor"); }
    if(strlen(tema) > 0) strcat(consulta, ",tema");
    strcat(consulta, " FROM libro NATURAL LEFT JOIN editorial ");
    if(strlen(autor) > 0) strcat(consulta, "NATURAL JOIN escrito_por NATURAL JOIN autor ");
    if(strlen(tema)> 0) strcat(consulta, "NATURAL JOIN trata_sobre NATURAL JOIN tema ");
    sprintf(filtro, "WHERE titulo LIKE '%s' AND idioma LIKE '%s' AND formato LIKE '%s' "
            "AND editorial LIKE '%s'",
            titulo, idioma, formato, editorial);
    strcat(consulta, filtro);
    if(strlen(autor) > 0) {
        sprintf(filtro, " AND autor LIKE '%s'", autor);
        strcat(consulta, filtro);
    }
    if(strlen(tema) > 0) {
        sprintf(filtro, " AND tema LIKE '%s'", tema);
        strcat(consulta, filtro);
    }
    strcat(consulta, ";");

    rc = sqlite3_prepare_v2(db, consulta, -1, &ppStmt, NULL);
    if( rc!=SQLITE_OK ){
        cout << "Error: " << sqlite3_errmsg(db) << "\n" << consulta << endl;
    } else {
        i = 0;
        while(SQLITE_ROW == sqlite3_step(ppStmt)) {
            cout.setf(ios::left);
            cout.width(64);
            cout << sqlite3_column_text(ppStmt, 0) << endl;
            cout << sqlite3_column_text(ppStmt, 1) << " ";
            cout.width(32);
            cout << sqlite3_column_text(ppStmt, 2) << endl;
            cout.width(32);
            cout << sqlite3_column_text(ppStmt, 4) << endl;
            if(strlen(autor)>0) {
                cout.width(64);
                cout << sqlite3_column_text(ppStmt, colautor) << endl;
                i++;
            }
            if(strlen(tema)>0) {
                cout.width(32);
                cout << sqlite3_column_text(ppStmt, coltema) << endl;
                i++;
            }
            i +=3;
            if(i >= 21) {
                cout << "Pulsa return";
                cin.ignore();
                cin.get();
            }
        }
        sqlite3_finalize(ppStmt);
    }
    cin.ignore();
    cin.get();
}

void AnexarAutor(sqlite3 *db) {
    char consulta[1024];
    int clavelibro, claveautor;
    int i;

    for(i = 0; i < 22; i++) cout << endl;
    // Búsqueda de editoriales por nombre, direccion o telefono:
    cout << "Seleccionar un libro y un autor de las listas siguientes:" << endl;
    cin.ignore();
    clavelibro = ListaLibros(db);
    claveautor = ListaAutores(db);

    if(clavelibro && claveautor) {
        sprintf(consulta, "INSERT INTO escrito_por(clavelibro,claveautor) VALUES(%d,%d);", clavelibro, claveautor);
        if(SQLITE_OK != sqlite3_exec(db, consulta, 0, 0, 0)) {
            cout << "Error: " << sqlite3_errmsg(db) << endl;
        }
        else cout << "Autor anexado a libro" << endl;
    }
}

void AnexarTema(sqlite3 *db) {
    char consulta[1024];
    int clavelibro, clavetema;
    int i;

    for(i = 0; i < 22; i++) cout << endl;
    // Búsqueda de editoriales por nombre, direccion o telefono:
    cout << "Seleccionar un libro y un tema de las listas siguientes:" << endl;
    cin.ignore();
    clavelibro = ListaLibros(db);
    clavetema = ListaTemas(db);

    if(clavelibro && clavetema) {
        sprintf(consulta, "INSERT INTO trata_sobre(clavelibro,clavetema) VALUES(%d,%d);", clavelibro, clavetema);
        if(SQLITE_OK != sqlite3_exec(db, consulta, 0, 0, 0)) {
            cout << "Error: " << sqlite3_errmsg(db) << endl;
        }
        else cout << "Tema anexado a libro" << endl;
    }
}

int ListaAutoresLibro(sqlite3 *db, int clavelibro) {
    sqlite3_stmt *ppStmt;
    int rc;
    char consulta[1024];
    int desplazamiento=0;
    char resp[10];
    bool salir=false;
    bool ultima;
    int fila=0;
    int i;

    // Mostrar una lista, teniendo en cuenta que puede haber más de las que caben en una pantalla.
    do {
        cout << "Elegir autor" << endl << endl;
        sprintf(consulta, "SELECT autor.claveautor,autor FROM escrito_por NATURAL JOIN autor "
                "WHERE clavelibro=%d ORDER BY autor LIMIT 20 OFFSET %d;", clavelibro, desplazamiento);
        rc = sqlite3_prepare_v2(db, consulta, -1, &ppStmt, NULL);
        if( rc!=SQLITE_OK ){
            cout << "Error: " << sqlite3_errmsg(db) << endl;
        } else {
            i = 0;
            while(SQLITE_ROW == sqlite3_step(ppStmt)) {
                cout << sqlite3_column_int(ppStmt, 0) << ") " <<
                    sqlite3_column_text(ppStmt, 1) << endl;
                i++;
            }
            sqlite3_finalize(ppStmt);
        }
        ultima = (i < 20);
        while(i < 20) { cout << endl; i++; }
        cout << "\n" << "(n) editar, (s)ig pagina, (a)nt pagina, (x)salir" << endl;
        cin >> resp;
        switch(resp[0]) {
            case 's':
                if(!ultima) desplazamiento+=20;
                break;
            case 'a':
                if(desplazamiento > 0) desplazamiento-=20;
                break;
            case 'x':
                salir=true;
                break;
            default:
                if(isdigit(resp[0])) {
                    fila = atoi(resp);
                    salir=true;
                }
                break;
        }
    } while(!salir);

    return fila;
}

void EliminarAutorLibro(sqlite3 *db) {
    char consulta[1024];
    int clavelibro, claveautor;
    int i;

    for(i = 0; i < 22; i++) cout << endl;
    // Búsqueda de editoriales por nombre, direccion o telefono:
    cout << "Seleccionar un libro:" << endl;
    cin.ignore();
    clavelibro = ListaLibros(db);
    if(clavelibro) {
        claveautor = ListaAutoresLibro(db, clavelibro);
        if(claveautor) {
            sprintf(consulta, "DELETE FROM escrito_por WHERE clavelibro=%d AND claveautor=%d;", clavelibro, claveautor);
            if(SQLITE_OK != sqlite3_exec(db, consulta, 0, 0, 0)) {
                cout << "Error: " << sqlite3_errmsg(db) << endl;
            }
            else cout << "Autor eliminado de libro" << endl;
        }
    }
}

int ListaTemasLibro(sqlite3 *db, int clavelibro) {
    sqlite3_stmt *ppStmt;
    int rc;
    char consulta[1024];
    int desplazamiento=0;
    char resp[10];
    bool salir=false;
    bool ultima;
    int fila=0;
    int i;

    // Mostrar una lista, teniendo en cuenta que puede haber más de las que caben en una pantalla.
    do {
        cout << "Elegir tema" << endl << endl;
        sprintf(consulta, "SELECT tema.clavetema,tema FROM trata_sobre NATURAL JOIN tema "
                "WHERE clavelibro=%d ORDER BY tema LIMIT 20 OFFSET %d;", clavelibro, desplazamiento);
        rc = sqlite3_prepare_v2(db, consulta, -1, &ppStmt, NULL);
        if( rc!=SQLITE_OK ){
            cout << "Error: " << sqlite3_errmsg(db) << endl;
        } else {
            i = 0;
            while(SQLITE_ROW == sqlite3_step(ppStmt)) {
                cout << sqlite3_column_int(ppStmt, 0) << ") " <<
                    sqlite3_column_text(ppStmt, 1) << endl;
                i++;
            }
            sqlite3_finalize(ppStmt);
        }
        ultima = (i < 20);
        while(i < 20) { cout << endl; i++; }
        cout << "\n" << "(n) editar, (s)ig pagina, (a)nt pagina, (x)salir" << endl;
        cin >> resp;
        switch(resp[0]) {
            case 's':
                if(!ultima) desplazamiento+=20;
                break;
            case 'a':
                if(desplazamiento > 0) desplazamiento-=20;
                break;
            case 'x':
                salir=true;
                break;
            default:
                if(isdigit(resp[0])) {
                    fila = atoi(resp);
                    salir=true;
                }
                break;
        }
    } while(!salir);

    return fila;
}

void EliminarTemaLibro(sqlite3 *db) {
    char consulta[1024];
    int clavelibro, clavetema;
    int i;

    for(i = 0; i < 22; i++) cout << endl;
    // Búsqueda de editoriales por nombre, direccion o telefono:
    cout << "Seleccionar un libro:" << endl;
    cin.ignore();
    clavelibro = ListaLibros(db);
    if(clavelibro) {
        clavetema = ListaTemasLibro(db, clavelibro);
        if(clavetema) {
            sprintf(consulta, "DELETE FROM trata_sobre WHERE clavelibro=%d AND clavetema=%d;", clavelibro, clavetema);
            if(SQLITE_OK != sqlite3_exec(db, consulta, 0, 0, 0)) {
                cout << "Error: " << sqlite3_errmsg(db) << endl;
            }
            else cout << "Tema eliminado de libro" << endl;
        }
    }
}

Procesar tabla de ejemplares

A veces encontramos errores de diseño en la fase de codificación. Este es uno de esos casos, al intentar eliminar cualquier fila de la tabla de ejemplares obtenemos un error, o más bien, una excepción:

sqlite> DELETE FROM ejemplar;
Error: foreign key mismatch
sqlite>

Esto se debe a un error de diseño de las tablas de la base de datos. Concretamente, en la tabla de préstamos usamos un campo clavelibro que es una clave foránea de la tabla ejemplar:

 ... clavelibro INTEGER REFERENCES ejemplar(clavelibro) ON DELETE ...

Sería más lógico usar como clave foránea una columna de la tabla ejemplar que no fuese a su vez una clave foránea de la tabla libro. Esto nos obliga a crear una nueva columna en la tabla ejemplar, claveejemplar que será además su clave primaria, y que usaremos para referenciar el ejemplar desde la tabla de prestamo.

Al mismo tiempo, crearemos una restricción de unicidad para la combinación clavelibro/numeroorden, para que no puedan existir dos ejemplares iguales.

Las tablas ejemplar y prestamo quedan así:

CREATE TABLE ejemplar(claveejemplar INTEGER PRIMARY KEY,
    clavelibro INTEGER NOT NULL
    REFERENCES libro(clavelibro)
    ON DELETE CASCADE ON UPDATE CASCADE,
    numeroorden INTEGER NOT NULL,
    edicion INTEGER,
    ubicacion TEXT,
    UNIQUE(numeroorden,clavelibro));
CREATE TABLE prestamo(clavesocio INTEGER REFERENCES socio(clavesocio)
    ON DELETE SET NULL ON UPDATE CASCADE,
    claveejemplar INTEGER REFERENCES ejemplar(claveejemplar)
    ON DELETE SET NULL ON UPDATE CASCADE,
    fecha_prestamo DATE NOT NULL,
    fecha_devolucion DATE DEFAULT NULL,
    notas TEXT);

En cuanto a las modificaciones, hacemos como en los casos anteriores. Primero añadimos los identificadores de menú necesarios en "menus.h":

#define NUEVOEJEMPLAR       22
#define EDITAREJEMPLAR      23
#define BORRAREJEMPLAR      24
#define CONSULTAEJEMPLAR    25

También añadimos las nuevas entradas en la tabla de menús, en menus.cpp:

    {9, "-", "---MENU EJEMPLARES---",0,TITULO},
    {9, "1", "Nuevo", 0, NUEVOEJEMPLAR},
    {9, "2", "Editar", 0, EDITAREJEMPLAR},
    {9, "3", "Borrar", 0, BORRAREJEMPLAR},
    {9, "4", "Consultar", 0, CONSULTAEJEMPLAR},
    {9, "0", "Salir <", 3, ABRIRMENU}

En main.cpp añadimos el include del nuevo fichero de cabecera (ejemplar.h), y los tratamientos de las nuevas opciones de menú dentro del switch:

#include "ejemplar.h"
...
            case NUEVOEJEMPLAR:
               NuevoEjemplar(db);
               break;
            case EDITAREJEMPLAR:
               EditarEjemplar(db);
               break;
            case BORRAREJEMPLAR:
               BorrarEjemplar(db);
               break;
            case CONSULTAEJEMPLAR:
               BuscarEjemplar(db);
               break;

Añadimos dos nuevos ficheros al proyecto: ejemplar.h, con las declaraciones de prototipos, y ejemplar.cpp con la implementación de las funciones:

/*
 * Aplicación de ejemplo de uso de SQLite en C++
 * EjemploSQLite
 * Salvador Pozo, Con Clase (www.conclase.net)
 * Abril de 2012
 * Fichero: ejemplar.h
 * fichero de cabecera para manupular datos de ejemplares de libros
 */

#ifndef __EJEMPLAR_H__
#define __EJEMPLAR_H__

#include <sqlite/sqlite3.h>

void NuevoEjemplar(sqlite3 *);
int ListaEjemplares(sqlite3 *);
void EditarEjemplar(sqlite3 *);
void BorrarEjemplar(sqlite3 *);
void BuscarEjemplar(sqlite3 *);
#endif
/*
 * Aplicación de ejemplo de uso de SQLite en C++
 * EjemploSQLite
 * Salvador Pozo, Con Clase (www.conclase.net)
 * Abril de 2012
 * Fichero: ejemplar.cpp
 * fichero de implementación para manipular datos de ejemplares de libros
 */

#include <iostream>
#include <iomanip>
#include "ejemplar.h"
#include "libro.h"

using namespace std;

void NuevoEjemplar(sqlite3 *db) {
    int clavelibro;
    int numeroorden;
    int edicion;
    char ubicacion[32];
    char categoria[2]; // "A".."F"
    sqlite3_stmt *ppStmt;
    int rc;
    char consulta[1024];
    bool existe, ignorar=false;
    char titulo[64];
    char numero[15];
    char resp[2];

    for(int i= 0; i < 24; i++) cout << endl;
    cout << "A continuacion se pedira el libro, numero de orden, edicion, ubicacion y categoría del ejemplar." << endl;

    cin.ignore();
    cout << "Libro: ";
    clavelibro = ListaLibros(db);
    cin.ignore();
    cout << "Numero de orden: ";
    cin.getline(numero,15);
    numeroorden = atoi(numero);
    cout << "Edicion: ";
    cin.getline(numero,15);
    edicion = atoi(numero);
    cout << "Ubicacion: ";
    cin.getline(ubicacion,32);
    cout << "Categoria: ";
    cin.getline(categoria,2);

    // Verificar si el nombre existe ya:
    sprintf(consulta, "SELECT titulo FROM ejemplar NATURAL JOIN libro "
            "WHERE clavelibro=%d AND numeroorden=%d;", clavelibro, numeroorden);
    rc = sqlite3_prepare_v2(db, consulta, -1, &ppStmt, NULL);
    existe=false;
    if( rc!=SQLITE_OK ){
        cout << "Error: " << sqlite3_errmsg(db) << endl;
    } else {
        if(SQLITE_ROW == sqlite3_step(ppStmt)) {
            existe = true;
            strncpy(titulo, (const char*)sqlite3_column_text(ppStmt, 0), 64);
            titulo[63]=0;
        }
        sqlite3_finalize(ppStmt);
    }

    if(!existe) {
        sprintf(consulta, "INSERT INTO ejemplar(clavelibro,numeroorden,edicion,ubicacion,categoria) VALUES(%d,%d,%d,'%s','%c');",
                clavelibro, numeroorden, edicion, ubicacion, categoria[0]);
    } else {
        cout << "Ya existe un ejemplar del libro [" << titulo << "] con el numero de orden [" << numeroorden;
        cout << "]\n(s)obrescribir, insert(a)r o (i)gnorar: " << endl;
        cin>> resp;
        switch(resp[0]) {
            case 's':
                sprintf(consulta, "UPDATE ejemplar SET edicion=%d,ubicacion='%s',categoria='%c' WHERE clavelibro=%d AND numeroorden=%d;",
                        edicion, ubicacion, categoria[0], clavelibro, numeroorden);
                break;
            case 'a':
                sprintf(consulta, "INSERT INTO ejemplar(clavelibro,numeroorden,edicion,ubicacion,categoria) VALUES(%d,%d,%d,'%s','%c');",
                        clavelibro, numeroorden, edicion, ubicacion,categoria[0]);
                break;
            case 'i':
            default:
                ignorar=true;
        }
    }
    if(!ignorar) {
        if(SQLITE_OK != sqlite3_exec(db, consulta, 0, 0, 0)) {
            cout << "Error: " << sqlite3_errmsg(db) << endl;
        }
        else cout << "Ejemplar insertado" << endl;
    }
    cin.ignore();
}

int ListaEjemplares(sqlite3 *db) {
    sqlite3_stmt *ppStmt;
    int rc;
    char consulta[1024];
    int desplazamiento=0;
    char resp[10];
    bool salir=false;
    bool ultima;
    int fila=0;
    int i;

    // Mostrar una lista, teniendo en cuenta que puede haber más de las que caben en una pantalla.
    do {
        cout << "Elegir ejemplar" << endl << endl;
        sprintf(consulta, "SELECT claveejemplar,titulo,numeroorden FROM ejemplar NATURAL JOIN libro "
                "ORDER BY titulo,numeroorden LIMIT 20 OFFSET %d;", desplazamiento);
        rc = sqlite3_prepare_v2(db, consulta, -1, &ppStmt, NULL);
        if( rc!=SQLITE_OK ){
            cout << "Error: " << sqlite3_errmsg(db) << endl;
        } else {
            i = 0;
            while(SQLITE_ROW == sqlite3_step(ppStmt)) {
                cout << sqlite3_column_int(ppStmt, 0) << ") " <<
                    sqlite3_column_text(ppStmt, 1) << " [" <<
                    sqlite3_column_int(ppStmt, 2) << "]" << endl;
                i++;
            }
            sqlite3_finalize(ppStmt);
        }
        ultima = (i < 20);
        while(i < 20) { cout << endl; i++; }
        cout << "\n" << "(n) editar, (s)ig pagina, (a)nt pagina, (x)salir" << endl;
        cin >> resp;
        switch(resp[0]) {
            case 's':
                if(!ultima) desplazamiento+=20;
                break;
            case 'a':
                if(desplazamiento > 0) desplazamiento-=20;
                break;
            case 'x':
                salir=true;
                break;
            default:
                if(isdigit(resp[0])) {
                    fila = atoi(resp);
                    salir=true;
                }
                break;
        }
    } while(!salir);

    return fila;
}

void EditarEjemplar(sqlite3 *db) {
    sqlite3_stmt *ppStmt;
    int rc;
    char consulta[1024];
    int numeroorden;
    int edicion;
    char ubicacion[32];
    char categoria[2]; // "A".."F"
    char numero1[16];
    char numero2[16];
    int i;
    int fila;
    bool salir=true;

    fila = ListaEjemplares(db);

    // Editar:
    for(i = 0; i < 22; i++) cout << endl;
    sprintf(consulta, "SELECT titulo,numeroorden,edicion,ubicacion,categoria FROM ejemplar NATURAL LEFT JOIN libro WHERE claveejemplar='%d';", fila);
    rc = sqlite3_prepare_v2(db, consulta, -1, &ppStmt, NULL);
    if( rc!=SQLITE_OK ){
        cout << "Error: " << sqlite3_errmsg(db) << endl;
    } else {
        i = 0;
        if(SQLITE_ROW == sqlite3_step(ppStmt)) {
            cout << "Titulo:          " << sqlite3_column_text(ppStmt, 0) << endl;
            cout << "Numero de orden: " << sqlite3_column_int(ppStmt, 1) << endl;
            cout << "Edicion:         " << sqlite3_column_int(ppStmt, 2) << endl;
            cout << "Ubicacion:       " << sqlite3_column_text(ppStmt, 3) << endl;
            cout << "Categoria:       " << sqlite3_column_text(ppStmt, 3) << endl;
            cout << "Dejar en blanco los campos que no se quieren modifiar" << endl;
            salir=false;
        }
        sqlite3_finalize(ppStmt);
    }
    if(!salir){
        cin.ignore();
        cout << "Numero de orden: ";
        cin.getline(numero1,15);
        numeroorden = atoi(numero1);
        cout << "Edicion: ";
        cin.getline(numero2,15);
        edicion = atoi(numero2);
        cout << "Ubicacion: ";
        cin.getline(ubicacion,32);
        cout << "Categoria: ";
        cin.getline(categoria,2);
        if(strlen(numero1)>0) {
            sprintf(consulta, "UPDATE ejemplar SET numeroorden=%d WHERE claveejemplar=%d;", numeroorden, fila);
            if(SQLITE_OK != sqlite3_exec(db, consulta, 0, 0, 0)) {
                cout << "Error: " << sqlite3_errmsg(db) << endl;
            }
        }
        if(strlen(numero2)>0) {
            sprintf(consulta, "UPDATE ejemplar SET edicion=%d WHERE claveejemplar=%d;", edicion, fila);
            if(SQLITE_OK != sqlite3_exec(db, consulta, 0, 0, 0)) {
                cout << "Error: " << sqlite3_errmsg(db) << endl;
            }
        }
        if(strlen(ubicacion)>0) {
            sprintf(consulta, "UPDATE ejemplar SET ubicacion='%s' WHERE claveejemplar=%d;", ubicacion, fila);
            if(SQLITE_OK != sqlite3_exec(db, consulta, 0, 0, 0)) {
                cout << "Error: " << sqlite3_errmsg(db) << endl;
            }
        }
        if(strlen(categoria)>0) {
            sprintf(consulta, "UPDATE ejemplar SET categoria='%c' WHERE clavelibro=%d;", categoria[0], fila);
            if(SQLITE_OK != sqlite3_exec(db, consulta, 0, 0, 0)) {
                cout << "Error: " << sqlite3_errmsg(db) << endl;
            }
        }
        cout << "Ejemplar modificado" << endl;
        cin.ignore();
    }
}

void BorrarEjemplar(sqlite3 *db) {
    sqlite3_stmt *ppStmt;
    int rc;
    char consulta[1024];
    int fila;
    char resp[2];
    int i;
    bool salir=true;

    fila = ListaEjemplares(db);
    cout << "Borrar: " << fila << endl;

    for(i = 0; i < 22; i++) cout << endl;
    sprintf(consulta, "SELECT titulo,numeroorden,edicion,ubicacion,categoria FROM ejemplar NATURAL LEFT JOIN libro WHERE claveejemplar='%d';", fila);
    rc = sqlite3_prepare_v2(db, consulta, -1, &ppStmt, NULL);
    if( rc!=SQLITE_OK ){
        cout << "Error: " << sqlite3_errmsg(db) << endl;
    } else {
        i = 0;
        if(SQLITE_ROW == sqlite3_step(ppStmt)) {
            cout << "Titulo:          " << sqlite3_column_text(ppStmt, 0) << endl;
            cout << "Numero de orden: " << sqlite3_column_int(ppStmt, 1) << endl;
            cout << "Edicion:         " << sqlite3_column_int(ppStmt, 2) << endl;
            cout << "Ubicacion:       " << sqlite3_column_text(ppStmt, 3) << endl;
            cout << "Categoria:       " << sqlite3_column_text(ppStmt, 4) << endl;
            cout << "Dejar en blanco los campos que no se quieren modifiar" << endl;
            salir=false;
        }
        sqlite3_finalize(ppStmt);
    }
    if(!salir){
        cin.ignore();
        cout << "Borrar este registro? (s/n)" << endl;
        cin >> resp;
        if(resp[0] == 's' || resp[0] == 'S') {
            sprintf(consulta, "DELETE FROM ejemplar WHERE claveejemplar=%d;", fila);
            if(SQLITE_OK != sqlite3_exec(db, consulta, 0, 0, 0)) {
                cout << "Error: " << sqlite3_errmsg(db) << endl;
            }
        }
    }
    cout << "Ejemplar borrado" << endl;
    cin.ignore();
}

void BuscarEjemplar(sqlite3 *db) {
    sqlite3_stmt *ppStmt;
    int rc;
    char consulta[1024];
    char filtro[256];
    char titulo[64];
    char numero1[16];
    char numero2[16];
    int edicion;
    int numeroorden;
    char ubicacion[32];
    char categoria[2];
    int i;

    for(i = 0; i < 22; i++) cout << endl;
    // Búsqueda de editoriales por nombre, direccion o telefono:
    cout << "Introducir cadenas de busqueda, _ para comodin de caracter, % para comodin de cadena" << endl;
    cout << "Dejar en blanco para ignorar el campo en la busqueda" << endl;
    cin.ignore();
    cout << "Titulo: ";
    cin.getline(titulo, 64);
    cout << "Numero de orden: ";
    cin.getline(numero1,15);
    numeroorden = atoi(numero1);
    cout << "Edicion: ";
    cin.getline(numero2,15);
    edicion = atoi(numero2);
    cout << "Ubicacion: ";
    cin.getline(ubicacion,2);
    cout << "Categoria: ";
    cin.getline(categoria,2);

    if(strlen(titulo) == 0) strcpy(titulo, "%");
    if(strlen(ubicacion) == 0) strcpy(ubicacion, "%");
    if(strlen(categoria) == 0) strcpy(categoria, "%");
    sprintf(consulta, "SELECT titulo,numeroorden,edicion,ubicacion,categoria FROM ejemplar NATURAL JOIN libro "
            "WHERE titulo LIKE '%s' AND ubicacion LIKE '%s' AND categoria LIKE '%s'", titulo, ubicacion, categoria);
    if(strlen(numero1) > 0) {
        sprintf(filtro, " AND numeroorden=%d", numeroorden);
        strcat(consulta, filtro);
    }
    if(strlen(numero2) > 0) {
        sprintf(filtro, " AND edicion=%d", edicion);
        strcat(consulta, filtro);
    }
    strcat(consulta, ";");

    rc = sqlite3_prepare_v2(db, consulta, -1, &ppStmt, NULL);
    if( rc!=SQLITE_OK ){
        cout << "Error: " << sqlite3_errmsg(db) << "\n" << consulta << endl;
    } else {
        i = 0;
        while(SQLITE_ROW == sqlite3_step(ppStmt)) {
            cout.setf(ios::left);
            cout.width(64);
            cout << sqlite3_column_text(ppStmt, 0);
            cout.width(13);
            cout << "[" << sqlite3_column_text(ppStmt, 1) << "]" << endl;
            cout << "Ed: " << sqlite3_column_text(ppStmt, 2);
            cout.width(32);
            cout << " Ubic:" << sqlite3_column_text(ppStmt, 3);
            cout.width(2);
            cout << " Cat:" << sqlite3_column_text(ppStmt, 4) << endl;
            i +=2;
            if(i >= 21) {
                cout << "Pulsa return";
                cin.ignore();
                cin.get();
            }
        }
        sqlite3_finalize(ppStmt);
    }
    cin.ignore();
    cin.get();
}

Procesar tabla de socios

Una vez más, añadiremos los identificadores para las nuevas opciones de menú en "menus.h":

#define NUEVOSOCIO          26
#define EDITARSOCIO         27
#define BORRARSOCIO         28
#define CONSULTASOCIO       29

También añadiremos las nuevas opciones de menú para los socios en "menus.cpp", y borraremos la tabla de menú usando la sentencia "DROP TABLE menu;" desde la consola de SQLite:

    {4, "-", "---MENU SOCIOS---",0,TITULO},
    {4, "1", "Nuevo", 0, NUEVOSOCIO},
    {4, "2", "Editar", 0, EDITARSOCIO},
    {4, "3", "Borrar", 0, BORRARSOCIO},
    {4, "4", "Consultar", 0, CONSULTASOCIO},
    {4, "0", "Salir <", 1, ABRIRMENU},

Volvemos a modificar el fichero "main.cpp", para añadir el fichero incluído "socio.h" y el proceso de las nuevas opciones de menú:

#include "socio.h"
...
            case NUEVOSOCIO:
               NuevoSocio(db);
               break;
            case EDITARSOCIO:
               EditarSocio(db);
               break;
            case BORRARSOCIO:
               BorrarSocio(db);
               break;
            case CONSULTASOCIO:
               BuscarSocio(db);
               break;

Finalmente, añadiremos dos ficheros más al proyecto, uno con los prototipos de funciones y otro con la implementación para el tratamiento de la tabla de socios:

/*
 * Aplicación de ejemplo de uso de SQLite en C++
 * EjemploSQLite
 * Salvador Pozo, Con Clase (www.conclase.net)
 * Abril de 2012
 * Fichero: socio.h
 * fichero de cabecera para manupular datos de socios
 */

#ifndef __SOCIO_H__
#define __SOCIO_H__

#include <sqlite/sqlite3.h>

void NuevoSocio(sqlite3 *);
int ListaSocios(sqlite3 *);
void EditarSocio(sqlite3 *);
void BorrarSocio(sqlite3 *);
void BuscarSocio(sqlite3 *);
#endif
/*
 * Aplicación de ejemplo de uso de SQLite en C++
 * EjemploSQLite
 * Salvador Pozo, Con Clase (www.conclase.net)
 * Abril de 2012
 * Fichero: socio.cpp
 * fichero de implementación para manipular datos de socios
 */

#include <iostream>
#include <iomanip>
#include "socio.h"

using namespace std;

void NuevoSocio(sqlite3 *db) {
    char nombre[64];
    char direccion[128];
    char telefono[32];
    char categoria[2];
    sqlite3_stmt *ppStmt;
    int rc;
    char consulta[1024];
    bool existe, ignorar=false;
    char nombre2[64];
    int clave;
    char resp[2];

    for(int i= 0; i < 24; i++) cout << endl;
    cout << "A continuacion se pedira el nombre, direccion, telefono y cateforia del socio." << endl;

    cin.ignore();
    cout << "Nombre: ";
    cin.getline(nombre, 64);
    cout << "Direccion: ";
    cin.getline(direccion, 128);
    cout << "Telefono: ";
    cin.getline(telefono, 32);
    cout << "Categoria (A-F): ";
    cin.getline(categoria, 2);

    // Verificar si el nombre existe ya:
    sprintf(consulta, "SELECT clavesocio,socio FROM socio WHERE socio LIKE '%s';", nombre);
    rc = sqlite3_prepare_v2(db, consulta, -1, &ppStmt, NULL);
    existe=false;
    if( rc!=SQLITE_OK ){
        cout << "Error: " << sqlite3_errmsg(db) << endl;
    } else {
        if(SQLITE_ROW == sqlite3_step(ppStmt)) {
            existe = true;
            clave = sqlite3_column_int(ppStmt, 0);
            strncpy(nombre2, (const char*)sqlite3_column_text(ppStmt, 1), 64);
            nombre2[63]=0;
        }
        sqlite3_finalize(ppStmt);
    }

    if(!existe) {
        sprintf(consulta, "INSERT INTO socio(socio,direccion,telefono,categoria) VALUES('%s','%s','%s','%c');",
                nombre, direccion, telefono, categoria[0]);
    } else {
        cout << "Ya existe un socio con el nombre " << nombre2 << " (s)obrescribir, insert(a)r o (i)gnorar: " << endl;
        cin >> resp;
        switch(resp[0]) {
            case 's':
                sprintf(consulta, "UPDATE socio SET autor='%s',direccion='%s',telefono='%s',categoria='%c' WHERE claveautor=%d;",
                        nombre, direccion, telefono, categoria[0], clave);
                break;
            case 'a':
                sprintf(consulta, "INSERT INTO socio(socio,direccion,telefono,categoria) VALUES('%s','%s','%s','%c');",
                        nombre, direccion, telefono, categoria[0]);
                break;
            case 'i':
            default:
                ignorar=true;
        }
    }
    if(!ignorar) {
        if(SQLITE_OK != sqlite3_exec(db, consulta, 0, 0, 0)) {
            cout << "Error: " << sqlite3_errmsg(db) << endl;
        }
        else cout << "Socio insertado" << endl;
    }
    cin.ignore();
}

int ListaSocios(sqlite3 *db) {
    sqlite3_stmt *ppStmt;
    int rc;
    char consulta[1024];
    int desplazamiento=0;
    char resp[10];
    bool salir=false;
    bool ultima;
    int fila=0;
    int i;

    // Mostrar una lista, teniendo en cuenta que puede haber más de las que caben en una pantalla.
    do {
        cout << "Elegir socio" << endl << endl;
        sprintf(consulta, "SELECT clavesocio,socio FROM socio ORDER BY socio LIMIT 20 OFFSET %d;", desplazamiento);
        rc = sqlite3_prepare_v2(db, consulta, -1, &ppStmt, NULL);
        if( rc!=SQLITE_OK ){
            cout << "Error: " << sqlite3_errmsg(db) << endl;
        } else {
            i = 0;
            while(SQLITE_ROW == sqlite3_step(ppStmt)) {
                cout << sqlite3_column_int(ppStmt, 0) << ") " <<
                    sqlite3_column_text(ppStmt, 1) << endl;
                i++;
            }
            sqlite3_finalize(ppStmt);
        }
        ultima = (i < 20);
        while(i < 20) { cout << endl; i++; }
        cout << "\n" << "(n) editar, (s)ig pagina, (a)nt pagina, (x)salir" << endl;
        cin >> resp;
        switch(resp[0]) {
            case 's':
                if(!ultima) desplazamiento+=20;
                break;
            case 'a':
                if(desplazamiento > 0) desplazamiento-=20;
                break;
            case 'x':
                salir=true;
                break;
            default:
                if(isdigit(resp[0])) {
                    fila = atoi(resp);
                    salir=true;
                }
                break;
        }
    } while(!salir);

    return fila;
}

void EditarSocio(sqlite3 *db) {
    sqlite3_stmt *ppStmt;
    int rc;
    char consulta[1024];
    char nombre[64];
    char direccion[128];
    char telefono[32];
    char categoria[2];
    int i;
    int fila;
    bool salir=true;

    fila = ListaSocios(db);

    // Editar:
    for(i = 0; i < 22; i++) cout << endl;
    sprintf(consulta, "SELECT socio,direccion,telefono,categoria FROM socio WHERE clavesocio='%d';", fila);
    rc = sqlite3_prepare_v2(db, consulta, -1, &ppStmt, NULL);
    if( rc!=SQLITE_OK ){
        cout << "Error: " << sqlite3_errmsg(db) << endl;
    } else {
        i = 0;
        if(SQLITE_ROW == sqlite3_step(ppStmt)) {
            cout << "Nombre:    " << sqlite3_column_text(ppStmt, 0) << endl;
            cout << "Direccion: " << sqlite3_column_text(ppStmt, 1) << endl;
            cout << "Telefono:  " << sqlite3_column_text(ppStmt, 2) << endl;
            cout << "Categoria: " << sqlite3_column_text(ppStmt, 3) << endl;
            cout << "Dejar en blanco los campos que no se quieren modifiar" << endl;
            salir=false;
        }
        sqlite3_finalize(ppStmt);
    }
    if(!salir){
        cin.ignore();
        cout << "Nombre: ";
        cin.getline(nombre, 64);
        cout << "Direccion: ";
        cin.getline(direccion, 128);
        cout << "Telefono: ";
        cin.getline(telefono, 32);
        cout << "Categoria: ";
        cin.getline(categoria, 2);
        if(strlen(nombre)>0) {
            sprintf(consulta, "UPDATE socio SET socio='%s' WHERE clavesocio=%d;", nombre, fila);
            if(SQLITE_OK != sqlite3_exec(db, consulta, 0, 0, 0)) {
                cout << "Error: " << sqlite3_errmsg(db) << endl;
            }
        }
        if(strlen(direccion)>0) {
            sprintf(consulta, "UPDATE socio SET direccion='%s' WHERE clavesocio=%d;", direccion, fila);
            if(SQLITE_OK != sqlite3_exec(db, consulta, 0, 0, 0)) {
                cout << "Error: " << sqlite3_errmsg(db) << endl;
            }
        }
        if(strlen(telefono)>0) {
            sprintf(consulta, "UPDATE socio SET telefono='%s' WHERE clavesocio=%d;", telefono, fila);
            if(SQLITE_OK != sqlite3_exec(db, consulta, 0, 0, 0)) {
                cout << "Error: " << sqlite3_errmsg(db) << endl;
            }
        }
        if(strlen(categoria)>0) {
            sprintf(consulta, "UPDATE socio SET categoria='%c' WHERE clavesocio=%d;", categoria[0], fila);
            if(SQLITE_OK != sqlite3_exec(db, consulta, 0, 0, 0)) {
                cout << "Error: " << sqlite3_errmsg(db) << endl;
            }
        }
        cout << "Socio modificado" << endl;
        cin.ignore();
    }
}

void BorrarSocio(sqlite3 *db) {
    sqlite3_stmt *ppStmt;
    int rc;
    char consulta[1024];
    int fila;
    char resp[2];
    int i;
    bool salir=true;

    fila = ListaSocios(db);
    cout << "Borrar: " << fila << endl;

    for(i = 0; i < 22; i++) cout << endl;
    sprintf(consulta, "SELECT socio,direccion,telefono,categoria FROM socio WHERE clavesocio='%d';", fila);
    rc = sqlite3_prepare_v2(db, consulta, -1, &ppStmt, NULL);
    if( rc!=SQLITE_OK ){
        cout << "Error: " << sqlite3_errmsg(db) << endl;
    } else {
        i = 0;
        if(SQLITE_ROW == sqlite3_step(ppStmt)) {
            cout << "Nombre:    " << sqlite3_column_text(ppStmt, 0) << endl;
            cout << "Direccion: " << sqlite3_column_text(ppStmt, 1) << endl;
            cout << "Telefono:  " << sqlite3_column_text(ppStmt, 2) << endl;
            cout << "Categoria: " << sqlite3_column_text(ppStmt, 3) << endl;
            salir=false;
        }
        sqlite3_finalize(ppStmt);
    }
    if(!salir){
        cin.ignore();
        cout << "Borrar este registro? (s/n)" << endl;
        cin >> resp;
        if(resp[0] == 's' || resp[0] == 'S') {
            sprintf(consulta, "DELETE FROM socio WHERE clavesocio=%d;", fila);
            if(SQLITE_OK != sqlite3_exec(db, consulta, 0, 0, 0)) {
                cout << "Error: " << sqlite3_errmsg(db) << endl;
            }
        }
    }
    cout << "Socio borrado" << endl;
    cin.ignore();
}

void BuscarSocio(sqlite3 *db) {
    sqlite3_stmt *ppStmt;
    int rc;
    char consulta[1024];
    char nombre[64];
    char direccion[128];
    char telefono[32];
    char categoria[2];
    int i;

    for(i = 0; i < 22; i++) cout << endl;
    // Búsqueda de Socios por nombre, direccion, telefono o categoria:
    cout << "Introducir cadenas de busqueda, _ para comodin de caracter, % para comodin de cadena" << endl;
    cout << "Dejar en blanco para ignorar el campo en la busqueda" << endl;
    cin.ignore();
    cout << "Nombre: ";
    cin.getline(nombre, 64);
    cout << "Direccion: ";
    cin.getline(direccion, 128);
    cout << "Telefono: ";
    cin.getline(telefono, 32);
    cout << "Categoria: ";
    cin.getline(categoria, 2);

    if(strlen(nombre) == 0) strcpy(nombre, "%");
    if(strlen(direccion) == 0) strcpy(direccion, "%");
    if(strlen(telefono) == 0) strcpy(telefono, "%");
    if(strlen(categoria) == 0) strcpy(categoria, "%");
    sprintf(consulta, "SELECT socio,direccion,telefono,categoria FROM socio "
            "WHERE socio LIKE '%s' AND direccion LIKE '%s' AND telefono LIKE '%s' AND categoria LIKE '%c';",
            nombre, direccion, telefono, categoria[0]);
    rc = sqlite3_prepare_v2(db, consulta, -1, &ppStmt, NULL);
    if( rc!=SQLITE_OK ){
        cout << "Error: " << sqlite3_errmsg(db) << endl;
    } else {
        i = 0;
        while(SQLITE_ROW == sqlite3_step(ppStmt)) {
            cout.setf(ios::left);
            cout.width(64);
            cout << sqlite3_column_text(ppStmt, 0) << endl;
            cout.width(128);
            cout << sqlite3_column_text(ppStmt, 1) << endl;
            cout.width(32);
            cout << sqlite3_column_text(ppStmt, 2) << " ";
            cout.width(32);
            cout << "Cat: " << sqlite3_column_text(ppStmt, 3) << endl;
            i += 3;
            if((i >= 22)) {
                cout << "Pulsa return";
                cin.ignore();
                cin.get();
            }
        }
        sqlite3_finalize(ppStmt);
    }
    cin.ignore();
    cin.get();
}

Procesar tabla de préstamos

Bueno, esta es la última tabla, pero no se ajusta al mismo patrón que las anteriores.

Con los préstamos las opereaciones son algo diferentes, al menos si queremos que el programa resulte operativo según el funcionamiento que vamos a suponer para la biblioteca.

Los préstamos siempre empiezan con un socio que pide un libro. Así, para crear una fila en la tabla de préstamos tenemos que obtener una clave de socio, y una clave de ejemplar, generalmente a través de una clave de libro. En las altas de préstamos dejaremos la fecha de devolución a NULL y la de entrega será, por defecto, la fecha actual, aunque pueda ser modificada por el usuario.

La función ListaEjemplares que teníamos definida en "ejemplar.cpp" no nos sirve para esta tarea, ya que necesitamos que nos deje elegir sólo entre los ejemplares que no han sido prestados, que son los disponibles en este caso. Por lo tanto, crearemos una función para seleccionar el ejemplar adecuado.

Esta tarea no es tan simple como puede parecer en principio, ya que no existe una consulta que nos de estos datos de forma directa.

Para conseguirlo primero crearemos una tabla temporal en la que insertaremos los ejemplares existentes del libro seleccionado. Una vez hecho esto, eliminaremos de esa tabla los ejemplares que estén en préstamo (aquellos cuya fecha de devolución sea nula). Podemos eliminar también los ejemplares que no puedan ser prestados al socio, debido a la categoría. Por ejemplo, un ejemplar de categoría C sólo puede ser prestado a socios de categoria B y C. (Recordemos que no puede haber socios de categoría A.

Además, una vez identificado el socio, podemos mostrar una lista de los ejemplares que tiene en préstamo actualmente, para ver si, por ejemplo, ya tiene prestados el número límite (si es que existe un límite).

Los registros de préstamos, en principio, no deberían poderse eliminar ni editar. Pero si se produce algún error en el préstamo podemos añadir una opción de anular, y podemos dejar editar algunos campos en los préstamos cerrados, por ejemplo, la fecha de préstamo.

Para editar y anular préstamos tendremos que elegirlos desde una lista de préstamos abiertos de un socio. Pero la tabla de préstamos no tiene una clave primaria arbitraria, de manera que necesitamos un valor único, a ser posible numérico, que identifique a cada préstamo.

Afortunadamente, cada ejemplar sólo puede estar prestado una vez en cada momento, por lo que podremos usar la clave de ejemplar para seleccionar el préstamo que queramos editar o anular.

Las devoluciones las haremos a partir de un socio, y una vez conocido éste, mostraremos una lista de los préstamos actuales. Se nos pedirá una fecha de devolución y podremos agregar alguna nota. De hecho, esta función será muy parecida a las de anular y modificar fecha.

Otras posibles opciones son la consulta de historiales de préstamos, tanto de un socio como de un libro.

También nos puede interesar un listado de préstamos que lleven abiertos más de n días.

Y en general, cualquier consulta que se nos ocurra que puede ser útil. De momento nos quedaremos con las anteriores.

En cualquier caso, el procedimiento para agregar las nuevas opciones es similar a los anteriores. Empezamos por crear los nuevos identificadores, en "menus.h":

#define NUEVOPRESTAMO       30
#define EDITARPRESTAMO      31
#define ANULARPRESTAMO      32
#define DEVOLUCION          33
#define EDITARDEVOLUCION    34
#define HISTORIALSOCIO      35
#define HISTORIALLIBRO      36
#define CONSULTAPRESTAMOS   37

También añadiremos las nuevas opciones de menú en "menus.cpp", y borraremos la tabla de "menu" para que se vuelva a generar completa:

    {5, "-", "---MENU PRESTAMOS---",0,TITULO},
    {5, "1", "Nuevo prestamo", 0, NUEVOPRESTAMO},
    {5, "2", "Editar prestamo", 0, EDITARPRESTAMO},
    {5, "3", "Anular prestamo", 0, ANULARPRESTAMO},
    {5, "4", "Devolucion", 0, DEVOLUCION},
    {5, "5", "Historial de libro", 0, HISTORIALLIBRO},
    {5, "6", "Historial de socio", 0, HISTORIALSOCIO},
    {5, "7", "Consultar prestamos", 0, CONSULTAPRESTAMOS},
    {5, "0", "Salir <", 1, ABRIRMENU},

A continuación, modificaremos el fichero "main.cpp" para que se incluya el nuevo fichero de cabecera "prestamo.h", y para que se procesen las nuevas opciones:

#include "prestamo.h"
...
            case NUEVOPRESTAMO:
               NuevoPrestamo(db);
               break;
            case EDITARPRESTAMO:
               CambiarFechaPrestamo(db);
               break;
            case ANULARPRESTAMO:
               AnularPrestamo(db);
               break;
            case DEVOLUCION:
               DevolucionPrestamo(db);
               break;
            case HISTORIALLIBRO:
               HistorialLibro(db);
               break;
            case HISTORIALSOCIO:
               HistorialSocio(db);
               break;
            case CONSULTAPRESTAMOS:
               ConsultaPrestamos(db);
               break;

Por último, añadimos dos nuevos ficheros al proyecto: 'prestamo.h' y 'prestamo.cpp', con su consiguiente contenido:

/*
 * Aplicación de ejemplo de uso de SQLite en C++
 * EjemploSQLite
 * Salvador Pozo, Con Clase (www.conclase.net)
 * Abril de 2012
 * Fichero: prestamo.h
 * fichero de cabecera para manupular datos de prestamos
 */

#ifndef __PRESTAMO_H__
#define __PRESTAMO_H__

#include <sqlite/sqlite3.h>

void NuevoPrestamo(sqlite3 *);
void MostrarEjemplaresPrestados(sqlite3 *, int);
char LeerCategoriaSocio(sqlite3 *, int);
int ListaEjemplaresBiblioteca(sqlite3 *, int, int);
void CambiarFechaPrestamo(sqlite3 *);
void AnularPrestamo(sqlite3 *);
int ListaEjemplaresLibro(sqlite3 *db, int clavelibro);
int ListaEjemplaresPrestados(sqlite3 *, int);
void DevolucionPrestamo(sqlite3 *);
void HistorialLibro(sqlite3 *);
void HistorialSocio(sqlite3 *);
void ConsultaPrestamos(sqlite3 *);
#endif
/*
 * Aplicación de ejemplo de uso de SQLite en C++
 * EjemploSQLite
 * Salvador Pozo, Con Clase (www.conclase.net)
 * Abril de 2012
 * Fichero: prestamo.cpp
 * fichero de implementación para manipular datos de prestamos
 */

#include <iostream>
#include <iomanip>
#include "prestamo.h"
#include "socio.h"
#include "libro.h"
#include "ejemplar.h"

using namespace std;

void NuevoPrestamo(sqlite3 *db) {
    int clavesocio;
    int clavelibro;
    int claveejemplar;
    char fecha[12];
    char consulta[1024];

    for(int i= 0; i < 24; i++) cout << endl;
    cout << "A continuacion se pedira un socio, un libro y un ejemplar para dar de alta un prestamo." << endl;

    cin.ignore();
    cout << "Socio: ";
    clavesocio = ListaSocios(db);
    if(!clavesocio) return;
    MostrarEjemplaresPrestados(db, clavesocio);
    cout << "Libro:";
    clavelibro = ListaLibros(db);
    if(!clavelibro) return;
    claveejemplar = ListaEjemplaresBiblioteca(db, clavesocio, clavelibro);
    if(!claveejemplar) {
        cout << "No hay ejemplares disponibles para prestamo" << endl;
        cin.ignore();
        cin.get();
        return;
    }
    cin.ignore();
    cout << "Fecha de entrega (AAAA/MM/DD) [dejar en blanco para fecha actual]: ";
    cin.getline(fecha, 12);

    if(clavesocio && claveejemplar) {
        if(strlen(fecha) > 0)
            sprintf(consulta, "INSERT INTO prestamo(clavesocio,claveejemplar,fecha_prestamo,fecha_devolucion,notas) VALUES(%d,%d,'%s',NULL,'');",
                clavesocio, claveejemplar, fecha);
        else
            sprintf(consulta, "INSERT INTO prestamo(clavesocio,claveejemplar,fecha_prestamo,fecha_devolucion,notas) VALUES(%d,%d,date('now'),NULL,'');",
                clavesocio, claveejemplar);
        if(SQLITE_OK != sqlite3_exec(db, consulta, 0, 0, 0)) {
            cout << "Error: " << sqlite3_errmsg(db) << endl;
        }
        else cout << "Prestamo creado" << endl;
    }
    cin.ignore();
}

void MostrarEjemplaresPrestados(sqlite3 *db, int clavesocio) {
    sqlite3_stmt *ppStmt;
    int rc;
    char consulta[1024];

    if(clavesocio) {
        sprintf(consulta, "SELECT fecha_prestamo,titulo,formato FROM prestamo NATURAL JOIN ejemplar NATURAL JOIN libro "
                "WHERE clavesocio=%d AND fecha_devolucion IS NULL;", clavesocio);
        rc = sqlite3_prepare_v2(db, consulta, -1, &ppStmt, NULL);
        if( rc!=SQLITE_OK ){
            cout << "Error: " << sqlite3_errmsg(db) << endl;
        } else {
            cout << "Este socio tiene prestados actualmente los siguientes ejemplares:" << endl;
            while(SQLITE_ROW == sqlite3_step(ppStmt)) {
                cout << sqlite3_column_text(ppStmt, 0) << " " <<
                    sqlite3_column_text(ppStmt, 1) << " (" <<
                    sqlite3_column_text(ppStmt, 2) << ")" << endl;
            }
            cout << "------------------------" << endl;
            sqlite3_finalize(ppStmt);
        }
    }
    cin.ignore();
    cin.get();
}

char LeerCategoriaSocio(sqlite3 *db, int clavesocio) {
    sqlite3_stmt *ppStmt;
    int rc;
    char consulta[128];
    char categoriasocio[2];

    sprintf(consulta, "SELECT categoria FROM socio WHERE clavesocio=%d;", clavesocio);
    rc = sqlite3_prepare_v2(db, consulta, -1, &ppStmt, NULL);
    if( rc!=SQLITE_OK ){
        cout << "Error: " << sqlite3_errmsg(db) << endl;
        return 0;
    } else {
        if(SQLITE_ROW == sqlite3_step(ppStmt)) {
            strncpy(categoriasocio, (char*)sqlite3_column_text(ppStmt, 0), 1);
            categoriasocio[2] = 0;
        }
        sqlite3_finalize(ppStmt);
    }
    return categoriasocio[0];
}

int ListaEjemplaresBiblioteca(sqlite3 *db, int clavesocio, int clavelibro) {
    sqlite3_stmt *ppStmt;
    sqlite3_stmt *ppStmt2;
    int rc;
    char consulta[1024];
    int desplazamiento=0;
    char resp[10];
    bool salir=false;
    bool ultima;
    int fila=0;
    int i;

    // Crear tabla temporal con los ejemplares del libro indicado:
    sprintf(consulta, "CREATE TEMPORARY TABLE ejemplar2 AS SELECT * FROM ejemplar WHERE clavelibro=%d;", clavelibro);
    if(SQLITE_OK != sqlite3_exec(db, consulta, 0, 0, 0)) {
        cout << "Error: " << sqlite3_errmsg(db) << endl;
        return 0;
    }
    // Eliminar ejemplares de categorias superiores a categoriasocio:
    sprintf(consulta, "DELETE FROM ejemplar2 WHERE categoria<'%c';", LeerCategoriaSocio(db, clavesocio));
    if(SQLITE_OK != sqlite3_exec(db, consulta, 0, 0, 0)) {
        cout << "Error: " << sqlite3_errmsg(db) << endl;
        return 0;
    }

    // Eliminar ejemplares prestados:
    sprintf(consulta, "SELECT claveejemplar FROM prestamo NATURAL JOIN ejemplar NATURAL JOIN libro "
            "WHERE clavelibro=%d AND fecha_devolucion IS NULL AND claveejemplar IS NOT NULL;", clavelibro);
    rc = sqlite3_prepare_v2(db, consulta, -1, &ppStmt, NULL);
    if( rc==SQLITE_OK ) {
        rc = sqlite3_prepare_v2(db, "DELETE FROM ejemplar2 WHERE claveejemplar=@clave;", -1, &ppStmt2, NULL);
    }
    if( rc!=SQLITE_OK ){
        cout << "Error: " << sqlite3_errmsg(db) << endl;
        return 0;
    } else {
        while(SQLITE_ROW == sqlite3_step(ppStmt)) {
            sqlite3_bind_int(ppStmt2, sqlite3_bind_parameter_index(ppStmt2, "@clave"), sqlite3_column_int(ppStmt, 0));
            sqlite3_step(ppStmt2);
            sqlite3_reset(ppStmt2);
        }
        sqlite3_finalize(ppStmt);
    }

    // Si la lista de ejemplares disponibles está vacía, retornar 0:
    sprintf(consulta, "SELECT COUNT(*) FROM ejemplar2;");
    rc = sqlite3_prepare_v2(db, consulta, -1, &ppStmt, NULL);
    if( rc!=SQLITE_OK ){
        cout << "Error: " << sqlite3_errmsg(db) << endl;
        return 0;
    } else {
        if(SQLITE_ROW == sqlite3_step(ppStmt)) {
            if(sqlite3_column_int(ppStmt, 0) == 0) {
                salir = true;
            }
        }
        sqlite3_finalize(ppStmt);
    }
    if(salir) {
        sqlite3_exec(db, "DROP TABLE ejemplar2;", 0, 0, 0);
        return 0;
    }

    // Mostrar una lista, teniendo en cuenta que puede haber más de las que caben en una pantalla.
    do {
        cout << "Elegir ejemplar" << endl << endl;
        sprintf(consulta, "SELECT claveejemplar,titulo,numeroorden FROM ejemplar2 NATURAL JOIN libro "
                "ORDER BY titulo,numeroorden LIMIT 20 OFFSET %d;", desplazamiento);
        rc = sqlite3_prepare_v2(db, consulta, -1, &ppStmt, NULL);
        if( rc!=SQLITE_OK ){
            cout << "Error: " << sqlite3_errmsg(db) << endl;
        } else {
            i = 0;
            while(SQLITE_ROW == sqlite3_step(ppStmt)) {
                cout << sqlite3_column_int(ppStmt, 0) << ") " <<
                    sqlite3_column_text(ppStmt, 1) << " [" <<
                    sqlite3_column_int(ppStmt, 2) << "]" << endl;
                i++;
            }
            sqlite3_finalize(ppStmt);
        }
        ultima = (i < 20);
        while(i < 20) { cout << endl; i++; }
        cout << "\n" << "(n) editar, (s)ig pagina, (a)nt pagina, (x)salir" << endl;
        cin >> resp;
        switch(resp[0]) {
            case 's':
                if(!ultima) desplazamiento+=20;
                break;
            case 'a':
                if(desplazamiento > 0) desplazamiento-=20;
                break;
            case 'x':
                salir=true;
                break;
            default:
                if(isdigit(resp[0])) {
                    fila = atoi(resp);
                    salir=true;
                }
                break;
        }
    } while(!salir);
    // Borrar tabla temporal:
    sqlite3_exec(db, "DROP TABLE ejemplar2;", 0, 0, 0);

    return fila;
}

void CambiarFechaPrestamo(sqlite3 *db) {
    int clavesocio;
    int claveejemplar;
    char fecha[12];
    char consulta[1024];

    for(int i= 0; i < 24; i++) cout << endl;
    cout << "A continuacion se pedira un socio, y se podrá editar la fecha de entrega de uno de sus prestamos abiertos." << endl;

    cin.ignore();
    cout << "Socio: ";
    clavesocio = ListaSocios(db);
    if(!clavesocio) return;
    claveejemplar = ListaEjemplaresPrestados(db, clavesocio);
    if(!claveejemplar) {
        cout << "No hay prestamos abiertos para este socio" << endl;
        cin.ignore();
        cin.get();
        return;
    }
    cin.ignore();
    cout << "Fecha de entrega (AAAA/MM/DD) [dejar en blanco para fecha actual]: ";
    cin.getline(fecha, 12);
    sprintf(consulta, "UPDATE prestamo SET fecha_prestamo='%s' WHERE claveejemplar=%d AND fecha_devolucion IS NULL;", fecha, claveejemplar);
    if(SQLITE_OK != sqlite3_exec(db, consulta, 0, 0, 0)) {
        cout << "Error: " << sqlite3_errmsg(db) << endl;
    }
}

void AnularPrestamo(sqlite3 *db) {
    int clavesocio;
    int claveejemplar;
    char consulta[1024];

    for(int i= 0; i < 24; i++) cout << endl;
    cout << "A continuacion se pedira un socio, y se podrá editar la fecha de entrega de uno de sus prestamos abiertos." << endl;

    cin.ignore();
    cout << "Socio: ";
    clavesocio = ListaSocios(db);
    if(!clavesocio) return;
    claveejemplar = ListaEjemplaresPrestados(db, clavesocio);
    if(!claveejemplar) {
        cout << "No hay prestamos abiertos para este socio" << endl;
        cin.ignore();
        cin.get();
        return;
    }
    cin.ignore();
    // Borrar fila:
    sprintf(consulta, "DELETE FROM prestamo WHERE claveejemplar=%d AND fecha_devolucion IS NULL;", claveejemplar);
    if(SQLITE_OK != sqlite3_exec(db, consulta, 0, 0, 0)) {
        cout << "Error: " << sqlite3_errmsg(db) << endl;
    }
}

int ListaEjemplaresPrestados(sqlite3 *db, int clavesocio) {
    sqlite3_stmt *ppStmt;
    int rc;
    char consulta[1024];
    char resp[10];
    int desplazamiento=0;
    bool ultima;
    bool salir=false;
    int fila=0, i;

    // Mostrar una lista, teniendo en cuenta que puede haber más de las que caben en una pantalla.
    if(clavesocio) {
        do {
            cout << "Elegir un prestamo:" << endl << endl;
            sprintf(consulta, "SELECT claveejemplar, fecha_prestamo,titulo,formato FROM prestamo NATURAL JOIN ejemplar NATURAL JOIN libro "
                    "WHERE clavesocio=%d AND fecha_devolucion IS NULL ORDER BY fecha_prestamo LIMIT 20 OFFSET %d;", clavesocio, desplazamiento);
            rc = sqlite3_prepare_v2(db, consulta, -1, &ppStmt, NULL);
            if( rc!=SQLITE_OK ){
                cout << "Error: " << sqlite3_errmsg(db) << endl;
            } else {
                i = 0;
                while(SQLITE_ROW == sqlite3_step(ppStmt)) {
                    cout << sqlite3_column_int(ppStmt, 0) << ") " <<
                        sqlite3_column_text(ppStmt, 1) << " " <<
                        sqlite3_column_text(ppStmt, 2) << " (" <<
                        sqlite3_column_text(ppStmt, 3) << ")" << endl;
                    i++;
                }
                sqlite3_finalize(ppStmt);
            }
            ultima = (i < 20);
            while(i < 20) { cout << endl; i++; }
            cout << "\n" << "(n) editar, (s)ig pagina, (a)nt pagina, (x)salir" << endl;
            cin >> resp;
            switch(resp[0]) {
                case 's':
                    if(!ultima) desplazamiento+=20;
                    break;
                case 'a':
                    if(desplazamiento > 0) desplazamiento-=20;

                    break;
                case 'x':
                    salir=true;
                    break;
                default:
                    if(isdigit(resp[0])) {
                        fila = atoi(resp);
                        salir=true;
                    }
                    break;
            }
        } while(!salir);
        return fila;
    } else return 0;
}

void DevolucionPrestamo(sqlite3 *db) {
    int clavesocio;
    int claveejemplar;
    char fecha[12];
    char consulta[1024];
    char notas[256];

    for(int i= 0; i < 24; i++) cout << endl;
    cout << "A continuacion se pedira un socio, y se podrá editar la fecha de devolución y algunas notas de uno de sus prestamos abiertos." << endl;

    cin.ignore();
    cout << "Socio: ";
    clavesocio = ListaSocios(db);
    if(!clavesocio) return;
    claveejemplar = ListaEjemplaresPrestados(db, clavesocio);
    if(!claveejemplar) {
        cout << "No hay prestamos abiertos para este socio" << endl;
        cin.ignore();
        cin.get();
        return;
    }
    cin.ignore();
    cout << "Fecha de devolucion (AAAA/MM/DD) [dejar en blanco para fecha actual]: ";
    cin.getline(fecha, 12);
    cout << "Algun comentario: ";
    cin.getline(notas, 256);
    if(strlen(fecha) > 0)
        sprintf(consulta, "UPDATE prestamo SET fecha_devolucion='%s',notas='%s' "
                "WHERE claveejemplar=%d AND fecha_devolucion IS NULL;", fecha, notas, claveejemplar);
    else
        sprintf(consulta, "UPDATE prestamo SET fecha_devolucion=date('now'),notas='%s' "
                "WHERE claveejemplar=%d AND fecha_devolucion IS NULL;", notas, claveejemplar);
    if(SQLITE_OK != sqlite3_exec(db, consulta, 0, 0, 0)) {
        cout << "Error: " << sqlite3_errmsg(db) << endl;
        cin.ignore();
        cin.get();
    }
}

int ListaEjemplaresLibro(sqlite3 *db, int clavelibro) {
    sqlite3_stmt *ppStmt;
    int rc;
    char consulta[1024];
    int desplazamiento=0;
    char resp[10];
    bool salir=false;
    bool ultima;
    int fila=0;
    int i;

    // Mostrar una lista, teniendo en cuenta que puede haber más de las que caben en una pantalla.
    do {
        cout << "Elegir ejemplar" << endl << endl;
        sprintf(consulta, "SELECT claveejemplar,titulo,numeroorden FROM ejemplar NATURAL JOIN libro "
                "WHERE clavelibro=%d ORDER BY titulo,numeroorden LIMIT 20 OFFSET %d;", clavelibro, desplazamiento);
        rc = sqlite3_prepare_v2(db, consulta, -1, &ppStmt, NULL);
        if( rc!=SQLITE_OK ){
            cout << "Error: " << sqlite3_errmsg(db) << endl;
        } else {
            i = 0;
            while(SQLITE_ROW == sqlite3_step(ppStmt)) {
                cout << sqlite3_column_int(ppStmt, 0) << ") " <<
                    sqlite3_column_text(ppStmt, 1) << " [" <<
                    sqlite3_column_int(ppStmt, 2) << "]" << endl;
                i++;
            }
            sqlite3_finalize(ppStmt);
        }
        ultima = (i < 20);
        while(i < 20) { cout << endl; i++; }
        cout << "\n" << "(n) editar, (s)ig pagina, (a)nt pagina, (x)salir" << endl;
        cin >> resp;
        switch(resp[0]) {
            case 's':
                if(!ultima) desplazamiento+=20;
                break;
            case 'a':
                if(desplazamiento > 0) desplazamiento-=20;
                break;
            case 'x':
                salir=true;
                break;
            default:
                if(isdigit(resp[0])) {
                    fila = atoi(resp);
                    salir=true;
                }
                break;
        }
    } while(!salir);

    return fila;
}

void HistorialLibro(sqlite3 *db) {
    sqlite3_stmt *ppStmt;
    int rc;
    int clavelibro;
    int claveejemplar;
    char consulta[1024];
    int i, desplazamiento=0;
    bool ultima;
    bool salir=false;
    char resp[10];

    for(int i= 0; i < 24; i++) cout << endl;
    cout << "A continuacion se pedira un libro y un ejemplar para mostrar su historial de prestamos." << endl;

    cin.ignore();
    cout << "Libro:";
    clavelibro = ListaLibros(db);
    if(!clavelibro) return;
    claveejemplar = ListaEjemplaresLibro(db, clavelibro);
    if(!claveejemplar) {
        cout << "No hay ejemplares disponibles de este libro" << endl;
        cin.ignore();
        cin.get();
        return;
    }
    cin.ignore();

    // Mostrar una lista, teniendo en cuenta que puede haber más de las que caben en una pantalla.
    do {
        cout << "Historial de ejemplar" << endl << endl;
        sprintf(consulta, "SELECT fecha_prestamo,fecha_devolucion,titulo,numeroorden FROM prestamo NATURAL JOIN ejemplar NATURAL JOIN libro "
                "WHERE claveejemplar=%d ORDER BY fecha_prestamo LIMIT 20 OFFSET %d;", claveejemplar, desplazamiento);
        rc = sqlite3_prepare_v2(db, consulta, -1, &ppStmt, NULL);
        if( rc!=SQLITE_OK ){
            cout << "Error: " << sqlite3_errmsg(db) << endl;
        } else {
            i = 0;
            while(SQLITE_ROW == sqlite3_step(ppStmt)) {
                cout << sqlite3_column_text(ppStmt, 0) << " ~ " <<
                    sqlite3_column_text(ppStmt, 1) << " " <<
                    sqlite3_column_text(ppStmt, 2) << " [" <<
                    sqlite3_column_int(ppStmt, 3) << "]" << endl;
                i++;
            }
            sqlite3_finalize(ppStmt);
        }
        ultima = (i < 20);
        while(i < 20) { cout << endl; i++; }
        cout << "\n" << "(s)ig pagina, (a)nt pagina, (x)salir" << endl;
        cin >> resp;
        switch(resp[0]) {
            case 's':
                if(!ultima) desplazamiento+=20;
                break;
            case 'a':
                if(desplazamiento > 0) desplazamiento-=20;
                break;
            case 'x':
                salir=true;
                break;
        }
    } while(!salir);
}

void HistorialSocio(sqlite3 *db) {
    sqlite3_stmt *ppStmt;
    int rc;
    int clavesocio;
    char consulta[1024];
    int i, desplazamiento=0;
    bool ultima;
    bool salir=false;
    char resp[10];

    for(int i= 0; i < 24; i++) cout << endl;
    cout << "A continuacion se pedira un socio para mostrar su historial de prestamos." << endl;

    cin.ignore();
    cout << "Socio:";
    clavesocio = ListaSocios(db);
    if(!clavesocio) return;
    cin.ignore();

    // Mostrar una lista, teniendo en cuenta que puede haber más de las que caben en una pantalla.
    do {
        cout << "Historial del socio" << endl << endl;
        sprintf(consulta, "SELECT fecha_prestamo,fecha_devolucion,titulo,numeroorden FROM prestamo NATURAL JOIN ejemplar NATURAL JOIN libro "
                "WHERE clavesocio=%d ORDER BY fecha_prestamo LIMIT 20 OFFSET %d;", clavesocio, desplazamiento);
        rc = sqlite3_prepare_v2(db, consulta, -1, &ppStmt, NULL);
        if( rc!=SQLITE_OK ){
            cout << "Error: " << sqlite3_errmsg(db) << endl;
        } else {
            i = 0;
            while(SQLITE_ROW == sqlite3_step(ppStmt)) {
                cout << sqlite3_column_text(ppStmt, 0) << " ~ ";
                // Verificar si la fecha de devolucion es null
                if(sqlite3_column_text(ppStmt, 1)) cout << sqlite3_column_text(ppStmt, 1);
                else cout << " prestado ";
                cout << " " << sqlite3_column_text(ppStmt, 2) << " [" <<
                    sqlite3_column_int(ppStmt, 3) << "]" << endl;
                i++;
            }
            sqlite3_finalize(ppStmt);
        }
        ultima = (i < 20);
        while(i < 20) { cout << endl; i++; }
        cout << "\n" << "(s)ig pagina, (a)nt pagina, (x)salir" << endl;
        cin >> resp;
        switch(resp[0]) {
            case 's':
                if(!ultima) desplazamiento+=20;
                break;
            case 'a':
                if(desplazamiento > 0) desplazamiento-=20;
                break;
            case 'x':
                salir=true;
                break;
        }
    } while(!salir);
}

void ConsultaPrestamos(sqlite3 *db) {
    sqlite3_stmt *ppStmt;
    int rc;
    int ndias;
    char consulta[1024];
    char numero[15];
    int i, desplazamiento=0;
    bool ultima;
    bool salir=false;
    char resp[10];

    for(int i= 0; i < 24; i++) cout << endl;
    cout << "Prestamos abiertos mas de un numero de dias." << endl;

    cin.ignore();
    cout << "Dias: ";
    cin.getline(numero, 15);
    ndias = atoi(numero);

    // Mostrar una lista, teniendo en cuenta que puede haber más de las que caben en una pantalla.
    do {
        cout << "Prestamos abiertos mas de " << ndias << " dias" << endl << endl;
        sprintf(consulta, "SELECT fecha_prestamo,titulo,numeroorden,socio,JULIANDAY('now')-JULIANDAY(fecha_prestamo) AS dias "
                "FROM prestamo NATURAL JOIN ejemplar NATURAL JOIN libro NATURAL JOIN socio "
                "WHERE fecha_devolucion IS NULL AND JULIANDAY('now')-JULIANDAY(fecha_prestamo)>%d ORDER BY fecha_prestamo LIMIT 20 OFFSET %d;", ndias, desplazamiento);
        rc = sqlite3_prepare_v2(db, consulta, -1, &ppStmt, NULL);
        if( rc!=SQLITE_OK ){
            cout << "Error: " << sqlite3_errmsg(db) << endl;
        } else {
            i = 0;
            while(SQLITE_ROW == sqlite3_step(ppStmt)) {
                cout << sqlite3_column_text(ppStmt, 0) << " ~ ";
                cout << sqlite3_column_text(ppStmt, 1);
                cout << " [" << sqlite3_column_int(ppStmt, 2) << "] ";
                cout << "Socio: " << sqlite3_column_text(ppStmt, 3);
                cout << " Dias: " << sqlite3_column_int(ppStmt, 4) << endl;
                i++;
            }
            sqlite3_finalize(ppStmt);
        }
        ultima = (i < 20);
        while(i < 20) { cout << endl; i++; }
        cout << "\n" << "(s)ig pagina, (a)nt pagina, (x)salir" << endl;
        cin >> resp;
        switch(resp[0]) {
            case 's':
                if(!ultima) desplazamiento+=20;
                break;
            case 'a':
                if(desplazamiento > 0) desplazamiento-=20;
                break;
            case 'x':
                salir=true;
                break;
        }
    } while(!salir);
}

Descarga de ejemplos

Nombre Fichero Fecha Tamaño Contador Descarga
Biblioteca con SQLite para consola ejsqlitecon.zip 2012-04-17 25004 bytes 787