Conceptos básicos de SQL

Todas las tareas realizadas en SQLite presuponen la presencia de una base de datos en funcionamiento (una o varias), por lo que crear y abrir una base de datos (de forma similar a un archivo) son operaciones marco obligatorias que establecen el necesario entorno de programación. En SQLite no existe la posibilidad de borrar la base de datos mediante programación, ya que se supone que basta con borrar el archivo de base de datos del disco.

Las acciones disponibles en el contexto de una base abierta pueden dividirse condicionalmente en los siguientes grupos principales:

  • Creación y eliminación de tablas, así como modificación de sus esquemas, es decir, las descripciones de las columnas, incluida la identificación de tipos, nombres y restricciones
  • Creación (adición), lectura, edición y eliminación de registros en tablas; estas operaciones se suelen designar con la abreviatura común CRUD (Create, Read, Update, Delete)
  • Creación de consultas para seleccionar registros de una tabla o de una combinación de varias tablas conforme a condiciones complejas
  • Optimización de algoritmos mediante la creación de índices en columnas seleccionadas, uso de vistas (vista), inclusión de acciones por lotes en transacciones, declaración de desencadenadores de procesamiento de eventos y otras herramientas avanzadas

En las bases de datos SQL, todas estas acciones se realizan mediante comandos (o sentencias) SQL reservados. Debido a las particularidades de la integración con MQL5, algunas de las acciones se realizan mediante funciones MQL5 integradas. Por ejemplo, la apertura, aplicación o cancelación de una transacción se realiza mediante la trinidad de funciones DatabaseTransaction, aunque el estándar SQL (y la implementación pública de SQLite) tiene los comandos SQL correspondientes (BEGIN TRANSACTION, COMMIT y ROLLBACK).

La mayoría de los comandos SQL también están disponibles en programas MQL: se pasan al motor de ejecución SQLite como parámetros de cadena de las funciones DatabaseExecute o DatabasePrepare. La diferencia entre estas dos opciones radica en varios matices.

DatabasePrepare permite preparar una consulta para su posterior ejecución cíclica masiva con diferentes valores de parámetros en cada iteración (los parámetros en sí, es decir, sus nombres en la consulta, son los mismos). Además, estas consultas preparadas proporcionan un mecanismo para leer los resultados utilizando DatabaseRead y DatabaseReadBind. Por lo tanto, puede utilizarlos para operaciones con un conjunto de registros seleccionados.

Por el contrario, la función DatabaseExecute ejecuta la consulta única pasada de forma unilateral: el comando entra en el motor SQLite, realiza algunas acciones sobre los datos, pero no devuelve nada. Suele utilizarse para crear tablas o modificar datos por lotes.

En el futuro, a menudo tendremos que operar con varios conceptos básicos. Vamos a presentarlos:

Tabla - un conjunto estructurado de datos, formado por filas y columnas. Cada fila es un registro de datos independiente con campos (propiedades) descritos mediante el nombre y el tipo de las columnas correspondientes. Todas las tablas de la base de datos se almacenan físicamente en el archivo de la base de datos y están disponibles para lectura y escritura (si no se restringieron los derechos al abrir la base de datos).

Vista - un tipo de tabla virtual calculada por el motor SQLite a partir de una consulta SQL dada u otras tablas o vistas. Las vistas son de sólo lectura. A diferencia de las tablas (incluidas las temporales que SQL permite crear en memoria durante una sesión de programa), las vistas se recalculan de forma dinámica cada vez que se accede a ellas.

Índice - una estructura de datos de servicio (el árbol equilibrado, árbol B) para la búsqueda rápida de registros por los valores de campos predefinidos (propiedades) o sus combinaciones.

Disparador - una subrutina de una o más sentencias SQL asignada para ejecutarse automáticamente en respuesta a eventos (antes o después) de añadir, modificar o eliminar un registro en una tabla determinada.

He aquí una breve lista de las sentencias SQL más populares y las acciones que realizan:

  • CREATE - crea un objeto de base de datos (tabla, vista, índice, disparador);
  • ALTER - modifica un objeto (tabla);
  • DROP - elimina un objeto (tabla, vista, índice, disparador);
  • SELECT - selecciona registros o calcula valores que satisfacen las condiciones dadas;
  • INSERT - añade nuevos datos (uno o un conjunto de registros);
  • UPDATE - modifica los registros existentes;
  • DELETE - elimina registros de la tabla;

La lista sólo muestra las palabras clave que inician la construcción del lenguaje SQL correspondiente. A continuación se muestra una sintaxis más detallada. Su aplicación práctica se mostrará en los siguientes ejemplos.

Cada sentencia puede abarcar varias líneas (se ignoran los caracteres de salto de línea y los espacios adicionales). Si es necesario, puede enviar varios comandos a SQLite a la vez. En este caso, después de cada comando, debe utilizar el carácter de terminación de comando ';' (punto y coma).

El texto de los comandos es analizado por el sistema sin tener en cuenta mayúsculas y minúsculas, pero en SQL es habitual escribir las palabras clave en mayúsculas.

Al crear una tabla, debemos especificar su nombre, así como una lista de columnas entre paréntesis, separadas por comas. A cada columna se le asigna un nombre, un tipo y, opcionalmente, una restricción. La forma más sencilla:

CREATE TABLE table_name
  ( column_name type [ constraints ] [, column_name type [ constraints ...] ...]);

Veremos las restricciones en SQL en la sección siguiente. Mientras tanto, veamos un ejemplo claro (con distintos tipos y opciones):

CREATE TABLE IF NOT EXISTS example_table
   (id INTEGER PRIMARY KEY,
    name TEXT,
    timestamp INTEGER DEFAULT CURRENT_STAMP,
    income REAL,
    data BLOB);

La sintaxis para crear un índice es:

CREATE [ UNIQUE ] INDEX index_name
  ON table_namecolumn_name [, column_name ...]);

Los índices existentes se utilizan automáticamente en consultas con condiciones de filtro en las columnas correspondientes. Sin índices, el proceso es más lento.

Borrar una tabla (junto con los datos, si se ha escrito algo en ella) es bastante sencillo:

DROP TABLE table_name;

Puede insertar datos en una tabla de la siguiente manera:

INSERT INTO table_name [ ( column_name [, column_name ...] ) ]
  VALUESvalue [, value ...]);

La primera lista entre paréntesis incluye los nombres de las columnas y es opcional (véase la explicación más abajo). Debe coincidir con la segunda lista con valores para ellos. Por ejemplo:

INSERT INTO example_table (nameincomeVALUES ('Morning Flat Breakout', 1000);

Tenga en cuenta que los literales de cadena se encierran entre comillas simples en SQL.

Si se omiten los nombres de las columnas en la sentencia INSERT, se supone que la palabra clave VALUES va seguida de los valores de todas las columnas de la tabla, y en el orden exacto en que se describen en la tabla.

También existen formas más complejas del operador, que permiten, en particular, insertar registros de otras tablas o resultados de consultas.

La selección de registros por condición, con una limitación opcional de la lista de campos devueltos (columnas), se realiza mediante el comando SELECT.

SELECT column_name [, column_name ...] FROM table_name [WHERE condition ];

Si desea devolver todos los registros coincidentes en su totalidad (todas las columnas), utilice la notación estrella:

SELECT *FROM table_name [WHERE condition ];

Cuando la condición no está presente, el sistema devuelve todos los registros de la tabla.

Como condición, puede sustituir una expresión lógica que incluya nombres de columnas y varios operadores de comparación, así como funciones SQL integradas y los resultados de una consulta SELECT anidada (dichas consultas se escriben entre paréntesis). Entre los operadores de comparación se incluyen:

  • AND lógico
  • OR lógico
  • IN para un valor de la lista
  • NOT IN para un valor fuera de la lista
  • BETWEEN para un valor del intervalo
  • LIKE - similar en ortografía a un patrón con caracteres comodín especiales ('%', '_')
  • EXISTS - comprueba que los resultados de la consulta anidada no estén vacíos

Por ejemplo, una selección de nombres de registros con un ingreso de al menos 1000 y no más de un año de antigüedad (redondeado preliminarmente al mes más próximo):

SELECT name FROM example_table
  WHERE income >= 1000 AND timestamp > datetime('now', 'start of month', '-1 year');

Además, la selección puede ordenarse de forma ascendente o descendente (ORDER BY), agruparse por características (GROUP BY) y filtrarse por grupos (HAVING). También podemos limitar el número de registros que contiene (LIMIT, OFFSET). Para cada grupo, puede devolver el valor de cualquier función agregada, en particular, COUNT, SUM, MIN, MAX y AVG, calculado sobre todos los registros del grupo.

SELECT [ DISTINCT ] column_name [, column_name...](iFROM table_name
  [ WHERE condition ]
  [ORDER BY column_name [ ASC | DESC ]
     [ LIMIT quantity OFFSET start_offset ] ]
  [ GROUP BY column_name ⌠ HAVING condition ] ];

La palabra clave opcional DISTINCT permite eliminar duplicados (si se encuentran en los resultados según los criterios de selección actuales). Sólo tiene sentido en ausencia de agrupación.

LIMIT sólo dará resultados reproducibles si la clasificación está presente.

Si es necesario, la selección SELECT puede hacerse no a partir de una tabla, sino de varias, combinándolas según la combinación de campos requerida. Para ello se utiliza la palabra clave JOIN.

SELECT [...] FROM table name_1
  [ INNER | OUTER | CROSS ] JOIN table_name_2
  ON boolean_condition

o

SELECT [...] FROM table name_1
  [ INNER | OUTER | CROSS ] JOIN table_name_2
  USING ( common_column_name [, common_column_name ...] )

SQLite admite tres tipos de JOINs: INNER JOIN, OUTER JOIN y CROSS JOIN. El libro proporciona una idea general de ellos a partir de ejemplos, mientras que usted puede profundizar en los detalles por su cuenta.

Por ejemplo, con JOIN, puede construir todas las combinaciones de registros de una tabla con registros de otra tabla o comparar transacciones de la tabla de transacciones (llamémoslas «transacciones») con transacciones de la misma tabla según el principio de coincidencia de identificadores de posición, pero de tal forma que la dirección de las transacciones (entrada al mercado/salida del mercado) fuera la opuesta, dando como resultado una tabla virtual de operaciones.

SELECT // list the columns of the results table with aliases (after 'as')
  d1.time as time_ind1.position_id as positiond1.type as type// table d1
   d1.volume as volumed1.symbol as symbold1.price as price_in,
  d2.time as time_outd2.price as price_out,                      // table d2
   d2.swap as swapd2.profit as profit,
  d1.commission + d2.commission as commission                      // combination
  FROM deals d1 INNER JOIN deals d2      // d1 and d2 - aliases of one table "deals"
  ON d1.position_id = d2.position_id     // merge condition by position
  WHERE d1.entry = 0 AND d2.entry = 1    // selection condition "entry/exit"

Esta es una consulta SQL de la ayuda de MQL5, donde hay ejemplos de JOIN en las descripciones de las funciones DatabaseExecute y DatabasePrepare.

La propiedad fundamental de SELECT es que siempre devuelve resultados al programa que la llama, a diferencia de otras consultas como CREATE, INSERT, etc. Sin embargo, a partir de SQLite 3,35, las sentencias INSERT, UPDATE y DELETE también tienen la capacidad de devolver valores, si es necesario, utilizando la palabra clave adicional RETURNING. Por ejemplo:

INSERT INTO example_table (nameincomeVALUES ('Morning Flat Breakout', 1000)
   RETURNING id;

En cualquier caso, en MQL5 se accede a los resultados de las consultas a través de las funciones DatabaseColumn, DatabaseRead y DatabaseReadBind.

Además, SELECT permite evaluar los resultados de expresiones y devolverlos tal cual o combinarlos con resultados de tablas. Las expresiones pueden incluir la mayoría de los operadores que conocemos de expresiones MQL5, así como funciones SQL integradas. Consulte la documentación de SQLite para obtener una lista completa. Por ejemplo, así es como puede encontrar la versión actual de SQLite en su terminal y en el editor, lo que puede ser importante para saber qué opciones están disponibles.

SELECT sqlite_version();

En este caso, toda la expresión consiste en una única llamada a la función sqlite_version. De forma similar a la selección de varias columnas de una tabla, puede evaluar varias expresiones separadas por comas.

También están disponibles varias funciones estadísticas y matemáticas populares.

Los registros deben editarse con una sentencia UPDATE.

UPDATE table_name SET column_name = value [, column_name = value ...] 
  WHERE condition;

La sintaxis del comando de eliminación es la siguiente:

DELETE FROM table_name WHERE condition;