En estos tiempos en los que absolutamente todo está más que medido, programado y tageado para poder ofrecerte contenido relacionado al que estes disfrutando en este o cualquier momento, que te resulte interesante, sin dejarlo al azar, y sigas pinchando, perdón, clickando en enlaces de esa web que estás viendo (¿alguién pincha, perdón, clicka en esos enlaces?) pues a mi me da por querer obtener una serie de items de forma aleatoria. ¿El objetivo? ninguno en particular, simplemente el azar, el caos absoluto.

Resumiendo, usando la típica función RAND() de PHP o MySQL o pasando por algo similar al random en Java o JavaScript, obtener una serie items, pero de la base de datos. Así, a lo bruto, sin pensar si tiene alguna relación una cosa con otra.

En concreto, lo que quiero es hacer una consulta a la base de datos, en este caso es MySQL, y obtener 3 items aleatorios de una tabla. MySQL (y seguro que otros gestores de base de datos) nos provee de una función, que ya he mencionado, RAND(), que usado junto al comando ORDER BY nos debería devolver los items desordenados.

Pues parece sencillo ¿no? Pues sí, pero resulta que estoy usando Doctrine, que es un ORM y que el puñetero no lo entiende:

[Syntax Error] line 0, col 48: Error: Expected known function, got 'RAND'

Ale, ya tenemos el problema. Pero bueno, hay varias soluciones. Pero yo voy a dejar aquí la mia que es la que más me gusta hasta que me convenzan de lo contrario.

Mi solución

Pues después de probar el RAND() varias veces en DQL (al principio pensé que estaba haciendo algo mal, no que Doctrine no lo permitiera) pensar en alguna solución que, basicamente, consistía en subconsultas o en dos consultas a la base de datos y, por supuesto, mirar en stackoverflow, y que nada satisfaciera mis caprichos pensé ¿soy gilipollas o qué?.

¿Alguién maś se insultará a sí mismo cuando no le sale algo? ¿hay alguién ahí?

Al final me decidí por usar la función array_rand() de PHP y una DQL pasándole varios criterios aleatorios al ORDER BY.

Leyendo la documentación de array_rand() vemos que nos devuelve la clave del array asociativo. Por lo tanto el array debería tener esta pinta:

array(
  'a.column0' => 'loquesea',
  'a.column1' => 'loquesea',
  'a.column2' => 'loquesea'
);

En la clave uso el nombre de las columnas de la tabla, que es lo que quiero pasarle más adelante a mi consulta escrita en el lenguaje de Doctrine. Una vez hecho esto, obtenemos un valor con array_rand():

$criteria = array_rand(array(
  'a.column0' => 'loquesea',
  'a.column1' => 'loquesea',
  'a.column2' => 'loquesea'
););

Además, voy a añadir el tipo de orden que quiero y ¡de forma aleatoria! ¡si señor! soy un puto genio. El ORDER BY acepta el tipo de ordenación ascendente con ASC (por defecto) o descendente con DESC.

$orderby = array_rand(array(
  'DESC' => 'DESC',
  'ASC' => 'ASC'
););

Y una vez listo esto, ya podemos pasarle los valores a nuestra consulta creada con DQL. Algo similar a esto:


return $objectEntityManager->getEntityManager()
  ->getRepository('src\DB\Entity')
  ->createQueryBuilder('a')
  ->where("a.state = 1")
  ->orderBy($criteria, $orderby)
  ->setMaxResults(3)
  ->getQuery()
  ->execute();

Y con esto solucioné mi problema con el azar y el caos, en una sola consulta, sin crear una consulta en SQL nativo (que siempre intento evitar) y sin perjudicar la performance, al menos no en exceso.

Recomendaciones

  • Recomendable poner un número máximo de resultados, sobretodo si la tabla en la que vas a hacer la consulta es muy grande.
  • Si quieres obtener más aleatoridad, cuantos más campos de la tabla tenga el array $criteria, pues mejor.
  • Al usar Doctrine evita las consultas nativas. ¿La razón? Pues no sé... lo habré leído por ahí. Pero vamos, evítalas. En todo caso Doctrine provee una clase con la que crear consultas nativas, pero claro, no están mapeadas por defecto con la entidad correspondiente y luego tienes que ir haciéndolo llamando a más método... Vamos, un lio.
  • ¡Úsalo mucho! para que el azar y el caos se apodere del mundo y no le pase a tus usuarios como a este chico con Netflix.

¿Existe otra alternativa a esto?

¿En serio quieres una alternativa a tal prodigio de ingeniería? Ok, pues sí, existe. Doctrine permite escribir tus propias funciones. Para ello ve a la página: Registrando tus  funciones DQL. Y como otro ejemplo a lo anterior (está claro que mucho peor que todo lo anterior) podrías usar lo que Marco Pivetta ha creado para este cometido: MySQL RAND() function in Doctrine2 DQL registrando su propia función.