Primera parte
Seguimos creando nuestro sencillo emulador de bolsa de valores. Esto es lo que haremos:
- Creemos un diagrama de organización de la base de datos.
- Describiremos qué, cómo y dónde se almacena.
- Descubramos cómo se relacionan los datos entre sí.
- Comencemos a aprender los conceptos básicos de SQL usando el ejemplo del comando de creación de tablas SQL CREATE TABLE , lenguaje de definición de datos ( DDL ) del lenguaje SQL.
- Sigamos escribiendo el programa Java. Implementamos las funciones principales del DBMS en términos de java.sql para crear nuestra base de datos mediante programación, utilizando JDBC y una arquitectura de tres niveles.
Estas dos partes resultaron ser más voluminosas, ya que necesitamos familiarizarnos con los conceptos básicos de SQL y la organización de un DBMS desde adentro, y hacer analogías con Java. Para no aburrirlo con listados de códigos, al final hay enlaces al repositorio de github de confirmación correspondiente con el programa.
diseño de SGBD
Descripción de la aplicación
Ya habrás oído que organizar el almacenamiento de datos es una parte integral de la programación. Permítanme recordarles que el propósito de nuestra aplicación es la emulación de intercambio más simple:
- Hay acciones cuyo valor puede cambiar durante el día de negociación según reglas determinadas;
- hay comerciantes con capital inicial;
- Los comerciantes pueden comprar y vender acciones según su algoritmo.
El intercambio opera
en ticks , períodos de tiempo fijos (en nuestro caso, 1 minuto). Durante un tick, el precio de las acciones puede cambiar y luego el operador puede comprar o vender acciones.
Estructura de datos de emulación de Exchange
Llamemos modelos de entidades de intercambio individuales. Para evitar errores de redondeo, trabajaremos con importes financieros a través de una clase
BigDecimal
(los detalles se pueden encontrar en el enlace al final del artículo). Describamos con más detalle la estructura de cada modelo:
Promoción:
Atributo |
Tipo |
Descripción |
name |
Srting |
Nombre |
changeProbability |
En t |
Probabilidad de cambio de tasa como porcentaje en cada tick |
startPrice |
GranDecimal |
Costo inicial |
delta |
En t |
La cantidad máxima en porcentaje por la cual el valor actual puede cambiar |
Precio de la acción:
Atributo |
Tipo |
Descripción |
operDate |
Fecha y hora local |
Tiempo (tick) para fijar la tarifa |
share |
Promoción |
Enlace a la promoción |
rate |
GranDecimal |
Precio de la acción |
Comerciante:
Atributo |
Tipo |
Descripción |
name |
Cadena |
Tiempo (tick) para fijar la tarifa |
sfreqTick |
En t |
Frecuencia de transacciones. Especificado por el período, en ticks, después del cual el comerciante realiza operaciones |
cash |
GranDecimal |
Cantidad de dinero distinta de acciones |
traidingMethod |
En t |
El algoritmo utilizado por el comerciante. Configurémoslo como un número constante, la implementación del algoritmo será (en las siguientes partes) en código Java |
changeProbability |
En t |
Probabilidad de completar la operación, porcentaje. |
about |
Cadena |
Probabilidad de cambio de tasa, en porcentaje, en cada tick |
Acciones del comerciante:
Atributo |
Tipo |
Descripción |
operation |
En t |
Tipo de transacción (compra o venta) |
traider |
Comerciante |
Enlace comerciante |
shareRate |
Precio de la acción |
Enlace al precio de las acciones (respectivamente, la acción en sí, su tasa y el momento de su emisión) |
amount |
Largo |
Número de acciones involucradas en la transacción |
Para garantizar la singularidad de cada modelo, agregaremos un atributo
id
de tipo
long . Este atributo será
único dentro de las instancias del modelo y lo identificará de forma única. Los atributos que hacen referencia a otros modelos (comerciante, acción, precio de la acción) pueden usar este
id
para identificar de forma única el modelo correspondiente. Inmediatamente nos viene a la mente la idea de que podríamos utilizar
Map<Long, Object>
para almacenar dichos datos, ¿dónde
Object
está el modelo correspondiente? Sin embargo, intente implementar esto en código bajo las siguientes condiciones:
- el tamaño de los datos supera significativamente la cantidad de RAM disponible;
- se espera acceso a los datos desde una docena de lugares diferentes;
- se requiere la capacidad de modificar y leer datos simultáneamente;
- es necesario garantizar reglas para la formación e integridad de los datos;
...y se enfrentará a tareas que requieren calificaciones y tiempo adecuados para implementarlas. No es necesario “reinventar la rueda”. Ya se ha pensado y escrito mucho para nosotros. Por eso utilizaremos lo que ya se ha probado a lo largo de los años.
Almacenamiento de datos en Java
Consideremos la acción. En Java, creamos una clase específica para este modelo
Share
con campos
name
,,,, . Y muchos recursos compartidos se almacenaron como , donde la clave es un identificador único para cada recurso compartido.
changeProbability
startPrice
delta
Map<Long, Share>
public class Share {
private String name;
private BigDecimal startPrice;
private int changeProbability;
private int delta;
}
Map<Long, Share> shares = new HashMap<>();
shares.put(1L, new Share("ibm", BigDecimal.valueOf(20.0), 15, 10));
shares.put(2L, new Share("apple", BigDecimal.valueOf(14.0), 25, 15));
shares.put(3L, new Share("google", BigDecimal.valueOf(12.0), 20, 8));
...
shares.put(50L, new Share("microsoft", BigDecimal.valueOf(17.5), 10,4 ));
Para acceder a la promoción deseada por ID utilice el método
shares.get(id)
. Para la tarea de encontrar una acción por nombre o precio, recorreríamos todos los registros buscando la que necesitamos, y así sucesivamente. Pero iremos por el otro lado y almacenaremos los valores en el DBMS.
Almacenamiento de datos en un DBMS
Formulemos un conjunto inicial de reglas de almacenamiento de datos para un DBMS:
- Los datos en un DBMS se organizan en tablas ( TABLE ), que son un conjunto de registros.
- Todos los registros tienen los mismos conjuntos de campos. Se configuran al crear la tabla.
- El campo se puede establecer en un valor predeterminado ( DEFAULT ).
- Para una tabla, puede establecer restricciones ( CONSTRAINT ) que describen los requisitos de sus datos para garantizar su integridad. Esto se puede hacer en la etapa de creación de la tabla ( CREATE TABLE ) o agregarlo más tarde ( ALTER TABLE ... ADD CONSTRAINT ).
- La RESTRICCIÓN más común :
- La clave principal es PRIMARIA (Id en nuestro caso).
- Campo de valor único ÚNICO (VIN para la tabla de vehículos).
- Comprobando el campo VERIFICAR (el valor porcentual no puede ser mayor que 100). Una de las restricciones privadas en un campo es NOT NULL o NULL , lo que prohíbe/permite almacenar NULL en un campo de tabla.
- Enlace a una tabla de terceros FOREIGN KEY (enlace a una acción en la tabla de precios de acciones).
- Index INDEX (indexar un campo para acelerar la búsqueda de valores en él).
- La modificación de un registro ( INSERT , UPDATE ) no ocurrirá si los valores de sus campos contradicen las restricciones (CONSTRAINT).
- Cada tabla puede tener un campo clave (o varios) que se pueden utilizar para identificar de forma única un registro. Dicho campo (o campos, si forman una clave compuesta) forma la clave principal de la tabla: PRIMARY KEY .
- La clave principal garantiza la unicidad de un registro en la tabla; se crea un índice en ella, lo que brinda acceso rápido a todo el registro en función del valor de la clave.
- Tener una clave principal hace que sea mucho más fácil crear vínculos entre tablas. A continuación, usaremos una clave primaria artificial: para el primer registro
id = 1
, cada registro posterior se insertará en la tabla con el valor de identificación aumentado en uno. Esta clave suele denominarse AutoIncrement o AutoIdentity .
En realidad, una tabla de acciones:
¿Es posible utilizar el nombre de la acción como clave en este caso? En general, sí, pero existe la posibilidad de que alguna empresa emita acciones diferentes y las llame únicamente por su propio nombre. En este caso ya no habrá unicidad. En la práctica, se utiliza con bastante frecuencia una clave primaria artificial. De acuerdo, utilizar un nombre completo como clave única en una tabla que contiene registros de personas no garantizará la unicidad. Además de utilizar una combinación de nombre completo y fecha de nacimiento.
Tipos de datos en DBMS
Como cualquier otro lenguaje de programación, SQL tiene tipificación de datos. Estos son los tipos de datos SQL más comunes:
Tipos enteros
tipo SQL |
Sinónimos de SQL |
Coincidencia en Java |
Descripción |
EN T |
INT4,ENTERO |
java.lang.Integer |
Entero de 4 bytes, -2147483648 … 2147483647 |
BOOLEANO |
BOOL, POCO |
java.lang.booleano |
Verdadero Falso |
PEQUEÑO |
|
java.lang.Byte |
Entero de 1 byte, -128 … 127 |
PEQUEÑO |
INT2 |
java.lang.corto |
Entero de 2 bytes, -32768 … 32767 |
EMPEZANDO |
INT8 |
java.lang.largo |
Entero de 8 bytes, -9223372036854775808… 9223372036854775807 |
AUTOINCREMENTO |
INCREMENTO |
java.lang.largo |
Un contador incremental exclusivo de la tabla. Si se inserta un nuevo valor en él, se incrementa en 1. Los valores generados nunca se repiten. |
Real
tipo SQL |
Sinónimos de SQL |
Coincidencia en Java |
Descripción |
DECIMALES(N,M) |
DICIEMBRE, NÚMERO |
java.math.BigDecimal |
Decimal de precisión fija (N dígitos enteros y M dígitos fraccionarios). Diseñado principalmente para trabajar con datos financieros. |
DOBLE |
FLOTADOR8 |
java.lang.Double |
Número real de doble precisión (8 bytes). |
REAL |
FLOTADOR4 |
java.lang.Real |
Número real de precisión simple (4 bytes). |
Cadena
tipo SQL |
Sinónimos de SQL |
Coincidencia en Java |
Descripción |
VARCHAR(N) |
NVARCHAR |
java.lang.String |
Cadena UNICODE de longitud N. Longitud limitada a 2147483647 Carga todo el contenido de la cadena en la memoria. |
fecha y hora
tipo SQL |
Sinónimos de SQL |
Coincidencia en Java |
Descripción |
TIEMPO |
|
java.time.LocalTime, java.sql.Time |
Tiempo de almacenamiento (hasta nanosegundos), al convertir a DATETIME, la fecha se establece en el 1 de enero de 1970. |
FECHA |
|
java.time.LocalDate, java.sql.Timestamp |
Almacenamiento de fechas en formato aaaa-mm-dd, la hora se establece en 00:00 |
FECHA Y HORA |
MARCA DE TIEMPO |
java.time.LocalDateTime, java.sql.Timestamp |
Almacenamiento de fecha + hora (sin tener en cuenta zonas horarias). |
Almacenamiento de grandes volúmenes de datos.
tipo SQL |
Coincidencia en Java |
Descripción |
GOTA |
java.io.InputStream, java.sql.Blob |
Almacenamiento de datos binarios (imágenes, archivos...). |
CLOB |
java.io.Reader, java.sql.Clob |
El almacenamiento de datos de texto de gran tamaño (libros, artículos...), a diferencia de VARCHAR, carga los datos en la memoria en porciones. |
estilo de escritura SQL
Para muchos idiomas, existen pautas de formato de código. Normalmente, estos documentos contienen reglas para nombrar variables, constantes, métodos y otras estructuras del lenguaje. Entonces, para Python existe PEP8, para
Java: convenciones de código Oracle para Java . Se han creado varios conjuntos diferentes para SQL, que son ligeramente diferentes entre sí. De todos modos, debes desarrollar el hábito de seguir reglas al formatear tu código, especialmente si trabajas en equipo. Las reglas podrían ser, por ejemplo, las siguientes (por supuesto, usted mismo puede desarrollar un conjunto diferente de reglas, lo principal es cumplirlas en el futuro):
- Las palabras clave y reservadas, incluidos comandos y operadores, deben escribirse en mayúsculas: CREATE TABLE, CONSTRAINT...
- Los nombres de tablas, campos y otros objetos no deben coincidir con las palabras clave del lenguaje SQL (consulte el enlace al final del artículo), pero pueden contenerlas.
- Los nombres de las tablas deben reflejar su propósito. Están escritos en letras minúsculas. Las palabras del nombre están separadas entre sí por guiones bajos. La palabra al final debe estar en plural : traders (comerciantes), share_rates (tasa de acciones).
- Los nombres de los campos de la tabla deben reflejar su propósito. Deben estar escritos en letras minúsculas, las palabras del nombre deben tener el formato Camel Case y la palabra al final debe usarse en singular : nombre (nombre), share_rates (tasa de participación).
- Los campos de clave artificial deben contener la palabra id.
- Los nombres de CONSTRAINT deben seguir las convenciones de nomenclatura de tablas. También deben incluir los campos y tablas involucrados en ellos, comenzar con un prefijo semántico: check_ (comprobar el valor del campo), pk_ (clave primaria), fk_ (clave externa), uniq_ (uniq_ del campo), idx_ (índice). Ejemplo: pk_traider_share_actions_id (clave principal en el campo de identificación de la tabla trader_share_actions).
- Y así sucesivamente, a medida que estudie SQL, la lista de reglas se irá reponiendo/cambiando.
diseño de SGBD
Inmediatamente antes de crear un DBMS, es necesario diseñarlo. El esquema final contiene tablas, un conjunto de campos, CONSTRAINT, claves, condiciones predeterminadas para los campos, relaciones entre tablas y otras entidades de la base de datos. En Internet puede encontrar muchos diseñadores gratuitos en línea y fuera de línea para diseñar DBMS pequeños. Intente escribir algo como "Diseñador de bases de datos gratis" en un motor de búsqueda. Estas aplicaciones tienen propiedades adicionales útiles:
- Puede generar comandos SQL para crear un DBMS.
- Muestre visualmente la configuración en el diagrama.
- Le permite mover tablas para una mejor visualización.
- Muestre claves, índices, relaciones, valores predeterminados y similares en el diagrama.
- Pueden almacenar de forma remota el esquema DBMS.
Por ejemplo,
dbdiffo.com resalta claves, muestra campos no vacíos y contadores AI (AutoIncrement) con la etiqueta NN:
Crear tablas en un DBMS
Entonces tenemos un diagrama. Ahora pasemos a la creación de tablas (CREAR TABLA). Para ello es recomendable que dispongamos de datos preliminares:
- nombre de la tabla
- nombres y tipos de campos
- restricciones (RESTRICCIONES) en los campos
- valores predeterminados para los campos (si están disponibles)
- clave primaria (CLAVE PRIMARIA) si está disponible
- conexiones entre tablas (CLAVE EXTRANJERA)
No estudiaremos en detalle todas las opciones del comando CREATE TABLE, veremos los conceptos básicos de SQL usando el ejemplo de creación de una tabla para traders:
CREATE TABLE traiders(
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
freqTiсk INTEGER NOT NULL,
cash DECIMAL(15,2) NOT NULL DEFAULT 1000,
tradingMethod INTEGER NOT NULL,
changeProbability INTEGER NOT NULL DEFAULT 50,
about VARCHAR(255) NULL
);
ALTER TABLE traiders ADD CONSTRAINT check_traiders_tradingMethod
CHECK(tradingMethod IN (1,2,3));
ALTER TABLE traiders ADD CONSTRAINT check_traiders_changeProbability
CHECK(changeProbability <= 100 AND changeProbability > 0)
Miremos más de cerca:
CREATE TABLE traiders
(descripción del campo): crea una tabla con el nombre especificado; en la descripción, los campos están separados por una coma. Cualquier comando termina con punto y coma.
- La descripción del campo comienza con su nombre, seguido de su tipo, CONSTRAINT y valor predeterminado.
id BIGINT AUTO_INCREMENT PRIMARY KEY
– el campo id de tipo entero es una clave primaria y un contador incremental (por cada nuevo registro para el campo id, se generará un valor mayor en uno al creado previamente para esta tabla).
cash DECIMAL(15,2) NOT NULL DEFAULT 1000
– campo de efectivo, decimal, 15 dígitos antes del punto decimal y dos después (datos financieros, por ejemplo, dólares y centavos). No se pueden aceptar valores NULL. Si no se proporciona ningún valor, obtendrá el valor 1000.
about VARCHAR(255) NULL
– el campo Acerca de, una cadena de hasta 255 caracteres, puede aceptar valores vacíos.
Tenga en cuenta que podemos establecer parte de
las condiciones CONSTRAINT después de crear la tabla. Consideremos la construcción para modificar la estructura de la tabla y sus campos:
ALTER TABLE nombre_tabla ADD CONSTRAINT nombre_restricción VERIFICAR (condición) usando ejemplos:
CHECK(tradingMethod IN (1,2,3))
– el campo tradingMethod solo puede tomar valores 1,2,3
CHECK(changeProbability <= 100 AND changeProbability > 0)
– el campo changeProbability puede tomar valores enteros en el rango de 1 a 100
Relaciones entre tablas
Para analizar la descripción de las relaciones entre tablas, veamos la creación de share_rates:
CREATE TABLE share_rates(
id BIGINT AUTO_INCREMENT PRIMARY KEY,
operDate datetime NOT NULL,
share BIGINT NOT NULL,
rate DECIMAL(15,2) NOT NULL
);
ALTER TABLE share_rates ADD FOREIGN KEY (share) REFERENCES shares(id)
Una referencia a los valores de otra tabla se puede establecer de la siguiente manera:
ALTER TABLE
table_from_what_referred
ADD FOREIGN KEY
(field_that_referred)
REFERENCES
table_to_what_referred (field_that_referred to) Dejemos que en
acciones tenemos registros sobre acciones, por ejemplo, para id=50 almacenamos acciones de Microsoft con un precio inicial de 17,5, un delta de 20 y una probabilidad de cambio del 4%. Para la tabla
share_rates obtenemos tres propiedades principales:
- Solo necesitamos almacenar en el campo compartido solo el valor de la clave de identificación de la tabla de acciones para poder usarlo para obtener la información restante (nombre, etc.) de la tabla de acciones.
- No podemos crear una tarifa para una promoción inexistente. No puede insertar un valor inexistente en el campo compartir (para el cual no hay ningún registro en la tabla de acciones con esta identificación), ya que no habrá correspondencia entre las tablas.
- No podemos eliminar una entrada de acciones para las cuales las tasas se establecen en share_rates.
Los dos últimos puntos sirven para garantizar la integridad de los datos almacenados. Puedes ver la creación de tablas SQL de nuestra emulación y ejemplos de consultas SQL en la implementación Java de métodos de las clases correspondientes usando el enlace al repositorio de github al final del artículo.
la tercera parte
GO TO FULL VERSION