1

Unidad 7 “SQLite”


¿QUÉ ES UNA BASE DE DATOS?


Una base de datos es una colección de datos relacionados. Por dato se entiende a un hecho conocido que
puede ser almacenado y que posee un significado implícito.


Por ejemplo, considere los nombres, números de teléfono y direcciones de un conjunto de personas
almacenadas en una libreta de direcciones o en una computadora personal. Esto es una colección de datos
relacionados con un significado implícito, de esta forma, es una base de datos.


La definición precedente es genérica, sin embargo, el uso común del término base de datos es usualmente
más restringido.


Una base de datos posee las siguientes propiedades implícitas:





En resumen, una base de datos posee alguna fuente a partir de la cual se derivan los datos, algún grado de
interacción con el mundo real, y una audiencia que está activamente interesada en los contenidos de la
base de datos.


Un Sistema de Gestión de Base de Datos
(DBMS – Database Management System) es
una colección de programas de software que
permite crear y mantener una base de datos.


De esta forma, el DBMS es un sistema de
software de propósito general que facilita el
proceso de definir, construir y manipular bases
de datos para diferentes aplicaciones.
2


Unidad 7 “SQLite”

Definir una base de datos consiste de especificar los tipos de datos, estructuras y restricciones para los
datos que serán almacenados en la base de datos.


Construir la base de datos es el proceso de almacenar los datos propiamente dichos en un medio de
almacenamiento controlado por el DBMS. Manipular una base de datos incluye funciones tales como:
consultar la base de datos para recuperar datos específicos, actualizar la base de datos para reflejar
cambios ocurridos en el mundo real, y generar reportes a partir de los datos.


No es necesario utilizar un software DBMS de propósito general para implementar
una base de datos computarizada. Uno podría escribir su propio conjunto de
programas para crear y mantener la base de datos, en efecto, creando su propio
software DBMS de propósito general. En cualquier caso, ya sea que se utilice un
DBMS de propósito general o no, usualmente se emplea una considerable cantidad
de software para manipular la base de datos.


La base de datos propiamente dicha más el software DBMS conforma un Sistema de Base de Datos


Un DBMS proporciona un entorno conveniente y eficiente para ser utilizado al extraer y almacenar
información en la base de datos. Un sistema de bases de datos proporciona a los usuarios una vista
abstracta de los datos ocultando ciertos detalles de cómo se almacenan y mantienen los datos. Oracle 11g
es un software DBMS de propósito general.



3


Unidad 7 “SQLite”


DESCRIPCIÓN DE RDBMS Y ORDBMS


Michael Stonebraker, en el paper "Object-Relational DBMS: The Next Wave," clasifica a las aplicaciones de
bases de datos en cuatro tipos: datos simples sin consulta, datos simples con consulta, datos complejos sin
consulta, y datos complejos con consultas. Estos cuatro tipos describen sistemas de archivos, DBMSs
relacionales, DBMS orientados a objeto, y DBMSs objeto relacionales, respectivamente.




Una base de datos relacional está compuesta de muchas relaciones en la forma de tablas de dos
dimensiones compuestas por filas y columnas conteniendo tuplas (o registros) relacionadas.


Los tipos de consultas que atienden un sistema de gestión de base de datos relacional ( RDBMS ) varían
desde simples consultas incluyendo una única tabla de base a consultas complicadas de múltiples tablas
incluyendo uniones, anidamiento, diferencia o unión de conjuntos, y otras. Oracle 11g es un RDBMS que
implementa todas las características relacionales más funcionalidades enriquecidas, como ser: commits
rápidos, backup y recuperación, conectividad escalable, bloque a nivel de filas, consistencia de lectura,
particionamiento de tablas, paralelización de consultas, bases de datos en clúster, exportación e
importación de datos, entre otras.


La principal desventaja de bases de datos relacionales ocurre debido a su inhabilidad de manejar áreas de
aplicación como bases de datos espaciales, aplicaciones que manipulan imágenes, y otros tipos de
aplicaciones que incluyen la interrelación compleja de datos.


La ambición de representar objetos complejos ha provocado el desarrollo de sistemas orientados a objeto
incluyendo características de orientación a objeto, tales como: tipos de dato abstractos y encapsulamiento
(la estructura internad de los datos es ocultada y las operaciones externas pueden ser invocadas sobre el
objeto especificado), herencia (tendiendo a la reutilización de definiciones existentes para crear nuevos
objetos), etc.
4


Unidad 7 “SQLite”

Una base de datos orientada a objetos emplea un modelo de datos que soporta características de
orientación a objeto y tipos de datos abstractos. Bases de datos orientadas a objeto utilizan el poderío de
los lenguajes de programación orientados a objeto para proporcionar capacidades de programación de
base de datos. Un sistema de gestión de base datos orientado a objetos ( OODBMS ) implementa un
modelo de objeto, estandarizado por ODMG (Object Database Management Group), que consiste de tipos
de dato, constructores de tipo, etc., similar al modelo estándar para bases de datos relacionales.


La principal desventaja de ODBMS es la mala performance en la manipulación y acceso a datos. No como en
RDBMS, la optimización de consultas en OODBMS es altamente compleja. OODBMS también sufren
problemas de escalabilidad, y no son capaces de soportar sistemas de gran escala.


El principal objetivo de un ORDBMS es brindar los beneficios tanto del modelo relacional como del modelo
de objetos, tales como: escalabilidad y soporte de tipos de dato enriquecidos. ORDBMSs emplean un
modelo de datos que incorpora características de orientación a objeto en RDBMSs. Toda la información es
almacenada en tablas, pero algunas de las entradas tabulares en la base de datos pueden poseer
estructuras de datos enriquecidas o complejas (tipos de dato abstractos). Un ORDBMS soporta una forma
extendida de SQL. Las extensiones son necesarias debido a que ORDBMS dan soporte a tipos de dato
abstractos.




NIVELES DE ABSTRACCIÓN DE DATOS


Una característica fundamental de una base de datos es que la misma provee un nivel de abstracción de
datos ocultando detalles acerca del almacenamiento de datos que no son necesarios para la mayoría de los
usuarios de base de datos. Dicha abstracción es provista a través de un modelo de datos.
5


Unidad 7 “SQLite”

Un modelo de datos es una colección de conceptos que pueden ser utilizados para describir la estructura
de una base de datos. La estructura de una base de datos está conformada por los tipos de datos, las
relaciones entre los datos, y las restricciones que existen sobre los datos. La mayoría de los modelos de
datos también incluyen un conjunto de operaciones básicas para especificar recuperaciones y
actualizaciones realizadas sobre la base de datos.




EL MODELO ENTIDAD – RELACIÓN


El modelo entidad relación (ER) es un modelo de datos conceptual de alto nivel. Este modelo, y sus
extensiones, frecuentemente es utilizado para el diseño conceptual de aplicaciones de base de datos, y
muchas herramientas de diseño de base de datos emplean sus conceptos.


El modelo ER datos como entidades,
son relaciones entre entidades y
atributos. El objeto básico que el
modelo ER representa es una entidad,
la cual es una “cosa” del mundo real
con existencia independiente. Una
entidad puede ser un objeto con
existencia física (una persona
determinada, un auto, una casa, un
empleado), o puede ser un objeto con
una existencia conceptual (una
compañía, un trabajo, un curso
universitario).


Unidad 7 “SQLite”

Cada entidad posee atributos; propiedades de interés que describen a la entidad. Por ejemplo, una entidad
empleado puede ser descrita por su apellido y nombre, edad, dirección, salario y tarea realizada. Una
entidad particular poseerá un valor para cada uno de sus atributos Los valores de los atributos que
describen cada entidad se tornan la mayor parte de los datos que son almacenados en una base de datos.


Una base de datos normalmente contiene grupos de entidades que son similares. Por ejemplo, una
compañía que posee cientos de empleados puede desear almacenar información similar para cada uno de
sus empleados. Estas entidades “empleado” comparten el mismo conjunto de atributos, pero cada entidad
posee sus propios valores para cada atributo.


Un tipo entidad define una colección (o conjunto) de entidades que poseen los mismos atributos. Cada tipo
entidad en la base de datos es descrita por su nombre y atributos. La colección de todas las entidades de un
tipo entidad particular en la base de datos en un momento determinado es denominado conjunto de
entidades, usualmente citado por el mismo nombre del tipo entidad correspondiente. Por ejemplo,
EMPLEADO se refiere tanto a un tipo de entidad como al conjunto de todos los empleados almacenados en
la base de datos.





Una relación entre dos o más entidades representa una interacción entre las entidades. Existen varias
relaciones implícitas entre tipos entidad. Una relación existe cuando un atributo de un tipo entidad hace
referencia a otro tipo entidad. Por ejemplo, el atributo MANAGER del tipo entidad DEPARTMENT hace
referencia al empleado que gerencia el departamento en cuestión. Un tipo relación entre tipos entidad
define un conjunto de asociaciones (conjunto de relaciones) entre entidades de esos tipos


Como ocurre con tipos entidad y conjuntos de entidades, un tipo relación y su correspondiente conjunto de
relaciones son citados por el mismo nombre. Informalmente, cada relación perteneciente al conjunto de
relaciones es una asociación de entidades, donde la asociación incluye una entidad de cada uno de los tipos
de entidades participantes.


6


Unidad 7 “SQLite”

Dichas relaciones instancias representan el hecho que las entidades participantes están relacionadas en
alguna forma en el dominio de problema correspondiente. Por ejemplo, la relación WORKS_FOR entre los
tipo entidad EMPLOYEE y DEPARTMENT asocia cada empleado con el departamento en el cual el mismo
trabaja.


EL MODELO DE DATOS RELACIONAL


El modelo de datos relacional utiliza el concepto de relación matemática, la cual puede representarse como
una tabla de valores como su bloque de construcción básico. Dicho modelo se basa en la teoría de
conjuntos y la lógica de predicados de primer orden, y sus principales características son la simplicidad y su
fundamentación matemática.


El modelo relacional representa la base de datos como una colección de relaciones, donde cada relación se
asemeja a una tabla de valores o archivo plano de registros. Cuando una relación es pensada como una
tabla de valores, cada fila en la tabla representa una colección de valores de datos relacionados.


En el modelo de datos relacional, cada fila en la tabla representa un hecho que corresponde a una entidad
o relación en el mundo real. El nombre de la tabla y el nombre de las columnas son utilizados para facilitar
la interpretación del significado de los valores en cada fila de la tabla.


Por ejemplo, la tabla EMPLOYEE es denominada de esta manera ya que cada fila representa hechos acerca
de una entidad empleado en particular. Los nombres de columnas especifican cómo interpretar los valores
de datos en cada fila de acuerdo a la columna a la cual cada valor pertenece. Todos los valores en una
columna son del mismo tipo de dato.
















7
8


Unidad 7 “SQLite”





9


Unidad 7 “SQLite”


ELEMENTOS QUE COMPONEN UNA TABLA



Tabla: La tabla es la estructura de almacenamiento básica en un Sistema de Administración de Base de
Datos Relacional (RDBMS). Los datos de las tablas se almacenan en filas y columnas. Cada tabla se define
con un nombre de tabla que la identifica unívocamente y un conjunto de columnas. Una vez que se crea
una tabla, se le pueden insertar filas de datos válidos. Las filas de las tablas pueden ser consultadas,
borradas o actualizadas.


Columna: Una columna representa un tipo de datos en una tabla (por ejemplo, el nombre del cliente en la
tabla Clientes). Una columna también puede ser referenciada como “atributo”. Cada columna tiene un
nombre de columna, un tipo de dato (tal como CHAR, DATE o NUMBER), y un ancho (que puede ser
predeterminado por el tipo de dato, como en el caso de DATE) o una escala y precisión (sólo para el tipo de
dato NUMBER).Todos los valores de una columna determinada tienen el mismo tipo de datos, y éstos están
extraídos de un conjunto de valores legales llamado el dominio de la columna. Las columnas de una tabla
están dispuestas en un orden específico de izquierda a derecha. Sin embargo, el orden de éstas cuando se
almacenan datos no es significativo, pero puede ser especificado cuando se los recupera.


Fila: Una fila es una combinación de valores de columnas de una tabla (por ejemplo, la información acerca
de un cliente en la tabla Clientes). Una fila a menudo se denomina “tupla” o “registro”. Cada tabla tiene
cero o más filas, conteniendo cada una un único valor en cada columna. Las filas están desordenadas; por
defecto, los datos están dispuestos de acuerdo a cómo se insertaron.
10


Unidad 7 “SQLite”

Campo: Un campo se encuentra en la intersección de una fila y una columna. El campo puede contener
datos. Si no hay datos en el campo, se dice que contiene un valor nulo. Los valores de los campos no se
pueden descomponer en componentes más pequeños.


Clave primaria: Una clave primaria es una columna o conjunto de columnas que identifican unívocamente
cada fila de una tabla (por ejemplo, un número de cliente). Una tabla tiene una única clave primaria y debe
contener un valor.


Clave foránea: Una clave foránea es una columna o conjunto de columnas que se refieren a una clave
primaria de la misma tabla o de otra. Se crean estas claves para reforzar las reglas de diseño de la base de
datos relacional. Una tabla puede contener más de una clave foránea. Una combinación clave
primaria/clave foránea crea una relación padre/hijo entre las tablas que las contienen.








































TIPOS DE SENTENCIAS SQL


Las sentencias SQL se dividen en las siguientes categorías:


• Sentencias de Lenguaje de Definición de Datos (DDL – Data Definition Language): crean,
modifican y eliminan objetos de la base de datos (por ejemplo: CREATE, ALTER, DROP, RENAME).
11


Unidad 7 “SQLite”

• Sentencias de Lenguaje de Manipulación de Datos (DML – Data Manipulation Language):
insertan, modifican, eliminan y consultan filas de tablas de la base de datos (INSERT, UPDATE,
DELETE, SELECT).


• Sentencias de Lenguaje de Control de Datos (DCL – Data Control Language): permiten dar o
restringir derechos de acceso a la base de datos y a objetos específicos dentro de la base de datos
(GRANT, REVOKE).


• Sentencias de Control de Transacciones: manejan los cambios hechos por los comandos del
lenguaje de manipulación de datos. Los cambios a los datos pueden ser agrupados en
transacciones lógicas (COMMIT, ROLLBACK).


• Sentencias de Control de Sesión: permiten que un usuario controle las propiedades de la sesión
corriente, incluyendo la posibilidad de habilitar o deshabilitar roles, y cambiar la configuración del
lenguaje (ALTER SESSION, SET ROLE).


• Sentencias de Control de Sistema: cambian las propiedades de una instancia de Oracle 11g.
Permiten cambiar diferentes parámetros de configuración, tal como el número mínimo de
servidores compartidos, matar una sesión determinada y ejecutar otras tareas (ALTER SYSTEM,
ALTER SESSION, etc.).


• Sentencias de SQL embebido: permite la incorporación de sentencias DDL, DML y de control de
transacciones en un programa escrito en lenguaje procedural (OPEN, CLOSE, FETCH, EXECUTE, etc.).



12


Unidad 7 “SQLite”


¿Qué es una Base de Datos Embebida?


Una Base de Datos Embebida o Empotrada es aquella que NO inicia un servicio en nuestra máquina, es
independiente de la aplicación, pudiéndose enlazar directamente a nuestro código fuente o bien utilizarse
en forma de librería. Se utiliza normalmente en tres áreas: en dispositivos móviles (por ejemplo: celulares,
PDAs), en sistemas de tiempo real y en aplicaciones de Internet donde el usuario no se relaciona con la
base de datos subyacente de forma directa.


Normalmente las bases de datos embebidas comparten una serie de características comunes:
• Su pequeño tamaño.
• "Small footprint", en términos de tamaño de código añadido a nuestra aplicación.
• Recursos que consumen.


Estas bases de datos pueden estar contenidas exclusivamente en memoria (y al cerrar la aplicación, las
tablas se guardan en disco) o en disco, siendo en el primer caso mucho más rápidas.


Ejemplos de DBMS embebidos: Berkeley DB de Oracle Corporation, EffiPRoz de EffiProz Systems, ElevateDB
de Elevate Software, Inc., Empress Embedded Database de Empress Software, Extensible Storage Engine de
Microsoft, eXtremeDB de McObject, Firebird Embedded, HSQLDB de HSQLDB.ORG, Informix Dynamic
Server (IDS) de IBM, InnoDB de Oracle Corporation, ITTIA DB de ITTIA, RDM Embedded and RDM Server de
Raima Inc., SolidDB de IBM, SQLite, SQL Server Compact de Microsoft Corporation, Valentina DB de
Paradigma Software, VistaDB de VistaDB Software, Inc., y Advantage_Database_Server de Sybase Inc.


13


Unidad 7 “SQLite”


¿QUÉ ES SQLite?




SQLite es un sistema gestor de bases de datos
embebido, su creador es D. Richard Hipp, el cual
implementa una pequeña librería de
aproximadamente 500kb, programado en el
lenguaje C, de dominio público, totalmente libre.


Uno de las primeras diferencia entre los motores de Bases de datos convencionales es su arquitectura
cliente/servidor, pues SQLite es independiente, simplemente se realizan llamadas a subrutinas o funciones
de las propias librerías de SQLite, lo cual reduce ampliamente la latencia en cuanto al acceso a las bases de
datos. Con lo cual podemos decir que las base de datos compuesta por la definición de las tablas, índices y
los propios datos son guardados por un solo archivo estándar y en una sola computadora.


La historia del proyecto SQLite


Cuando D. Richard Hipp trabajaba desarrollando software para la fuerza naval de los Estados Unidos,
comenzó a desarrollar SQLite, según él cuenta con sus propias palabras: El proyecto SQLite surgió de una
necesidad personal, para mi propio uso.


En enero de 2000 D. Richard Hipp estaba trabajando con su equipo de la General
Dynamics en la Fuerza naval de los Estados Unidos, en un proyecto de software, el
cual se conectaba a una base de datos Informix, el motor funcionaba muy bien,
pero habían tenido problemas para hacer una reconfiguración cuando el sistema se
reiniciaba. Luego cambiaron a PostgreSQL, pero administrar la base de datos era un
poco más compleja. Fue en ese momento cuando surgió la idea de escribir un
simple motor de base de datos SQL que permitiera leer los archivos del disco duro,
y luego ser llamados en diferentes solicitudes.
Cinco meses más tarde comenzó a escribir las primeras versiones de lo que hoy conocemos como SQLite,
con el pensamiento de que sería útil en algún problema similar.


Es claro que SQLite tiene la capacidad de reemplazar a grandes motores de Bases de Datos y acoplarse al
desarrollo de nuestros proyectos informáticos, ya sea en ambientes de prototipos de sistemas como así
también en complejos y robustos software.


Características


Tamaño: SQLite tiene una pequeña memoria y una única biblioteca es necesaria para acceder a bases de
datos, lo que lo hace ideal para aplicaciones de bases de datos incorporadas.


Rendimiento de base de datos: SQLite realiza operaciones de manera eficiente y es más rápido que MySQL
y PostgreSQL.
14


Unidad 7 “SQLite”

Estabilidad: SQLite es compatible con ACID, reunión de los cuatro criterios de Atomicidad, Consistencia,
Aislamiento y Durabilidad.


SQL: implementa un gran subconjunto de la ANSI – 92 SQL estándar, incluyendo sub-consultas, generación
de usuarios, vistas y triggers.


Interfaces: cuenta con diferentes interfaces del API, las cuales permiten trabajar con C++, PHP, Perl,
Python, Ruby, Tcl, Groovy, etc.


Costo: SQLite es de dominio público, y por tanto, es libre de utilizar para cualquier propósito sin costo y se
puede redistribuir libremente.


No posee configuración: De la forma en que fue creado y diseñado SQLite, NO necesita ser instalado. NO
prender, reiniciar o apagar un servidor, e incluso configurarlo. Esta cualidad permite que no haya un
administrador de base de datos para crear las tablas, vistas, asignar permisos. O bien la adopción de
medidas de recuperación de servidor por cada caída del sistema.


Portabilidad: SQLite puede ser ejecutado en diferentes sistemas operativos, como ser Windows, Linux,
BSD, Mac OS X, Solaris, HPUX,AIX o estar embebido en muchos otros como QNX, VxWorks, Symbian, Palm
OS, Windows CE. Se pude notar que muchos de ellos trabajan a 16, 32 y 64 Bits. La portabilidad no está
dada en sí por el software, sino por la base de datos condensada en un solo fichero, que puede estar
situado en cualquier directorio, trayendo como ventaja que la base de datos puede ser fácilmente copiada
a algún dispositivo USB o ser enviada vía correo electrónico.



15


Unidad 7 “SQLite”

Registros de longitud variable: Generalmente los motores asignan una cantidad fija de espacio en disco
para cada fila en la mayoría de los campos de una determinada tabla. Por ejemplo, tomemos un campo de
tipo VARCHAR(255), esto significa que el motor le asignará 255 bytes de espacio fijo en disco,
independientemente de la cantidad de información que se almacene en ese campo. En cambio, SQLite
aplica su tecnología y realizará todo lo contrario, utilizando para ello la cantidad de espacio en disco
necesario para almacenar la información real del campo. Tomando el ejemplo anterior, si quisiera
almacenar un solo carácter en un campo definido como VARCHAR(255), entonces un único byte de espacio
de disco se consume. El uso de registros de longitud variable por SQLite, tiene una serie de ventajas, entre
ellas el resultado de un pequeño archivo de base de datos y optimización de la velocidad de la misma,
puesto que hay menos información desperdiciada que leer y recorrer.


Así como encontramos algunas ventajas y características realmente asombrosas, también cuenta con
algunas limitaciones:


Limitaciones en Where: esta limitación está dada por el soporte para clausulas anidadas.


Falta de Clave Foránea: se hace caso omiso de las claves foráneas; esto quiere decir, cuando se realice la
creación de la tabla desde el modo consola, está permitiendo el uso de la clausura, aunque no realizara el
chequeo de la misma.


Falta de documentación en español: si bien ya contamos con una comunidad latino americana de SQLite,
sería importante encontrar mucha más documentación, libros, review, etc. como muchos otros motores de
bases de datos cuentan hoy en día.


Sintaxis SQL


• Distinción entre mayúsculas y minúsculas: las declaraciones SQL, incluidos los nombres de objetos,
no distinguen entre mayúsculas y minúsculas. No obstante, las declaraciones SQL se suelen escribir
con palabras clave SQL en mayúscula, por lo que seguiremos dicha convención en este documento.
Aunque la sintaxis SQL no distingue entre mayúsculas y minúsculas, los valores de texto literales sí
lo hacen en SQL. Las operaciones de comparación y ordenación también distinguen entre
mayúsculas y minúsculas, según se haya especificado en la secuencia de intercalación definida para
una columna o una operación. Para obtener más información, consulte COLLATE.


• Espacio en blanco: debe usarse un carácter de espacio en blanco (por ejemplo, un espacio, una
tabulación, un salto de línea, etc.) para separar palabras independientes en una declaración SQL.
Sin embargo, el espacio en blanco es optativo entre palabras y símbolos. El tipo y la cantidad de
caracteres de espacio en blanco en una declaración SQL no es relevante. Puede utilizar espacios en
blanco (como la sangría o saltos de línea) para dar un formato más claro a las declaraciones SQL sin
que afecte a su funcionalidad.



Declaraciones de manipulación y definición de datos
16


Unidad 7 “SQLite”

Las declaraciones de manipulación de datos son declaraciones SQL más utilizadas. Estas declaraciones se
utilizan para recuperar, añadir y eliminar datos de las tablas de la base de datos. Se admiten las siguientes
declaraciones de manipulación de datos.


SELECT


La declaración SELECT se utiliza para consultar la base de datos. El resultado de una declaración SELECT es
cero o más filas de datos, donde cada fila tiene un número fijo de columnas. El número de columnas del
resultado se especifica mediante el nombre de la columna result o la lista de expresiones entre las palabras
clave SELECT y FROM (opcional).





sql-statement ::= SELECT [ALL | DISTINCT] result
[FROM table-list]
[WHERE expr]
[GROUP BY expr-list]
[HAVING expr]
[compound-op select-statement]*
[ORDER BY sort-expr-list]
[LIMIT integer [( OFFSET | , ) integer]]

result ::= result-column [, result-column]*

result-column ::= * | table-name . * | expr [[AS] string]

table-list ::= table [ join-op table join-args ]*

table ::= table-name [AS alias] |
( select ) [AS alias]

join-op ::= , | [NATURAL] [LEFT | RIGHT | FULL] [OUTER | INNER | CROSS]
JOIN

join-args ::= [ON expr] [USING ( id-list )]

compound-op ::= UNION | UNION ALL | INTERSECT | EXCEPT
sort-expr-list ::= expr [sort-order] [, expr [sort-order]]*
sort-order ::= [COLLATE collation-name] [ASC | DESC]
collation-name ::= BINARY | NOCASE
17


Unidad 7 “SQLite”

Cualquier expresión arbitraria se puede utilizar como resultado. Si una expresión del resultado es *, todas
las columnas de todas las tablas se reemplazan por dicha expresión. Si la expresión es el nombre de una
tabla seguido de .*, el resultado es todas las columnas de dicha tabla.


La palabra clave DISTINCT provoca la devolución de un subconjunto de filas de resultados (donde cada fila
es distinta). Se asume que todos los valores de NULL son iguales. El comportamiento predeterminado es la
devolución de todas las filas de resultados, que se pueden explicitar con la palabra clave ALL.


La consulta se ejecuta en una o varias tablas especificadas con la palabra clave FROM. Si los distintos
nombres de las tablas están separados por comas, la consulta utiliza la unión cruzada de las distintas tablas.
La sintaxis de JOIN también se puede utilizar para especificar el modo de unión de las tablas. El único tipo
de unión externa admitida es LEFT OUTER JOIN. La expresión de la cláusula ON en join-args debe producir
como resultado un valor booleano. Se puede utilizar una subconsulta entre paréntesis como una tabla de la
cláusula FROM. Se puede omitir toda la cláusula FROM, en cuyo caso, el resultado será una única fila con
los valores de la lista de expresiones result.


La cláusula WHERE se utiliza para limitar el número de filas recuperadas por la consulta. Las expresiones de
la cláusula WHERE deben producir como resultado un valor booleano. El filtrado de la cláusula WHERE se
lleva a cabo antes de cualquier agrupación, por lo que las expresiones de la cláusula WHERE no pueden
incluir funciones de agregación.


La cláusula GROUP BY combina una o varias filas del resultado en una sola fila de resultados. La cláusula
GROUP BY es especialmente útil cuando el resultado contiene funciones de agregación. Las expresiones de
la cláusula GROUP BY no tienen por qué ser expresiones que aparezcan en la lista de expresiones SELECT.



18


Unidad 7 “SQLite”



La cláusula HAVING se parece a WHERE, en el sentido que limita las filas devueltas por la declaración. Sin
embargo, la cláusula HAVING se aplica tras producirse cualquier agrupación especificada por una cláusula
GROUP BY. En consecuencia, la expresión HAVING puede hacer referencia a valores que incluyan funciones
de agregación. No es necesario que aparezca ninguna expresión de la cláusula HAVING en la lista SELECT. Al
igual que ocurre con la expresión WHERE, la expresión HAVING debe producir como resultado un valor
booleano.




La cláusula ORDER BY provoca la ordenación de las filas de resultados. El argumento sort-expr-list de la
cláusula ORDER BY es una lista de expresiones utilizadas como clave para la ordenación. Las expresiones no
tienen por qué formar parte del resultado de una declaración SELECT, pero en una declaración SELECT
compuesta (una declaración SELECT que utilice uno de los operadores compound-op), cada expresión de
ordenación debe coincidir exactamente con una de las columnas de resultados. Cada expresiones de
ordenación puede ir acompañada, de forma opcional, de una cláusula sort-order formada por la palabra
clave COLLATE y el nombre de la función de intercalación utilizada para ordenar el texto, o bien la palabra
clave ASC o DESC para especificar el tipo de ordenación (ascendente o descendente). sort-order se puede
omitir y utilizar el valor predeterminado (orden ascendente). Para obtener una definición de la cláusula
COLLATE y de las funciones de intercalación, consulte COLLATE.


La cláusula LIMIT pone un límite superior al número de filas devueltas en el resultado. Un valor negativo de
LIMIT indica la ausencia de límite superior. El OFFSET que sigue a LIMIT especifica el número de filas que se
deben saltar al principio del conjunto de resultados. En una consulta SELECT compuesta, la cláusula LIMIT
tal vez aparezca sólo tras la declaración SELECT final. El límite se aplica a toda la consulta. Tenga en cuenta
19


Unidad 7 “SQLite”

que si utiliza la palabra clave OFFSET en la cláusula LIMIT, el límite es el primer entero y el desfase es el
segundo entero. Si se utiliza una coma en vez de la palabra clave OFFSET, el desfase es primer número y el
límite es el segundo. Esta aparente contradicción es intencionada: maximiza la compatibilidad con sistemas
de bases de datos SQL heredados.


Un SELECT compuesto está formado por dos o más declaraciones SELECT simples conectadas por uno de los
operadores UNION, UNION ALL, INTERSECT o EXCEPT. En un SELECT compuesto, todas las declaraciones
SELECT que lo forman deben especificar el mismo número de columnas de resultados. Sólo puede haber
una cláusula ORDER BY única tras la declaración SELECT final (y antes de la cláusula LIMIT única, si se ha
especificado). Los operadores UNION y UNION ALL combinan los resultados de las declaraciones SELECT
siguiente y anterior en una sola tabla. La diferencia reside en que en UNION, todas las filas de resultados
son distintas, mientras que en UNION ALL puede haber duplicados. El operador INTERSECT toma la
intersección de los resultados de las declaraciones SELECT anterior y siguiente. EXCEPT toma el resultado
del SELECT anterior tras eliminar los resultados del SELECT siguiente. Si se conectan tres o más
declaraciones SELECT en una compuesta, se agrupan de la primera a la última.





INSERT


La declaración INSERT se expresa de dos formas básicas y se utiliza para llenar tablas con datos.


sql-statement ::=

INSERT [OR conflict-algorithm] INTO [database-name.] table-name [(column-
list)] VALUES (value-list) |

INSERT [OR conflict-algorithm] INTO [database-name.] table-name [(column-
list)] select-statement

REPLACE INTO [database-name.] table-name [(column-list)] VALUES (value-
list) |

REPLACE INTO [database-name.] table-name [(column-list)] select-statement


La primera expresión (con la palabra clave VALUES) crea una única nueva fila en una tabla existente. Si no
se especifica un column-list, el número de valores debe coincidir con el número de columnas de la tabla. Si
se especifica un column-list, el número de valores debe coincidir con el número de columnas especificadas.
20


Unidad 7 “SQLite”

Las columnas de la tabla que no aparecen en la lista de columnas se llenan con valores predeterminados en
el momento de creación de la tabla, o con NULL si no hay ningún valore predeterminado definido.


La segunda expresión de la declaración INSERT toma los datos de una declaración SELECT. El número de
columnas del resultado de SELECT debe coincidir exactamente con el número de columnas de la tabla si no
se especifica column-list, o debe coincidir con el número de columnas especificadas en el column-list. Se
realiza una nueva entrada en la tabla por cada fila del resultado de SELECT. El SELECT puede ser simple o
compuesto. Para obtener una definición de las declaraciones SELECT admitidas, consulte SELECT.


El conflict-algorithm opcional permite especificar un algoritmo de resolución de conflictos de restricciones
alternativo y utilizarlo durante este comando. Para obtener una explicación y una definición de los
algoritmos de conflictos, consulte ON CONFLICT (algoritmos de conflictos).


Las dos formas de REPLACE INTO de la declaración equivalen a utilizar la expresión INSERT [OR conflict-
algorithm] estándar con el algoritmo de conflictos REPLACE (es decir, el formulario INSERT OR REPLACE...).


UPDATE


La declaración UPDATE se utiliza para cambiar el valor de las columnas de un conjunto de filas de una tabla.


sql-statement ::= UPDATE [OR conflict-algorithm] [database-name.] table-
name


SET assignment [, assignment]*



conflict-algorithm



::=
[WHERE expr]

ROLLBACK | ABORT | FAIL | IGNORE | REPLACE

assignment

::=

column-name = expr

Cada asignación de un UPDATE especifica un nombre de columna a la izquierda del signo igual y una
expresión arbitraria a la derecha. La expresión puede utilizar los valores de otras columnas. Todas las
expresiones se calculan antes de realizar ninguna asignación. Para obtener una definición de las
expresiones permitidas, consulte Expresiones.


La cláusula WHERE se utiliza para limitar las filas que se actualizan. La expresión de la cláusula WHERE debe
producir como resultado un valor booleano.


El conflict-algorithm opcional permite especificar un algoritmo de resolución de conflictos de restricciones
alternativo y utilizarlo durante este comando. Para obtener una explicación y una definición de los
algoritmos de conflictos, consulte ON CONFLICT (algoritmos de conflictos).


DELETE


El comando DELETE se utiliza para eliminar registros de una tabla.
21


Unidad 7 “SQLite”


sql-statement ::= DELETE FROM [database-name.] table-name [WHERE expr]


El comando está formado por las palabras clave DELETE FROM seguidas del nombre de la tabla de la que se
eliminarán los registros. Sin una cláusula WHERE, se eliminan todas las filas de la tabla. Si se facilita una
cláusula WHERE, sólo se eliminan las filas que coincidan con la expresión. La expresión de la cláusula
WHERE debe producir como resultado un valor booleano.


CREATE TABLE


Una declaración CREATE TABLE está formada por las palabras clave CREATE TABLE, seguidas del nombre de
la nueva tabla y (entre paréntesis) una lista de definiciones y restricciones de columnas. El nombre de la
tabla puede ser un identificador o una cadena.


sql-statement ::= CREATE [TEMP | TEMPORARY] TABLE [IF NOT EXISTS]
[database-name.] table-name
( column-def [, column-def]* [, constraint]* )

sql-statement ::= CREATE [TEMP | TEMPORARY] TABLE [database-name.] table-
name AS select-statement

column-def ::= name [type] [[CONSTRAINT name] column-constraint]*

type ::= typename | typename ( number ) | typename ( number ,
number )

column-constraint ::= NOT NULL [ conflict-clause ] |
PRIMARY KEY [sort-order] [ conflict-clause ]
[AUTOINCREMENT] |


UNIQUE [conflict-clause] |
CHECK ( expr ) |
DEFAULT default-value |
COLLATE collation-name

constraint ::= PRIMARY KEY ( column-list ) [conflict-clause] |
UNIQUE ( column-list ) [conflict-clause] |
CHECK ( expr )

conflict-clause ::= ON CONFLICT conflict-algorithm

conflict-algorithm ::= ROLLBACK | ABORT | FAIL | IGNORE | REPLACE

default-value ::= NULL | string | number | CURRENT_TIME | CURRENT_DATE |
CURRENT_TIMESTAMP

sort-order ::= ASC | DESC

collation-name ::= BINARY | NOCASE
22


Unidad 7 “SQLite”

Cada definición de columna es el nombre de la columna seguido de su tipo de datos correspondiente y, por
último, una o varias restricciones de columna opcionales. El tipo de datos de la columna limita los datos
que pueden guardarse en dicha columna. Si se intenta guardar un valor en una columna con tipos de datos
distintos, el motor de ejecución convertirá el valor en el tipo adecuado si es posible, o lanzará un error.
Consulte la sección Compatibilidad de tipos de datos para obtener más información.


La restricción de la columna NOT NULL indica que la columna no puede contener valores NULL.


Una restricción UNIQUE provoca la creación de un índice en la columna o columnas especificadas. Este
índice puede contener claves únicas: dos filas no pueden contener valores duplicados o combinaciones de
valores para la columna o columnas especificadas. Una declaración CREATE TABLE puede tener varias
restricciones UNIQUE, incluidas varias columnas con una restricción UNIQUE en la definición de la columna
o múltiples restricciones UNIQUE en el nivel de las tablas.


Una restricción CHECK define una expresión que se calcula y debe ser true para poder insertar o actualizar
los datos de una fila. La expresión CHECK debe producir como resultado un valor booleano.


Una cláusula COLLATE en una definición de columna especifica qué función de intercalación de texto debe
usarse al comparar entradas de texto en la columna. La función de intercalación BINARY se utiliza de forma
predeterminada. Para obtener más información sobre la cláusula COLLATE y sus funciones de intercalación,
consulte COLLATE.


La restricción DEFAULT especifica un valor predeterminado para utilizarlo con un INSERT. El valor puede ser
NULL, una constante de cadena o un número. El valor predeterminado también puede ser una de las
palabras clave especiales (no distinguen entre mayúsculas y minúsculas) CURRENT_TIME, CURRENT_DATE o
CURRENT_TIMESTAMP. Si el valor es NULL, se inserta (literalmente) una constante de cadena o un número
en la columna cada vez que una declaración INSERT no especifique un valor para la columna. Si el valor es
CURRENT_TIME, CURRENT_DATE o CURRENT_TIMESTAMP, se inserta en la columna la fecha/hora UTC
actual. Para CURRENT_TIME, el formato es HH:MM:SS. Para CURRENT_DATE, el formato es YYYY-MM-DD.
El formato de CURRENT_TIMESTAMP es YYYY-MM-DD HH:MM:SS.


Al especificar un PRIMARY KEY, normalmente sólo se crea un índice de UNIQUE en la columna o columnas
correspondientes. Sin embargo, si la restricción PRIMARY KEY se encuentra en una sola columna con el tipo
de datos INTEGER, dicha columna se puede utilizar internamente como clave principal real de la tabla. Esto
significa que la columna sólo puede contener valores enteros únicos. Si una tabla no contiene una columna
INTEGER PRIMARY KEY, se generará automáticamente una clave de enteros al insertar una fila. Siempre se
puede acceder a la clave principal de una fila mediante uno de los nombres especiales ROWID, OID o
_ROWID_. Puede utilizar estos nombres tanto si son un INTEGER PRIMARY KEY declarado explícitamente o
un valor interno generado. Una columna INTEGER PRIMARY KEY también puede contener la palabra clave
AUTOINCREMENT. Cuando se utiliza la palabra clave AUTOINCREMENT, la base de datos genera e inserta
automáticamente una clave de enteros que aumenta secuencialmente en la columna INTEGER PRIMARY
KEY cuando se ejecuta una declaración INSERT.
23


Unidad 7 “SQLite”

Sólo puede haber una restricción PRIMARY KEY en una declaración CREATE TABLE. Puede ser parte de una
definición de columna o tratarse de una restricción PRIMARY KEY única en el nivel de tablas. Una columna
de clave principal es NOT NULL implícitamente.


La cláusula conflict-clause opcional con restricciones permite especificar un algoritmo de resolución de
conflictos de restricciones alternativo predeterminado para dicha restricción. El valor predeterminado es
ABORT. Las restricciones distintas de la misma tabla pueden tener distintos algoritmos de resolución de
conflictos predeterminados. Si una declaración INSERT o UPDATE especifica un algoritmo de resolución de
conflictos de restricciones distinto, dicho algoritmo se utilizará en vez del especificado en la declaración
CREATE TABLE. Consulte la sección ON CONFLICT (algoritmos de conflictos) para obtener más información.


Las restricciones adicionales, como las FOREIGN KEY, no producen errores porque el motor de ejecución las
ignora.


Si las palabras clave TEMP o TEMPORARY se encuentran entre CREATE y TABLE, la tabla creada sólo será
visible dentro de la misma conexión de base de datos (instancia de SQLConnection). Se elimina
automáticamente cuando se cierra la conexión de base de datos. Todos los índices creados en una tabla
temporal también son temporales. Las tablas y los índices temporales se guardan en un archivo
independiente distinto del archivo principal de la base de datos.


No hay límites arbitrarios en el número de columnas o en el número de restricciones de una tabla.
Tampoco hay límites arbitrarios en la cantidad de datos de una fila.


La expresión CREATE TABLE AS define la tabla como el conjunto de resultados de una consulta. Los
nombres de las columnas de la tabla son los nombres de las columnas del resultado.


Si la cláusula IF NOT EXISTS opcional está presente y ya existe otra tabla con el mismo nombre, la base de
datos ignora el comando CREATE TABLE.


Se puede eliminar una tabla con la declaración DROP TABLE, y se pueden realizar cambios limitados con la
declaración ALTER TABLE.


ALTER TABLE


El comando ALTER TABLE permite al usuario renombrar o agregar una nueva columna a una tabla existente.
No es posible eliminar una columna de una tabla.


sql-statement ::= ALTER TABLE [database-name.] table-name alteration

alteration ::= RENAME TO new-table-name
alteration ::= ADD [COLUMN] column-def
24


Unidad 7 “SQLite”

Se utiliza la sintaxis RENAME TO para renombrar la tabla identificada por [database-name.] table-name
como new-table-name. Este comando no se puede utilizar para mover una tabla entre bases de datos
asociadas; sólo sirve para renombrar una tabla dentro de la misma base de datos.


Si la tabla que se va a renombrar contiene activadores o índices, éstos permanecen asociados a la tabla una
vez renombrada. No obstante, si hay definiciones o declaraciones de vistas ejecutadas por activadores que
hacen referencia a la tabla que se está renombrando, no se actualizan automáticamente con el nuevo
nombre de la tabla. Si la tabla renombrada tiene vistas o activadores asociados, debe descartarlos
manualmente y recrear las definiciones de los activadores o las vistas con el nuevo nombre de la tabla.


Se utiliza la sintaxis ADD [COLUMN] para agregar una nueva columna a una tabla existente. La nueva
columna siempre se añade al final de la lista de las columnas existentes. La cláusula column-def puede
expresarse de todas las formas permitidas en una declaración CREATE TABLE, aunque con las siguientes
restricciones:
• La columna no puede tener una restricción PRIMARY KEY o UNIQUE.
• La columna no puede tener un valor predeterminado CURRENT_TIME, CURRENT_DATE o
CURRENT_TIMESTAMP.
• Si se especifica una restricción NOT NULL, la columna debe tener un valor predeterminado distinto
de NULL.


El tiempo de ejecución de la declaración ALTER TABLE no se ve afectado por la cantidad de datos de la
tabla.


DROP TABLE


La declaración DROP TABLE elimina una tabla agregada con una declaración CREATE TABLE. La tabla con el
table-name especificado es la que se descarta. Se elimina completamente de la base de datos y del archivo
del disco. No es posible recuperar la tabla. Todos los índices asociados a la tabla se eliminan también.


sql-statement ::= DROP TABLE [IF EXISTS] [database-name.] table-name


De forma predeterminada, la declaración DROP TABLE no reduce el tamaño del archivo de base de datos. El
espacio libre de la base de datos se conserva y se utiliza en operaciones INSERT posteriores. Para eliminar
espacio libre de la base de datos, utilice el método SQLConnection.clean(). Si el parámetro autoClean se
establece en true al crear la base de datos por primera vez, el espacio se libera automáticamente.


La cláusula IF EXISTS opcional desactiva el error que se produciría si la tabla no existiese.


Compatibilidad de tipos de datos


Al contrario de lo que ocurre con la mayoría de bases de datos SQL, el motor de base de datos SQLite no
exige que las columnas de las tablas contengan valores de tipos concretos. El motor de ejecución utiliza dos
conceptos, las clases de almacenamiento y la afinidad de columnas, para controlar los tipos de datos. En
25


Unidad 7 “SQLite”

esta sección se describen las clases de almacenamiento y la afinidad de columnas, así como el modo en que
se resuelven distintos tipos de datos en diversas condiciones:


Las clases de almacenamiento representan los tipos de datos reales que se utilizan para guardar valores en
una base de datos. Están disponibles las siguientes clases de almacenamiento:


• INTEGER: el valor es un entero con signo.
• REAL: el valor es un valor de número de coma flotante.
• TEXT: el valor es una cadena de texto.
• BLOB: el valor es un objeto grande binario (BLOB); dicho de otro modo, datos binarios sin procesar.
• NULL: el valor es un valor NULL.




Todos los valores suministrados a la base de datos como literales incorporados en una declaración SQL o
valores ligados al uso de parámetros en una declaración SQL preparada se asignan a una clase de
almacenamiento antes de que se ejecute la declaración SQL.


A los literales que forman parte de una declaración SQL se les asigna una clase de almacenamiento TEXT si
están encerrados con comillas simples o dobles, una clase INTEGER si el literal está especificado como un
número sin comillas, sin punto decimal ni exponente, una clase REAL si el literal es un número sin comillas,
con punto decimal o exponente y una clase de almacenamiento NULL si el valor es NULL. Los literales con
clase de almacenamiento BLOB se especifican con la notación X'ABCD'. Para obtener más información,
consulte Valores literales en expresiones.
26


Unidad 7 “SQLite”


Nuestra primera base de datos. Creación y Manipulación


Para utilizar SQLite disponemos de los conocimientos generales sobre SQL. En nuestro ejemplo, pasaremos
directamente a crear una clase de ayuda de base de datos para la aplicación. Creamos una clase de ayuda
para que los detalles relacionados con la creación y actualización de la base de datos, de apertura y cierre
de conexiones, y de ejecución de consultas se incluyan en un mismo punto y no se repitan en el código de
la aplicación.


De este modo, nuestras clases Activity y Service podrán utilizar sencillos métodos get e insert, con objetos
específicos que representen el modelo, o Collections en lugar de abstracciones específicas de base de datos
(como el objeto Cursor de Android que representa un conjunto de resultados de consulta). Imagine que
esta clase es una Capa de Acceso de Datos (DAL) en miniatura.



public class DBHelper {

public static final String DEVICE_ALERT_ENABLED_ZIP = "DAEZ99";
public static final String DB_NAME = "w_alert";
public static final String DB_TABLE = "w_alert_loc";
public static final int DB_VERSION = 3;

private static final String CLASSNAME = DBHelper.class.getSimpleName();
private static final String[] COLS = new String[] { "_id", "zip", "city", "region",
"lastalert", "alertenabled" };

private SQLiteDatabase db;
private final DBOpenHelper dbOpenHelper;

//
// inner classes
//

public static class Location {

public long id;
public long lastalert;
public int alertenabled;
public String zip;
// include city and region because geocode is expensive
public String city;
public String region;

public Location() {
}

public Location(final long id, final long lastalert, final int alertenabled,
final String zip,
final String city, final String region) {
this.id = id;
this.lastalert = lastalert;
this.alertenabled = alertenabled;
this.zip = zip;
this.city = city;
this.region = region;
27


Unidad 7 “SQLite”


}

@Override
public String toString() {
return this.zip + " " + this.city + ", " + this.region;
}
}

private static class DBOpenHelper extends SQLiteOpenHelper {

private static final String DB_CREATE = "CREATE TABLE "
+ DBHelper.DB_TABLE
+ " (_id INTEGER PRIMARY KEY, zip TEXT UNIQUE NOT NULL, city TEXT, region
TEXT, lastalert INTEGER, alertenabled INTEGER);";

public DBOpenHelper(final Context context) {
super(context, DBHelper.DB_NAME, null, DBHelper.DB_VERSION);
}

@Override
public void onCreate(final SQLiteDatabase db) {
try {
db.execSQL(DBOpenHelper.DB_CREATE);
} catch (SQLException e) {
Log.e(Constants.LOGTAG, DBHelper.CLASSNAME, e);
}
}

@Override
public void onOpen(final SQLiteDatabase db) {
super.onOpen(db);
}

@Override
public void onUpgrade(final SQLiteDatabase db, final int oldVersion, final int
newVersion) {
db.execSQL("DROP TABLE IF EXISTS " + DBHelper.DB_TABLE);
onCreate(db);
}
}

//
// end inner classes
//

public DBHelper(final Context context) {
this.dbOpenHelper = new DBOpenHelper(context);
establishDb();
}

private void establishDb() {
if (this.db == null) {
this.db = this.dbOpenHelper.getWritableDatabase();
}
}

public void cleanup() {
if (this.db != null) {
this.db.close();
28


Unidad 7 “SQLite”


this.db = null;
}
}

public void insert(final Location location) {
ContentValues values = new ContentValues();
values.put("zip", location.zip);
values.put("city", location.city);
values.put("region", location.region);
values.put("lastalert", location.lastalert);
values.put("alertenabled", location.alertenabled);
this.db.insert(DBHelper.DB_TABLE, null, values);
}

public void update(final Location location) {
ContentValues values = new ContentValues();
values.put("zip", location.zip);
values.put("city", location.city);
values.put("region", location.region);
values.put("lastalert", location.lastalert);
values.put("alertenabled", location.alertenabled);
this.db.update(DBHelper.DB_TABLE, values, "_id=" + location.id, null);
}

public void delete(final long id) {
this.db.delete(DBHelper.DB_TABLE, "_id=" + id, null);
}

public void delete(final String zip) {
this.db.delete(DBHelper.DB_TABLE, "zip='" + zip + "'", null);
}

public Location get(final String zip) {
Cursor c = null;
Location location = null;
try {
c = this.db.query(true, DBHelper.DB_TABLE, DBHelper.COLS, "zip = '" + zip +
"'", null, null, null, null,
null);
if (c.getCount() > 0) {
c.moveToFirst();
location = new Location();
location.id = c.getLong(0);
location.zip = c.getString(1);
location.city = c.getString(2);
location.region = c.getString(3);
location.lastalert = c.getLong(4);
location.alertenabled = c.getInt(5);
}
} catch (SQLException e) {
Log.v(Constants.LOGTAG, DBHelper.CLASSNAME, e);
} finally {
if (c != null && !c.isClosed()) {
c.close();
}
}
return location;
}
29


Unidad 7 “SQLite”


public List<Location> getAll() {
ArrayList<Location> ret = new ArrayList<Location>();
Cursor c = null;
try {
c = this.db.query(DBHelper.DB_TABLE, DBHelper.COLS, null, null, null, null,
null);

int numRows = c.getCount();
c.moveToFirst();
for (int i = 0; i < numRows; ++i) {
Location location = new Location();
location.id = c.getLong(0);
location.zip = c.getString(1);
location.city = c.getString(2);
location.region = c.getString(3);
location.lastalert = c.getLong(4);
location.alertenabled = c.getInt(5);
// don't return special device alert enabled marker location in all list
if (!location.zip.equals(DBHelper.DEVICE_ALERT_ENABLED_ZIP)) {
ret.add(location);
}
c.moveToNext();
}
} catch (SQLException e) {
Log.v(Constants.LOGTAG, DBHelper.CLASSNAME, e);
} finally {
if (c != null && !c.isClosed()) {
c.close();
}
}
return ret;
}

public List<Location> getAllAlertEnabled() {
Cursor c = null;
ArrayList<Location> ret = new ArrayList<Location>();
try {
c = this.db.query(DBHelper.DB_TABLE, DBHelper.COLS, "alertenabled = 1", null,
null, null, null);
int numRows = c.getCount();
c.moveToFirst();
for (int i = 0; i < numRows; ++i) {
Location location = new Location();
location.id = c.getLong(0);
location.zip = c.getString(1);
location.city = c.getString(2);
location.region = c.getString(3);
location.lastalert = c.getLong(4);
location.alertenabled = c.getInt(5);
// don't return special device alert enabled marker location in all list
if (!location.zip.equals(DBHelper.DEVICE_ALERT_ENABLED_ZIP)) {
ret.add(location);
}
c.moveToNext();
}
} catch (SQLException e) {
Log.v(Constants.LOGTAG, DBHelper.CLASSNAME, e);
} finally {
if (c != null && !c.isClosed()) {
c.close();
30


Unidad 7 “SQLite”


}
}
return ret;
}
}



En la clase DBHelper tenemos varias constantes que definen importantes valores estáticos relacionados con
la base de datos con la que queremos trabajar, como el nombre, la versión y un nombre de tabla.


La primera clase interna es un sencillo bean Location que se utiliza para representar la ubicación
seleccionada por el usuario para guardar. Intencionadamente, esta clase carece de mecanismos de acceso y
mutadores, ya que añaden sobrecarga y no los necesitamos para la aplicación (no la mostraremos).
La segunda clase interna es una implementación SQLiteOpenHelper.


Nuestra clase interna DBOpenHelper amplía SQLiteOpenHelper, que es una clase proporcionada por
Android para ayudar a la creación, actualización y apertura de bases de datos. En esta clase incluimos una
cadena que representa la consulta CREATE que utilizaremos para crear la tabla de la base de datos; muestra
las columnas exactas y los tipos que tendrá nuestra tabla. Los tipos de datos empleados son muy sencillos:
utilizaremos principalmente INTEGER y TEXT (si necesita información adicional sobre otros tipos admitidos
por SQLite, consulte la documentación en http://www.sqlite.org/datatype3.html).





Además, en DBOpenHelper implementamos varios métodos de retrollamada SQLiteOpenHelper, en
especial onCreate y onUpgrade (también se admite onOpen pero no lo utilizaremos). Explicaremos el
funcionamiento de estas retrollamadas y la utilidad de la clase en la segunda parte de DBHelper (la clase
externa).


Nuestra clase DBHelper contiene una referencia de variable miembro a un objeto SQLiteDatabase. Este
objeto es el motor de bases de datos de Android y se utiliza para abrir conexiones, ejecutar elementos SQL
y otras operaciones.


Tras ello, se crea una instancia de la clase interna DBOpenHelper en el constructor. Seguidamente, se utiliza
dbOpenHelper, dentro del método establishDb si la referencia db es nuil, para invocar openDatabase con el
31


Unidad 7 “SQLite”

contexto, nombre y versión de la base de datos actuales. De este modo se establece db como instancia de
SQLiteDatabase a través de DBOpenHelper.


Aunque puede abrir una conexión de base de datos directamente, el uso de open in¬voca las retrollamadas
proporcionadas y facilita el proceso. Con esta técnica, al intentar abrir la conexión a la base de datos, se
crea o actualiza automáticamente (o se devuelve), si es necesario, a través de DBOpenHelper. Aunque el
uso de DBOpenHelper implica pasos adicionales, una vez aplicado resulta muy útil para modificar la
estructura de tablas (basta con incrementar la versión y hacer lo necesario en la retrollamada onUp-grade;
sin esto, sería necesario modificar y/o eliminar manualmente y volver a crear la estructura existente). Otro
elemento importante que proporcionar en una clase de ayuda como ésta es un método cleanup. Lo utilizan
los invocadores para llamarlo cuando se detienen, para cerrar conexiones y liberar recursos.


Tras el método cleanup vemos los métodos SQL que encapsulan las operaciones proporcionadas por la
clase de ayuda. En esta clase contamos con métodos para aña¬dir, actualizar, eliminar y obtener datos.
También tenemos los métodos especializados get y get all, en los que vemos cómo se utiliza el objeto db
para ejecutar consultas. La clase SQLiteDatabase dispone de numerosos métodos como insert, update y
delete, y proporciona acceso query directo que devuelve un Cursor sobre un con¬junto de resultados.


Utilizando sqlite3


Al crear una base de datos para una aplicación en Android, los archivos de la misma se crean en
/datos/datos/ [NOMBRE_PAQUETE] /base de datos/db.nombre. Estos archivos son SQLite, pero existe
una forma de manipular, volcar, restaurar y trabajar con bases de datos a través de los mismos por medio
de la herramienta sqlite3.


Puedes acceder a esta herramienta a través de la línea de comandos si ejecuta los siguientes comandos


cd [ANDROID_HOME]/tools
adb shell
sqlite3 /ruta_donde_se_almaceno_la_base_de_datos


Una vez en la ventana de símbolo del sistema (con el signo #), puede ejecutar comandos sqlite3, como. help
para empezar.


Desde la herramienta puedes ejecutar comandos básicos como SELECT o INSERT, u otros más avanzados
para tablas como CREATE o ALTER. Esta herramienta resulta muy útil para operaciones básicas y de
resolución de problemas, y para volcar y abrir datos (.dump y .load, respectivamente). Como otras
herramientas SQL de línea de comandos, se necesita tiempo para familiarizarse con su formato pero no hay
mejor forma de crear copias de seguridad de los datos o abrirlos. (Si necesita estas funciones, en la mayoría
de los casos el desarrollo para móviles no exige bases de datos de gran tamaño. Recuerde que esta
herramienta sólo está disponible a través de la consola de desarrollo y no para abrir datos en una
aplicación real).
32


Unidad 7 “SQLite”


ContentProvider y manipulación de datos


Ahora que sabemos cómo utilizar la compatibilidad de Android con SQLite, para crear y acceder a tablas,
almacenar datos o investigar bases de datos con las herramientas proporcionadas por la consola, el
siguiente paso consiste en crear y utilizar ContentProvider, el último aspecto de procesamiento de datos en
la plataforma.


En Android se utiliza ContentProvider para compartir datos entre aplicaciones. Ya hemos analizado el hecho
de que cada aplicación se ejecuta en su propio proceso (normalmente) y que los archivos y datos que
almacena no son accesibles para otras aplicaciones de forma predeterminada. También hemos explicado
cómo conseguir que preferencias y archivos estén disponibles entre aplicaciones gracias a los permisos
correctos y al contexto y ruta de cada aplicación. No obstante, es una solución limitada para aplicaciones
relacionadas que ya conocen sus correspondientes detalles. Por el contrario, con ContentProvider puede
publicar y mostrar un determinado tipo de datos que otras aplicaciones utilicen para consultar, añadir,
actualizar y eliminar, sin necesidad de que conozcan con antelación las rutas o recursos, o quién
proporciona el contenido.


El ejemplo convencional de ContentProvider en Android es una lista de contactos, la lista de nombres,
direcciones y teléfonos almacenada en el teléfono. Puede acceder a estos datos desde cualquier aplicación
si utiliza un URI concreto, content://con-tacts/people/, y una serie de métodos proporcionados por las
clases Activity y ContentResolver para recuperar y almacenar datos. En un apartado posterior encontrará
más información sobre ContentResolver. Otro concepto relacionado con datos de ContentProvider es
Cursor, el mismo objeto que utilizamos anteriormente para trabajar con conjuntos de resultados de bases
de datos SQLite. Cursor también lo devuelven los métodos de consulta que veremos en breve.


En este apartado crearemos varias aplicaciones de ejemplo para ilustrar ContentProvider desde todos los
ángulos. Primero crearemos una aplicación basa-da en una única actividad, ProviderExplorer, que utiliza la
base de datos de contactos incorporada para consultar, añadir, actualizar y eliminar datos. Tras ello,
crearemos otra aplicación que implementa su propio ContentProvider e incluye una actividad de tipo
explorador para manipular los datos. Además, analizaremos otros proveedores incorporados de la
plataforma.


La aplicación ProviderExplorer tendrá una pantalla de gran tamaño en la que podemos desplazarnos.
Recuerde que nos centraremos en una actividad, para mostrar todas las operaciones ContentProvider en el
mismo punto, en lugar de en aspectos estéticos o de facilidad de uso (la aplicación es intencionadamente
horrible, al menos esta vez).


Para comenzar, analizaremos la sintaxis de URI y las combinaciones y rutas utilizadas para realizar distintas
operaciones con las clases ContentProvider y ContentResolver.


Formas de representar las URI y manipulación de registros


Todo ContentProvider debe mostrar un CONTENT_URI exclusivo que se utiliza para identificar el tipo de
contenido que procesa. Este URI se utiliza de dos formas para consultar los datos, singular o plural
33


Unidad 7 “SQLite”



URI DESCRIPCIÓN
cont ent : //contacta/peopl e/
Devuelve una lista (List) de todas las personas del proveedor
registrado para procesar content: //contacts

cont ent : / / cont act s/
peopl e/ 1
Devuelve o manipula una única persona con el ID =1 del
proveedor registrado para procesar content: //contacts



El concepto de URI aparece independientemente de que consulte datos, los añada o elimine, como veremos en breve.
Para familiarizarnos con el proceso, veremos los métodos básicos de manipulación de datos CRUD y su relación con la
base de datos de contactos y sus respectivos URI.

Analizaremos cada tarea para destacar los detalles: crear, leer, actualizar y eliminar. Para hacerlo de forma concisa,
crearemos una única actividad que se encarga de todas estas acciones en el ejemplo ProviderExplorer. En apartados
posteriores analizare¬mos distintas partes de la actividad para centrarnos en cada tarea.
Lo primero será configurar la estructura del proveedor de contactos, en la primera parte del siguiente listado, el inicio
de la clase ProviderExplorer.


publ i c cl ass Pr ovi der Expl or er ext ends Act i vi t y {

pr i vat e Edi t Text addName;
pr i vat e Edi t Text addPhoneNumber ;
pr i vat e Edi t Text edi t Name;
pr i vat e Edi t Text edi t PhoneNumber ;
pr i vat e But t on addCont act ;
pr i vat e But t on edi t Cont act ;

pr i vat e l ong cont act I d;

pr i vat e cl ass Cont act {

publ i c l ong i d;
publ i c St r i ng name;
publ i c St r i ng phoneNumber ;

publ i c Cont act ( f i nal l ong i d, f i nal St r i ng name, f i nal St r i ng phoneNumber ) {
t hi s. i d = i d;
t hi s. name = name;
t hi s. phoneNumber = phoneNumber ;
}

@Over r i de
publ i c St r i ng t oSt r i ng( ) {
r et ur n t hi s. name + " \ n" + t hi s. phoneNumber ;
}
}

pr i vat e cl ass Cont act But t on ext ends But t on {

publ i c Cont act cont act ;

publ i c Cont act But t on( f i nal Cont ext ct x, f i nal Cont act cont act ) {
super ( ct x) ;
t hi s. cont act = cont act ;
}
34


Unidad 7 “SQLite”


}

@Over r i de
publ i c voi d onCr eat e( f i nal Bundl e i ci cl e) {
super . onCr eat e( i ci cl e) ;
t hi s. set Cont ent Vi ew( R. l ayout . pr ovi der _expl or er ) ;

t hi s. addName = ( Edi t Text ) f i ndVi ewByI d( R. i d. add_name) ;
t hi s. addPhoneNumber = ( Edi t Text ) f i ndVi ewByI d( R. i d. add_phone_number ) ;
t hi s. edi t Name = ( Edi t Text ) f i ndVi ewByI d( R. i d. edi t _name) ;
t hi s. edi t PhoneNumber = ( Edi t Text ) f i ndVi ewByI d( R. i d. edi t _phone_number ) ;
t hi s. addCont act = ( But t on) f i ndVi ewByI d( R. i d. add_cont act _but t on) ;
t hi s. addCont act . set OnCl i ckLi st ener ( new OnCl i ckLi st ener ( ) {

publ i c voi d onCl i ck( f i nal Vi ew v) {
addCont act ( ) ;
}
}) ;
t hi s. edi t Cont act = ( But t on) f i ndVi ewByI d( R. i d. edi t _cont act _but t on) ;
t hi s. edi t Cont act . set OnCl i ckLi st ener ( new OnCl i ckLi st ener ( ) {

publ i c voi d onCl i ck( f i nal Vi ew v) {
edi t Cont act ( ) ;
}
}) ;
}

Para comenzar la actividad ProviderExplorer creamos una sencilla clase interna para representar un registro
Contact (no es una representación completa pero captura los campos que nos interesan). Tras ello,
incluimos otra clase interna para representar ContactButton, clase que amplía Button e incluye una
referencia a un determinado contacto.


Después de establecer los botones Add y Edit, creamos implementaciones OnClickListener anónimas que
invocan los correspondientes métodos add y edit al hacer clic en un botón.


Con esto terminan las tareas de configuración de ProviderExplorer. A continuación implementaremos el
método onStart, que añade más botones dinámicamente para completar y eliminar datos.


@Over r i de
publ i c voi d onSt ar t ( ) {

super . onSt ar t ( ) ;
Li st <Cont act > cont act s = get Cont act s( ) ;
Li near Layout . Layout Par ams par ams = new Li near Layout . Layout Par ams( 200,
andr oi d. vi ew. Vi ewGr oup. Layout Par ams. WRAP_CONTENT) ;
i f ( cont act s ! = nul l ) {
Li near Layout edi t Layout = ( Li near Layout )
f i ndVi ewByI d( R. i d. edi t _but t ons_l ayout ) ;
Li near Layout del et eLayout = ( Li near Layout )
f i ndVi ewByI d( R. i d. del et e_but t ons_l ayout ) ;
par ams. set Mar gi ns( 10, 0, 0, 0) ;

f or ( Cont act c : cont act s) {
Cont act But t on cont act Edi t But t on = new Cont act But t on( t hi s, c) ;
cont act Edi t But t on. set Text ( c. t oSt r i ng( ) ) ;
35


Unidad 7 “SQLite”


edi t Layout . addVi ew( cont act Edi t But t on, par ams) ;
cont act Edi t But t on. set OnCl i ckLi st ener ( new OnCl i ckLi st ener ( ) {

publ i c voi d onCl i ck( f i nal Vi ew v) { Cont act But t on vi ew
= ( Cont act But t on) v;
edi t Name. set Text ( vi ew. cont act . name) ;
edi t PhoneNumber . set Text ( vi ew. cont act . phoneNumber ) ;
cont act I d = vi ew. cont act . i d;
}
}) ;

Cont act But t on cont act Del et eBut t on = new Cont act But t on( t hi s, c) ;
cont act Del et eBut t on. set Text ( " Del et e " + c. name) ;
del et eLayout . addVi ew( cont act Del et eBut t on, par ams) ;
cont act Del et eBut t on. set OnCl i ckLi st ener ( new OnCl i ckLi st ener ( ) {

publ i c voi d onCl i ck( f i nal Vi ew v) {
Cont act But t on vi ew = ( Cont act But t on) v;
cont act I d = vi ew. cont act . i d;
del et eCont act ( ) ;
}
}) ;
}
} el se {
Li near Layout l ayout = ( Li near Layout ) f i ndVi ewByI d( R. i d. edi t _but t ons_l ayout ) ;
Text Vi ew empt y = new Text Vi ew( t hi s) ;
empt y. set Text ( " No cur r ent cont act s" ) ;
l ayout . addVi ew( empt y, par ams) ;
}
}


El método onStart () invoca el método getContacts que (véase el listado 5.14) devuelve una lista de los
objetos Contact actuales de la base de datos de contactos de Android. Una vez obtenidos los contactos,
iteramos por los mismos y creamos dinámicamente un diseño en el código para edit y delete,
respectivamente. Seguidamente, creamos objetos de vista, incluido ContactButton para completar un
formulario de edición y un botón para eliminar un contacto. Tras ello, cada botón se añade manualmente a
su correspondiente LinearLayout al que hemos hecho referencia a través de R.java.
Una vez añadido el método onStart, tenemos una vista para mostrar todos los con¬tactos actuales y todos
los botones, estáticos y dinámicos, necesarios para añadir, editar y eliminar datos de contactos.
Seguidamente, implementamos los métodos para realizar estas acciones, para lo que utilizamos
ContentResolver y otras clases relacionadas.
Inicialmente tendremos que completar la pantalla de contactos actuales para lo que necesitamos consultar
(leer) datos.


Consultando datos


La clase Activity dispone de un método managedQuery que se utiliza para invocar clases ContentProvider
registradas. Al crear nuestro propio ContentProvider en un apartado posterior, veremos cómo se registran
proveedores en la plataforma; por el momento, nos centraremos en la invocación de métodos existentes.
Cada proveedor debe publicar el CONTENT_URI que admite. Para consultar el proveedor de contactos,
36


Unidad 7 “SQLite”

(véase el listado que se presenta a continuación), necesitamos saber este URI y obtener un Cursor
mediante la invocación de managedQuery.


private List<Contact> getContacts() {
List<Contact> results = null;
long id = 0L;
String name = null;
String phoneNumber = null;
String[] projection = new String[] { BaseColumns._ID, PeopleColumns.NAME,
PhonesColumns.NUMBER };
ContentResolver resolver = getContentResolver();
Cursor cur = resolver.query(Contacts.People.CONTENT_URI, projection, null, null,
Contacts.People.DEFAULT_SORT_ORDER);

while (cur.moveToNext()) {
if (results == null) {
results = new ArrayList<Contact>();
}
id = cur.getLong(cur.getColumnIndex(BaseColumns._ID));
name = cur.getString(cur.getColumnIndex(PeopleColumns.NAME));
phoneNumber = cur.getString(cur.getColumnIndex(PhonesColumns.NUMBER));
results.add(new Contact(id, name, phoneNumber));
}
return results;
}


En realidad, la base de datos de contactos de Android está formada por varios tipos de datos. Un contacto
incluye detalles de una persona (nombre, empresa, fotografía, etc.), uno o varios números de teléfono
(cada uno con un número, tipo, etiqueta y demás) e información adicional. ContentProvider suele
proporcionar todos los detalles del URI y los tipos que admite como constantes en una clase. En el paquete
android. provider se incluye una clase Contacts correspondiente al proveedor de contactos. Esta clase
cuenta con clases internas anidadas que representan People y Phones.


A su vez, éstas contienen clases internas con constantes que representan campos o columnas de datos para
cada tipo. Esta estructura de clases internas puede resultar complicada de asumir pero simplemente
recuerde que los datos Contacts acaban en varias tablas y que los datos que debe consultar y manipular
provienen de las clases internas de cada tipo.
Las columnas que utilizaremos para establecer y obtener datos se definen en estas clases. Únicamente
trabajaremos con la parte correspondiente a individuos y teléfonos. Primero creamos una proyección de las
columnas que devolver como matriz String. Tras ello, obtenemos una referencia a ContentResolver, que
nos permite obtener un objeto Cursor. Este objeto representa las filas de los datos devueltos, por los que
iteramos para crear los objetos de los contactos.


El método quer y de la clase ContentResolver también le permite pasar argumentos adicionales para limitar
los resultados. En concreto, donde pasamos null, null, podemos pasar un filtro para limitar las filas que
devolver en forma de cláusula WHERE y objetos opcionales de sustitución para dicha cláusula (inyectados
en ?). Es un uso convencional de SQL, muy sencillo de utilizar. El inconveniente aparece si no se utiliza una
base de datos para ContentProvider.
37


Unidad 7 “SQLite”

Aunque sea posible, tendrá que procesar instrucciones SQL en la implementación del proveedor y necesita
que todo el que utilice el proveedor también lo haga. Después de ver cómo consultar datos para devolver
resultados, aprenderemos a añadir nuevos datos: una fila.




Insertando datos


El siguiente fragmento forma parte de la clase ProviderExplorer, haciendo énfasis en el método
addContent; el cual se utiliza para añadir elementos de formulario a la actividad e insertar una nueva fila de
datos en las tablas relacionadas con contactos.


pr i vat e voi d addCont act ( ) {
Cont ent Resol ver r esol ver = get Cont ent Resol ver ( ) ;
Cont ent Val ues val ues = new Cont ent Val ues( ) ;

/ / cr eat e Cont act s. Peopl e r ecor d f i r st , usi ng hel per met hod t o get per son i n " My
Cont act s"
/ / gr oup
val ues. put ( Peopl eCol umns. NAME, t hi s. addName. get Text ( ) . t oSt r i ng( ) ) ;
Ur i per sonUr i = Cont act s. Peopl e. cr eat ePer sonI nMyCont act sGr oup( r esol ver , val ues) ;
Log. v( " Pr ovi der Expl or er " , " ADD per sonUr i - " + per sonUr i . t oSt r i ng( ) ) ;

/ / append ot her cont act dat a, l i ke phone number
val ues. cl ear ( ) ;
Ur i phoneUr i = Ur i . wi t hAppendedPat h( per sonUr i ,
Cont act s. Peopl e. Phones. CONTENT_DI RECTORY) ;
Log. v( " Pr ovi der Expl or er " , " ADD phoneUr i - " + phoneUr i . t oSt r i ng( ) ) ;
val ues. put ( PhonesCol umns. TYPE, PhonesCol umns. TYPE_MOBI LE) ;
val ues. put ( PhonesCol umns. NUMBER, t hi s. addPhoneNumber . get Text ( ) . t oSt r i ng( ) ) ;

/ / i nser t manual l y ( t hi s t i me no hel per met hod)
r esol ver . i nser t ( phoneUr i , val ues) ;

st ar t Act i vi t y( new I nt ent ( t hi s, Pr ovi der Expl or er . cl ass) ) ;
}


Lo primero que vemos en el método addContact es la referencia ContentResolver y el uso de un objeto
ContentValues para asignar nombres de columna a valores. Es un tipo de objeto específico de Android. Una
vez definidas las variables, utilizamos el método de ayuda especial createPersonlnMyContactsGroup de la
clase Contacta . People para añadir un registro y devolver Uri. Este método utiliza Resolver y, entre
bastidores, realiza la inserción. La estructura de la clase Contacts dispone de diversos métodos de ayuda
(consulte la documentación) y que permiten reducir la cantidad de código necesario para realizar
determinadas tareas como añadir un contacto al grupo My Contacts (el predeterminado que muestra el
teléfono en la aplicación de contactos).
Tras crear un nuevo registro People, añadimos datos al Uri existente para crear un registro de teléfono
asociado a la misma persona. Es una característica del API. Por lo general puede añadir o ampliar un Uri
existente para acceder a distintos aspectos de la estructura de datos. Una vez obtenido el Uri y después de
establecer y actualizar el objeto de valores, añadimos directamente un registro de teléfono, con el método
ContentResolver insert (no el de ayuda).
Tras añadir los datos, veremos cómo actualizar datos ya existentes.
38


Unidad 7 “SQLite”



Actualizando datos


Para actualizar una fila de datos primero debe obtener una referencia de fila Cursor y utilizar los métodos
de actualización de Cursor.


pr i vat e voi d edi t Cont act ( ) {
Cont ent Resol ver r esol ver = get Cont ent Resol ver ( ) ;
Cont ent Val ues val ues = new Cont ent Val ues( ) ;

/ / anot her way t o append t o a Ur i , use bui l dUpon
Ur i per sonUr i =
Cont act s. Peopl e. CONTENT_URI . bui l dUpon( ) . appendPat h( Long. t oSt r i ng( t hi s. cont act I d) ) . bui l d( )
;
Log. v( " Pr ovi der Expl or er " , " EDI T per sonUr i - " + per sonUr i . t oSt r i ng( ) ) ;

/ / once we have t he per son Ur i we can edi t per son val ues, l i ke name
val ues. put ( Peopl eCol umns. NAME, t hi s. edi t Name. get Text ( ) . t oSt r i ng( ) ) ;
r esol ver . updat e( per sonUr i , val ues, nul l , nul l ) ;

/ / separ at e st ep t o updat e phone val ues
val ues. cl ear ( ) ;
/ / j ust edi t t he f i r st phone, wi t h i d 1
/ / ( i n r eal l i f e we woul d need t o par se mor e phone dat a and edi t t he cor r ect
phone out of a
/ / possi bl e many)
Ur i phoneUr i = Ur i . wi t hAppendedPat h( per sonUr i ,
Cont act s. Peopl e. Phones. CONTENT_DI RECTORY + " / 1" ) ;
val ues. put ( PhonesCol umns. NUMBER, t hi s. edi t PhoneNumber . get Text ( ) . t oSt r i ng( ) ) ;
r esol ver . updat e( phoneUr i , val ues, nul l , nul l ) ;

st ar t Act i vi t y( new I nt ent ( t hi s, Pr ovi der Expl or er . cl ass) ) ;
}


Al actualizar datos, comenzamos con People. CONTENT_URI y le añadimos una ruta ID concreta por medio
de UriBuilder. Es una clase de ayuda que utiliza el patrón Builder para que pueda construir y acceder a los
componentes de un objeto Uri. Seguidamente, actualizamos los valores e invocamos resolver. update para
realizar la actualización. Como puede apreciar, el proceso de actualización con ContentResolver es muy
similar al de creación, con la excepción de que el método update le permite pasar una cláusula WHERE y
objetos de sustitución (estilo SQL).
En este ejemplo, después de actualizar el nombre de la persona, necesitamos obtener el Ur i correcto para
actualizar también su registro de teléfono. Para ello añadimos datos de ruta Uri adicionales a un objeto que
ya tenemos, que adjuntamos al ID específico deseado. En otras circunstancias, sería necesario determinar
qué registro telefónico del contacto hay que actualizar (en este caso hemos utilizado el ID I para resumir).
Aunque únicamente actualizamos un registro en función de un URI concreto, recuer¬de que puede
actualizar un grupo de registros si utiliza la forma no específica del URI y la clausula WHERE. Por último,
tendremos que implementar el método delete.


Eliminando datos
39


Unidad 7 “SQLite”

Para eliminar datos volveremos al objeto ContentResolver utilizado para añadir datos. En esta ocasión
invocaremos el método delete,


pr i vat e voi d del et eCont act ( ) {
Ur i per sonUr i = Cont act s. Peopl e. CONTENT_URI ;
per sonUr i =
per sonUr i . bui l dUpon( ) . appendPat h( Long. t oSt r i ng( t hi s. cont act I d) ) . bui l d( ) ;
Log. v( " Pr ovi der Expl or er " , " DELETE per sonUr i - " + per sonUr i . t oSt r i ng( ) ) ;
get Cont ent Resol ver ( ) . del et e( per sonUr i , nul l , nul l ) ;
st ar t Act i vi t y( new I nt ent ( t hi s, Pr ovi der Expl or er . cl ass) ) ;
}


El concepto de eliminación es muy similar, una vez comprendido el resto del proceso. Volvemos a utilizar el
enfoque UriBuilder para configurar un Uri para un registro concreto y después obtenemos una referencia
ContentResolver, en esta ocasión con la invocación del método delete.


El archivo Manifest y los ContentProviders


Para que la plataforma sepa qué contenido ofrecen los proveedores y qué tipo de datos representan,
deben definirse en un archivo de manifiesto de aplicación e instalarse en la plataforma. El código muestra
un manifiesto ejemplo haciendo referencia a cierto proveedor.


<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://sobernas.android.com/apk/res/android"
package="com.msi.manning.chapterS.widget"> Opplication android: icon="@drawable/icon"
android:label="@string/app_short_name"> <activity android:name=".WidgetExplorer" android:
label = "@string/app name"> <intent-filter>
<action android:name="android.intent .action .MAIN" /> •Ccategory android: name="android.
intent. category. LAUNCHER" />
</intent-filter> </activity>
<provider android: name="WidgetProvider" android:authorities=
"com.msi.manning.chapterS.Widget" />
</application>
</manifest>


La parte más importante del manifiesto relacionada con proveedores de contenidos es el elemento
<provider>. Se utiliza para definir la clase que implementa el proveedor y para asociar una determinada
autoridad a dicha clase.
Un proyecto completo capaz de añadir, recuperar, actualizar y eliminar registros nos ha permitido concluir
el análisis del uso y creación de clases ContentProvider. Y con ello hemos demostrado muchas de las formas
de almacenar y recuperar datos en la plataforma Android.