Optimización de consultas en MySQL

Vamos con una serie de consejos a la hora de optimizar consultas en MySQL:

  • No uses el comodín * si no quieres recuperar todos los datos. Estarás aumentando innecesariamente el volumen de datos.
  • En selects, updates y deletes utiliza LIMIT 1 cuando sólo esperes un resultado. De esa forma cuando lo encuentre parará, y no recorrerá toda la tabla. Esto además te valdrá como medida de seguridad, ya que un pudate o delete mal hecho puede joderte una tabla y empantanarte en una recuperación de datos.
  • En las cláusulas where no utilices paréntesis innecesarios.
  • Si al hacer una consulta con GROUP BY o DISTINCT sabes que el resultado va a ser reducido usa la opción SQL_SMALL_RESULT, que usará tablas temporales dándote mayor velocidad en el acceso a datos.
  • COUNT(*) sólo se debe utilizar en consultas sin WHERE y que afecten a sólo una tabla.
  • En los selects que impliquen varias tablas utiliza alias y ponlos antes de los campos. Ahorrarás tiempo al no tener que buscar la tabla a la que pertenece.
  • Para unir tablas no uses un where. Funciona, pero ahí el motor hace el producto cartesiano de todas las tablas implicadas y luego lo filtra. INNER JOIN, LEFT JOIN o RIGHT JOIN son tus fieles amigos. Si los usas las tablas se irán uniendo a medida que las declaras
  • Usa columnas NOT NULL si es posible. Las columnas NULL requieren de un bit extra.
  • Para ordenación aleatoria no uses ORDER BY RAND(). En ese mismo blog ya dimos opciones alternativas
  • Analiza tus consultas con EXPLAIN. Si pones EXPLAIN antes de tu consulta recibirás una explicación de cómo se realiza esta en el servidor. Esto te permitirá ver formas de optimizarla.
  • A la hora de relacionar tablas usa siempre campos que estén indizados, ganarás en velocidad.
  • Optimiza tus índices. Ten en cuenta que suelen agilizar las lecturas pero ralentizar las inserciones (bueno, lo de ralentizar es en Microsoft SQL-Server, debería mirar sin en MySQL también ocurre eso). Aquí tienes un buen artículo sobre el tema (aunque puede que dentro de poco haga yo uno)

En fin, una serie de consejillos para mejorar el rendimiento de vuestra base de datos. Por cierto, y a modo anecdótico. Hace años un ingenieron me dijo que no usara LIMIT 1 porque era «meter más consultas que no van a hacer nada porque estás buscando sobre un ID»… todavía no he encontrado a nadie más que defienda esa teoría (igual era ingeniero agrónomo y no informático, eso explicaría mucho).

Insert IF NOT EXISTS en MySQL

Muchas veces necesitamos crear una entrada en una tabla sólo en el caso de que no exista ya. Por desgracia no existe una sintaxis de INSERT IF NOT EXISTS, como sí existe por ejemplo con CREATE. Os estaréis cagando en mi porque el título os dio esperanzas… Tranquilos, existe una simple alternativa, haciendo uso de SELECT FROM dual con una condición WHERE NOT EXISTS:

Veamos un ejemplo, y debajo lo explico. Vamos a insertar un pueblo en una tabla sólo si no ha sido insertado ya:

INSERT INTO poblaciones(Nombre)
SELECT 'Bergantiños'
FROM dual
WHERE NOT EXISTS (SELECT Nombre FROM poblaciones WHERE Nombre = ‘Bergantiños’)LIMIT 1

Como veis usamos SELECT FROM dual, un nombre de tabla que se especifica en MySQL, Oracle o PostgreSQL como nombre de tabla falsa en asignaciones en las que no usamos tablas realmente. De esta forma podemos usar la condición WHERE NOT EXISTS para comprobar con una búsqueda que el campo no está en la tabla. En la búsqueda uso LIMIT 1 para que pare en cuanto encuentre un resultado y reducir la carga de trabajo.

(Editado, y gracias a infocat20, que me avisó de que tenía el limit mal colocado)

Bucles y condicionales en procedimientos almacenados de MySQL

A la hora de crear procedimientos almacenados en MySQL tenemos la opción de utilizar distintos tipos de bucles y de condicionales, lo que nos dotará de la capacidad de llevar a cabo acciones más complejas dentro de nuestros procedimientos.

Para esto disponemos de las estructuras de control IF THEN ELSE, CASE, WHILE y REPEAT, además de poder definir y declarar variables locales dentro de los procedimientos.

Variables:
Para declarar una variable la definimos con DEFINE y la inicializamos con SET:

DEFINE varprueba INT; #definimos la variable y su tipo
SET varpruebra = 10; #le damos un valor concordante con el tipo

IF THEN ELSE:
El condicional simple IF que tenemos en la mayoría de los lenguajes de programación, que puede ser complementado o no con un ELSE para el caso de que no se cumpla. La sintaxis es IF condición THEN (el ELSE si hace falta) END IF. Podéis ver un ejemplo:

delimiter $$
CREATE procedure ej(IN val int)     /* Parámetro de entrada */
  begin    
    IF val = 0 then
      INSERT INTO tbejemplo VALUES('No disponible');
    else
      INSERT INTO tbejemplo VALUES(val);
    end IF;
  end$$
delimiter ;

CASE:
Condicional que nos permite evaluar el valor de una variable y realizar diversas acciones según varias posibilidades. Es una forma de abreviar un montón de IF anidados. En este caso el ELSE nos permite definir un valor por defecto (como el default en un switch/case de C, Java o PHP):

delimiter $$
CREATE procedure ej(IN val int)     /* Parámetro de entrada */
  begin
    case val
     when 5 then INSERT INTO prueba VALUES('Suficientes');
     when 4 then INSERT INTO prueba VALUES('Reserva');
     else INSERT INTO prueba VALUES ('Insuficientes);
    end case;
  end$$
delimiter;

WHILE:
Un bucle que hace que un procedimiento se repita mientras se de una condición. Si la condición ya no se da antes de entrar al bucle, no entra.

delimiter $$
CREATE procedure ej(IN val int)     /* Parámetro de entrada */
  begin
    define i int;
    set i = 0;
    while i<5 do
      INSERT INTO prueba VALUES (i);
      set i=i+1;
    end while;
  end$$
delimiter ;

REPEAT:
Lo contratio que WHILE, repeat es un bucle UNTIL, se realiza hasta que se de una condición. Si bien también comparte con el DO…WHILE de C (y Java, y PHP…) que siempre se ejecuta al menos una vez.

delimiter $$
CREATE procedure ej(IN val int)     /* Parámetro de entrada */
  begin
    define i int;
    set i = 0;
    repeat
      INSERT INTO prueba VALUES (i);
      set i=i+1;
      until i <= 5;
    end repeat;
  end$$
delimiter ;

Con esto ya podéis empezar a programar un poco más en serio dentro de MySQL.

Poblar un combo dinámicamente con jQuery y JSON

Es habitual que, creando formularios, nos encontremos con la situación de tener dos combos (o campo select si lo preferís) y que uno tenga que cargar/modificar sus datos según el resultado seleccionado en el otro, de forma dinámica. Esto es posible con Javascript, y muy cómodo si utilizamos JSON y jQuery.

Os planteo un ejemplo simple: tenemos dos combos, uno con provincias y otro con ayuntamientos. El marcado HTML va a ser más o menos tal que así:

<select id="provincias" name="provincias">
  <option value=""></option>
  <option value="1">A Coruña</option>
  <option value="2">Lugo</option>
  <option value="3">Ourense</option>
  <option value="4">Pontevedra</option>
</select>
<select id="poblaciones" name="poblaciones" disabled="disabled">
</select>

Como véis, al momento de cargar la página el campo provincias tendrá las cuatro provincias gallegas, y el campo poblaciones estará desactivado. ¿Ahora qué necesitamos? Pues primero necesitamos un script que nos saque las poblaciones de la base de datos y nos las envíe como un JSON. Cualquier lenguaje de lado del servidor nos vale, para el ejemplo va a ser PHP (pero vamos, que podría aplicarse con Java, Ruby, node.js, Python…). Crearemos un script llamado getPoblacionesJson.php

<?php
include 'conexionbd.php';
if ($mysqli -> multi_query("CALL sp_GetPoblaciones(" . $_GET['pr'] . ")")) {
	$poblaciones = array();
	do {
		if ($result = $mysqli -> store_result()) {
			while ($fila = $result -> fetch_assoc()) {				
				$poblaciones[$fila['Id']] = $fila['Nombre'];
			}
		}
	} while($mysqli->next_result());
	print_r(json_encode($poblaciones));
}
?>

En este caso veis que lo que hacemos es llamar a un procedimiento almacenado que nos devuelve las provincias, recorremos el resultado y vamos guardándolo en un array. Finalmente lo convertimos a JSON y lo imprimios para que lo recoja la función de Javascript. Si os estáis preguntando cómo va el procedimiento almacenado, es una simple select en MySQL, tal que así:

DELIMITER $$
CREATE PROCEDURE sp_GetPoblaciones(IN provincia int)
begin
	SELECT Id, Nombre FROM poblaciones WHERE (provincia is null or IdProvincia = provincia) ORDER BY Nombre;
end $$
DELIMITER ;

Ok, tenemos entonces el script del servidor, el marcado y el procedimiento en la base de datos. ¿Qué nos queda? Pues el Javascript, vitaminado con jQuery para ganar productividad:

$("#provincias").change(function() {
	$("#poblaciones").empty();
	$.getJSON('http://localhost/getPoblacionesJson.php?pr='+$("#provincias").val(),function(data){
		console.log(JSON.stringify(data));
		$.each(data, function(k,v){
			$("#poblaciones").append("<option value=\""+k+"\">"+v+"</option>");
		}).removeAttr("disabled");
	});
});

La idea es simple: Si se registra algún cambio en el combo de provincias vaciamos el combo de poblaciones y lo rellenamos con los nuevos datos, obtenidos mediante la función getJSON() y que recorreremos con each() como un conjunto de claves/valores. Finalmente, por si está desactivado, lo reactivamos. He hecho un console.log por si queréis ver cómo funciona la cosa en la consola de javascript de Chrome o del Firebug.

Espero que os sirva de ayuda esta entrada para trabajar con combos dinámicos.

Procedimientos almacenados con parámetros variables en el WHERE

Me excusaba ayer en Twitter, tengo un nuevo empleo y menos tiempo para escribir en el blog. No os preocupéis, que no lo dejaré de lado pero sí es cierto que el ritmo de publicaciones bajará un poquillo. Pero lo bueno de tener un nuevo trabajo es que al afrontar nuevos retos tengo material para nuevos artículo sobre programación (aparte de lo de cobrar regularmente a fin de mes y hacer currículum).

Y una cosa con la que estoy trabajando estos días mucho es con procedimientos almacenados en MySQL. Hoy se me planteada una cuestión, que era un procedimiento que recibe varios parámetros desde un formulario en PHP (para otro día dejo «Llamar a procedimientos almacenados desde PHP») para realizar una búsqueda. El problema: hay parámetros que pueden ir vacíos, ya que el usuario puede estar buscando por todos o por uno. Y el procedimiento almacenado no permite definir parámetros opcionales, como sí podría hacer en una función de PHP. ¿Solución? Simple y fácil.

Lo primero es pasar todos los parámetros que no están definidos al procedimiento con valor NULL. Lo siguiente es que cada comparación que se haría en el WHERE ahora formará parte de un OR donde también comprobamos si el parámetro enviado es nulo. Esto que suena a churro se explica fácilmente con un supuesto y un ejemplo de código:

Imaginemos que queremos hacer una búsqueda en una lista de, por ejemplo, alumnos de un centro. Podemos buscar por nombre, apellidos y un máximo y un mínimo de edad. Bueno, pues el procedimiento sería algo así:

DELIMITER $$ --esto para no tener conflicto con el punto y coma, puede valer otra opción
CREATE PROCEDURE ps_BusquedaVarParam(IN nombre_in varchar(30), apellidos_in varchar(50), minEdad int, maxEdad int)
BEGIN
  SELECT * FROM alumnos WHERE (nombre_in is null or Nombre = nombre_in) and (apellidos_in is null or Apellidos = apellidos_in) and (minEdad is null or Edad>=minEdad) and (maxEdad is null or Edad<=maxEdad);
END $$
DELIMITER;

La idea de hacer así este tipo de comparación es que si le pasamos un nulo la comparación con null devolverá TRUE. De esa forma si es NULL devuelve TRUE, si se da la condición también devuelve TRUE y si no es nulo pero no comple devuelve FALSE. O lo que es lo mismo, si va un NULL te devuelve TRUE y así ese parámetro no te jode que se cumple el AND de la comparativa. Así logramos que no haya problema para hacer la búsqueda sin mandar todos los parámetros.

Y listo, tranquilos, irán más espaciados, pero seguirá habiendo movimientos en este blog. Y de calidad, como siempre.

Ojo a los nombres de variables, por cierto. Si el parámetro tiene el mismo nombre que el campo, aunque uno esté en mayúsculas y otro en minúsculas, fallará. En teoría debería no dar problemas, pero dependiendo de la configuración de MySQL puede fallar o no (de hecho tuve que modificar la consulta, porque en una base de datos me funcionaba pero en otra fallaba), así que mejor les añadís a las variables un prefijo o sufijo y vía (en este caso le puse _in)

Importar datos desde un fichero CSV a una tabla de SQLite

Ya hemos hablado en el pasado de ficheros CSV en este blog, y hoy vuelven a la primera línea.

En este caso porque ayer estaba aconsejando a un amigo sobre cómo agilizar una tarea de inserción de datos en SQLite y llegamos a la conclusión de que, teniendo los datos en un fichero CSV tenía que haber una forma de importarlo. El caso es que San Google nos dio la respuesta, pero me fijé en que no había mucha cosa válida sobre el tema en español. Total, que me he decidido a traducir la entrada que nos fue más útil, que fue esta de HackGeo. Bueno, como siempre no será una traducción literal, sino una adaptación/explicación de su solución.

En fin, como dice el artículo original, hay una serie de puntos a considerar antes de ponerse a trabajar:

  • SQLite instalado en tu sistema operativo.
  • Una base de datos ya creada (si quieres seguir el ejemplo del original, podéis descargar ejemplos de código desde allí)
  • Una tabla creada en esa base de datos cuyos campos y tipos coincidan con los datos de nuestro CSV, tal que así:
    CREATE TABLE counties (
        name        CHAR,
        state       CHAR,
        state_abbrv CHAR( 2 ),
        FIPS        CHAR( 5 )
    );
    
  • Un fichero CSV cuyo esquema coincida con vuestra tabla, como por ejemplo:

    Maui,Hawaii,HI,15009
    Kauai,Hawaii,HI,15007
    Kalawao,Hawaii,HI,15005
    Honolulu,Hawaii,HI,15003
    Hawaii,Hawaii,HI,15001

Ahora, siguiendo el ejemplo original, ya tenemos todos los datos y el software listos. Ya sólo quedan cuatro pasos para tener todo listo:

  1. Lanza un terminal
  2. Abre la herramienta de terminal de comandos de SQLite, escribiendo sqlite3 y pasándole como parámetros la ruta absoluta a vuestra base de datos:
    sqlite3 '/Path/To/HackGeo - SQLite CSV Import Example/HackGeo.db'
    
  3. Pon la coma como separador por defecto (si usas tu propio CSV y no el del ejemplo entonces define el separador que corresponda, en caso de que no sea la coma):
    .separator ','
    
  4. Ahora ejecuta el importador de SQLite para volcar los datos del CSV en tu equipo. Le tenemos que pasar dos parámetros a este comando: la ruta del csv y el nombre de la tabla donde insertaremos los valores:
    .import '/Path/To/HackGeo - SQLite CSV Import Example/Counties.csv' counties
    

Y sólo con esto ya tenemos los datos pasados del CSV a la base de datos SQLite. Ya podéis cerrar el terminal de línea de comandos y empezar a trabajar con vuestros nuevos datos. Y si sabéis inglés en HackGeo tenéis muchos artículos interesantes sobre desarrollo, sistemas, etc…

Eventos en MySQL (planificar tareas por fecha y hora)

Seas un dba o seas un programador (y yo en mi caso soy ambas cosas) la automatización de tareas es algo no sólo útil, sino a veces indispensable. Al estilo de los cron jobs de Linux MySQL nos ofrece la posibilidad de definir una acción o serie de acciones que se repita periódicamente o se ejecuten en un momento concreto.

Lo primero es habilitar a nuestro servidor MySQL para que pueda hacerlo, con la siguiente línea:

SET GLOBAL event_scheduler = ON;

Si usas PHPMyAdmin tienes por ahí un botón para activarlo, no necesitas comandos.

Bueno, la sintaxis básica de la creación de eventos, y a la guía de usuario de MySQL me remito, es la siguiente:


CREATE
[DEFINER = { user | CURRENT_USER }]
EVENT
[IF NOT EXISTS]
event_name
ON SCHEDULE schedule
[ON COMPLETION [NOT] PRESERVE]
[ENABLE | DISABLE | DISABLE ON SLAVE]
[COMMENT 'comment']
DO sql_statement;

schedule:
AT timestamp [+ INTERVAL interval] ...
| EVERY interval
[STARTS timestamp [+ INTERVAL interval] ...]
[ENDS timestamp [+ INTERVAL interval] ...]

interval:
quantity {YEAR | QUARTER | MONTH | DAY | HOUR | MINUTE |
WEEK | SECOND | YEAR_MONTH | DAY_HOUR | DAY_MINUTE |
DAY_SECOND | HOUR_MINUTE | HOUR_SECOND | MINUTE_SECOND}

Ok, visto así, acojona. Pero no todos los parámetros son obligatorios, y explicado es fácil. Reamente necesitas sólo un nombre para el event_name, una condición para que se cumpla el ON SCHEDULE y un código sql que ejecutar después del DO. Te meto un ejemplillo sacado del curro, por ejemplo:

CREATE EVENT `close_expired_campaigns` 
ON SCHEDULE EVERY 1 DAY STARTS '2013-01-21 00:00:01' 
ON COMPLETION NOT PRESERVE ENABLE 
DO update tabla
set valido = 0
where
DATE_ADD(fechaCreacion, INTERVAL 45 DAY)<NOW() and valido=1;

Tiene algo más de código porque lo genera automáticamente PHPMyAdmin pero la idea es que crea un evento con ese nombre que, cada día un segundo después de medianoche actualice la tabla seleccionada poniendo a cero la columna indicada cuando se cumpla una condición (en este caso, que hayan pasado 45 días desde la creación). Bueno, como el código es tocho, os pongo debajo un ejemplo con la sintaxis más básica posible. Imaginemos una funcion que actualiza el saldo de una cuenta diaria de gastos a 1000 euros:

CREATE EVENT e_ActualizaSaldoDiario
ON SCHEDULE EVERY 1 DAY STARTS ‘2013-01-01 00:00:00’
DO UPDATE gastos SET saldo = 1000

Los dos ejemplos son recurrentes, se ejecutan todos los días a una hora (el parámetro Starts no permite definir el momento en que comenzará a ejecutarse, acuérdate de ponerlo porque si pones solo EVERY 1 DAY debería fallar, y no tiene por qué ser cada día, puedes poner un periodo concreto de horas, minutos, días, semanas…). Existe la posibilidad de que un evento se ejecute sólo una vez, en el momento que le indiques y luego se borre. Por ejemplo imaginemos que quieres que dentro de 6 horas se borren todos los registros del año 2012:

CREATE EVENT e_Borra2012
ON SCHEDULE AT now() + INTERVAL 6 HOUR
DO DELETE cuentas WHERE year = 2012

Y en estos tres ejemplos sólo hemos utilizado sentencias de SQL simples, pero imagina la potencia (y los riesgos de joderlo todo si la cagas, claro está) si los combinamos con procedimientos almacenados y con disparadores… Me imagino una bombillita alumbrando tu cabeza, y todas las cuestiones de mantenimiento y actualización que se te están ocurriendo.

Seleccionar filas aleatoriamente en MySQL

Existen varias opciones a la hora de seleccionar filas de forma aleatoria en MySQL, haciendo uso de la función RAND(). Vamos a imaginar que quieres obtener 3 filas aleatorias de una base de datos. Lo más sencillo es usar la siguiente sintaxis:

SELECT * FROM tabla ORDER BY RAND() LIMIT 3;

El problema de este método es que tiene que generar una tabla temporal completa con todos los datos de la tabla original reordenados aleatoriamente… en fin, que es una sangría de recursos. Existe una opción, que es generar sólo una tabla con los registros que queramos, mediante una subconsulta. Tal que así:

SELECT * FROM tabla WHERE RAND()<(SELECT ((3/COUNT(*))*10) FROM tabla) ORDER BY RAND() LIMIT 3;

Un método que sigue gastando muchos recursos, pero menos al crear sólo una tabla temporal con la cantidad de filas que necesitamos (cantidad que necesitamos / total * 10).

Pero si tenemos un índice autonúmerico en nuestra tabla podemos ahorrar más recursos:

SELECT t.* FROM tabla AS t JOIN (SELECT ROUND(RAND() * (SELECT MAX(id) FROM tabla)) AS id) AS x WHERE t.id >= x.id LIMIT 3;

Este método no genera un valor aleatorio para cada fila consultada, por lo que ahorra muchos recursos en el servidor. Eso sí, no te dará los registros ordenados aleatoriamente, sino que será registros secuenciales, pero comenzando en un valor aleatorio. Es decir, los dos primeros podrían devolverte las filas 1, 21 y 42, este en cambio te devolvería 1,2,3 – 41,42,43… si quieres que sea un resultado aleatorio de verdad deberías realizar la consulta varias veces con limit 1… con lo que el ahorro de recursos se va al carajo. Además, puede fallar si hay algún hueco en la secuencia del campo autonumérico (por ejemplo, si has borrado un valor).

Una última opción, en lugar de generar el valor aleatorio a nivel de base de datos generarlo antes, en el código del servidor. Un ejemplo en PHP (que también tiraría de id autonumérica y que también daría problemas si tuviera huecos, ojo):

$result = $mysqli->query("SELECT * FROM tabla WHERE id in ROUND(".lcg_value()."*(SELECT COUNT(*) FROM tabla)) LIMIT 1")

El ejemplo de arriba nos devolvería una fila aleatoriamente, por lo que bastaría con repetirla en un bucle tantas veces como necesitemos (en este caso el problema vendría de la cantidad de conexiones que hay que abrir a la base de datos). Puedes intentar implementar lcg_value() en los otros métodos citados, y comprobar si el rendimiento mejora.

Evitar SQL-injection en PHP

Los ataques SQL-Injection son unos de los más habituales en el mundo web. Aquí vamos a ver una serie de consejos para evitar estos agujeros en la seguridad:

Lo primero es seguir una serie de consejos a nivel de administración, como limitar los permisos del usuario a nivel de base de datos. Por ejemplo, en la mayoría de aplicaciones web lo habitual es que no tenga que utilizarse DROP, por lo que por seguridad sería mejor no permitir ya al usuario hacerlo. También sería interesante no dar información extra al atacante evitando sacar en pantalla los errores de la base de datos. En ese caso lo ideal es capturar el error haciendo uso de exception. Tampoco hay que confiarlo todo a las validaciones por javascript, porque el atacante puede saltárselas desactivándolo desde el navegador, por lo que mejor hacer las validaciones del lado del servidor. Y finalmente escapar las comillas con msqli_real_scape_string (en caso de que usemos MySQL), pg_scape_string (en caso de PostgreSQL) o addslashes (por si utilizamos otro SGBD). También podemos utilizar htmlentities para convertir los textos en entidades html, como un plus a la seguridad. Abajo un simple ejemplo:


<?php

try{
  $query = sprintf("SELECT * FROM users WHERE id=%d", mysqli_real_escape_string($id));
  $query = htmlentities($query);
  mysqli_query($query);
}catch(Exception $e){
    echo('Lo sentimos, ha habido un error en la conexión');
}
?>

Como medida extra se podrían utilizar expresiones regulares para evitar la inserción de ciertas palabras (SELECT, DROP, UNION…), si bien puede resultar poco práctico, sobre todo si tu software está destinado al mercado británico.

Pero si todo esto te parece lioso, hay una alternativa: la clase PDO. Dicha clase nos facilitará mucho la vida a la hora de trabajar con bases de datos, ya que nos permite abstraernos del SGDB que estemos utilizando. Si por ejemplo, en una página donde no utilices PDO o algo similar sino el mysql_connect simple, decides migrar tu web de MySQL a PostgreSQL tendrías que cambiar todos los métodos del conector de MySQL por los métodos de PostgreSQL. Con PDO bastaría con que cambiaras una sola línea de código, concretamente la de la creación del objeto PDO, y el resto de la aplicación seguiría funcionando. PDO además te permitirá usar consultas parametrizadas (como los Prepared Statements de java) o realizar transacciones. En fin, en el enlace de arriba tenéis todo el manual de PDO para estudiarlo si queréis. Ahora vamos con un simple ejemplo de Prepared Statement, para que veáis lo sencillo que es (dando por sentado para el ejemplo que ya hemos creado el objeto PDO, tal cual está explicado en el manual del enlace).


<?php
  $prepared_statement = $pdo->prepare("SELECT name FROM usuarios WHERE id = :id");/*preparamos la consulta*/
  $prepared_statement->bindParam(':id', $_GET['id'], PDO::PARAM_INT); /*Le pasamos el parámetro, asociado al parámetro de la consulta y definiendo su tipo (si no, por defecto lo trata como string)*/
  $prepared_statement->execute(); /*ejecutamos*/
  $prepared_statement = $statement->fetch(); /*recogemos los resultados, como un array. Se pueden utilizar parámetros para especificar otro tipo de respuesta, como por ejemplo PDO::FETCH_ASSOC para obtener un array asociativo*/
?>

Y con estos breves consejos lograrás que tu página sea más segura. El consejo: utiliza PDO, por ahorrarte comeduras de cabeza, por seguridad y por portabilidad de tu código.

Añadir columna a una tabla en MySQL (y MariaDB)

Una pequeña entrada sobre SQL básico, porque me lo han preguntado hace un rato, así que aprovecho para contestar por aquí.

Muy bien, tienes una tabla y quieres añadir una columna o campo que se te olvidó al crearla, o que por cambios en la aplicación necesitas añadir. Si te vas a la guía de referencia de MySQL puedes leer todas las posibilidades que tienes, pero como se puede hacer largo y pesado mejor unos ejemplillos de código rápidos aquí:

La sintaxis básica es ALTER TABLE nombreDeLaTabla add nombreDeLaColumna tipodedatos(longitud)

ALTER TABLE tabla1 add colNueva varchar(2);

Vamos con más ejemplos. Por ejemplo, queremos que la columna nueva no permita valores nulos (ojo, esto puede dar problemas si la tabla ya tiene valores añadidos):

ALTER TABLE tabla1 add colNueva varchar(2) NOT NULL;

O por ejemplo queremos colocarlo específicamente después de una columna que ya existe (si no especificamos nada, añadirá la nueva al final):

ALTER TABLE tabla1 add colNueva varchar(2) NOT NULL AFTER col1;

O al revés, queremos insertarla antes de una columna concreta:

ALTER TABLE tabla1 add colNueva varchar(2) NOT NULL BEFORE col2;

También puede venir con un valor por defecto:

ALTER TABLE tabla1 add colNueva varchar(35) DEFAULT 'Ni de ajo';

¿Y si en vez de una quieres insertar varias? Sin problema:

ALTER TABLE tabla1 add (
colNueva varchar(2) NOT NULL,
colMásNueva tinyint(1) default 0
)

Como ves, es sencillo. SQL básico. Como siempre, todo lo aplicable en MySQL lo es también en MariaDB.