No es un bug, es una característica no documentada

Mostrando entradas con la etiqueta MySQL. Mostrar todas las entradas
Mostrando entradas con la etiqueta MySQL. Mostrar todas las entradas

4/7/16

Java y MySQL. Patrón Singleton

21:35 Posted by Inazio Claver , 7 comments
Image
¿Necesitas una conexión en tu aplicación Java? ¿Harto de abrir varias instancias cada vez que quieres conectarte a MySQL? ¿Estás leyendo esto con voz de anuncio?
Entonces el patrón Singleton es tu nuevo mejor amigo.

El patrón Singleton, o patrón de instancia única, es un patrón de diseño encargado de restringir la creación de objetos de una clase (o el valor de un tipo) a un único objeto. Genera una única instancia en la ejecución del programa y proporciona un acceso global a la misma.

Sirve para multitud de funcionalidades, pero en este caso vamos a ver su aplicación a la hora de conectarnos a una base de datos MySQL.

1/11/15

24/10/15

Acceso a datos. JDBC (V). Uso del ResultSet

20:03 Posted by Inazio , , No comments
El ResultSet es el objeto que representa el resultado de una consulta.
No carga toda la información en memoria, y se pueden usar para actualizar, borrar e insertar nuevas filas.

Características

Al crear un Statement, un PreparedStatement o un CallableStatement, se pueden configurar aspectos del ResultSet que devolverá al ejecutar la consulta.

createStatement(int resultSetType, int resultSetConcurrency);

prepareStatement(String SQL, int resultSetType, int resultSetConcurrency);

prepareCall(String sql, int resultSetType, int resultSetConcurrency);

resultSetType
  • ResultSet.TYPE_FORWARD_ONLY. Sólo movimiento hacia delante (por defecto).
  • ResultSet.TYPE_SCROLL_INSENSITIVE. Puede hacer cualquier movimiento pero no refleja los cambios en la base de datos.
  • ResultSet.TYPE_SCROLL_SENSITIVE. Puede hacer cualquier movimiento y además refleja los cambios en la base de datos.


resultSetConcurrency
  • ResultSet.CONCUR_READ_ONLY. Sólo lectura (por defecto).
  •  ResultSet.CONCUR_UPDATABLE. Actualizable.


Actualización de datos

rs.updateString("campo", "valor");
rs.updateInt(1, 3);
rs.updateRow();

Inserción de datos

rs.moveToInsertRow();
rs.updateString(1, "AINSWORTH");
rs.updateInt(2, 35);
rs.updateBoolean(3, true);
rs.insertRow();
rs.moveToCurrentRow();

La última línea, por cierto, mueve el cursor a la posición anterior al movimiento de inserción.

Posicionamiento del cursor

El cursor puede estar en una fila concreta, pero también puede estar en dos filas especiales.
  • Antes de la primera fila (Before the First Row, BFR)
  • Después de la última fila (After the Last Row, ALR)

Inicialmente el ResultSet está en BFR, y next() mueve el cursor hacía delante, devolviendo true si se encuentra en una fila concreta y false si alcanza el ALR.

while (rs.next()){
     String name = rs.getString("titulo");
     float price = rs.getFloat("precio");
     System.out.println(name + "\t" + price);
}


Image

Acceso a datos. JDBC (III). Conexiones y sentencias SQL

15:46 Posted by Inazio , , No comments

Conexiones a una base de datos


Cada objeto Connection representa una conexión física con la base de datos.
Se pueden especificar más propiedades además del usuario y la contraseña al crear una conexión.
Estas propiedades se pueden especificar:
  • Codificadas en la URL (ver detalles de la base de datos)
  •  Usando métodos getConnection(…) sobrecargardos de la clase DriverManager.

Ejemplo 1
String url = "jdbc:mysql://localhost:3306/sample";
String name = "root";
String password = "pass";
Connection c = DriverManager.getConnection(url, user, password);

Ejemplo 2
String url = "jdbc:mysql://localhost:3306/sample?user=root&password=pass";
Connection c = DriverManager.getConnection(url);

Ejemplo 3
String url = "jdbc:mysql://localhot:3306/sample";
Properties prop = new Properties();
prop.setProperty("user", "root");
prop.setProperty("password", "pass");
Connection c = DriverManager.getConnection(url, prop);

Sentencias SQL

Con JDBC se pueden usar diferentes tipos de Statement.

Statement.
SQL estático en tiempo de ejecución, no acepta parámetros
Statement stmt = conn.createStatement();

PreparedStatement
Para ejecutar la misma sentencia muchas veces la “prepara”. Acepta parámetros
PreparedStatement ps = conn.prepareStatement(...);

CallableStatement
Llamadas a procedimientos almacenados.

CallableStatement s = conn.prepareCall(...);
Image

Uso de Statement

Tiene diferentes métodos para ejecutar una sentencia.
  •  executeQuery(…). Seusa para sentencias SELECT. Devuelve un ResultSet
  • executeUpdate(…). Se usa para sentencias INSERT, UPDATE, DELETE o sentencias DDL. Devuelve el número de filas afectadas por la sentencia.
  • execute(…). Método genérico de ejecución de consultas. Puede devolver uno o más ResultSet y uno o más contadores de filas afectadas.

Uso de PreparedStatement

Los PreparedStatement se utilizan:
  • Cuando se requieren parámetros.
  • Cuando se ejecuta muchas veces la misma sentencia.
    •  La sentencia se prepara al crear el objeto.
    • Puede llamarse varias veces a los métodos execute.

PreparedStatement ps = conn.preparedStatement("INSERT INTO Libros VALUES (?, ?, ?)");
ps.setInt(1, 23);
ps.setString(2, "Bambi");
ps.setInt(3, 45);
ps.executeUpdate();

Uso de CallableStatement

Permite hacer llamadas a los procedimientos almacenados de la base de datos.
Permite parámetros de entrada IN (como el PreparedStatement), parámetros de entrada – salida INOUT y parámetros de salida OUT.

CallableStatement cstmt = conn.prepareCall("{call getEmpName (?, ?)}");
cstmt.setInt(1, 111111111);
cstmt.registerOutParameter(2, java.sql.Types.VARCHAR);
cstmt.execute();

String empName = cstmt.getString(2);

Acceso a datos .JDBC (II). Diseño de una aplicación con BD

0:19 Posted by Inazio , , No comments
Se sigue el patrón DAO (Data Access Object).

Cuando se desarrolla una aplicación con BD los detalles de la comunicación con la base de datos se implementan en una clase o módulo.

La información se gestiona como objetos definidos en una clase de Java (clase Libro, Autor…), y el sistema de persistencia (BD, XML, fichero de texto, servicio web…) se puede cambiar fácilmente.

Se pueden distribuir responsabilidades entre los integrantes del equipo de desarrollo.

Por ejemplo, vamos a implementar una sencilla aplicación Java que permita gestionar libros y autores en una base de datos.

Solo soportará listado de libros, siendo estos de todos los libros, por título o por su precio. Además, al listar los libros deberán mostrarse los autores.


Las clases a implementar deberían ser las siguientes:

Image

Acceso a datos. JDBC (I). Introducción

0:01 Posted by Inazio , , No comments
JDBC es un interfaz orientado a objetos de Java para SQL.
Se utiliza para enviar sentencias SQL a un sistema gestor de bases de datos (DBMS).
Con JDBC tenemos que continuar escribiendo las sentencias SQL, y ni le añade ni quita potencia al SQL.

Arquitectura JDBC

La filosofía de JDBC es proporcionar transparencia al desarrollador frente al gestor de bases de datos.

JDBC utiliza un Gestor de Controladores que hace de interfaz con el controlador específico de la BD.

Image

JDBC en MySQL

Hay que añadir la librería mysql-connector-java-x.x.x-bin.jar a nuestro proyecto.
Por ejemplo, si compilamos desde línea de comandos, añadimos el fichero a la variable del sistema CLASSPATH, y si usamos Eclipse, vamos a Project à Properties à Java Build Path à Libraries à Add External JARs…

El driver es com.mysql.jdbc.Driver y la URL para conectar a la BD es jdbc:mysql://localhost:3306/simple

Introducción a JDBC

JDBC (Java DataBase Connectivity) es la API estándar de acceso a base de datos desde Java.
Está incluida en Java SE (Standard Edition). En Java SE 6 se incluye JDBC 4.0, pero actualmente la mayoría de bases de datos soportan JDBC 3.0.

Si se desa más información se puede visitar los siguientes enlaces

Para conectarse a una base de datos concreta, es necesario su driver JDBC.
El driver es un fichero JAR que se añade a la aplicación como cualquier otra librería (no necesita instalación adicional), y la mayoría de las bases de datos incorporan un driver JDBC.

ODBC (Open DataBase Connectivity) es un estándar de acceso a base de datos desarrollado por Microsoft. Sun ha desarrollado un driver que hace de puente entre JDBC y ODBC aunque no suele usarse.

Los pasos para que una aplicación se comunique con una base de datos son:
  • Cargar el driver necesario para comprender el protocolo que usa la base de datos concreta.
  • Establecer una conexión con la base de datos, normalmente a través de red.
  • Enviar consultas SQL y procesar el resultado
  • Liberar los recursos al terminar
  • Manejar los errores que se puedan producir

Veamos el siguiente código

import java.sql.*;

public class HolaMundoBaseDatos {

     public static void main(String[] args) throws ClassNotFoundException, SQLException {
         
          Class.forName("com.mysql.jdbc.Driver");
          Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/sample", "root", "password");
         
          Statement stmt = conn.createStatement();
          ResultSet rs = stmt.executeQuery("SELECT titulo, precio FROM Libros WHERE precio > 2");
         
          while(rs.next()){
                String name = rs.getString("titulo");
                float price = rs.getFloat("precio");
                System.out.println(name + "\t" + price);
          }
         
          rs.close();
          stmt.close();
          conn.close();
     }
}

Veamos que hace este código línea por línea:

Carga del driver

Class.forName("com.mysql.jdbc.Driver");

Antes de poder conectarse a la base de datos es necesario cargar el driver JDBC, y solo hay que hacerlo una única vez al comienzo de la aplicación.
El nombre del driver debe venir especificado en la documentación de la base de datos.

Se puede elevar la excepción ClassNotFoundException si hay un error en el nombre del driver o si el fichero .jar no está correcamente en el CLASSPATH o en el proyecto.

Establecer una conexión

Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/sample", "root", "password");

Las bases de datos actúan como servidores y las aplicaciones como clientes que se comunican a través de la red.

Un objeto Connection representa una conexión física entre el cliente y el servidor. Para crear una conexión se usa la clase DriverManager, especificando la URL, el nombre y la contraseña de nuestra base de datos.

El formato de la URL debe especificarse en el manual de la base de datos. En el ejemplo es una muestra de como conectar a MySQL. Por supuesto, el nombre de usuario y la contraseña también dependen de la base de datos.

Ejecutar una sentencia SQL

Statement stmt = conn.createStatement();
          ResultSet rs = stmt.executeQuery("SELECT titulo, precio FROM Libros WHERE precio > 2");

Una vez que tienes una conexión puedes ejecutar sentencias SQL.
Primero se crea el objeto Statement desde la conexión, y posteriormente se ejecuta la consulta y su resultado se devuelve como un ResultSet.

Acceso al conjunto de resultados

while(rs.next()){
String name = rs.getString("titulo");
float price = rs.getFloat("precio");
     System.out.println(name + "\t" + price);
}

El ResultSet es el objeto que representa el resultado.
No carga toda la información en memoria, internamente tiene un cursor en memoria que apunta a una fila concreta del resultado en la base de datos, por lo que hay que posicionar el cursor en cada fila y obtener toda la información de la misma.

El cursor puede estar en una fila concreta, y también puede estar en dos filas especiales:
  • Antes de la primera fila (Before de First Row, BFR)
  • Después de la última fila (After the Last Row, ALR)

Inicialmente el ResultSet está en BFR. next() mueve el cursor hacía delante, devolviendo true si se encuentra en una fila concreta y false si alcanza el ALR.

Image

Cuando el ResultSet se encuentra en una fila concreta se pueden usar los métodos de acceso a las columnas
  •  String getString(String columnLabel)
  • String getString(int columnIndex)
  • int getInt(String columnLabel)
  • int getInt(int columnIndex)
  •  … (existen dos métodos por cada tipo)

Teniendo en cuenta que los índices empiezan en 1 y no en 0.

Liberar recursos

rs.close();
stmt.close();
conn.close();

Cuando se termina de usar una Connection, un Statement o un ResultSet es necesario liberar los recursos que necesitan.

Puesto que la información de un ResultSet no se carga en memoria, existen conexiones de red abiertas.

Métodos close():
  • ResultSet.close() – Libera los recursos del ResultSet. Se cierran automáticamente al cerrar el Statement que lo creó o al reejecutar el Statement.
  • Statement.close() – Libera los recursos del Statement.
  • Connection.close() – Finaliza la conexión con la base de datos.


Manejar los errores

throws ClassNotFoundException, SQLException

Hay que gestionar los errores apropiadamente, ya que se pueden producir excepciones ClassNotFoundException si no se encuentra el driver, o excepciones SQLException al interactuar con la base de datos. Por ejemplo:
  • SQL mal formado
  • Conexión de red rota
  • Problemas de integridad al insertar datos (claves duplicadas)

Lo que hacemos con la sintaxis anterior es dejar que el error lo gestionen en un nivel superior, pero si lo queremos controlar nosotros mismos, la sentencia del código quedaría tal que así:

import java.sql.*;

public class HolaMundoBaseDatos {

     public static void main(String[] args) {
         
          try{
                Class.forName("com.mysql.jdbc.Driver");
          }
          catch(ClassNotFoundException e){
                System.err.println("El driver no se encuentra");
                System.exit(-1);
          }
         
          Connection conn = null;
         
          try{
                conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/sample", "root", "password");
                Statement stmt = conn.createStatement();
                ResultSet rs = stmt.executeQuery("SELECT titulo, precio FROM Libros WHERE precio > 2");
               
                while(rs.next()){
                     String name = rs.getString("titulo");
                     float price = rs.getFloat("precio");
                     System.out.println(name + "\t" + price);
                }
               
                rs.close();
                stmt.close();
          }
          catch(SQLException e){
                System.err.println("Error en la base de datos" + e.getMessage());
                e.printStackTrace();
          }
          finally{
                if (conn != null){
                     try{
                          conn.close();
                     }
                     catch(SQLException e){
                          System.err.println("Error al cerrar la conexión" + e.getMessage());
                     }
                }
          }
     }
}

25/5/15

Bases de datos. SQL programado (XVII). Examen de la tercera evaluación

9:57 Posted by Inazio , No comments
Voy a compartir la parte práctica del examen del tercer trimestre de bases de datos.
Para la prueba hemos usado el script mayo15.sql, que puedes descargarlo pulsando aquí.

A continuación los ejercicios:

Pregunta 1. Crea un procedimiento que acepte una cadena como argumento y visualice titulo y editorial que contenga esa cadena en los campos titulo o cadena. Usa SQL dinámico

delimiter $$
drop procedure if exists pregunta1 $$
create procedure pregunta1(in cadena varchar(255))
begin
     set @consulta = concat('select titulo, Editorial from libros where titulo like "','%',cadena,'%" or resumen like "','%',cadena,'%"; ');
     prepare consulta from @consulta;
     execute consulta;
     deallocate prepare consulta;
end $$
delimiter ;

Pregunta 2. Realiza un procedimiento que actualice la tabla usuarios con un comentario para cada usuario. La variable con el comentario y el usuarios serán variables pasadas por parámetro. Utiliza transacciones.
Si la inserción es correcta, se validará y el procedimiento devolverá, en una nueva variable de salida:
à Si la actualización se ha llevado a cabo correctamente la variable devolverá 1.
à Si el usuario pasado por parámetro no existe devolverá -1.
à Si la fila implicada está siendo modificada por otra transacción (error 1205), devolverá -2.
à Si se produce cualquier otro error se devolverá -3.

delimiter $$
drop procedure if exists pregunta2 $$
create procedure pregunta2(in cadena varchar(255), in usuario int, out salida int)
begin
     declare ultimaFila int default 0;
     declare resultado int default 1;
     declare continue handler for not found
         set ultimaFila = 1;
     declare continue handler for 1205
         set resultado = -2;
     declare continue handler for sqlexception
         set resultado = -3;
    
     if usuario not in (select registro from usuarios) then
         set resultado = -1;
     end if;
    
     start transaction;
     if resultado = 1 then
         update usuarios set Observaciones = cadena
         where registro = usuario;
         commit;
     else
         rollback;
     end if;

     set salida = resultado;
end $$
delimiter ;

Pregunta 3. Realiza un trigger que cada vez que se indique que se ha devuelto un libro en la tabla prestados se indique en la tabla libros que ese libro ya no se encuentra en prestamo.

delimiter $$
drop trigger if exists pregunta3 $$
create trigger pregunta3
after update
on prestados
for each row
begin
     if old.devuelto = 'N' and new.devuelto = 'S' then
         update libros
         set prestado = 'N'
         where registro = old.reg_libro;
     end if;
end $$

delimiter ;

12/5/15

Bases de datos. SQL programado (XVI). Ejercicios (VII)

12:39 Posted by Inazio , No comments
Para practicar previa realización del examen, vamos a realizar el del tercer trimestre del año pasado.
Para ello hay que cargar el .sql eval3, que puedes descargar desde aquí

Pregunta 1 (3 puntos)
Realiza una función de nombre pregunta1 que devuelva el porcentaje de municipios cuya población no ha disminuido a lo largo de los años que figuran en la tabla municipios.

-- SOLUCION 1
delimiter $$
drop function if exists pregunta1 $$
create function pregunta1()
returns double
reads sql data
begin
     -- Declaraciones
     declare ultimaFila int default 0;
     declare v2003 int;
     declare v2001 int;
     declare v1996 int;
     declare v1991 int;
     declare totalPueblos int default 0;
     declare pueblosRequisito int default 0;
     declare porcentaje double;
     declare pueblos cursor for
         select poblacion2003, poblacion2001, poblacion1996, poblacion1991
         from municipio;
     declare continue handler for not found
         set ultimaFila = 1;

     -- Operaciones
     open pueblos;
         verPueblos: loop
              fetch pueblos into v2003, v2001, v1996, v1991;
              if ultimaFila = 1 then
                   leave verPueblos;
              end if;
              set totalPueblos = totalPueblos + 1;
              if v1996 >= v1991 and v2001 >= v1996 and v2003 >= v2001 then
                   set pueblosRequisito = pueblosRequisito + 1;
              end if;
         end loop verPueblos;
     close pueblos;

     set porcentaje = (pueblosRequisito * 100) / totalPueblos;
     return porcentaje;
end $$
delimiter ;

-- SOLUCION 2
delimiter $$
drop function if exists pregunta1 $$
create function pregunta1()
returns double
reads sql data
begin
     -- Declaraciones
     declare totalPueblos int;
     declare pueblosRequisito int;
     declare porcentaje double;
     -- Operaciones
     set totalPueblos = (select count(*) from municipio);
     set pueblosRequisito = (select count(*) from municipio where poblacion1996 >= poblacion1991 and poblacion2001 >= poblacion1996 and poblacion2003 >= poblacion2001);
     set porcentaje = (pueblosRequisito * 100) / totalPueblos;
     return porcentaje;
end $$
delimiter ;

Pregunta 2 (3,75 puntos)
Realiza un procedimiento que reciba los datos de un municipio (excepto el identificador de municipio) y los inserte en la tabla municipio. Usa transacciones. Utilizar una variable OUT para indicar el resultado de la ejecución del procedimiento. Aspectos a tener en cuenta:
- El identificador de municipio será una unidad más que el mayor existente en la tabla municipio.
- Si la comunidad que se le pasa como argumento no existe entonces el procedimiento no realizará la inserción y devolverá -1 en el parámetro OUT.
- Si alguno de los valores pasado es nulo entonces el procedimiento tampoco realizará la inserción y devolverá -2 en el parámetro OUT.
- Ante cualquier situación de error provocada en el intento de hacer la inserción, el procedimiento deshará la operación y devolverá -3 en su parámetro OUT.
- Si la inserción se realiza correctamente, se validará la misma y se devolverá un 1 en la variable OUT.

delimiter $$
drop procedure if exists pregunta2 $$
create procedure pregunta2(vNombre varchar(70), v2003 int, v2001 int, v1996 int, v1991 int, vSuperficie int, cAutonoma int, out resultado int)
modifies sql data
begin
     declare id int;
     declare exit handler for sqlexception
         begin
              set resultado = -3;
              rollback;
         end;
     start transaction;
     if vNombre is null or v2003 is Null or v2001 is null or v1996 is null or v1991 is null or vSuperficie is null or cAutonoma is null then
         set resultado = -2;
         rollback;
     elseif cAutonoma not in(select ca_id from comunidad) then
         set resultado = -1;
         rollback;
     else
         set id = (select m_id from municipio order by m_id desc limit 1) + 1;
         insert into municipio values(id, vNombre, v2003, v2001, v1996, v1991, vSuperficie, cAutonoma);
         set resultado = 1;
         commit;
     end if;
end $$
delimiter ;

Pregunta 3 (3,25 puntos)
Realiza un trigger que no permita realizar modificaciones en la tabla municipio los fines de semana (sábados o domingos).

delimiter $$
drop trigger if exists problema3 $$
create trigger problema3
     before update on municipio
     for each row
begin
     if dayofweek(curdate()) = 1 or dayofweek = 7 then
         signal sqlstate '20000';
     end if;
end $$

delimiter ;

6/5/15

Bases de datos. SQL programado (XV). Ejercicios (VI)

0:03 Posted by Inazio , No comments
Ejercicios correspondientes a la hoja número siete. Se han realizado sobre el script primerospasos.sql, que puedes descargar haciendo clic aquí

Ejercicio 27. Realizar un procedimiento que actualice el salario de los empleados, con una cantidad por hijo que se pase al procedimiento,  para aquellos trabajadores que su comisión es nula. Durante el proceso de actualización debe garantizarse que ningún otro usuario pueda cambiar los datos que están siendo modificados. Utilizar un manejador de tipo SQLEXCEPTION (que incluya la operación ROLLBACK) para tratar cualquier situación de error distinta de la excepción NOT FOUND que también deberá manejarse. Al final del proceso,  confirmar la transacción si la ejecución ha sido correcta.

Ejemplo de funcionamiento. Antes de llamar al procedimiento de actualización:

+-------+---------+----------+-------+
| numem | salario | comision | numhi |
+-------+---------+----------+-------+
| 110   | 310     | NULL     | 3     |
| 120   | 350     | 110      | 1     |
| 130   | 290     | 110      | 2     |
| 150   | 440     | NULL     | 0     |
| 160   | 310     | 110      | 2     |
| 180   | 480     | 50       | 2     |
| 190   | 300     | NULL     | 4     |
….
Después de ejecutar  CALL EJERCICIO3_1(30);

+-------+---------+----------+-------+
| numem | salario | comision | numhi |
+-------+---------+----------+-------+
| 110   | 400     | NULL     | 3     |
| 120   | 350     | 110      | 1     |
| 130   | 290     | 110      | 2     |
| 150   | 440     | NULL     | 0     |
| 160   | 310     | 110      | 2     |
| 180   | 480     | 50       | 2     |
| 190   | 420     | NULL     | 4     |

...
...

delimiter $$
drop procedure if exists veintisiete $$
create procedure veintisiete(in subidaSueldo int, out numError int, out textoError varchar(100))
modifies sql data
begin
     declare ultimaFila int default 0;
     declare vEmpleado int;
     declare vHijos int;
     declare emp cursor for
         select numem, numhi
         from empleados
         where comision is null and numhi > 0
         for update;
     declare continue handler for not found
         begin
              set ultimaFila = 1;
              rollback; -- Deshago cambios de la transacción
         end;
     declare continue handler for sqlexception
         begin
              set numError = -1;
              set textoError = 'Ocurrió un error durante la consulta';
              rollback;
         end;
     set numError = 0; -- Inicio numero de error a 0

     start transaction;
     open emp;
         empleadosCursor: loop
              fetch emp into vEmpleado, vHijos;
              if ultimaFila = 1 then -- Controlo lectura de cursor
                   leave empleadosCursor;
              end if;
              update empleados
              set salario = salario + (subidaSueldo * vHijos)
              where numem = vEmpleado;
         end loop empleadosCursor;
     close emp;
     if numError = 0 then
         set textoError = 'Consulta de actualización realizada correctamente';
         commit;
     end if;
end $$
delimiter ;

Ejercicio 28.  “Representación gráfica de los salarios de los empleados”. Realizar un procedimiento que utilice un cursor de actualización para rellenar la columna estrellas de la tabla empleados con un asterisco por cada 100 unidades de salario. Antes de realizar el ejercicio añade la columna estrellas a la tabla empleados (ALTER TABLE empleados ADD COLUMN estrellas VARCHAR(10)). Incluir los dos manejadores de error del ejercicio anterior.

Ejemplo de funcionamiento:

+-------+---------+-----------+
| NUMEM | SALARIO | ESTRELLAS |
+-------+---------+-----------+
| 110   | 400     | ****      |
| 120   | 350     | ***       |
| 130   | 290     | **        |
| 150   | 440     | ****      |
| 160   | 310     | ***       |
| 180   | 480     | ****      |
| 190   | 420     | ****      |
| 210   | 440     | ****      |
| 240   | 280     | **        |
| 250   | 450     | ****      |
| 260   | 900     | ********* |
| 270   | 380     | ***       |

...
...

alter table empleados add column estrellas varchar(10); -- Añado columna para las estrelllas

delimiter $$
drop procedure if exists veintiocho $$
create procedure veintiocho(out numError int, out textoError varchar(100))
begin
     -- Declaración de variables
     declare ultimaFila int default 0;
     declare vEmpleado int;
     declare vSalario int;
     declare vEstrellas int;
     declare contador int;
     declare calidad varchar(10);
     -- Declaración de cursor
     declare emp cursor for
         select numem, salario
         from empleados
         for update;
     -- Declaracion de control de errores
     declare continue handler for not found
         set ultimaFila = 1;
     declare continue handler for sqlexception
         begin
              set numError = -1;
              set textoError = 'Ocurrió un error al lanzar procedimiento';
              rollback;
         end;
     set numError = 0; -- Inicialización de errores a 0
     -- Inicio transacción
     start transaction;
     open emp;
         salarioEmpleados: loop
              fetch emp into vEmpleado, vSalario;
              if ultimaFila = 1 then
                   leave salarioEmpleados;
              end if;
              set vEstrellas = vSalario / 100;
              if vEstrellas > 10 then
                   set vEstrellas = 10; -- La valoración de salario máximo es diez
              end if;
              set contador = 0; -- Reinicio contador a 0 para cada empleado
              while contador < vEstrellas do
                   if calidad is null then
                        set calidad = '*'; -- Si valor nulo inicializo variable
                   else
                        set calidad = concat(calidad, '*'); -- Si no concateno
                   end if;
                   set contador = contador + 1; -- Incremento en uno el contador aux
              end while;
              update empleados
              set estrellas = calidad
              where numem = vEmpleado;
              set calidad = null; -- Vuelvo valor de calidad a nulo para el siguiente empleado
         end loop salarioEmpleados;
     close emp;
     -- Si no ha ocurrido ningún error, valido la operación
     if numError = 0 then
         set textoError = 'Operación completada';
         commit;
     end if;
end $$
delimiter ;

Ejercicio 29. Realizar un procedimiento que teniendo en cuenta el procedimiento transac6 del apartado 6.10.5 de los materiales implemente otro procedimiento similar pero utilizando la estrategia optimista, que asume que es muy poco probable que el valor de una fila que se acaba de leer con intención de modificarla un tiempo más tarde cambie antes de poder hacerlo; en ese caso como mínimo habrá que asegurarse de que la fila no ha sido modificada después de haber sido leída y si así ha sido entonces la transacción no debe llevarse a cabo aun pudiéndose realizar (ROLLBACK).

delimiter $$
drop procedure if exists veintinueve $$
create procedure veintinueve(pId int, pAlumno varchar(30), out numError int, out textoError varchar(100))
modifies sql data
begin
     declare vAlumno varchar(30);
     declare vAlumno2 varchar(30);
     declare continue handler for sqlexception
         begin
              set numError = -1;
              set textoError = 'Ocurrió un error';
              rollback;
         end;
     set numError = 0;
     start transaction;
         select alumno
         into vAlumno
         from alumnos
         where id = pId;

         select sleep(15) into sleep; -- Espacio de tiempo que queda esperando

         select alumno
         into vAlumno2
         from alumnos
         where id = pId
         for update;

         if vAlumno = vAlumno2 then
              update alumnos
              set alumno = pAlumno
              where id = pId;
              if numError = 0 then
                   commit;
              else
                   rollback;
              end if;
         else
              rollback;
         end if;
end $$

delimiter ;