MySQL: Formateando fechas con DATE_FORMAT()

La función DATE_FORMAT() de MySQL, que fue incorporada en la versió 4.0 del conocido SGBD, nos permite dentro de un select definir el formato de salida de una fecha. La función recibe dos parámetros: una fecha o campo de fecha y una cadena de texto con el formato.

Los siguientes valores pueden ser usados en el formateo de fecha:

%aNombre del día abreviado (Sun a Sat)
%bNombre de mes abreviado (Jan a Dec)
%cMes en formato número (0 a 12)
%DDía del mes en formato numérico seguido de sufijo numeral (1st, 2nd, 3rd, …)
%dDía del mes en formato numérico, forzando dos dígitos  siempre ( de 01 a 31)
%eDía del mes en formato numérico, sin forzar dos dígitos (1 a 31)
%fMicrosegundos (000000 to 999999)
%HHora en formato 24 horas, con dos dígitos
%hHora en formato 12 horas, dos dígitos
%IHora en formato 12 horas, dos dígitos
%iMinutos
%jDía del año (001 a 366)
%kHora en formato 24 horas, sin forzar los dos dígitos
%lHora en formato 12 horas, sin forzar los dos dígitos
%MNombre del mes (January a December)
%mMes en formato numérico, forzando dos dígitos.
%pAM o PM
%rHora en formato 12 horas AM o PM (hh:mm:ss AM/PM)
%SSegundos (00 a 59)
%sSegundos (00 a 59)
%THora en formato 24 horas (hh:mm:ss)
%USemana, tomando el domingo como primer día (00 a 53)
%uSemana, tomando el lunes como primer día (00 to 53)
%VSemana, tomando el domingo como primer día (01 a 53). Usado con %X
%vSemana, tomando el lunes como primer día (01 a 53). Usado con %X
%WNombre del día completo (Sunday to Saturday)
%wNúmero del día en la semana, siendo el domindo 0 y el sábado 6.
%XAño para la semana, tomando el domingo como primer día. Usado con %V
%xAño para la semana, tomando el lunes como primer día. Usado con %V
%YAño en formato de cuatro dígitos
%yAño en formato de dos dígitos

Veamos unos ejemplos si quieres probarlo en casa:

Select DATE_FORMAT("2018-11-20", "%d/%m/%Y"); 
#Pintaría 20/11/2018, el formato europeo
Select DATE_FORMAT("2018-11-20", "%V %X"); 
#Pintaría 46 2018
Select DATE_FORMAT("2018-11-20", "%j-%y"); 
#Pintaría 324-2018
Select DATE_FORMAT("2018-11-20", "%W %d %M"); 
#Pintaría Tuesday 20 November

En el ejemplo le hemos pasado una cadena con una fecha como primer parámetro, pero puedes probar una consulta sobre un campo de una tabla que almacena alguna fecha.

MySQL: Consulta para saber cuántas tablas o vistas hay en una base de datos

El otro día veíamos cómo hacerlo con SQL-Server ¿y con MySQL? Más o menos lo mismo, con el pequeño cambio que implica que MySQL obligue a meter siempre en la consulta el nombre del esquema que vamos a usar, mientra que en SQL-Server cogerá por defecto el que tengamos seleccionado.

Entonces en MySQL la cosa quedaría así:

#Contar Tablas
SELECT COUNT(*) from Information_Schema.Tables where TABLE_TYPE = 'BASE TABLE' and table_schema = 'LaBaseDeDatosQueSea';

#Contar Vistas
SELECT COUNT(*) from Information_Schema.Tables where TABLE_TYPE = 'VIEW' and table_schema = 'LaBaseDeDatosQueSea';

#Contar Tablas y vistas
SELECT COUNT(*) from Information_Schema.Tables where table_schema = 'LaBaseDeDatosQueSea';

SQL: Formas Normales

Recuperados ya del fin de semana de rock and roll vamos a volver al código con algo de SQL teórico: la normalización de datos. El diseño lógico de las tablas y sus relaciones es fundamental para la optimización de una base de datos. Existen cinco reglas de normalización que debemos cumplir para aseverar que nuestra base de datos SQL está normalizada, aunque es cierto que hay quien considera que la cuarta y la quinta son rizar el rizo y que con cumplir la tres primeras generalmente ya tenemos una base de datos normalizada. Estas son las cinco formas normales:

  1. Primera Forma Normal: La primera forma requiere la eliminación de todas las columnas duplicadas de una tabla, la separación en otras tablas de esos datos que se duplicarían y la identificación de cada tabla con una atributo de clave primaria. Por ejemplo, si tenemos una tabla donde registramos ventas no guardamos el nombre y el precio del producto vendido en ella varias veces, sino que tendremos una tabla de productos con esos datos separado de la tabla de ventas, y ambas tendrían claves primarias que identifican a cada fila.
  2. Segunda forma Normal: Implica que se cumpla lo dicho en la primera forma normal y que, además, se creen relaciones entre tablas a través de claves externas. Es decir, la tabla Ventas del ejemplo anterior incluye como Clave Externa un valor único que lo relaciona con la tabla Productos, generalmente su clave primaria.
  3. Tercera Forma Normal: Esta norma implica que se cumplan las dos anteriores y que, además, todas las columnas de un registro deben hacer referencia únicamente a la clave primaria, y además elimina todos los atributos derivados. Volvemos al ejemplo: la tabla Productos nos dará el nombre del producto, su precio, en qué almacén se guarda y su fecha de caducidad. No tendremos, por ejemplo, un registro para decir en qué piso del almacén se guarda, ya que ese dato sería de la tabla Almacén. Tampoco tendríamos una columna con los días que faltan hasta que caduque, ya que ese sería un atributo derivado que podemos calcular con la fecha de caducidad.
  4. Cuarta Forma Normal: Agrega un requisito adicional, que es la eliminación de cualquier dependencia multivaluada en las relaciones. Una tabla con una dependencia multivaluada es una donde la existencia de dos o más relaciones independientes muchos a muchos causa redundancia.
  5. Quinta Forma Normal: Rizando el rizo, vendría a decir que sólo se podrían realizar relaciones entre tablas utilizando claves candidatas, con la idea de reducir la redundancia de datos entre múltiples tablas.

A la hora de normalizar bases de datos hay una frase en inglés que lo resume todo: «The key, the whole key, and nothing but the key.» En la propia web de Microsoft la primera recomendación que dan para mejorar el rendimiento de SQL-Server y tener un diseño eficiente de la base de datos es usar un índice autonumérico como clave primaria de cada tabla, identificando así de forma unívoca cada registro y facilitando la relación entre ellos.

MySQL y MariaDB: Consulta para saber en qué tabla está una columna

El otro día lo vimos en SQL-Server, ayer en Oracle, vamos hoy con MySQL y su fork MariaDB, seguramente los gestores de bases de datos SQL libres más populares de la actualidad ¿Cómo puedo saber a qué tabla pertenece una columna sabiendo sólo el nombre de columna? Como en los casos anteriores, basta una consulta:

SELECT DISTINCT TABLE_NAME
    FROM INFORMATION_SCHEMA.COLUMNS
    WHERE COLUMN_NAME = 'Nombre_Columna'
        AND TABLE_SCHEMA='Nombre_BaseDatos';

Ya sabéis, os toca cambiar el nombre de columna y el nombre del esquema por los que correspondan en vuestro caso.

MySQL: Calcular una edad a partir de una fecha.

En fin, seguimos con el cálculo de edades a partir de fechas en diferentes SGBD. Ya hablamos de SQL-Server y de PostgreSQL, vamos ahora a ver cómo iría la cosa en MySQL/MariaDB.

Vamos a asumir que los datos están bien guardados, en una columna del tipo DATETIME. Como en el caso de Postgres, en MySQL dispones de una función que nos ayudará a hacer esto directamente, llamada TIMESTAMPDIFF(). Esta función recibe como argumentos la unidad de tiempo en la que queremos recibir la respuesta y las dos fechas entre las que queremos obtener la diferencia. Veamos el ejemplo para obtener la edad de alguien con esta función, sirviéndonos de una tabla de ejemplo llamada clientes que contendrá una columna FechaNac con la fecha de nacimiento de los mismos:

SELECT TIMESTAMPDIFF(YEAR,FechaNac,CURDATE()) AS edad
     FROM clientes;

Borrar un evento de MySQL

Hoy me preguntaba en la entrada sobre Eventos en MySQL cómo se borran, así que en vez de contestar en los comentarios voy a dejarlo por aquí en una minientrada general. ¿Cómo se borra un evento? Pues con Drop Event, cuya sintaxis es DROP EVENT [IF EXISTS] event_name. En el viejo ejemplo creábamos uno llamado e_ActualizaSaldoDiario, así que si quisiéramos borrarlo nos bastaría con hacer lo siguiente:

DROP EVENT e_ActualizaSaldoDiario

En caso de que el evento que intentamos borrar no existiera (por haberse borrado previamente) nos devolverá un error. Si queremos evitar esto podemos añadir IF EXISTS:

DROP EVENT IF EXISTS e_ActualizaSaldoDiario

Recordad que para borrarlos, igual que para crearlos, tenéis que tener permisos sobre los eventos en el schema.

Seguridad en WordPress ¿cómo cambiar el prefijo de la base de datos?

Si alguna vez has hecho una instalación de WordPress verás que te solicita, para crear el sitio, el nombre de una base de datos, el usuario con el que se conectará a ella, la contraseña de ese usuario y un prefijo. ¿Por qué un prefijo? Simplemente porque así puedes tener varias instalaciones de WordPress (o de otros CMS que usen nombres de tabla muy genéricos tipo «users», «posts», etc.) en una misma base de datos.

Si no cambiamos esta configuración por defecto el sistema pondrá el prefijo «wp_» a las tablas. El no poner un prefijo personalizado, en si, constituye un error. Porque dejando el prefijo por defecto estás provocando: a)Que si un atacante logra ver el nombre de una tabla descubra que el CMS que estás utilizando es WordPress y b) Que dicho atacante, entonces, pueda conocer los nombres de las tablas de tu base de datos. La solución es simple, durante la instalación define un prefijo personalizado.

¿Ya lo tienes instalado? Don’t worry, be nécora. No está todo perdido, tienes todavía varias opciones. La más rápida y simple, instalas este plugin que te permitará cambiarla cómodamente desde la interfaz gráfica. ¿La versión más larga y compleja y élite? Pues los siguientes pasos te lo explican:

Bueno, como paso previo, o paso 0 del proceso HAZ UN BACKUP DE TU BASE DE DATOS POR SI ALGO FALLA Y TIENES QUE RECUPERARLA. Consejo que debes tener siempre en mente cuando te pongas a tocar tablas de una instalación de cualquier cosa.

El primer paso: ir a wp-config.php y cambiar ahí el prefijo (en nuestro caso pondremos como prefijo personalizado my_b457Bch33s_ ).

$table_prefix  = 'my_b457Bch33s_';

El siguiente paso es renombrar todas las tablas de tu instalación de wordpress:

/*Básicamente vas haciendo esto con todas las tablas*/
RENAME table `wp_comments` TO `my_b457Bch33s_comments`;

Tras renombrar las tablas haces un update sobre la tabla options buscando todas las líneas que hagan referencia a tablas con el prefijo viejo para actualizarlas:

UPDATE `my_b457Bch33s_options` SET `option_name`=REPLACE(`option_name`,'wp_','my_b457Bch33s_') WHERE `option_name` LIKE '%wp_%';

Y con la tabla usermeta tres cuartos de lo mismo, update que te crió.

UPDATE `my_b457Bch33s_usermeta` SET `meta_key`=REPLACE(`meta_key`,'wp_','my_b457Bch33s_') WHERE `meta_key` LIKE '%wp_%';

Y tras esto deberías tener todo funcionando de nuevo, pero con el nuevo prefijo, más seguro contra potenciales atacantes.

Función para calcular la distancia entre dos coordenadas en MySQL

Llevo un par de días trabajando con coordenadas geográficas en un proyecto personal, por lo que voy a hablar del tema estos días.

Para saber qué distancia separa dos coordenadas recurrimos a la fórmula del Haversine, o fórmula del semiverseno. Delego la parte matemática teórica y vamos a la parte MySQL, que supongo que es lo que estabas buscando cuando llegaste aquí. Os dejo el código y una pequeña explicación paso a paso. La función requiere cuatro variables (latitud y longitud de cada uno de los puntos) y devuelve el resultado en kilómetros.

/*vamos a inicializar nuestras variables,
en este caso con un valor constante
pero en vuestro caso lo podéis hacer
con datos sacados de una consulta Select*/

Set @Lat1 = 42.880501;
Set @Lng1 = -8.545732;
/*Son las coordenadas de la catedral de Santiago
o eso me ha dicho DuckDuckGo*/ 

Set @Lat2 = 53.731951;
Set @Lng2 = -6.877150;
/*Como segundo punto he elegido una calle cualquiera 
en Kells, Irlanda*/

/*Y ahora aplicamos la función SQL 
Que nos devolverá la distancia en kilómetros*/

SELECT (acos(sin(radians(@Lat1)) * sin(radians(@Lat2)) + 
cos(radians(@Lat1)) * cos(radians(@Lat2)) * 
cos(radians(@Lng1) - radians(@Lng2))) * 6371) as distanciaKm;

/*el resultado con estos datos es de 
1212.8306071122186 kilómetros*/

/*Si quieres el resultado el millas
cambia el valor 6371 en la función
por 3959*/

Próximamente veremos como hacerlo este cálculo con Javascript, que es mucho más divertido.

Diferencias entre clave primaria y clave foránea

Vamos con una entrada de SQL básico, uno de los fundamentos del modelo relacional ¿Qué diferencia existe entre una clave primaria y una foránea?

En el diseño de una base de datos relacional la clave primaria es el campo, o conjunto de campos, que nos permite identificar de forma única un registro. Por así decirlo es como el DNI de esa tabla. Se trata de un valor, o grupo de valores, único que nos permitirá diferenciar un registro concreto. Podemos definir la clave primaria en el momento de la creación de la tabla, dentro de la sintaxis del CREATE TABLE, o a posteriori añadiendo la regla en un ALTER TABLE.

Por su parte la clave foránea es un campo, o conjunto de campos, que nos permite relacionar un registro de una tabla con otro, generalmente de una tabla distnta . Como ejemplo piensa en la clásica base de datos de una tienda, en la que tienes una tabla con productos, cada uno de los cuales tiene un código (que será la clave primaria), una descripción y un precio. Por otra parte tienes una tabla en la que registras las ventas, y podrías tener un código para cada una, la fecha, la hora y el producto. Para identificar el producto en esa tabla lo más práctico, para no repetir información, sería utilizar el código de la tabla de productos (la clave primaria de la primera tabla, que en esta se convierte en clave externa).

Una tabla puede tener relaciones con varias a través de distintas claves foráneas, e incluso referenciarse a si misma (clave foránea recursiva). Al igual que con la clave primaria podemos definir las claves foráneas dentro de la sintaxis de creación de la tabla o a posteriori con un ALTER TABLE.

Por tanto la diferencia es sencilla: la clave primaria identifica un registro único de una tabla. La clave foránea relaciona los datos de un registro de una tabla con los de otra, o con un registro distinto de la misma tabla.

Llamar a procedimientos almacenados en PHP

Una entrada que llevaba mucho tiempo en el TODO list, sobre trabajar con procedimientos almacenados con PHP.

Para empezar vamos a plantear un escenario en el que tenemos tres procedimientos almacenados en MySQL (dos selects y un insert) contra una tabla llamada productos, que tiene tres campos (id como clave primaria, descripcion con una descripción del producto, precio con un valor numérico decimal).

El SP de la primera select, que nos devuelve la descripción y el precio del producto según su id, sería este:

CREATE PROCEDURE getProducto(IN id_val INT) 
  BEGIN 
    SELECT descripcion, precio from productos WHERE id = id_val; 
  END;

El SP de la segunda select, que nos devuelve la descripción y el precio todos los productos, por lo que no recibe parámetros:

CREATE PROCEDURE getAllProductos() 
  BEGIN 
    SELECT descripcion, precio from productos ; 
  END;

Y el sp del insert sería este, al que le pasamos los tres valores a meter:

CREATE PROCEDURE addProducto(IN id_val INT,descripcion_in varchar(50), precio_in float) 
  BEGIN 
    INSERT INTO productos VALUES (id_val, descripcion_in, precio_in);
  END;

Bueno, ya tenemos nuestro escenario, entonces ¿cómo hacemos para llamar a estos procedimientos desde nuestro código php? Pues simplemente lanzamos una consulta en la que llamamos a la instrucción CALL de MySQL para que ejecute el procedimiento almacenado que le digamos con los parámetros que tocan.

/*Empezamos con el procedimiento de inserción*/
/*Lo primero es crear un objeto mysqli*/
$mysqli = new mysqli("servidor", "usuario", "pass", "db");

/*Y llamamos al procedimiento para hacer la inserción*/
/*Si falla imprimimos el error*/
if (!$mysqli->query("CALL addProducto(1001, 'Nueces de Macadamia', 12.23)")) {
    echo "Falló la llamada: (" . $mysqli->errno . ") " . $mysqli->error;
}

La propia función query del objeto mysqli nos devolverá TRUE si no viene con errores y FALSE si pasa algo inesperado.

¿Y qué pasa con la recuperación de datos? Ahí nos vamos a un terreno más pantanoso. Para una consulta con un solo resultado no es problema:

/*Empezamos con el procedimiento de recuperación de una fila*/
/*Lo primero es crear un objeto mysqli*/
$mysqli = new mysqli("servidor", "usuario", "pass", "db");

/*Y llamamos al procedimiento para recoger los datos*/
/*Si falla imprimimos el error*/
if (!($res = $mysqli->query("CALL getProducto(1001)"))) {
    echo "Falló la llamada: (" . $mysqli->errno . ") " . $mysqli->error;
}

/*E imprimimos el resultado para ver que el ejemplo ha funcionado*/
var_dump($res->fetch_assoc());

Pero claro, en este caso podemos hacerlo así porque esperamos un solo resultado, pero si esperamos varias filas la cosa es distinta ya que mysqli->query() devuelve sólo el primer conjunto de resultados. Tenemos dos alternativas para esta situación: mysqli_real_query() o mysqli_multi_query(). En el ejemplo usaremos la segunda, y veremos también la forma de recorrer este resultado:

/*Empezamos con el procedimiento de recuperación de una fila*/
/*Lo primero es crear un objeto mysqli*/
$mysqli = new mysqli("servidor", "usuario", "pass", "db");

/*Y llamamos al procedimiento para recoger los datos*/
/*Si falla imprimimos el error*/
if (!$mysqli->multi_query("CALL getAllProductos()")) {
    echo "Falló la llamada: (" . $mysqli->errno . ") " . $mysqli->error;
}

/*Ahora con este bucle recogemos los resultados y los recorremos*/
do {
    /*En el if recogemos una fila de la tabla*/
    if ($res = $mysqli->store_result()) { 
        /*Imprimimos el resultado de la fila y debajo un salto de línea*/
        var_dump($res->fetch_all());
        printf("\n");
        /*La llamada a free() no es obligatoria, pero si recomendable para aligerar memoria y para evitar problemas si después hacemos una llamada a otro procedimiento*/
        $res->free();
    } else {
        if ($mysqli->errno) {
            echo "Store failed: (" . $mysqli->errno . ") " . $mysqli->error;
        }
    }
} while ($mysqli->more_results() && $mysqli->next_result());
/*El bucle se ejecuta mientras haya más resultados y se pueda saltar al siguiente*/

Añadiré a esto que también es posible utilizar las llamadas a procedimientos almacenados usando sentencias preparadas, procediendo del modo habitual a la hora de pasar los parámetros.