Bases de datos en Android LSUB, GYSC, URJC
Base de Datos Relacional
• Permite guardar datos relacionados
• Preservando ACID
http://www.amazon.com/dp/0321197844/
SQL
• Lenguaje estándar (casi todas las implementaciones tienen extensiones)
http://www.iso.org/iso/iso_catalogue/catalogue_tc/catalogue_detail.htm?csnumber=53681
• Para acceder y manipular bases de datos relacionales
Álgebra relacional: Relación
• Una base de datos guarda relaciones: ej. El nombre de un empleado, su fecha de nacimiento, DNI, puesto, etc.
• Una relación es un conjunto de tuplas de atributos. Los atributos son como variables (nombre, DNI…).
• Por ejemplo, (Juan Perez, 1998, 12323435J, C/ Luna) es una tupla, Juan Perez, 1998, C/ Luna son valores de los atributos de esa dupla.
• Las tuplas de valores son filas, los conjuntos del elementos i de la tupla de valores son columnas
Álgebra relacional: operaciones
• Proyección (quedarme con algunas columnas): SELECT X, Y FROM AA
• Selección (quedarme con algunas filas): SELECT * FROM R WHERE XXX
• Producto Cartesiano: combinar relaciones (tamaño NxM, ojo)
SELECT * FROM R, S (producto cartesiano, CROSS JOIN)
- Producto Cartesiano + Selección XXX (JOIN, INNER JOIN)
SELECT * FROM R JOIN S ON XXX (si ON es una condición de igualdad EQUIJOIN)
- NATURAL JOIN: join R con S la comparación de los conjuntos de atributos de igual nombre en
SELECT * FROM R NATURAL JOIN S (es un tipo especial de equijoin)
• Unión de conjuntos/Diferencia de conjuntos
Álgebra relacional: operaciones
θ-join: Producto cartesiano + proyección (inner join)Join en una relación (coche vale más que barca)
Álgebra relacional: operaciones, outer join
Outer Join, como un join pero, las que no encajan las relleno,
left y right (abajo left outer join)
SELECT A FROM T1LEFT JOIN T2
ON T1.DEPTNAME== T2.DEPTNAME
Álgebra relacional: operaciones, outer join
Outer Join: right
SELECT A FROM T1RIGHT JOIN T2
ON T1.DEPTNAME== T2.DEPTNAME
Claves• Para identificar unívocamente a una tupla: dos tuplas,
diferentes claves (ej: para cambiar un atributo)
• Puede un atributo o varios
• Se puede asignar automáticamente (AUTOINCREMENT, implícita)
• Puede haber más de una clave, pero hay una esencial: clave primaria
• Si un atributo es clave de otra tabla: foreign key, clave ajena
• Índices: traducen de clave a tupla, hay uno siempre, para la clave primaria, puedo crear otros para ir más rápido (árbol B)
Normalización• Cuidado al definir las tablas
• Anomalías de actualización:
- Ej: La clave son dos atributos, pero los datos dependen de uno, datos repetidos…
- Ej: Dos tablas, profesores y clases. En las clases el email del profesor. Actualizar en dos sitios, quito la clase y pierdo el email…
• Tres formas normales. Al final:
- Cualquier atributo (no clave) debe decir algo sobre la clave, toda la clave y nada más que la clave
SQLite• Implementación de SQL, más detalles:
http://it-ebooks.info/book/147/
• Android permite usar bases de datos SQLite de serie.
• No requiere administración. Una base de datos SQLite es un fichero único.
• Permite almacenar datos: TEXT (como String de Java), INTEGER (como long de Java), REAL (como double de Java), BLOB binario (x’ac23ab’ de longitud arbitraria)
SQLite
• “Dynamic typing”: cualquier tipo de datos puede insertarse en cualquier columna (¡aunque no coincida el tipo!). Ojo.
• Si queremos comprobación de violación de claves ajenas (foreign keys), hay que activarlo:
PRAGMA foreign_keys = ON;
SQLite• Una base de datos tiene un schema que la define (tablas,
índices, etc.).
• Toda base de datos tiene una tabla llamada SQLITE_MASTER (sólo lectura) que representa el esquema de la base de datos (se actualiza al cambiar el esquema). Sus columnas son:
• type (TEXT): tabla o índice.• name (TEXT): nombre.• tbl_name (TEXT): nombre de la tabla para índices.• rootpage (INTEGER): estructura de almacenamiento (b-tree).• sql (TEXT): sentencia que creó la tabla o índice.
SQLite
CREATE TABLE Designations ( DesignationId INTEGER PRIMARY KEY AUTOINCREMENT, Designation TEXT, Description TEXT );
SQLite
CREATE TABLE Departments ( DeptId INTEGER PRIMARY KEY AUTOINCREMENT, DeptName TEXT, Description TEXT );
SQLiteCREATE TABLE Employees ( EmpId INTEGER PRIMARY KEY AUTOINCREMENT, EmpCode INTEGER, FirstName TEXT, LastName TEXT, Email TEXT, DeptId INTEGER, DesignationId INTEGER, ManagerId INTEGER, Address TEXT, FOREIGN KEY(DeptId) REFERENCES Departments(DeptId), FOREIGN KEY(DesignationId) REFERENCES Designations(DesignationId) );
SQLiteINSERT INTO Departments ( DeptName, Description ) VALUES( "IT", "Technical staff" );
INSERT INTO Departments ( DeptName, Description ) VALUES( "Sales", "Sales executives" );
SQLite
INSERT INTO Designations ( Designation, Description ) VALUES( "Programmer", "C and Java developer" );
SQLiteINSERT INTO Employees ( EmpCode, FirstName, LastName, Email, DeptId, DesignationId, ManagerId, Address ) VALUES ( 554, "John", "Doe", "[email protected]", 1, 1, 32, "Foo Rd. 12 D3" );
SQLiteSELECT * FROM Departments;
SELECT * FROM Designations;
SELECT * FROM Employees;
SELECT FirstName, LastName, DesignationId FROM Employees WHERE LastName == "Doe";
SQLiteSELECT Employees.FirstName, Employees.LastName, Designations.Description FROM Employees NATURAL JOIN Designations WHERE Employees.LastName == "Doe" ;
SELECT Employees.FirstName, Employees.LastName, Departments.DeptName FROM Employees NATURAL JOIN Departments WHERE Employees.LastName == "Doe" ;
SQLiteSELECT Employees.FirstName, Employees.LastName, Departments.DeptName, Designations.Description FROM Employees INNER JOIN Designations ON Employees.DesignationId == Designations.DesignationId INNER JOIN Departments ON Employees.DeptId == Departments.DeptId WHERE Employees.LastName == "Doe" ;
SQLite
• DELETE: borra filas.
• DROP: borra tablas/índices.
• Más info:
http://www.sqlite.org/lang.html
SQLiteOpenHelper
• Heredando de esa clase podemos creamos nuestra clase para acceder a la base de datos.
• En el constructor, al llamar a super(), hay que proporcionar el nombre de la base de datos y la versión que queremos.
• Hay que cerrarlo después de usarlo.
SQLiteOpenHelper
public class DB extends SQLiteOpenHelper {private final static String NAME = "company.db";private final static int VERSION = 1;
public DB(Context context){super(context, NAME, null, VERSION);
}
...}
SQLiteOpenHelper
• Tendremos que redefinir los métodos:
• onCreate(): se invoca cuando se crea la base de datos si no existe.
• onUpgrade(): se invoca cuando se invoca super() con una nueva versión de la DB. Aquí debemos hacer lo necesario para pasar de una versión a otra (borrar tablas, crear tablas, etc.).
SQLiteOpenHelperpublic final static String DESIGNATIONS = "Designations";
private final static String CREATE_DESIGNATIONS = "CREATE TABLE " + DESIGNATIONS + " (" +" _id INTEGER PRIMARY KEY AUTOINCREMENT, " +" Designation TEXT, " +" Description TEXT);";
private final static String DESIGNATIONS = "Designations";private final static String EMPLOYEES = "Employees";
// the two other CREATE SQL queries (CREATE_DEPARTMENTS and CREATE_EMPLOYEES) are excluded...
@Overridepublic void onCreate(SQLiteDatabase database) {
Log.v(DB.class.getName(),"Creating DB.");database.execSQL(CREATE_DESIGNATIONS);database.execSQL(CREATE_DEPARTMENTS);database.execSQL(CREATE_EMPLOYERS);
}
private void doReset(SQLiteDatabase database){database.execSQL("DROP TABLE IF EXISTS " + DESIGNATIONS);database.execSQL("DROP TABLE IF EXISTS " + DEPARTMENTS);database.execSQL("DROP TABLE IF EXISTS " + EMPLOYEES);onCreate(database);
}
@Overridepublic void onUpgrade(SQLiteDatabase database, int from, int to) {
Log.v(DB.class.getName(),"Upgrade DB, new version: " + to + ", deleting data");
doReset(database);}
SQLiteOpenHelper
• De la clase helper podemos conseguir un objeto SQLiteDatabase para acceder a los datos:
• getReadableDatabase(): da acceso de sólo lectura a la base de datos.
• getWriteableDatabase(): da acceso de escritura.
SQLiteDatabase
• Esta clase representa una DB tiene métodos para realizar queries SQL.
• Convenio en Android: la clave primaria de una tabla se debe llamar _id
• Hay que cerrarlo después de usarlo.
SQLiteDatabase
• insert(), update(), delete(): métodos que facilitan este tipo de peticiones.
public void insertDepartment(Department d) {//myHelper is a DB reference (SQLiteOpenHelper)
SQLiteDatabase database = MyHelper.getWritableDatabase();ContentValues values = new ContentValues();
values.put("DeptName", d.getDeptname());values.put("Description", d.getDescription());if(database.insert(DEPARTMENTS, null , values) == -1){
database.close();throw new RuntimeException("Can't insert department in database");
}database.close();
}
SQLiteDatabase
• rawQuery(): ejecuta una sentencia SQL expresada en una String. La query no debe acabar en ‘;’. Retorna un Cursor con los resultados.
• Un parámetro (selection): array de Strings para insertar valores en la query de forma cómoda.
• Cursor: clase que representa los resultados de una query. Es una colección iterable de filas.
SQLiteDatabase
private Integer getDepartmentID(SQLiteDatabase database, Department dept) { String query = "SELECT _id FROM "+ DEPARTMENTS +" WHERE DeptName == ?";
String selection[] = {dept.getDeptname()};Cursor cursor = database.rawQuery(query, selection);
if(cursor.getCount() != 1){cursor.close();throw new RuntimeException("Department does not exist");
}cursor.moveToFirst();int ret = cursor.getInt(0);cursor.close();
return ret;}
SQLiteDatabasepublic String listEmployees(){
String text = null;SQLiteDatabase database = this.getReadableDatabase();
String query = "SELECT " + EMPLOYEES + ".FirstName, " + EMPLOYEES + ".LastName, " + DEPARTMENTS + ".DeptName, " + DESIGNATIONS + ".Description " + " FROM " + EMPLOYEES + " INNER JOIN "+ DESIGNATIONS + " ON " + EMPLOYEES + ".DesignationId == " + DESIGNATIONS + "._id " + " INNER JOIN " + DEPARTMENTS + " ON " + EMPLOYEES + ".DeptId == " + DEPARTMENTS + "._id";
Cursor cursor = database.rawQuery(query, null); if(cursor.moveToFirst()){ text = ""; do{ text = text + cursor.getString(cursor.getColumnIndex("FirstName")) + " " + cursor.getString(cursor.getColumnIndex("LastName")) + ", Department: " +cursor.getString(cursor.getColumnIndex("DeptName")) + ", Current designation: " + cursor.getString(cursor.getColumnIndex("Description")) + "\n"; }while(cursor.moveToNext());
} cursor.close(); database.close(); return text;
}
SQLiteDatabase• query() argumentos: parámetros de la
consulta. Retorna un Cursor con los resultados.
• execSQL(): para ejecutar una sentencia SQL que no retorne datos, una String.
• setForeignKeyConstraintsEnabled(): activa la comprobación de claves ajenas en SQLite.
SQLiteQueryBuilder• Es una clase que facilita la realización de queries. El
método query()
public Cursor query (SQLiteDatabase db, String[] projectionIn, String selection, String[] selectionArgs, String groupBy, String having, StringsortOrder, String limit)
• projectionIn: lista de columnas a retornar, null significa todas.
• selection: filtro para seleccionar las filas, que contiene las expresiones del WHERE.
• selectionArgs: array con los valores de los ‘?’.
• groupBy: filtro para agrupar las filas (la cláusula GROUP BY de SQL).
• having: cláusula HAVING de SQL.
• sortOrder: cláusula ORDER BY de SQL.
• limit: número máximo de filas retornadas, formateadas como la cláusula LIMIT.
SQLiteQueryBuilder
• query(uri, selection=”column=”+value, selectionArgs=null, sortOrder)
query(uri, “number=2423434”, sortOrder)
• query(uri, projection, selection=”column=?”, selectionArgs={value_as_string}, sortOrder)
• SELECT * from contacts_table WHERE number=‘134134134’
SQLiteQueryBuilder
SQLiteQueryBuilder qBuilder = new SQLiteQueryBuilder();SQLiteDatabase database = MyHelper.getReadableDatabase();
qBuilder.setTables(EMPLOYEES);
Cursor c = qBuilder.query(database, projection, selection,selectionArgs, null, null, sortOrder);
ContentProvider
• Abstracción general de conjunto de datos
• Las aplicaciones acceden a ellos deben a través de un ContentResolver
ContentProvider• Las bases de datos de una aplicación son privadas.
• Para ofrecer datos a otras aplicaciones: crear un ContentProvider.
• Un ContentProvider abstrae la DB subyacente.
• El esquema puede cambiar, la interfaz con el cliente no.
• Por omisión es accesible desde otras aplicaciones.
• Tiene que ocuparse de controlar el acceso concurrente a los datos.
ContentProvider• Los datos del ContentProvider se representan
mediante URIs en el manifiesto de la aplicación.
• AndroidManifiest.xml:
• Sólo para uso interno, debemos poner en el manifiesto:
<provider android:authorities="org.lsub.employees.contentprovider" android:name=".contentprovider.EmployeesContentProvider" ></provider>
android:exporte=false
ContentProvider• Para crear un ContenteProvider clase que extiende
android.content.ContentProvider.
• Hay que reescribir, (reciben como parámetro una URI representando el recurso):
• onCreate(): inicialización del proveedor.
• query(), insert(), update(), delete().
• getType(): devuelve el tipo MIME de la URI.
• Si no se soporta alguna operación, elevar UnsupportedOperationException.
• Los métodos que modifican contenidos deben llamar a notifyChange() por cortesía.
Usar ContentProvider
• Hacen falta permisos.
• En el manifiesto.
• Ej: para los contactos hay que usar el permiso
• android.permission.READ_CONTACTS
Usar ContentProvider
La organización de los datos de los contactos es la siguiente:
• ContactsContract.Contacts: tabla con los contactos, con la clave de raw contact.• ContactsContract.RawContacts: tabla con el resumen de los datos de un contacto, como nombre de cuenta, tipo de cuenta (p. ej. Google), etc. sin estructurar.
• ContactsContract.Data: tabla con los detalles de un contacto estructurados, nombre y apellidos, email o número de teléfono. Tiene columnas como el tipo MIME, etc.
http://developer.android.com/guide/topics/providers/contacts-provider.html
Usar ContentProvider• Hay un contrato entre el proveedor y los clientes.
• El contrato define las URIs y las columnas que ofrece el proveedor, independientemente de sus esquema interno.
• El contrato de los contactos es
http://developer.android.com/reference/android/provider/ContactsContract.html
• Por ej. podemos usar el contrato del recurso para localizar la URI de una tabla:
Uri uri = ContactsContract.Contacts.CONTENT_URI;
Usar ContentProvider
• Por ejemplo: conseguir el _id y nombre de todos los contactos, ordenados por su _id:
Uri uri = ContactsContract.Contacts.CONTENT_URI;String[] projection = new String[] { ContactsContract.Contacts._ID,
ContactsContract.Contacts.DISPLAY_NAME };String sortOrder = ContactsContract.Contacts._ID;
Cursor cursor = getContentResolver().query(uri, projection, null, null, sortOrder);
Usar ContentProvider (REST)
• Por ejemplo: conseguir el nombre de un _id concreto:
//append the record number for the query, REST style.Uri uri = ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, id);String[] projection = new String[] {ContactsContract.Contacts.DISPLAY_NAME};
cursor = getContentResolver().query(uri, projection, null, null, null);
Usar ContentProvider (Data)
• Para acceder a los datos comunes (número de teléfono, email, etc.) de la tabla ContactContract.Data se usa ContactContract.CommonDataKinds:
uri = ContactsContract.CommonDataKinds.Phone.CONTENT_URI;projection = new String[] {ContactsContract.CommonDataKinds.Phone.NUMBER}; selection = ContactsContract.CommonDataKinds.Phone.CONTACT_ID +" = "+ contactId;
cursor = getContentResolver().query(uri, projection, selection, null, null);