Download LSUB, GYSC, URJC

Document related concepts
no text concepts found
Transcript
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 a bases de datos relacionales
Álgebra relacional:
Relación
• Una base de datos guarda relaciones: ej. El
nombre de un empleado y su fecha de
nacimiento
• Una relación es un conjunto de tuplas de
atributos (Juan Perez, 1998, C/ Luna) es una
tupla, Juan Perez, 1998, C/ Luna son atributos
• Las tuplas de valores son filas, los conjuntos
del elementos i de la tupla 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
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...
• 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
• Ejemplo:
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
);
SQLite
CREATE 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)
);
SQLite
También se puede volcar el esquema con el comando .schema
SQLite
!
INSERT 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"
);
!
SQLite
INSERT INTO Employees (
EmpCode,
FirstName,
LastName,
Email,
DeptId,
DesignationId,
ManagerId,
Address
)
VALUES (
554,
"John",
"Doe",
"[email protected]",
1,
1,
32,
"Foo Rd. 12 D3"
);
!
SQLite
SELECT * FROM Departments;
!
SELECT * FROM Designations;
!
SELECT * FROM Employees;
!
SELECT FirstName, LastName, DesignationId
FROM Employees WHERE LastName == "Doe";
!
SQLite
SELECT 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" ;
!
!
!
SQLite
SELECT 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
UPDATE Employees SET FirstName = "Manolo"
WHERE EmpId == 1;
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.).
SQLiteOpenHelper
!
public 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
...
!
!
@Override
public 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);
}
@Override
public 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 (SQLiteDatabaseHelper) 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;
}
SQLiteDatabase
public 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:
! <provider
!
android:authorities="org.lsub.employees.contentprovider"
android:name=".contentprovider.EmployeesContentProvider" >
</provider> • Sólo para uso interno, debemos poner en el
manifiesto:
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
Add →Uses Permission
Usar ContentProvider
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);