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;
        }
    }
}