From be3c77ca0527833ba04375820ba6fb40425420ea Mon Sep 17 00:00:00 2001 From: devfzn Date: Fri, 27 Oct 2023 18:14:06 -0300 Subject: [PATCH] MySQL: Stored Procedures & Funtions --- .gitignore | 1 + 011_mysql/dml.md | 526 +++++++++ 011_mysql/procedures.md | 1493 ++++++++++++++++++++++++ 011_mysql/table_data_import_wizard.png | Bin 0 -> 38469 bytes 4 files changed, 2020 insertions(+) create mode 100644 011_mysql/procedures.md create mode 100644 011_mysql/table_data_import_wizard.png diff --git a/.gitignore b/.gitignore index 9fc7696..5529635 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ *.pdf 006_challenge_encriptador/test/ otros/ +*.bak/ .idea/ out/ *.class diff --git a/011_mysql/dml.md b/011_mysql/dml.md index af6c514..951f907 100644 --- a/011_mysql/dml.md +++ b/011_mysql/dml.md @@ -1,3 +1,529 @@ # Data Manipulation Language +Se repasa lo visto en el primer curso sobre +[bases de datos](../010_spring_boot/base_de_datos.md) +### Tipos de datos + +- MySQL data [types](https://dev.mysql.com/doc/refman/8.0/en/data-types.html) +- MariaDB data [types](https://mariadb.com/kb/en/data-types/) + +## Diseño de la base de datos + +### 1. Análisis de requerimientos + +- Comprender las reglas del negocio (entrevistas, reuniones) +- El diseño del modelo debe corresponder con la realidad + +### 2. Modelo Conceptual + +- Diagrama **E**ntidad **R**elación +- Establecer la cardinalidad de las entidades (relaciones `1-1`, `1-N`, +`N-N`) + + + +```mermaid +%%{init: {'theme': 'dark','themeVariables': {'clusterBkg': '#2b2f38'}, 'flowchart': {'curve': 'linear'}}}%% +flowchart LR +V[Vendedor] ---- R{Realiza} +C[Cliente] ---- I{Involucra} +P[Productos] ---- Co{Contiene} +Ve[Ventas] --- Po{Posee} +IV["Items +Vendidos"] +R --- Ve +I --- Ve +Co --- IV +IV --- Po +``` + +### 3. Establecer atributos de las entidades + +#### Vendedor + +| Vendedor | Cliente | Venta | Producto | Items Vendidos | +| - | - | - | - | - | +| ***Matrícula*** | ***DNI*** | ***Número*** | ***Código*** | *Número* | +| Nombre | Nombre | Fecha | Sabor | *Código* | +| Barrio | Dirección | *Matrícula* | Tamaño | Cantidad | +| Comisión | Barrio | Impuesto | Envase | Precio | +| Fecha Admisión | Ciudad | *DNI* | Precio Lista | | +| Vacaciones | Estado | | Descripción | | +| | CP | | | | +| | Fecha nacimiento | | | | +| | Edad | | | | +| | Sexo | | | | +| | Límite credito | | | | +| | Volumen compra | | | | +| | Primera compra | | | | + +### 4. Creación de diagrama entidad relación + +```mermaid +erDiagram + Productos { + CODIGO varchar PK + DESCRIPCION varchar + SABOR varchar + TAMANO varchar + ENVASE varchar + PRECIO_LISTA float + } + Vendedor { + MATRICULA varchar PK + NOMBRE varchar + BARRIO varchar + COMISION float + FECHA_ADMISION date + VACACIONES bit + } + Clientes { + DNI varchar PK + NOMBRE varchar + BARRIO varchar + CIUDAD varchar + ESTADO varchar + FECHA_NACIMIENTO date + EDAD int + SEXO varchar + LIMITE_CREDITO int + VOLUMEN_COMPRA int + PRIMERA_COMPRA bit + } + Ventas { + NUMERO varchar PK + FECHA date + DNI varchar FK + MATRICULA varchar FK + IMPUESTO float + } + Items_Vendidos { + NUMERO varchar FK + CODIGO varchar FK + CANTIDAD bigint + PRECIO float + } + Vendedor ||--|{ Ventas : realiza + Clientes ||--|{ Ventas : involucra + Productos ||--|{ Items_Vendidos : contiene + Ventas ||--|{ Items_Vendidos : posee +``` + +### 5. Crear base de datos + +Se pueden utilizar herramientas para esto. Computer-Aide Software Engineering, +Start UML, Astah, ERWin, etc. + +#### Creación + +```sql +CREATE {DATABASE | SCHEMA} [IF NOT EXISTS] db_name + [create_options] ... + +create_options: [DEFAULT] { + CHARACTER SET [=] charset_name + | COLLATE [=] collation_name + | ENCRYPTION [=] {'Y' | 'N'} +} +``` + +#### Eliminación + +```sql +DROP {DATABASE | SCHEMA} [IF EXISTS] db_name +``` + +#### Creación de tablas + +```sql +CREATE [TEMPORARY] TABLE [IF NOT EXISTS] tbl_name + (create_definitions, ...) + [table_options] + [partition_options] + +CREATE [TEMPORARY] TABLE [IF NOT EXISTS] tbl_name + (create_definitions, ...) + [table_options] + [partition_options] + [IGNORE | REPLACE] + [AS] query_expression + +CREATE [TEMPORARY] TABLE [IF NOT EXISTS] tbl_name + (create_definitions, ...) + { LIKE old_tbl_name | (LIKE old_tbl_name) } +``` + +### Creación tablas + +```sql +USE ventas_jugos; + +CREATE TABLE tb_vendedor( + MATRICULA VARCHAR(5) NOT NULL, + NOMBRE VARCHAR(100) NULL, + BARRIO VARCHAR(50) NULL, + COMISION FLOAT NULL, + FECHA_ADMISION DATE NULL, + DE_VACACIONES BIT(1) NULL, + PRIMARY KEY(MATRICULA) +); + +CREATE TABLE tb_producto( + CODIGO VARCHAR(10) NOT NULL, + DESCRIPCION VARCHAR(100) NULL, + SABOR VARCHAR(50) NULL, + TAMANO VARCHAR(50) NULL, + ENVASE VARCHAR(50) NULL, + PRECIO_LISTA BIT(1) NULL, + PRIMARY KEY(CODIGO) +); + +... +``` + +```sql +CREATE TABLE tb_venta( + NUMERO VARCHAR(5) NOT NULL, + FECHA DATE NULL, + DNI VARCHAR(11) NOT NULL, + MATRICULA VARCHAR(5) NOT NULL, + IMPUESTO FLOAT, + PRIMAREY KEY(NUMERO) +); + +/* tb_cliente.DNI como llave foranea en tb_venta */ +ALTER TABLE tb_venta ADD CONSTRAIN FK_CLIENTE +FOREIGN KEY (DNI) REFERENCES tb_cliente(DNI); + +/* tb_vendedor.MATRICULA como llave foranea tb_venta */ +ALTER TABLE tb_venta ADD CONSTRAIN FK_VENDEDOR +FOREIGN KEY (MATRICULA) REFERENCES tb_vendedor(MATRICULA); + +CREATE TABLE tb_items_facturas +(NUMERO VARCHAR(5) NOT NULL, +CODIGO VARCHAR(10) NOT NULL, +CANTIDAD INT, +PRECIO FLOAT, +PRIMARY KEY (NUMERO, CODIGO)); + +/* Renombrando tb_venta a tb_factura */ +ALTER TABLE tb_venta RENAME tb_factura; + +/* tb_factura.MATRICULA como llave foranea tb_factura */ +ALTER TABLE tb_items_facturas ADD CONSTRAINT FK_PRODUCTO +FOREIGN KEY (NUMERO) REFERENCES tb_facturas(NUMERO); + +/* tb_producto.CODIGO como llave foranea tb_items_facturas */ +ALTER TABLE tb_items_facturas ADD CONSTRAINT FK_PRODUCTO +FOREIGN KEY (NUMERO) REFERENCES tb_producto(CODIGO); +``` + +## Insert + +```sql +INSET [LOW_PRIORITY | DELAYED | HIGH_PRIORITY] [IGNORE] + [INTO] tbl_name + [PARTITION (partition_name [, partition_name] ...)] + [(col_name [, col_name] ...)] + { {VALUES | VALUE} (value_list) [, (value_list)] ... + | + VALUES row_constructor_list + } + [AS row_alias[(col_alias [, col_alias] ...)]] + + +INSET [LOW_PRIORITY | DELAYED | HIGH_PRIORITY] [IGNORE] + [INTO] tbl_name + [PARTITION (partition_name [, partition_name] ...)] + [AS row_alias[(col_alias [, col_alias] ...)]] + SET assignment_list + [ON DUPLICATE KEY UPDATE assignment_list] +``` + +```sql +INSERT INTO tb_producto ( + CODIGO, DESCRIPCION, SABOR, TAMANO, ENVASE, PRECIO_LISTA +) VALUES + ('1040107', 'Light', 'Sandía', '350 ml', 'Lata', 4.56), + ('1040108', 'Light', 'Guanába', '350 ml', 'Lata', 4.56), + ('1040109', 'Light', 'Asaí', '350 ml', 'Lata', 3.50); +``` + +```sql +INSERT INTO TABLA_DE_CLIENTES + DNI, NOMBRE, DIRECCION, BARRIO, CIUDAD, ESTADO, CP, FECHA_NACIMIENTO, + EDAD, SEXO, LIMITE_CREDITO, VOLUMEN_COMPRA, PRIMERA_COMPRA +VALUES + ('9283760794', 'Edson Calisaya', 'Sta Úrsula Xitla', 'Barrio del Niño Jebús', + 'Ciudad de México', 'EM', '22002002', '1995-01-07', 25, 'M', 150000, 250000, 1), + ('7771579779', 'Marcelo Perez', 'F.C. de Cuernavaca 13', 'Carola', + 'Ciudad de México', 'EM', '88202912', '1992-01-25', 29, 'M', 120000, 200000, 1), + ('5576228758', 'Patricia Olivera', 'Pachuca 75', 'Condesa', 'Ciudad de México', + 'EM', '88192029', '1995-01-14', 25, 'F', 70000, 160000, 1); +``` + +### Insertar datos desde otro schema + +```sql +USER jugos_ventas; + +INSERT INTO tb_producto +SELECT CODIGO_DEL_PRODUCTO AS CODIGO, NOMBRE_DEL_PRODUCTO AS DESCRIPCION, +SABOR, TAMANO, ENVASE, PRECIO_DE_LISTA AS PRECIO_LISTA +FROM jugos.vetnas.tabla_de_productos +WHERE CODIGO_DEL_PRODUCTO NOT IN (SELECT CODIGO FROM tb_producto); + +INSERT INTO tb_cliente +SELECT DNI, NOMBRE, DIRECCION_1 AS DIRECCION, +BARRIO, CIUDAD, ESTADO, CP, FECHA_DE_NACIMIENTO +AS FECHA_NACIMIENTO, EDAD, SEXO, LIMITE_DE_CREDITO +AS LIMITE_CREDITO, VOLUMEN_DE_COMPRA AS VOLUMEN_COMPRA, +PRIMERA_COMPRA FROM jugos_ventas.tabla_de_clientes +WHERE DNI NOT IN (SELECT DNI FROM tb_cliente); +``` + +### Table Data Import Wizard + +![img](./table_data_import_wizard.png) + +## Update + +```sql +UPDATE [LOW_PRIORITY] [IGNORE] table_reference + SET assignment_list + [WHERE where_condition] + [ORDER BY ...] + [LIMIT row_count] + +value: {expr | DEFAULT} + +assignment: + col_name = value + +assignment_list: + assignment [, assignment] ... +``` + +### Actualizar un campo + +```sql +UPDATE tb_producto SET PRECIO_LISTA = 5 +WHERE CODIGO = '1000889'; +``` + +### Actualizar varios campos + +```sql +UPDATE tb_producto SET + DESCRIPCION = 'Sabor de la Montaña', + TAMANO = '1 Litro', + ENVASE = 'Botella PET' +WHERE CODIGO = '1000889'; + +UPDATE tb_cliente SET + DIRECCION = 'Ruben Ramirez 16', + BARRIO = 'Antonio', + CIUDAD = 'Guadalajara', + ESTADO = 'Jalisco', + CP = '44700000' +WHERE DNI = '5840119709'; +``` + +### Actualizar todos los registros de una columna + +```sql +UPDATE tb_cliente SET VOLUMEN_COMPRA = VOLUMEN_COMPRA/10; +``` + +### Update con datos de otro schema + +```sql +/* Se usa substring ya que el format de matrícula en la tabla B + es '00???' */ +UPDATE tb_vendedor A + SELECT * FROM tb_vendedor A + INNER JOIN + jugos_ventas.tabla_de_vendedores B + ON A.MATRICULA = SUBSTRING(B.MATRICULA, 3, 3) +SET A.DE_VACACIONES = B.VACACIONES; +``` + +***Aumentar en 30% el volumen de compra de los clientes*** +***cuyos barrios tengan vendedores con domicilio allí*** + +```sql +UPDATE tb_clientes A +INNER JOIN + tb_vendedor B +ON A.BARRIO = B.BARRIO +SET A.VOLUMEN_COMPRA = A.VOLUMEN_COMPRA * 1.3; +``` + +## DELETE + +```sql +DELETE [LOW_PRIORITY] [QUICK] [IGNORE] FROM tbl_name [[AS] tbl_alias] + [PARTITION (partition_name [, partition_name] ...)] + [ORDER BY ...] + [LIMIT row_count] +``` + +```sql +DELETE FROM tb_producto WHERE CODIGO = '1001000'; + +DELETE FROM tb_producto WHERE TAMANO = '1 Litro'; +``` + +```sql +DELTE FROM tb_producto +WHERE CODIGO NOT IN ( + SELECT CODIGO_DEL_PRODUCTO + FROM jugos_.tabla_de_productos +); +``` + +***Eliminar las facturas cuyos clientes tengan menos de 18 años*** + +```sql +DELETE A FROM tb_facturas A +INNER JOIN tb_vendedor B +ON A.DNI = B.DNI +WHERE B.EDAD < 18; +``` + +## COMMIT y ROLLBACK + +Confirmar o revertir una transacción + +```sql +START TRANSACTION + +-- TRANSACCIÓN + +ROLLBACK +-- o +COMMIT +``` + +## AUTO_INCREMENT + +```sql +CREATE TABLE tb_identificacion( + ID INT AUTO_INCREMENT NOT NULL, + DESCRIPCION VARCHAR(50) NULL, + PRIMARY KEY(ID) +); +``` + +## Campo con valor DEFAULT + +```sql +CREATE TABLE tb_default( + ID INT AUTO_INCREMENT, + DESCRIPCION VARCHAR(50) NOT NULL, + DIRECCION VARCHAR(100) NULL, + CIUDAD VARCHAR(50) DEFAULT 'Gaza', + CREACION TIMESTAMP DEFAULT CURRENT_TIMESTAMP(), + PRIMARY KEY(ID) +); +``` + +## TRIGGERS + +```sql +CREATE + [ DEFINER = user ] + TRIGGER trigger_name + trigger_time trigger_event + ON tbl_name FOR EACH ROW + [trigger_order] + trigger_boddy + +trigger_time: { BEFORE | AFTER } + +trigger_event: { INSERT | UPDATE | DELETE } + +trigger_order: { FOLLOWS | PRECEDES } other_trigger_name +``` + +```sql +CREATE TABLE tb_facturacion( + FECHA DATE NULL, + VENTA FLOAT +); + +DELIMITER // + +CREATE TRIGGER TG_FACTURACION_INSERT +AFTER INSERT ON tb_items_facturas +FOR EACH ROW BEGIN + + DELETE FROM tb_facturacion; + INSERT INTO tb_facturacion + SELECT A.FECHA, SUM(B. CANTIDAD * B.PRECIO) AS VENTA + FROM tb_factura A + INNER JOIN + tb_items_facturas B + ON A.NUMERO = B.NUMERO + GROUP BY A.FECHA; + +END // +``` + +***Crear trigger que actualize la edad de los clientes cada vez que se +inserte un nuevo cliente*** + +```sql +DELIMITER // +CREATE TRIGGER TG_EDAD_CLIENTES_INSERT +BEFORE INSERT ON tb_clientes +FOR EACH ROW BEGIN + SET NEW.EDAD = timestampdiff(YEAR, NEW.FECHA_NACIMIENTO, NOW()); +END// +``` + +### Triggers para DELETE y UPDATE + +```sql +DELIMITER // + +CREATE TRIGGER TG_FACTURACION_DELETE +AFTER DELETE ON tb_items_facturas +FOR EACH ROW BEGIN + + DELETE FROM tb_facturacion; + INSERT INTO tb_facturacion + SELECT A.FECHA, SUM(B. CANTIDAD * B.PRECIO) AS VENTA + FROM tb_factura A + INNER JOIN + tb_items_facturas B + ON A.NUMERO = B.NUMERO + GROUP BY A.FECHA; + +END // + +DELIMITER // + +CREATE TRIGGER TG_FACTURACION_UPDATE +AFTER UPDATE ON tb_items_facturas +FOR EACH ROW BEGIN + + DELETE FROM tb_facturacion; + INSERT INTO tb_facturacion + SELECT A.FECHA, SUM(B. CANTIDAD * B.PRECIO) AS VENTA + FROM tb_factura A + INNER JOIN + tb_items_facturas B + ON A.NUMERO = B.NUMERO + GROUP BY A.FECHA; + +END // +``` + +## Manipulación de datos + +- Stored Procedures +- Herramientas ETL (extract transform load) Pentaho, Power Center, SAP Data +Services, SQL Server Information Services, ... +- Lenguajes Python, Java, PHP, ... diff --git a/011_mysql/procedures.md b/011_mysql/procedures.md new file mode 100644 index 0000000..cf07ded --- /dev/null +++ b/011_mysql/procedures.md @@ -0,0 +1,1493 @@ +# Stored Procedures + +- [MySQL](https://dev.mysql.com/doc/refman/8.0/en/create-procedure.html) +- [MariaDB](https://mariadb.com/kb/en/stored-procedures/) +- [MariaDB Create Procedure](https://mariadb.com/kb/en/create-procedure/) +- MariaDB [W3Schools](https://www.w3schools.blog/procedure-mariadb) procedure + +```sql +CREATE + [DEFINER = user] + PROCEDURE sp_name ([proc_parameter[, ...]]) + [characteristic ...] routine_body + +CREATE + [DEFINER = user] + FUNCTION sp_name ([func_parameter[, ...]]) + RETURN type + [characteristic ...] routine_body + +proc_parameter: + [ IN | OUT | INOUT ] param_name type + +func_parameter: + param_name type + +type: + Any valid MySQL data type + +characteristic: { + COMMENT 'string' + | LANGUAGE SQL + | [NOT] DETERMINISTIC + | { CONTAINS SQL | NO SQL | READS SQL DATA | MODIFIES SQL DATA } + | SQL SECURITY { DEFINER | INVOKER } +} + +routine_body: + Valid SQL routine statement +``` + +```sql +CREATE PROCEDURE + (parámetros) +BEGIN + DECLARE ; + ... + ; + ... +END; +``` + +### Ejemplo STORE PROCEDURE + +```sql +USE jugos_ventas; + +DROP PROCEDURE IF EXISTS print_procedure; + +DELIMITER $$ +USE jugos_ventas$$ +CREATE PROCEDURE print_procedure () +BEGIN + SELECT CONCAT("Procedimiento,", " de ", "ejemplo"); +END$$ +DELIMITER ; +``` + +### Stored Procedures Sintaxis + +- Puede tener **letras**, **números** y los caracteres `$` y `_` +- Tamaño máximo: 64 caracteres +- Nombre único +- Case Sensitive + +```sql +USE jugos_ventas; +DROP PROCEDURE IF EXISTS hola_pianola; + +DELIMITER $$ +USE jugos_ventas$$ +CREATE PROCEDURE hola_pianola () +BEGIN + SELECT "Hola pianola!"; +END$$ + +DELIMITER ; +``` + +### CALL Procedure + +```sql +CALL print_procedure; ++-----------------------------+ +| CONCAT("hola,", " pianola") | ++-----------------------------+ +| hola, pianola | ++-----------------------------+ +``` + + +```sql +CALL hola_pianola; + ++---------------+ +| Hola pianola! | ++---------------+ +| Hola pianola! | ++---------------+ +``` + +#### Otros ejemplos + +```sql +USE jugos_ventas; +DROP PROCEDURE IF EXISTS calcular; + +DELIMITER $$ +USE jugos_ventas$$ +CREATE PROCEDURE calcular () +BEGIN + /* Este procedure tiene un comentario */ + -- Suma 6+3 y lo mútliplica por 5 + # Y muestra el "RESULTADO" + SELECT (6+3)*5 AS RESULTADO; +END$$ + +DELIMITER ; + +CALL calcular; + ++-----------+ +| RESULTADO | ++-----------+ +| 45 | ++-----------+ +``` + +## Eliminar procedure + +```sql +DROPR PROCEDURE jugos_ventas.hola_pianola; +``` + +### Ejemplo de un procedimiento almacenado mas extenso + +```sql +USE jugos_ventas; +DROP PROCEDURE IF EXISTS proc_extenso; + +DELIMITER $$ +USE jugos_ventas$$ +CREATE PROCEDURE proc_extenso () +BEGIN + INSERT INTO tabla_de_productos ( + CODIGO_DEL_PRODUCTO, + NOMBRE_DEL_PRODUCTO, + SABOR, + TAMANO, + ENVASE, + PRECIO_DE_LISTA + ) VALUES + ('1001001','Sabor Alpino','Mango','700 ml','Botella',7.50), + ('1001000','Sabor Alpino','Melón','700 ml','Botella',7.50), + ('1001002','Sabor Alpino','Guanábana','700 ml','Botella',7.50), + ('1001003','Sabor Alpino','Mandarina','700 ml','Botella',7.50), + ('1001004','Sabor Alpino','Banana','700 ml','Botella',7.50), + ('1001005','Sabor Alpino','Asaí','700 ml','Botella',7.50), + ('1001006','Sabor Alpino','Mango','1 Litro','Botella',7.50), + ('1001007','Sabor Alpino','Melón','1 Litro','Botella',7.50), + ('1001008','Sabor Alpino','Guanábana','1 Litro','Botella',7.50), + ('1001009','Sabor Alpino','Mandarina','1 Litro','Botella',7.50), + ('1001010','Sabor Alpino','Banana','1 Litro','Botella',7.50), + ('1001011','Sabor Alpino','Asaí','1 Litro','Botella',7.50); + + SELECT * FROM tabla_de_productos + WHERE NOMBRE_DEL_PRODUCTO LIKE 'Sabor Alp%'; + + UPDATE tabla_de_productos + SET PRECIO_DE_LISTA = 5 + WHERE NOMBRE_DEL_PRODUCTO LIKE 'Sabor Alp%'; + + SELECT * FROM tabla_de_productos + WHERE NOMBRE_DEL_PRODUCTO LIKE 'Sabor Alp%'; + + DELETE FROM tabla_de_productos + WHERE NOMBRE_DEL_PRODUCTO LIKE 'Sabor Alp%'; + + SELECT * FROM tabla_de_productos + WHERE NOMBRE_DEL_PRODUCTO LIKE 'Sabor Alp%'; +END$$ + +DELIMITER ; +``` + +```sql +CALL proc_extenso; +``` + +```txt ++---------------------+---------------------+---------+------------+---------+-----------------+ +| CODIGO_DEL_PRODUCTO | NOMBRE_DEL_PRODUCTO | TAMANO | SABOR | ENVASE | PRECIO_DE_LISTA | ++---------------------+---------------------+---------+------------+---------+-----------------+ +| 1001000 | Sabor Alpino | 700 ml | Melón | Botella | 7.5 | +| 1001001 | Sabor Alpino | 700 ml | Mango | Botella | 7.5 | +| 1001002 | Sabor Alpino | 700 ml | Guanábana | Botella | 7.5 | +| 1001003 | Sabor Alpino | 700 ml | Mandarina | Botella | 7.5 | +| 1001004 | Sabor Alpino | 700 ml | Banana | Botella | 7.5 | +| 1001005 | Sabor Alpino | 700 ml | Asaí | Botella | 7.5 | +| 1001006 | Sabor Alpino | 1 Litro | Mango | Botella | 7.5 | +| 1001007 | Sabor Alpino | 1 Litro | Melón | Botella | 7.5 | +| 1001008 | Sabor Alpino | 1 Litro | Guanábana | Botella | 7.5 | +| 1001009 | Sabor Alpino | 1 Litro | Mandarina | Botella | 7.5 | +| 1001010 | Sabor Alpino | 1 Litro | Banana | Botella | 7.5 | +| 1001011 | Sabor Alpino | 1 Litro | Asaí | Botella | 7.5 | ++---------------------+---------------------+---------+------------+---------+-----------------+ +12 rows in set (0.008 sec) + ++---------------------+---------------------+---------+------------+---------+-----------------+ +| CODIGO_DEL_PRODUCTO | NOMBRE_DEL_PRODUCTO | TAMANO | SABOR | ENVASE | PRECIO_DE_LISTA | ++---------------------+---------------------+---------+------------+---------+-----------------+ +| 1001000 | Sabor Alpino | 700 ml | Melón | Botella | 5 | +| 1001001 | Sabor Alpino | 700 ml | Mango | Botella | 5 | +| 1001002 | Sabor Alpino | 700 ml | Guanábana | Botella | 5 | +| 1001003 | Sabor Alpino | 700 ml | Mandarina | Botella | 5 | +| 1001004 | Sabor Alpino | 700 ml | Banana | Botella | 5 | +| 1001005 | Sabor Alpino | 700 ml | Asaí | Botella | 5 | +| 1001006 | Sabor Alpino | 1 Litro | Mango | Botella | 5 | +| 1001007 | Sabor Alpino | 1 Litro | Melón | Botella | 5 | +| 1001008 | Sabor Alpino | 1 Litro | Guanábana | Botella | 5 | +| 1001009 | Sabor Alpino | 1 Litro | Mandarina | Botella | 5 | +| 1001010 | Sabor Alpino | 1 Litro | Banana | Botella | 5 | +| 1001011 | Sabor Alpino | 1 Litro | Asaí | Botella | 5 | ++---------------------+---------------------+---------+------------+---------+-----------------+ +12 rows in set (0.011 sec) + +Empty set (0.016 sec) + +Query OK, 36 rows affected (0.016 sec) +``` + +## Variables en procedure + +```sql +DECLARE DEFAULT ; +``` + +- **Datatype** es olbligatorio +- **DEFAULT** es opcional, si no se establece, default es **NULL** +- Solo **letras**, **números**, `$` y `_` +- Se pueden declarar varias, con la restricción que sean del mismo tipo +- Tamaño máximo de 255 caracteres +- Nombre único, **case sesitive** +- La declaración termina con `;` + + +```sql +USE jugos_ventas; +DROP PROCEDURE IF EXISTS mostrar_vars; + +DELIMITER $$ +USE jugos_ventas$$ +CREATE PROCEDURE mostrar_vars () +BEGIN + DECLARE texto CHAR(20) DEFAULT 'Hola'; + DECLARE numero INTEGER DEFAULT 789; + DECLARE decimales DECIMAL(5,3) DEFAULT 45.678; + DECLARE fecha DATE DEFAULT '2023-10-10'; + DECLARE tiempo TIMESTAMP DEFAULT CURRENT_TIMESTAMP(); + SELECT texto, numero, decimales, fecha, tiempo; + /* Modificando valor */ + SET numero = 1; + SELECT numero AS numero_modificado; +END$$ + +DELIMITER ; + +CALL mostrar_vars; +``` + +```txt ++-------+--------+-----------+------------+---------------------+ +| texto | numero | decimales | fecha | tiempo | ++-------+--------+-----------+------------+---------------------+ +| Hola | 789 | 45.678 | 2023-10-10 | 2023-10-21 13:11:34 | ++-------+--------+-----------+------------+---------------------+ +``` + +***Crear un Stored Procedure que actualice la edad de los clientes.*** + +```sql +/* Cálculo de edad */ +TIMESTAMPDIFF(YEAR, FECHA_DE_NACIMIENTO, CURDATE()) as EDAD +``` + +```sql +DELIMITER $$ +CREATE PROCEDURE `calcula_edad`() +BEGIN + UPDATE tabla_de_clientes + SET EDAD = TIMESTAMPDIFF(YEAR, FECHA_DE_NACIMIENTO, CURDATE()); +END $$ +``` + +## Procedure params + +### Ejemplo de procedimiento con variables + +```sql +USE jugos_ventas; +DROP PROCEDURE IF EXISTS insert_prod; + +DELIMITER $$ +USE jugos_ventas$$ +CREATE PROCEDURE insert_prod ( + vcodigo VARCHAR(20), + vnombre VARCHAR(20), + vsabor VARCHAR(20), + vtamano VARCHAR(20), + venvase VARCHAR(20), + vprecio DECIMAL(4,2) +) +BEGIN + /* + DECLARE vcodigo VARCHAR(20) DEFAULT '30030001'; + DECLARE vnombre VARCHAR(20) DEFAULT 'Sabor Intenso'; + DECLARE vsabor VARCHAR(20) DEFAULT 'Tutti Frutti'; + DECLARE vtamano VARCHAR(20) DEFAULT '700 ml'; + DECLARE venvase VARCHAR(20) DEFAULT 'Botella PET'; + DECLARE vprecio DECIMAL(4,2) DEFAULT '7.25'; + */ + INSERT INTO tabla_de_productos ( + CODIGO_DEL_PRODUCTO, NOMBRE_DEL_PRODUCTO, SABOR, + TAMANO, ENVASE, PRECIO_DE_LISTA + ) VALUES + (vcodigo, vnombre, vtamano, vsabor, venvase, vprecio); +END$$ + +DELIMITER ; +``` + +```sql +CALL insert_prod( + '1000800', 'Sabor del Mar', '700 ml', + 'Naranja', 'Botella de Vidrio', 2.25 +); +``` + +***Crear un Stored Procedure para reajustar la comisión de los vendedores.*** +***Usando como parámetro el valor (Ej. 0,90).*** + +```sql +DELIMITER $$ +CREATE PROCEDURE `reajuste_comision`(vporcentaje FLOAT) +BEGIN + UPDATE tabla_de_vendedores + SET PORCENTAJE_COMISION = PORCENTAJE_COMISION * (1 + vporcentaje); +END $$ +``` + +#### Ejemplo de procedimiento que establece una variable `a` + +```sql +USE jugos_ventas; +DROP PROCEDURE IF EXISTS ejm_params; + +DELIMITER // +USE jugos_ventas// + +CREATE PROCEDURE ejm_params (OUT param1 INTEGER) +BEGIN + DECLARE total INTEGER DEFAULT 16; + SET param1 = total; +END; // + +DELIMITER ; + +CALL ejm_params(@a); + +SELECT @a; +``` + +## Manejo de errores en procedures + +```sql +USE jugos_ventas; +DROP PROCEDURE IF EXISTS insert_prod; + +DELIMITER $$ +USE jugos_ventas$$ +CREATE PROCEDURE insert_prod ( + vcodigo VARCHAR(20), + vnombre VARCHAR(20), + vsabor VARCHAR(20), + vtamano VARCHAR(20), + venvase VARCHAR(20), + vprecio DECIMAL(4,2) +) +BEGIN + DECLARE mensaje VARCHAR(40); + DECLARE EXIT HANDLER FOR 1062 + BEGIN + SET mensaje = 'PK duplicada!'; + SELECT mensaje AS ERROR_PK; + END; + INSERT INTO tabla_de_productos ( + CODIGO_DEL_PRODUCTO, NOMBRE_DEL_PRODUCTO, SABOR, + TAMANO, ENVASE, PRECIO_DE_LISTA + ) VALUES + (vcodigo, vnombre, vtamano, vsabor, venvase, vprecio); + SET mensaje = "Producto insertado con exito"; + SELECT mensaje AS INFORMACION; +END$$ + +DELIMITER ; +``` + +```sql +CALL insert_prod( + '1000800', 'Sabor del Mar', '700 ml', + 'Naranja', 'Botella de Vidrio', 2.25 +); + ++---------------+ +| ERROR_PK | ++---------------+ +| PK duplicada! | ++---------------+ + +CALL insert_prod( + '1000801', 'Sabor del Mar', '900 ml', + 'Naranja', 'Botella de Vidrio', 2.45 +); + ++------------------------------+ +| INFORMACION | ++------------------------------+ +| Producto insertado con exito | ++------------------------------+ +``` + +### Ejemplo rutine que muestra el sabor del produco insertado + +```sql +USE jugos_ventas; +DROP PROCEDURE IF EXISTS mostrar_sabor; + +DELIMITER $$ +USE jugos_ventas$$ +CREATE PROCEDURE mostrar_sabor ( + vcodigo VARCHAR(20) COLLATE 'utf8mb4_uca1400_as_ci' +) +BEGIN + DECLARE vsabor VARCHAR(20); + SELECT SABOR INTO vsabor FROM tabla_de_productos + WHERE CODIGO_DEL_PRODUCTO = `vcodigo`; + SELECT vsabor AS SABOR; +END$$ + +DELIMITER ; +``` + +```sql +CALL mostrar_sabor('1000800'); + ++---------+ +| SABOR | ++---------+ +| Naranja | ++---------+ +``` + +***Crear una variable `N_FACTURAS` y asignar el número de facturas del día*** +***`01/01/2017` y mostrar el valor de la variable.*** + +```sql +DELIMITER $$ +CREATE PROCEDURE `cantidad_facturas`() +BEGIN + DECLARE N_FACTURAS INTEGER; + SELECT COUNT(*) INTO N_FACTURAS FROM facturas WHERE + FECHA_VENTA = '2017-01-01'; + SELECT N_FACTURAS; +END $$ +``` + +## IF THEN ELSE + +```sql +IF THEN + ; +ELSE + ; +END IF +``` + +```sql +DROP PROCEDURE IF EXISTS edad_clientes; +DELIMITER $$ +CREATE PROCEDURE edad_clientes( + vdni VARCHAR(20) COLLATE 'utf8mb4_uca1400_as_ci' +) +BEGIN + DECLARE vresultado VARCHAR(50); + DECLARE vfecha DATE; + SELECT fecha_de_nacimiento INTO vfecha + FROM tabla_de_clientes WHERE dni=vdni; + IF + vfecha < '19950101' + THEN + SET vresultado = 'Cliente Antiguo'; + ELSE + SET vresultado = 'Cliente Normal'; + END IF; + SELECT vresultado AS TIPO_CLIENTE; +END $$ + +DELIMITER ; +``` + +``` +CALL edad_clientes('1471156710'); ++-----------------+ +| TIPO_CLIENTE | ++-----------------+ +| Cliente Antiguo | ++-----------------+ + +CALL edad_clientes('3623344710'); ++----------------+ +| TIPO_CLIENTE | ++----------------+ +| Cliente Normal | ++----------------+ +``` + +***Crear un Stored Procedure que, según una fecha dada, calcule el número*** +***de facturas. Si el resultado son más de 70 facturas mostar el mensaje:*** +***'Muchas facturas'. En otro caso, el mensaje será 'Pocas facturas'.*** +***En ambos casos se debe mostrar el número de facturas*** + +```sql +DROP PROCEDURE IF EXISTS cant_facturas; +DELIMITER $$ +CREATE PROCEDURE cant_facturas(vfecha DATE) +BEGIN + DECLARE vresultado VARCHAR(50); + DECLARE vfacturas INTEGER; + SELECT COUNT(matricula) INTO vfacturas + FROM facturas WHERE fecha_venta=vfecha; + IF + vfacturas > 70 + THEN + SET vresultado = CONCAT('Muchas Facturas: ', vfacturas); + # SET vresultado = 'Muchas Facturas: '; + ELSE + SET vresultado = CONTACT('Pocas Facturas: ', vfacturas); + # SET vresultado = 'Pocas Facturas: '; + END IF; + SELECT vresultado AS FACTURAS_EN_FECHA; +END $$ + +DELIMITER ; +``` + +```sql +CALL cant_facturas('2018-01-01'); ++---------------------+ +| FACTURAS_EN_FECHA | ++---------------------+ +| Muchas Facturas: 87 | ++---------------------+ +``` + +### ELSE IF + +```sql +IF THEN + ; +ELSEIF + ; + (...) +ELSEIF + ; +ELSE + ; +END IF; +``` + +```sql +/* + precio >= 12 producto costoso + precio >= 12 < 12 producto asequible + precio < 7 producto barato +*/ +DROP PROCEDURE IF EXISTS clasific_precio; +DELIMITER $$ +CREATE PROCEDURE clasific_precio( + vcodigo VARCHAR(20) + COLLATE 'utf8mb4_uca1400_as_ci' +) +BEGIN + DECLARE vresultado VARCHAR(40); + DECLARE vprecio FLOAT; + + SELECT precio_de_lista INTO vprecio + FROM tabla_de_productos + WHERE codigo_del_producto = vcodigo; + + IF vprecio >= 12 THEN + SET vresultado = CONCAT( + 'Producto Costoso: $', vprecio, '.-' + ); + ELSEIF vprecio >= 7 AND vprecio < 12 THEN + SET vresultado = CONCAT( + 'Producto Asequible: $', vprecio, '.-' + ); + ELSE + SET vresultado = CONCAT( + 'Producto Barato: $', vprecio, '.-' + ); + END IF; + SELECT vresultado AS "Clasificación por precio"; +END $$ + +DELIMITER ; +``` + +```sql +CALL clasific_precio('1000800'); ++--------------------------+ +| Clasificación por precio | ++--------------------------+ +| Producto Barato: $2.25.- | ++--------------------------+ + +CALL clasific_precio('783663'); ++-----------------------------+ +| Clasificación por precio | ++-----------------------------+ +| Producto Asequible: $7.71.- | ++-----------------------------+ + +CALL clasific_precio('1004327'); ++----------------------------+ +| Clasificación por precio | ++----------------------------+ +| Producto Costoso: $19.51.- | ++----------------------------+ +``` + +***Crear un Stored Procedure `comparacion_ventas` que compare las ventas*** +***en entre 2 fechas distintas. Si el porcentaje de variación es mayor al*** +***10% debe retornar 'Verde'. Si está entre -10% y 10% debe restornar*** +***'Amarillo'. Si es menor al -10% debe retornar 'Rojo'*** + +```sql +/* + total > 10% Verde + precio <= 10% y >= -10% Amarillo + precio < -10% Rojo +*/ +DROP PROCEDURE IF EXISTS comparacion_ventas; +DELIMITER $$ +CREATE PROCEDURE comparacion_ventas( + vfecha1 DATE, + vfecha2 DATE +) +BEGIN + DECLARE vresultado VARCHAR(50); + DECLARE vtotal1 INTEGER; + DECLARE vtotal2 INTEGER; + DECLARE vtotal FLOAT; + + SELECT SUM(B.CANTIDAD * B.PRECIO) INTO vtotal1 + FROM facturas A INNER JOIN items_facturas B + ON A.NUMERO = B.NUMERO + WHERE A.FECHA_VENTA = vfecha1; + + SELECT SUM(B.CANTIDAD * B.PRECIO) INTO vtotal2 + FROM facturas A INNER JOIN items_facturas B + ON A.NUMERO = B.NUMERO + WHERE A.FECHA_VENTA = vfecha2; + + SELECT ROUND(((vtotal2*100)/vtotal1)-100, 2) INTO vtotal; + + IF vtotal > 10 THEN + SET vresultado = CONCAT('Verde: ', vtotal, '%'); + ELSEIF vtotal >= -10 AND vtotal <= 10 THEN + SET vresultado = CONCAT('Amarillo: ', vtotal, '%'); + ELSE + SET vresultado = CONCAT('Rojo: ', vtotal, '%'); + END IF; + SELECT vresultado AS "Porcentaje variación ventas"; +END $$ + +DELIMITER ; +``` + +```sql +CALL comparacion_ventas('2016-12-31','2017-12-31'); ++------------------------------+ +| Porcentaje variación ventas | ++------------------------------+ +| Verde: 28.23% | ++------------------------------+ +``` + +```sql +CALL comparacion_ventas('2018-03-28','2018-03-27'); ++------------------------------+ +| Porcentaje variación ventas | ++------------------------------+ +| Amarillo: -2.2% | ++------------------------------+ +``` + +```sql +CALL comparacion_ventas('2018-03-28','2015-03-28'); ++------------------------------+ +| Porcentaje variación ventas | ++------------------------------+ +| Rojo: -41.86% | ++------------------------------+ +``` + +## CASE + +```sql +CASE + WHEN THEN ; + WHEN THEN ; + (...) + WHEN THEN ; + [ELSE ] +END CASE; +``` + +```sql +/* +Maracuya = Rico +Frutilla = Rico +Uva = Rico +Sandía = Normal +Mango = Normal +Otros = Comunes +*/ +DROP PROCEDURE IF EXISTS clasif_sabores; +DELIMITER $$ +CREATE PROCEDURE clasif_sabores( + vcodigo VARCHAR(20) + COLLATE 'utf8mb4_uca1400_as_ci' +) +BEGIN + DECLARE msg_error VARCHAR(40); + DECLARE vresultado VARCHAR(50); + DECLARE vsabor VARCHAR(50); + # Se comenta el ELSE para testear HANDLER CASE NOT FOUND + #DECLARE CONTINUE HANDLER FOR 1339 + DECLARE EXIT HANDLER FOR 1339 + BEGIN + SET msg_error = "Sabor sin clasificar!"; + SELECT msg_error AS "ERROR CASE NOT FOUND"; + END; + + SELECT sabor INTO vsabor + FROM tabla_de_productos + WHERE codigo_del_producto = vcodigo; + + CASE vsabor + WHEN 'Maracuya' THEN SELECT 'Rico' INTO vresultado; + WHEN 'Limón' THEN SELECT 'Rico' INTO vresultado; + WHEN 'Frutilla' THEN SELECT 'Rico' INTO vresultado; + WHEN 'Uva' THEN SELECT 'Rico' INTO vresultado; + WHEN 'Sandía' THEN SELECT 'Normal' INTO vresultado; + WHEN 'Mango' THEN SELECT 'Normal' INTO vresultado; + #ELSE SELECT 'Común' INTO vresultado; + END CASE; + SELECT CONCAT(vsabor, ': ', vresultado) AS "Clasificación de sabor"; +END $$ + +DELIMITER ; +``` + +```sql +CALL clasif_sabores('1042712'); ++-------------------------+ +| Clasificación de sabor | ++-------------------------+ +| Limón: Rico | ++-------------------------+ +``` + +```sql +CALL clasif_sabores('1004327'); ++-------------------------+ +| Clasificación de sabor | ++-------------------------+ +| Sandía: Normal | ++-------------------------+ +``` + +```sql +CALL clasif_sabores('394479'); ++-------------------------+ +| Clasificación de sabor | ++-------------------------+ +| Cereza: Común | ++-------------------------+ +``` + +### CASE condicional + +```sql +DROP PROCEDURE IF EXISTS clasif_precio; +DELIMITER $$ +CREATE PROCEDURE clasif_precio( + vcodigo VARCHAR(20) + COLLATE 'utf8mb4_uca1400_as_ci' +) +BEGIN + DECLARE vresultado VARCHAR(40); + DECLARE vprecio FLOAT; + + SELECT precio_de_lista INTO vprecio + FROM tabla_de_productos + WHERE codigo_del_producto = vcodigo; + + CASE + WHEN vprecio >= 12 THEN + SET vresultado = CONCAT( + 'Producto Costoso: $', vprecio, '.-' + ); + WHEN vprecio >= 7 AND vprecio < 12 THEN + SET vresultado = CONCAT( + 'Producto Asequible: $', vprecio, '.-' + ); + ELSE + SET vresultado = CONCAT( + 'Producto Barato: $', vprecio, '.-' + ); + END CASE; + SELECT vresultado AS "Clasificación por precio"; +END $$ + +DELIMITER ; +``` + +```sql +CALL clasif_precio('1013793'); ++----------------------------+ +| Clasificación por precio | ++----------------------------+ +| Producto Costoso: $24.01.- | ++----------------------------+ +``` + +```sql +CALL clasif_precio('1096818'); ++-----------------------------+ +| Clasificación por precio | ++-----------------------------+ +| Producto Asequible: $7.71.- | ++-----------------------------+ +``` + +```sql +CALL clasif_precio('1000801'); ++---------------------------+ +| Clasificación por precio | ++---------------------------+ +| Producto Barato: $2.45.- | ++---------------------------+ +``` + +***Modificar SP `comparacion_ventas` usando CASE*** + +```sql +DROP PROCEDURE IF EXISTS compara_ventas; +DELIMITER $$ +CREATE PROCEDURE compara_ventas( + vfecha1 DATE, + vfecha2 DATE +) +BEGIN + DECLARE vresultado VARCHAR(50); + DECLARE vtotal1 INTEGER; + DECLARE vtotal2 INTEGER; + DECLARE vtotal FLOAT; + + SELECT SUM(B.CANTIDAD * B.PRECIO) INTO vtotal1 + FROM facturas A INNER JOIN items_facturas B + ON A.NUMERO = B.NUMERO + WHERE A.FECHA_VENTA = vfecha1; + + SELECT SUM(B.CANTIDAD * B.PRECIO) INTO vtotal2 + FROM facturas A INNER JOIN items_facturas B + ON A.NUMERO = B.NUMERO + WHERE A.FECHA_VENTA = vfecha2; + + SELECT ROUND(((vtotal2*100)/vtotal1)-100, 2) INTO vtotal; + + CASE + WHEN vtotal > 10 THEN + SET vresultado = CONCAT('Verde: ', vtotal, '%'); + WHEN vtotal >= -10 AND vtotal <= 10 THEN + SET vresultado = CONCAT('Amarillo: ', vtotal, '%'); + ELSE + SET vresultado = CONCAT('Rojo: ', vtotal, '%'); + END CASE; + SELECT vresultado AS "Porcentaje variación ventas"; +END $$ + +DELIMITER ; +``` + +```sql +CALL comparacion_ventas('2016-12-31','2017-12-31'); ++------------------------------+ +| Porcentaje variación ventas | ++------------------------------+ +| Verde: 28.23% | ++------------------------------+ +``` + +```sql +CALL comparacion_ventas('2018-03-28','2018-03-27'); ++------------------------------+ +| Porcentaje variación ventas | ++------------------------------+ +| Amarillo: -2.2% | ++------------------------------+ +``` + +```sql +CALL comparacion_ventas('2018-03-28','2015-03-28'); ++------------------------------+ +| Porcentaje variación ventas | ++------------------------------+ +| Rojo: -41.86% | ++------------------------------+ +``` + +## WHILE + +```sql +WHILE + DO ; +END WHILE; +``` + +```sql +DROP PROCEDURE IF EXISTS looping; +DELIMITER $$ +CREATE PROCEDURE looping(vinicial INT, vfinal INT) +BEGIN + DECLARE vcontador INT; + SET vcontador = vinicial; + CREATE TABLE tb_looping (ID INT); + WHILE vcontador <= vfinal + DO + INSERT INTO tb_looping VALUES(vcontador); + SET vcontador = vcontador+1; + END WHILE; + SELECT * FROM tb_looping; + DROP TABLE tb_looping; +END $$ + +DELIMITER ; +``` + +```sql +CALL looping(1,5); ++------+ +| ID | ++------+ +| 1 | +| 2 | +| 3 | +| 4 | +| 5 | ++------+ +``` + +***Crear un SP que, a partir del día `01/01/2017`, cuente el número de*** +***facturas hasta el día `10/01/2017`. Debe mostrar la fecha y el número*** +***de facturas día tras día*** + +#### Solución propuesta + +```sql +DROP PROCEDURE IF EXISTS loop_date; +DELIMITER $$ +CREATE PROCEDURE loop_date(vfecha_inicial DATE, vfecha_final DATE) +BEGIN + CREATE TEMPORARY TABLE informe (Fecha DATE, Facturas_Emitidas INT); + WHILE vfecha_inicial <= vfecha_final + DO + INSERT INTO informe + SELECT fecha_venta, COUNT(numero) FROM facturas + WHERE fecha_venta = vfecha_inicial; + SET vfecha_inicial = ADDDATE(vfecha_inicial, INTERVAL 1 DAY); + END WHILE; + SELECT * FROM informe; + DROP TABLE informe; +END $$ + +DELIMITER ; +``` + +```sql +CALL loop_date('2017-01-01','2017-01-10'); ++------------+-------------------+ +| Fecha | Facturas_Emitidas | ++------------+-------------------+ +| 2017-01-01 | 74 | +| 2017-01-02 | 77 | +| 2017-01-03 | 81 | +| 2017-01-04 | 71 | +| 2017-01-05 | 65 | +| 2017-01-06 | 75 | +| 2017-01-07 | 82 | +| 2017-01-08 | 80 | +| 2017-01-09 | 85 | +| 2017-01-10 | 72 | ++------------+------------------- +``` + +#### Solución + +```sql +DROP PROCEDURE IF EXISTS suma_dias_facturas; +DELIMITER $$ +CREATE PROCEDURE suma_dias_facturas() +BEGIN + DECLARE fecha_inicial DATE; + DECLARE fecha_final DATE; + DECLARE n_facturas INT; + SET fecha_inicial = '20170101'; + SET fecha_final = '20170110'; + WHILE fecha_inicial <= fecha_final + DO + SELECT COUNT(*) INTO n_facturas FROM facturas + WHERE FECHA_VENTA = fecha_inicial; + SELECT concat( + DATE_FORMAT(fecha_inicial, '%d/%m/%Y'), + '-' , CAST(n_facturas AS CHAR(50)) + ) AS RESULTADO; + SELECT ADDDATE(fecha_inicial, INTERVAL 1 DAY) INTO fecha_inicial; + END WHILE; +END $$ +DELIMITER ; +``` + +## Problemas con SELECT INTO + +```sql +DROP PROCEDURE IF EXISTS problema_select_into; +DELIMITER $$ +CREATE PROCEDURE problema_select_into() +BEGIN + DECLARE vnombre VARCHAR(50); + SELECT nombre INTO vnombre FROM tabla_de_clientes; + SELECT vnombre; +END $$ +DELIMITER ; + +CALL problema_select_into(); +ERROR 1172 (42000): Result consisted of more than one row +``` + +## CURSOR + +Un **CURSOR** es una estructura que permite la interación línea a línea +mediante un orden determinado + +#### Fases para utilizar Cursor + +- **Declaración:** Definir la consulta que será depositada en el cursor +- **Apertura:** Abrir el cursor para recorrerlo línea a línea +- **Recorrido:** Línea a línea hasta el final +- **Cierre:** Cierre del cursor +- **Limpieza:** Limpiar el cursor de la memoria + +#### Tabla_1 + +| Codigo | Nombre | Valor | +| - | - | - | +| 1 | Juan | 2 | +| 2 | José | 67 | +| 3 | María | 7 | +| 4 | Lucia | 54 | + +```sql +DECLARE @nombre VARCHAR(10) +DECLARE CURSOR_1 CURSOR FOR + SELECT NOMBRE FROM tabla_1; + OPEN CURSOR_1; + FETCH CURSOR_1 INTO @nombre; + FETCH CURSOR_1 INTO @nombre; + FETCH CURSOR_1 INTO @nombre; + FETCH CURSOR_1 INTO @nombre; + CLOSE CURSOR_1; +``` + +#### Ejemplo CURSOR + +```sql +DROP PROCEDURE IF EXISTS ejm_cursor; +DELIMITER $$ +CREATE PROCEDURE ejm_cursor() +BEGIN + DECLARE vnombre VARCHAR(50); + DECLARE c CURSOR FOR + SELECT nombre FROM tabla_de_clientes LIMIT 4; + OPEN c; + FETCH c INTO vnombre; + SELECT vnombre; + FETCH c INTO vnombre; + SELECT vnombre; + FETCH c INTO vnombre; + SELECT vnombre; + FETCH c INTO vnombre; + SELECT vnombre; + CLOSE c; +END $$ +DELIMITER ; +``` + +```sql +CALL ejm_cursor; ++---------------+ +| vnombre | ++---------------+ +| Erica Carvajo | ++---------------+ + ++--------------+ +| vnombre | ++--------------+ +| Marcos Rosas | ++--------------+ + ++--------------+ +| vnombre | ++--------------+ +| Jorge Castro | ++--------------+ + ++-------------+ +| vnombre | ++-------------+ +| Abel Pintos | ++-------------+ +``` + +### Iterando el CURSOR + +```sql +DROP PROCEDURE IF EXISTS iter_cursor; +DELIMITER $$ +CREATE PROCEDURE iter_cursor() +BEGIN + DECLARE fin_c BIT(1) DEFAULT b'0'; + DECLARE vnombre VARCHAR(50); + DECLARE c CURSOR FOR + SELECT nombre FROM tabla_de_clientes; + DECLARE CONTINUE HANDLER FOR NOT FOUND + SET fin_c = b'1'; + OPEN c; + WHILE fin_c = b'0' DO + FETCH c INTO vnombre; + IF fin_c = 0 THEN + SELECT vnombre; + END IF; + END WHILE; + CLOSE c; +END $$ +DELIMITER ; +``` + +```sql +CALL iter_cursor; ++---------------+ +| vnombre | ++---------------+ +| Erica Carvajo | ++---------------+ + ++--------------+ +| vnombre | ++--------------+ +| Marcos Rosas | ++--------------+ + ++--------------+ +| vnombre | ++--------------+ +| Jorge Castro | ++--------------+ + +... +``` + +***Crear un SP usando un cursor para hallar el valor total de todos los*** +***créditos, de todos los clientes*** + +```sql +DROP PROCEDURE IF EXISTS total_creditos; +DELIMITER $$ +CREATE PROCEDURE total_creditos() +BEGIN + DECLARE fin_c BIT(1) DEFAULT b'0'; + DECLARE vcred_total FLOAT DEFAULT 0; + DECLARE vcred_temp FLOAT DEFAULT 0; + DECLARE c CURSOR FOR + SELECT limite_de_credito FROM tabla_de_clientes; + DECLARE CONTINUE HANDLER FOR NOT FOUND + SET fin_c = b'1'; + OPEN c; + WHILE fin_c = b'0' DO + FETCH c INTO vcred_temp; + IF fin_c = 0 THEN + SET vcred_total = vcred_total + vcred_temp; + END IF; + END WHILE; + CLOSE c; + SELECT vcred_total AS "Limite de Credito TOTAL"; +END $$ +DELIMITER ; +``` + +```sql +CALL total_creditos; ++-------------------------+ +| Limite de Credito TOTAL | ++-------------------------+ +| 1780000 | ++-------------------------+ +``` + +### Asignación de multiples campos al CURSOR + +```sql +DROP PROCEDURE IF EXISTS cursor_multicampo; +DELIMITER $$ +CREATE PROCEDURE cursor_multicampo() +BEGIN + DECLARE fin_c BIT(1) DEFAULT b'0'; + DECLARE vbarrio, vciudad, vcodp VARCHAR(50); + DECLARE vnombre, vdireccion VARCHAR(150); + DECLARE c CURSOR FOR + SELECT nombre, direccion_1, barrio, ciudad, cp + FROM tabla_de_clientes; + DECLARE CONTINUE HANDLER FOR NOT FOUND + SET fin_c = b'1'; + OPEN c; + WHILE fin_c = b'0' DO + FETCH c INTO vnombre, vdireccion, vbarrio, vciudad, vcodp; + IF fin_c = b'0' THEN + SELECT CONCAT(vnombre, ', ', vdireccion, ', ', vbarrio, ', ', + vciudad, ', ', vcodp) AS Muticampos; + END IF; + END WHILE; + CLOSE c; +END $$ +DELIMITER ; +``` + +```sql +CALL cursor_multicampo; ++------------------------------------------------------------------------------+ +| Muticampos | ++------------------------------------------------------------------------------+ +| Erica Carvajo, Heriberto Frías 1107, Del Valle, Ciudad de México, 80012212 | ++------------------------------------------------------------------------------+ + ++-----------------------------------------------------------------------+ +| Muticampos | ++-----------------------------------------------------------------------+ +| Marcos Rosas, Av. Universidad, Del Valle, Ciudad de México, 22002012 | ++-----------------------------------------------------------------------+ + ++---------------------------------------------------------------------------------+ +| Muticampos | ++---------------------------------------------------------------------------------+ +| Jorge Castro, Federal México-Toluca 5690, Locaxco, Ciudad de México, 22012002 | ++---------------------------------------------------------------------------------+ + +... +``` + +***Crear un SP usando un cursor para hallar el valor total de la*** +***facturación para un determinado mes y año.*** + +#### Solución propuesta + +```sql +DROP PROCEDURE IF EXISTS facturacion_mes_ano; +DELIMITER $$ +CREATE PROCEDURE facturacion_mes_ano(ano_mes VARCHAR(10)) +BEGIN + DECLARE fin_c BIT(1) DEFAULT b'0'; + DECLARE vcantidad INT; + DECLARE vprecio FLOAT; + DECLARE vtotal FLOAT DEFAULT 0; + DECLARE vfecha DATE; + DECLARE c CURSOR FOR + SELECT IFa.cantidad, IFa.precio + FROM items_facturas IFa + INNER JOIN facturas F ON F.numero = IFa.numero + WHERE MONTH(F.fecha_venta) = MONTH(vfecha) + AND YEAR(F.fecha_venta) = YEAR(vfecha); + DECLARE CONTINUE HANDLER FOR NOT FOUND + SET fin_c = b'1'; + SET vfecha = DATE(CONCAT(ano_mes, '-01')); + OPEN c; + WHILE fin_c = b'0' DO + FETCH c INTO vcantidad, vprecio; + IF fin_c = b'0' THEN + SET vtotal = vtotal+(vcantidad*vprecio); + END IF; + END WHILE; + CLOSE c; + SELECT ano_mes AS "Año-Mes", + CONCAT('$ ', vtotal, '.-') AS "Venta Total"; +END $$ +DELIMITER ; +``` + +```sql +CALL facturacion_mes_ano('2017-01'); ++----------+-------------+ +| Año-Mes | Venta Total | ++----------+-------------+ +| 2017-01 | $3838320.- | ++----------+-------------+ +``` + +#### Solución + +```sql +DROP PROCEDURE IF EXISTS campo_adicional; +DELIMITER $$ +CREATE PROCEDURE campo_adicional() +BEGIN + DECLARE cantidad INT; + DECLARE precio FLOAT; + DECLARE facturacion_acumulada FLOAT; + DECLARE fin_cursor INT; + DECLARE c CURSOR FOR + SELECT IFa.CANTIDAD, IFa.PRECIO FROM items_facturas IFa + INNER JOIN facturas F ON F.NUMERO = IFa.NUMERO + WHERE MONTH(F.FECHA_VENTA) = 1 + AND YEAR(F.FECHA_VENTA) = 2017; + DECLARE CONTINUE HANDLER FOR NOT FOUND + SET fin_cursor = 1; + OPEN c; + SET fin_cursor = 0; + SET facturacion_acumulada = 0; + WHILE fin_cursor = 0 DO + FETCH c INTO cantidad, precio; + IF fin_cursor = 0 THEN + SET facturacion_acumulada = + facturacion_acumulada + (cantidad * precio); + END IF; + END WHILE; + CLOSE c; + SELECT facturacion_acumulada; +END $$ +DELIMITER ; +``` + +```sql +CALL campo_adicional; ++-----------------------+ +| facturacion_acumulada | ++-----------------------+ +| 3838320 | ++-----------------------+ +``` + +## FUNCTION + +```sql +CREATE FUNCTION function_name (parameters) +RETURNS datatype; +BEGIN + DECLARE ; + (...) + ; + (...) + RETURN + (...) +END; +``` + +```sql +DROP FUNCTION IF EXISTS f_clas_sabores; +DELIMITER $$ +CREATE FUNCTION f_clas_sabores(vsabor VARCHAR(20)) +RETURNS VARCHAR(40) COLLATE 'utf8mb4_uca1400_as_ci' +BEGIN + DECLARE vretorno VARCHAR(40) DEFAULT ''; + CASE vsabor + WHEN 'Maracuya' THEN SET vretorno = 'Rico'; + WHEN 'Limón' THEN SET vretorno = 'Rico'; + WHEN 'Frutilla' THEN SET vretorno = 'Rico'; + WHEN 'Uva' THEN SET vretorno = 'Rico'; + WHEN 'Sandía' THEN SET vretorno = 'Normal'; + WHEN 'Mango' THEN SET vretorno = 'Normal'; + ELSE SET vretorno = 'Común'; + END CASE; + RETURN vretorno; +END $$ +DELIMITER ; +``` + +```sql +SELECT f_clas_sabores('Sandía') AS "Clasificación"; ++----------------+ +| Clasificación | ++----------------+ +| Normal | ++----------------+ +``` + +```sql +SELECT nombre_del_producto, sabor, f_clas_sabores(sabor) AS "Clasificación" +FROM tabla_de_productos; ++---------------------+-----------------+----------------+ +| nombre_del_producto | sabor | Clasificación | ++---------------------+-----------------+----------------+ +| Sabor del Mar | Naranja | Común | +| Sabor del Mar | Naranja | Común | +| Sabor da Montaña | Uva | Rico | +... +``` + +```sql +SELECT nombre_del_producto, sabor +FROM tabla_de_productos +WHERE f_clas_sabores(SABOR) = 'Normal'; ++---------------------+---------+ +| nombre_del_producto | sabor | ++---------------------+---------+ +| Vida del Campo | Sandía | +| Light | Sandía | +| Verano | Mango | +| Refrescante | Mango | +| Refrescante | Mango | +| Verano | Mango | +| Vida del Campo | Sandía | +| Refrescante | Mango | +| Light | Sandía | ++---------------------+---------+ +``` + +***Transformar el sgte. SP en una función que recibe como parámetro la*** +***fecha y retorna el número de facturas*** + +```sql +DELIMITER $$ +CREATE PROCEDURE `sp_numero_facturas` () +BEGIN + DECLARE n_facturas INT; + SELECT COUNT(*) INTO n_facturas FROM facturas + WHERE FECHA_VENTA = '20170101'; + SELECT n_facturas; +END $$ +``` + +```sql +DROP FUNCTION IF EXISTS f_cantidad_facturas; +DELIMITER $$ +CREATE FUNCTION f_cantidad_facturas(fecha DATE) +RETURNS INT +BEGIN + DECLARE n_facturas INT; + SELECT COUNT(*) INTO n_facturas FROM facturas + WHERE FECHA_VENTA = fecha; + RETURN n_facturas; +END $$ +DELIMITER ; +``` + +```sql +SELECT f_cantidad_facturas('2018-01-01'); ++-----------------------------------+ +| f_cantidad_facturas('2018-01-01') | ++-----------------------------------+ +| 87 | ++-----------------------------------+ +``` diff --git a/011_mysql/table_data_import_wizard.png b/011_mysql/table_data_import_wizard.png new file mode 100644 index 0000000000000000000000000000000000000000..8982645fb94a7c3c547526c7b396a61258296180 GIT binary patch literal 38469 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 literal 0 HcmV?d00001