Android de la A a la Z - Unidad 7
Upcoming SlideShare
Loading in...5
×
 

Android de la A a la Z - Unidad 7

on

  • 1,029 views

by Jorge Ulises Gonzalez Medina

by Jorge Ulises Gonzalez Medina

Statistics

Views

Total Views
1,029
Views on SlideShare
1,029
Embed Views
0

Actions

Likes
0
Downloads
28
Comments
0

0 Embeds 0

No embeds

Accessibility

Categories

Upload Details

Uploaded via as Microsoft Word

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

Android de la A a la Z - Unidad 7 Android de la A a la Z - Unidad 7 Document Transcript

  • Android de la A a la Z Unidad 7 “SQLite” 1 ¿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.
  • Android de la A a la Z Unidad 7 “SQLite” 2 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.
  • Android de la A a la Z Unidad 7 “SQLite” 3 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.
  • Android de la A a la Z Unidad 7 “SQLite” 4 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.
  • Android de la A a la Z Unidad 7 “SQLite” 5 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).
  • Android de la A a la Z Unidad 7 “SQLite” 6 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.
  • Android de la A a la Z Unidad 7 “SQLite” 7 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.
  • Android de la A a la Z Unidad 7 “SQLite” 8
  • Android de la A a la Z Unidad 7 “SQLite” 9 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.
  • Android de la A a la Z Unidad 7 “SQLite” 10 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).
  • Android de la A a la Z Unidad 7 “SQLite” 11 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.).
  • Android de la A a la Z Unidad 7 “SQLite” 12 ¿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.
  • Android de la A a la Z Unidad 7 “SQLite” 13 ¿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.
  • Android de la A a la Z Unidad 7 “SQLite” 14 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.
  • Android de la A a la Z Unidad 7 “SQLite” 15 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
  • Android de la A a la Z Unidad 7 “SQLite” 16 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
  • Android de la A a la Z Unidad 7 “SQLite” 17 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.
  • Android de la A a la Z Unidad 7 “SQLite” 18 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 que si utiliza la palabra clave OFFSET en la cláusula LIMIT, el límite es el primer entero y el desfase es el
  • Android de la A a la Z Unidad 7 “SQLite” 19 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.
  • Android de la A a la Z Unidad 7 “SQLite” 20 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]* [WHERE expr] conflict-algorithm ::= 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.
  • Android de la A a la Z Unidad 7 “SQLite” 21 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
  • Android de la A a la Z Unidad 7 “SQLite” 22 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.
  • Android de la A a la Z Unidad 7 “SQLite” 23 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
  • Android de la A a la Z Unidad 7 “SQLite” 24 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
  • Android de la A a la Z Unidad 7 “SQLite” 25 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.
  • Android de la A a la Z Unidad 7 “SQLite” 26 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;
  • Android de la A a la Z Unidad 7 “SQLite” 27 } @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();
  • Android de la A a la Z Unidad 7 “SQLite” 28 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; }
  • Android de la A a la Z Unidad 7 “SQLite” 29 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();
  • Android de la A a la Z Unidad 7 “SQLite” 30 } } 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
  • Android de la A a la Z Unidad 7 “SQLite” 31 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).
  • Android de la A a la Z Unidad 7 “SQLite” 32 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
  • Android de la A a la Z Unidad 7 “SQLite” 33 URI DESCRIPCIÓN content://contacta/people/ Devuelve una lista (List) de todas las personas del proveedor registrado para procesar content: //contacts content://contacts/ people/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. public class ProviderExplorer extends Activity { private EditText addName; private EditText addPhoneNumber; private EditText editName; private EditText editPhoneNumber; private Button addContact; private Button editContact; private long contactId; private class Contact { public long id; public String name; public String phoneNumber; public Contact(final long id, final String name, final String phoneNumber) { this.id = id; this.name = name; this.phoneNumber = phoneNumber; } @Override public String toString() { return this.name + "n" + this.phoneNumber; } } private class ContactButton extends Button { public Contact contact; public ContactButton(final Context ctx, final Contact contact) { super(ctx); this.contact = contact; } }
  • Android de la A a la Z Unidad 7 “SQLite” 34 @Override public void onCreate(final Bundle icicle) { super.onCreate(icicle); this.setContentView(R.layout.provider_explorer); this.addName = (EditText) findViewById(R.id.add_name); this.addPhoneNumber = (EditText) findViewById(R.id.add_phone_number); this.editName = (EditText) findViewById(R.id.edit_name); this.editPhoneNumber = (EditText) findViewById(R.id.edit_phone_number); this.addContact = (Button) findViewById(R.id.add_contact_button); this.addContact.setOnClickListener(new OnClickListener() { public void onClick(final View v) { addContact(); } }); this.editContact = (Button) findViewById(R.id.edit_contact_button); this.editContact.setOnClickListener(new OnClickListener() { public void onClick(final View v) { editContact(); } }); } 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. @Override public void onStart() { super.onStart(); List<Contact> contacts = getContacts(); LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(200, android.view.ViewGroup.LayoutParams.WRAP_CONTENT); if (contacts != null) { LinearLayout editLayout = (LinearLayout) findViewById(R.id.edit_buttons_layout); LinearLayout deleteLayout = (LinearLayout) findViewById(R.id.delete_buttons_layout); params.setMargins(10, 0, 0, 0); for (Contact c : contacts) { ContactButton contactEditButton = new ContactButton(this, c); contactEditButton.setText(c.toString()); editLayout.addView(contactEditButton, params); contactEditButton.setOnClickListener(new OnClickListener() {
  • Android de la A a la Z Unidad 7 “SQLite” 35 public void onClick(final View v) { ContactButton view = (ContactButton) v; editName.setText(view.contact.name); editPhoneNumber.setText(view.contact.phoneNumber); contactId = view.contact.id; } }); ContactButton contactDeleteButton = new ContactButton(this, c); contactDeleteButton.setText("Delete " + c.name); deleteLayout.addView(contactDeleteButton, params); contactDeleteButton.setOnClickListener(new OnClickListener() { public void onClick(final View v) { ContactButton view = (ContactButton) v; contactId = view.contact.id; deleteContact(); } }); } } else { LinearLayout layout = (LinearLayout) findViewById(R.id.edit_buttons_layout); TextView empty = new TextView(this); empty.setText("No current contacts"); layout.addView(empty, params); } } 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, (véase el listado que se presenta a continuación), necesitamos saber este URI y obtener un Cursor mediante la invocación de managedQuery.
  • Android de la A a la Z Unidad 7 “SQLite” 36 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. 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
  • Android de la A a la Z Unidad 7 “SQLite” 37 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. private void addContact() { ContentResolver resolver = getContentResolver(); ContentValues values = new ContentValues(); // create Contacts.People record first, using helper method to get person in "My Contacts" // group values.put(PeopleColumns.NAME, this.addName.getText().toString()); Uri personUri = Contacts.People.createPersonInMyContactsGroup(resolver, values); Log.v("ProviderExplorer", "ADD personUri - " + personUri.toString()); // append other contact data, like phone number values.clear(); Uri phoneUri = Uri.withAppendedPath(personUri, Contacts.People.Phones.CONTENT_DIRECTORY); Log.v("ProviderExplorer", "ADD phoneUri - " + phoneUri.toString()); values.put(PhonesColumns.TYPE, PhonesColumns.TYPE_MOBILE); values.put(PhonesColumns.NUMBER, this.addPhoneNumber.getText().toString()); // insert manually (this time no helper method) resolver.insert(phoneUri, values); startActivity(new Intent(this, ProviderExplorer.class)); } 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. 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.
  • Android de la A a la Z Unidad 7 “SQLite” 38 private void editContact() { ContentResolver resolver = getContentResolver(); ContentValues values = new ContentValues(); // another way to append to a Uri, use buildUpon Uri personUri = Contacts.People.CONTENT_URI.buildUpon().appendPath(Long.toString(this.contactId)).build() ; Log.v("ProviderExplorer", "EDIT personUri - " + personUri.toString()); // once we have the person Uri we can edit person values, like name values.put(PeopleColumns.NAME, this.editName.getText().toString()); resolver.update(personUri, values, null, null); // separate step to update phone values values.clear(); // just edit the first phone, with id 1 // (in real life we would need to parse more phone data and edit the correct phone out of a // possible many) Uri phoneUri = Uri.withAppendedPath(personUri, Contacts.People.Phones.CONTENT_DIRECTORY + "/1"); values.put(PhonesColumns.NUMBER, this.editPhoneNumber.getText().toString()); resolver.update(phoneUri, values, null, null); startActivity(new Intent(this, ProviderExplorer.class)); } 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 Para eliminar datos volveremos al objeto ContentResolver utilizado para añadir datos. En esta ocasión invocaremos el método delete, private void deleteContact() { Uri personUri = Contacts.People.CONTENT_URI; personUri = personUri.buildUpon().appendPath(Long.toString(this.contactId)).build(); Log.v("ProviderExplorer", "DELETE personUri - " + personUri.toString());
  • Android de la A a la Z Unidad 7 “SQLite” 39 getContentResolver().delete(personUri, null, null); startActivity(new Intent(this, ProviderExplorer.class)); } 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.