Ok, me downvoted en este, así que decidí probarlo:
CREATE TABLE userrole (
userid INT,
roleid INT,
PRIMARY KEY (userid, roleid)
);
CREATE INDEX ON userrole (roleid);
Ejecutar este:
<?php
ini_set('max_execution_time', 120); // takes over a minute to insert 500k+ records
$start = microtime(true);
echo "<pre>\n";
mysql_connect('localhost', 'scratch', 'scratch');
if (mysql_error()) {
echo "Connect error: " . mysql_error() . "\n";
}
mysql_select_db('scratch');
if (mysql_error()) {
echo "Selct DB error: " . mysql_error() . "\n";
}
$users = 200000;
$count = 0;
for ($i=1; $i<=$users; $i++) {
$roles = rand(1, 4);
$available = range(1, 5);
for ($j=0; $j<$roles; $j++) {
$extract = array_splice($available, rand(0, sizeof($available)-1), 1);
$id = $extract[0];
query("INSERT INTO userrole (userid, roleid) VALUES ($i, $id)");
$count++;
}
}
$stop = microtime(true);
$duration = $stop - $start;
$insert = $duration/$count;
echo "$count users added.\n";
echo "Program ran for $duration seconds.\n";
echo "Insert time $insert seconds.\n";
echo "</pre>\n";
function query($str) {
mysql_query($str);
if (mysql_error()) {
echo "$str: " . mysql_error() . "\n";
}
}
?>
Salida:
499872 users added.
Program ran for 56.5513510704 seconds.
Insert time 0.000113131663847 seconds.
que añade 500.000 por el usuario al azar combinaciones de roles y hay aproximadamente 25,000 que coinciden con los criterios elegidos.
Primera consulta:
SELECT userid
FROM userrole
WHERE roleid IN (1, 2, 3)
GROUP by userid
HAVING COUNT(1) = 3
tiempo de consulta: tiempo 0.312s
SELECT t1.userid
FROM userrole t1
JOIN userrole t2 ON t1.userid = t2.userid AND t2.roleid = 2
JOIN userrole t3 ON t2.userid = t3.userid AND t3.roleid = 3
AND t1.roleid = 1
Consulta: 0.016s
Eso es correcto. La versión de unión que propuse es veinte veces más rápida que la versión agregada.
Lo siento pero lo hago para vivir y trabajar en el mundo real y en el mundo real probamos SQL y los resultados hablan por sí mismos.
La razón para esto debería ser bastante clara. La consulta agregada se escalará en costo con el tamaño de la tabla. Cada fila se procesa, agrega y filtra (o no) a través de la cláusula HAVING
. La versión de unión (utilizando un índice) seleccionará un subconjunto de usuarios en función de un rol dado, luego verificará ese subconjunto con el segundo rol y, finalmente, ese subconjunto con el tercer rol. Cada selection (en relational algebra términos) funciona en un subconjunto cada vez más pequeño. De esto puede concluir:
El rendimiento de la versión de combinación mejora aún más con una menor incidencia de coincidencias.
Si solo había 500 usuarios (de la muestra de 500k anterior) que tenían los tres roles indicados, la versión de unión se volverá significativamente más rápida. La versión agregada no lo hará (y cualquier mejora en el rendimiento es el resultado de transportar 500 usuarios en lugar de 25k, lo que obviamente también obtiene la versión de unión).
También tenía curiosidad por ver cómo una base de datos real (es decir, Oracle) se ocuparía de esto. Así que básicamente repetí el mismo ejercicio en Oracle XE (ejecutándose en la misma máquina de escritorio de Windows XP que el MySQL del ejemplo anterior) y los resultados son casi idénticos.
Las uniones parecen estar mal vistas pero, como he demostrado, las consultas agregadas pueden ser de un orden de magnitud más lentas.
Actualización: Después de algúnextensive testing, la situación es más complicada y la respuesta dependerá de sus datos, su base de datos y otros factores. La moraleja de la historia es prueba, prueba, prueba.
Se han agregado numerosos detalles a mi respuesta. – cletus
Cualquier pregunta que provoque una respuesta como la que @cletus dio merece +1 – cori