<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	>

<channel>
	<title>QuarkBlog &#187; MySQL</title>
	<atom:link href="http://quarkblog.org/category/bases-de-datos/mysql/feed/" rel="self" type="application/rss+xml" />
	<link>http://quarkblog.org</link>
	<description>If you can read this, you need another beer.</description>
	<pubDate>Thu, 07 Aug 2008 07:20:00 +0000</pubDate>
	<generator>http://wordpress.org/?v=2.5.1</generator>
	<language>en</language>
			<item>
		<title>Sun compra MySQL</title>
		<link>http://quarkblog.org/2008/01/17/sun-compra-mysql/</link>
		<comments>http://quarkblog.org/2008/01/17/sun-compra-mysql/#comments</comments>
		<pubDate>Wed, 16 Jan 2008 22:18:46 +0000</pubDate>
		<dc:creator>Moisés Maciá</dc:creator>
		
		<category><![CDATA[Bases de datos]]></category>

		<category><![CDATA[MySQL]]></category>

		<category><![CDATA[Software Libre]]></category>

		<guid isPermaLink="false">http://quarkblog.org/2008/01/17/sun-compra-mysql/</guid>
		<description><![CDATA[Iba a decir algo del Macbook Air no muy positivo, pero es que al enterarme de la compra de MySQL por parte de Sun Microsystems se me han quitado las ganas por completo. Un billón de dolares le ha costado a Sun la bromita, creo que ha sido la operación más cara en el mundo [...]]]></description>
			<content:encoded><![CDATA[<p>Iba a decir algo del Macbook Air no muy positivo, pero es que al enterarme de la <a href="http://mysql.com/news-and-events/sun-to-acquire-mysql.html">compra de MySQL por parte de Sun Microsystems</a> se me han quitado las ganas por completo. <strong>Un billón de dolares</strong> le ha costado a Sun la bromita, creo que ha sido la operación más cara en el mundo del software libre.</p>
<p>Con esta compra, Sun reafirma su apuesta a muerte por el software libre, obtiene una posición privilegiada en el mundo del desarrollo de aplicaciones y servicios web y pasa a competir con Oracle en el negocio de las bases de datos. Y aunque para que MySQL pueda mirar cara a cara a la todopoderosa Oracle le queda bastante camino por andar, ahí está.</p>
<p>En el último año MySQL cambió el rumbo acercándose más al mundo de los negocios con la versión <a href=http://mysql.com/products/enterprise/"">Enterprise</a> y todos los servicios de soporte asociados. También lanzaron un montón de software prometedor como el driver nativo para PHP <a href="http://dev.mysql.com/downloads/connector/php-mysqlnd/">mysqlnd</a> y el dispatcher de conexiones <a href="http://dev.mysql.com/downloads/mysql-proxy/index.html">MySQL proxy</a>, entre otros.</p>
<p>Espero que Sun aporte programadores además de capital para agilizar el desarrollo del <a href="http://dev.mysql.com/doc/refman/6.0/en/se-falcon.html">motor Falcon</a>, mejorar el sistema de clustering, replicación, y como no, la integración con Java.</p>
]]></content:encoded>
			<wfw:commentRss>http://quarkblog.org/2008/01/17/sun-compra-mysql/feed/</wfw:commentRss>
		</item>
		<item>
		<title>JOINs para seres humanos</title>
		<link>http://quarkblog.org/2007/04/22/joins-para-seres-humanos/</link>
		<comments>http://quarkblog.org/2007/04/22/joins-para-seres-humanos/#comments</comments>
		<pubDate>Sun, 22 Apr 2007 20:50:09 +0000</pubDate>
		<dc:creator>Moisés Maciá</dc:creator>
		
		<category><![CDATA[Bases de datos]]></category>

		<category><![CDATA[MySQL]]></category>

		<guid isPermaLink="false">http://quarkblog.org/2007/04/22/joins-para-seres-humanos/</guid>
		<description><![CDATA[En este artículo voy a explicar cómo hacer uso del operador JOIN del álgebra relacional en el mundo real de las bases de datos y las ventajas que reporta en la selección de datos. No pongáis caras que no voy a hablar ni de álgebra relacional ni de teoría de conjuntos. Esto son JOINs para [...]]]></description>
			<content:encoded><![CDATA[<p>En este artículo voy a explicar cómo hacer uso del <strong>operador JOIN del álgebra relacional</strong> en el mundo real de las bases de datos y las ventajas que reporta en la selección de datos. No pongáis caras que no voy a hablar ni de álgebra relacional ni de teoría de conjuntos. Esto son JOINs para seres humanos <img src='http://quarkblog.org/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> </p>
<h4>Qué es una <em>JOIN</em></h4>
<p>Es una operación que combina registros de dos tablas en una base de datos relacional que resulta en una nueva tabla (temporal) llamada tabla de JOIN. En el lenguaje de consulta SQL hay dos tipos de JOIN: <strong>INNER</strong> y <strong>OUTER</strong>, si bien cada vendedor añade a sus productos modificaciones y atajos para hacer más versátiles estas operaciones.</p>
<p>Como caso especial, una tabla (tabla base, vista o una tabla JOIN) puede realizar la operación JOIN sobre ella misma otra vez. Esto se conoce como <strong>self-JOIN</strong>.</p>
<p>Matemáticamente, un JOIN es una relación de composición. Estas son las operaciones fundamentales en el álgebra relacional.</p>
<p>Supongamos que tenemos dos tablas: una de <strong>películas</strong> y otra de <strong>directores</strong> relacionadas entre sí:</p>
<table style="border:solid 1px #000; margin-left:auto; margin-right:auto; margin-bottom: 15px;">
<caption style="font-weight: bold;">movies</caption>
<tr>
<th>id</th>
<th>title</th>
<th>year</th>
<th>director</th>
</tr>
<tr>
<td>1</td>
<td>Four Rooms</td>
<td>1995</td>
<td>3</td>
</tr>
<tr>
<td>2</td>
<td>Die Hard</td>
<td>1988</td>
<td>1</td>
</tr>
<tr>
<td>3</td>
<td>The Hunt for Red October</td>
<td>1990</td>
<td>1</td>
</tr>
<tr>
<td>4</td>
<td>Psycho</td>
<td>1960</td>
<td>2</td>
</tr>
</table>
<table style="border:solid 1px #000; margin-left:auto; margin-right:auto; margin-bottom: 15px;">
<caption style="text-align:center;font-weight: bold;">directors</caption>
<tr>
<th>id</th>
<th>name</th>
</tr>
<tr>
<td>1</td>
<td>John McTiernan</td>
</tr>
<tr>
<td>2</td>
<td>Alfred Hitchcock</td>
</tr>
<tr>
<td>3</td>
<td>Quentin Tarantino</td>
</tr>
</table>
<p>Si quiero sacar todas las peliculas y el nombre de su director haría lo siguiente:</p>
<div class="dean_ch" style="white-space: wrap;">
<ol>
<li class="li1">
<div class="de1"><span class="kw1">SELECT</span> title, name </div>
</li>
<li class="li1">
<div class="de1"><span class="kw1">FROM</span> movies m, directors d</div>
</li>
<li class="li1">
<div class="de1"><span class="kw1">WHERE</span> m.director = d.id</div>
</li>
</ol>
</div>
<p>Internamente la base de datos crearía una tabla temporal con todas las filas de la tabla <code>movies</code> cruzadas a su vez con todas las filas de la tabla <code>directors</code>, para después seleccionar las filas que cumplen la condición <code>m.id = d.id</code>. En total maneja 4&#215;3 = <strong>12</strong> filas para obtener un resultado final de <strong>4</strong> registros.</p>
<p>En este caso no es problemático, pero cuando tenemos una tabla de <strong>un millón de registros</strong> y otra de <strong>diez millones</strong>, la cosa se vuelve muy fea: la base de datos tiene que manejar <strong>10.000.000.000.000 filas</strong> cuando el resultado final quizá este formado por unas pocas decenas.</p>
<p>Aqui es donde entran en juego los JOINs: cuando aplicamos esa operación, la base de datos <strong>sólo devuelve el conjunto de filas afectadas por el JOIN</strong>, descartando todas las demás.</p>
<blockquote><p>
NOTA: esto no siempre es cierto, ya que las bases de datos modernas disponen de un optimizador de consultas que en determinados casos convertirá la sentencia SQL con el tradicional <code>WHERE</code> en una <code>JOIN</code>. Como he dicho <strong>en determinados casos funcionará bien, en otros no</strong>.
</p></blockquote>
<p>La sentencia reescrita como una <code>JOIN</code> quedaría de la siguiente manera:</p>
<div class="dean_ch" style="white-space: wrap;">
<ol>
<li class="li1">
<div class="de1"><span class="kw1">SELECT</span> title, name </div>
</li>
<li class="li1">
<div class="de1"><span class="kw1">FROM</span> movies m</div>
</li>
<li class="li1">
<div class="de1"><span class="kw1">INNER</span> <span class="kw1">JOIN</span> directors d <span class="kw1">ON</span> <span class="br0">&#40;</span>m.director = d.id<span class="br0">&#41;</span>;</div>
</li>
</ol>
</div>
<h4><code>INNER</code>, <code>OUTER</code>, <code>LEFT</code>, &#8230; ¿Cuál utilizo en cada momento?</h4>
<p>El circulo <code>T1</code> representa todos los registros de nuestra primera tabla mientras que el circulo <code>T2</code> representa todos los registros de la segunda tabla. Hay una intersección entre los dos circulos, eso representa los registros de ambas tablas que están relacionados entre sí por sus claves ajenas y primarias. ¿Fácil, no?</p>
<p>El color azul representará los datos que devuelve cada tipo de <code>JOIN</code>.</p>
<div class="center"><img src='http://quarkblog.org/wp-content/uploads/2007/04/empty-join.png' alt='empty join' /></div>
<h4>INNER JOIN</h4>
<p>Una <code>INNER JOIN</code> sólo devuelve aquellos registros que coinciden en ambas tablas. Así cada registro que devuelva <code>T1</code> debe tener su pareja en <code>T2</code> enlazada por una clave ajena. En términos de lógica de primer orden sería equivalente al operador <code>AND</code>.</p>
<div class="center"><img src='http://quarkblog.org/wp-content/uploads/2007/04/inner-join.png' alt='inner join' /></div>
<h4>OUTER JOIN</h4>
<p>Una <code>OUTER JOIN</code> es la operación complementaria a una <code>INNER JOIN</code>. Sólo devuelve aquellos registros que no estén emparejados en <code>T1</code> y en <code>T2</code>. En términos de lógica de primer orden sería equivalente a la operación <code>NOT AND</code>.</p>
<div class="center"><img src='http://quarkblog.org/wp-content/uploads/2007/04/outer-join.png' alt='outer join' /></div>
<h4>LEFT JOIN</h4>
<p>Una <code>LEFT JOIN</code> devuelve los registros que están en la tabla de la izquierda (<code>T1</code>) tanto si tienen pareja en <code>T2</code> como si no.</p>
<p>Si tienen pareja, devuelve el dato relacionado. Si no, rellena los huecos con <code>NULL</code>.</p>
<div class="center"><img src='http://quarkblog.org/wp-content/uploads/2007/04/left-join.png' alt='left join' /></div>
<p>Es posible hacer la misma operación con la tabla de la derecha, en ese caso estaríamos hablando de una <code>RIGHT JOIN</code> pero lo habitual es utilizar como pivote siempre la izquierda.</p>
<h4>LEFT OUTER JOIN</h4>
<p>Una <code>LEFT OUTER JOIN</code> combina las ideas de la <code>LEFT JOIN</code> y la <code>OUTER JOIN</code>. Basicamente si utilizas una <code>LEFT OUTER JOIN</code> obendrás los registros de la tabla izquierda que no emparejan con ninguno de los de la tabla de la derecha.</p>
<div class="center"><img src='http://quarkblog.org/wp-content/uploads/2007/04/left-outer-join.png' alt='left outer' /></div>
<p>De nuevo, se puede realizar la operación equivalente en la tabla de la derecha aunque no suele ser lo habitual.</p>
<h4>&theta; JOIN</h4>
<p>La composición Theta es el producto cartesiano de dos tablas. Existe como operación matemática pero normalmente no es una consulta que la gente utilice, porque devuelve <strong>todos los registros de todas las tablas</strong>.</p>
<div class="center"><img src='http://quarkblog.org/wp-content/uploads/2007/04/theta-join.png' alt='theta join' /></div>
<h4>Más sobre JOINs</h4>
<p>Dependiendo del RDBMS que utilicéis, os será posible utilizar más tipos de composiciones (por ejemplo MySQL soporta las <code>NATURAL JOIN y <strong>STRAIGHT JOIN</strong></code>) así como definir composiciones que afecten a mas de dos tablas.</p>
<p>Estas que he comentado están disponibles en la mayoría de las bases de datos modernas.</p>
]]></content:encoded>
			<wfw:commentRss>http://quarkblog.org/2007/04/22/joins-para-seres-humanos/feed/</wfw:commentRss>
		</item>
		<item>
		<title>Paginar una consulta desde la consola MySQL</title>
		<link>http://quarkblog.org/2007/02/04/paginar-una-consulta-desde-la-consola-mysql/</link>
		<comments>http://quarkblog.org/2007/02/04/paginar-una-consulta-desde-la-consola-mysql/#comments</comments>
		<pubDate>Sun, 04 Feb 2007 00:49:06 +0000</pubDate>
		<dc:creator>Moisés Maciá</dc:creator>
		
		<category><![CDATA[Bases de datos]]></category>

		<category><![CDATA[MySQL]]></category>

		<guid isPermaLink="false">http://quarkblog.org/2007/02/04/paginar-una-consulta-desde-la-consola-mysql/</guid>
		<description><![CDATA[Este truco va para los amantes de la línea de comandos y para los que han salido escaldados de las herramientas desktop de MySQL:
En muchas ocasiones, al realizar una consulta, la pantalla se llena de datos y el buffer del terminal acaba por agotarse, de forma que resulta imposible ver detenidamente todas las filas del [...]]]></description>
			<content:encoded><![CDATA[<p>Este truco va para los amantes de la línea de comandos y para los que han salido escaldados de las herramientas <em>desktop</em> de MySQL:</p>
<p>En muchas ocasiones, al realizar una consulta, la pantalla se llena de datos y el buffer del terminal acaba por agotarse, de forma que resulta imposible ver detenidamente todas las filas del resultado, especialmente las primeras.</p>
<p>Más de una vez he pensado que sería genial disponer de una herramienta como <em>less</em> para trabajar dentro de la consola de MySQL. Hasta que el otro día ví la luz:</p>
<p><code>mysql&gt; \P less</code><br />
<code>PAGER set to 'less'</code></p>
<p><code>mysql&gt; select foo,bar from table</code></p>
<p>Simplemente genial.</p>
]]></content:encoded>
			<wfw:commentRss>http://quarkblog.org/2007/02/04/paginar-una-consulta-desde-la-consola-mysql/feed/</wfw:commentRss>
		</item>
		<item>
		<title>MySQL FULL TEXT para humanos</title>
		<link>http://quarkblog.org/2007/01/14/mysql-full-text-para-humanos/</link>
		<comments>http://quarkblog.org/2007/01/14/mysql-full-text-para-humanos/#comments</comments>
		<pubDate>Sun, 14 Jan 2007 20:39:58 +0000</pubDate>
		<dc:creator>Moisés Maciá</dc:creator>
		
		<category><![CDATA[Bases de datos]]></category>

		<category><![CDATA[MySQL]]></category>

		<category><![CDATA[Programación]]></category>

		<guid isPermaLink="false">http://quarkblog.org/2007/01/14/mysql-full-text-para-humanos/</guid>
		<description><![CDATA[En la base de datos MySQL existe una consulta que devuelve filas atendiendo a su relevancia. ¿Pero qué es relevancia? Es un número de coma flotante resultado de la aplicación de una serie de formulas. Conociendo la manera en la que estas formulas funcionan se pueden construir poderosas consultas que devuelvan resultados relevantes para los [...]]]></description>
			<content:encoded><![CDATA[<p>En la base de datos MySQL existe una consulta que devuelve filas atendiendo a su relevancia. ¿Pero qué es relevancia? Es un número de coma flotante resultado de la aplicación de una serie de formulas. Conociendo la manera en la que estas formulas funcionan se pueden construir poderosas consultas que devuelvan resultados relevantes para los usuarios de nuestras aplicaciones.</p>
<p>En este artículo voy a comentar como se gestionan los índices <code>FULLTEXT</code> en la base de datos MySQL, versiones 4.1 o superior.</p>
<p>Primero crearemos una tabla de ejemplo:</p>
<div class="dean_ch" style="white-space: wrap;">
<ol>
<li class="li1">
<div class="de1">&nbsp;</div>
</li>
<li class="li1">
<div class="de1"><span class="kw1">CREATE</span> <span class="kw1">TABLE</span> quotes <span class="br0">&#40;</span>quote CHAR<span class="br0">&#40;</span><span class="nu0">100</span><span class="br0">&#41;</span>,FULLTEXT <span class="br0">&#40;</span>quote<span class="br0">&#41;</span><span class="br0">&#41;</span>;</div>
</li>
<li class="li1">
<div class="de1">&nbsp;</div>
</li>
<li class="li1">
<div class="de1"><span class="kw1">INSERT</span> <span class="kw1">INTO</span> quotes <span class="kw1">VALUES</span></div>
</li>
<li class="li2">
<div class="de2">&nbsp; <span class="br0">&#40;</span><span class="st0">&#8216;Special times require special socks&#8217;</span><span class="br0">&#41;</span>,</div>
</li>
<li class="li1">
<div class="de1">&nbsp; <span class="br0">&#40;</span><span class="st0">&#8216;Knock three times on the ceiling&#8217;</span><span class="br0">&#41;</span>,</div>
</li>
<li class="li1">
<div class="de1">&nbsp; <span class="br0">&#40;</span><span class="st0">&#8216;Boliauns are weeds&#8217;</span><span class="br0">&#41;</span>,</div>
</li>
<li class="li1">
<div class="de1">&nbsp; <span class="br0">&#40;</span><span class="st0">&#8216;The leprechaun&#8217;</span><span class="st0">&#8217;s gold&#8217;</span><span class="br0">&#41;</span>;</div>
</li>
</ol>
</div>
<blockquote><p> Frases extraídas de <a href="http://www.sacred-texts.com/neu/celt/cft/cft06.htm"><q>The Field of Boliauns</q></a>, un cuento clásico de la cultura Celta.</p></blockquote>
<p>Algunas de las palabras se repiten varias veces. Otras palabras no formarán parte del indice <code>FULLTEXT</code> debido a que son muy cortas o demasiado frecuentes. Esta base de datos es lo suficientemente compleja para mostrar todos los trucos de las formulas de cálculo de relevancia.</p>
<h4>myisam_ftdump</h4>
<p>Para ver que es lo que se ha guardado en el indice <code>FULLTEXT</code> utilizaremos el programa <strong>myisam_ftdump</strong>. Viene con la distribución estándar de la base de datos.</p>
<p>Lo primerro es saber dónde se están guardando físicamente los datos de la tabla con una sentencia <code>SHOW</code>:</p>
<div class="dean_ch" style="white-space: wrap;">
<ol>
<li class="li1">
<div class="de1"><span class="kw1">SHOW</span> <span class="kw1">VARIABLES</span> <span class="kw1">LIKE</span> <span class="st0">&#8216;datadir%&#8217;</span>;</div>
</li>
</ol>
</div>
<pre>
+---------------+-----------------------+
| Variable_name | Value                 |
+---------------+-----------------------+
| datadir       | /var/lib/mysql/        |
+---------------+-----------------------+

1 row in set (0.00 sec)</pre>
<p>Otra sentencia <code>SELECT</code> para saber el nombre de la base de datos en la que me encuentro:</p>
<div class="dean_ch" style="white-space: wrap;">
<ol>
<li class="li1">
<div class="de1"><span class="kw1">SELECT</span> <span class="kw1">DATABASE</span><span class="br0">&#40;</span><span class="br0">&#41;</span>;</div>
</li>
</ol>
</div>
<pre>
+------------+
| DATABASE() |
+------------+
| db1        |
+------------+

1 row in set (0.01 sec)</pre>
<p>Todos estos datos son los que necesito para poder ejecutar <strong>myisam_ftdump</strong>. Veamos primero las opciones que tiene:</p>
<pre>
$&gt; myisam_ftdump --help

Use: myisam_ftdump &lt;table_name&gt; &lt;index_num&gt;

  -d, --dump          Dump index (incl. data offsets and word weights).
  -s, --stats         Report global stats.
  -v, --verbose       Be verbose.
  -c, --count         Calculate per-word stats (counts and global weights).
  -l, --length        Report length distribution.
  -h, --help          Display help and exit.
  -?, --help          Synonym for -h.</pre>
<p>Veamos que aspecto tiene el volcado del indice:</p>
<pre>
$&gt; myisam_ftdump /var/lib/mysql/db1/quotes 0 -d
       ca            0.9775171 boliauns
       65            0.9666505 ceiling
      12f            0.9775171 gold
       65            0.9666505 knock
      12f            0.9775171 leprechaun's
        0            0.8148246 require
        0            0.8148246 socks
        0            1.3796179 special
        0            0.8148246 times
       65            0.9666505 times
       ca            0.9775171 weeds</pre>
<p>Veamos ahora los pesos por palabra:</p>
<pre>
$&gt; myisam_ftdump /var/lib/mysql/db1/quotes 0 -c
        1            1.0986123 boliauns
        1            1.0986123 ceiling
        1            1.0986123 gold
        1            1.0986123 knock
        1            1.0986123 leprechaun's
        1            1.0986123 require
        1            1.0986123 socks
        1            1.0986123 special
        2            0.0000000 times
        1            1.0986123 weeds</pre>
<p>Claramente se observa que MySQL asocia números a términos, lo que nos queda por ver es qué significan esos números.</p>
<h4>Las formulas</h4>
<p>Hay tres formulas, y no son para nada complicadas <img src='http://quarkblog.org/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> </p>
<ul>
<li><code>peso_local = (log(dtf)+1)/sumdtf * U/(1+0.0115*U)</code></li>
<li><code>peso_global = log((N-nf)/nf)</code></li>
<li><code>peso_consulta = peso_local * peso_global * qf</code></li>
</ul>
<h5>Parámetros</h5>
<ul>
<li><strong>dtf</strong> el número de veces que el término aparece en la fila.</li>
<li><strong>sumdtf</strong> el sumatorio de <code>(log(dtf)+1)</code> para todos los términos de la misma fila.</li>
<li><strong>U</strong> el número de términos únicos que hay en la fila.</li>
<li><strong>N</strong> el número de filas que hay en la tabla.</li>
<li><strong>nf</strong> el número de filas que contienen el término.</li>
<li><strong>qf</strong> el número de veces que el término aparece en la consulta.</li>
</ul>
<p>Por último <em>log(n)</em> hace referencia al logaritmo neperiano de <em>n</em>.</p>
<p>Tomemos por ejemplo la búsqueda de la palabra <q>special</q> sobre la primera fila de la tabla, que <code>myisam_ftdump</code> identifica como fila 0 (cero).</p>
<p>Para la primera formula: (log(dtf)+1)/sumdtf * U/(1+0.0115*U);</p>
<table style="font-family: verdana; font-size: 0.8em">
<tr>
<td>dft</td>
<td><q>special</q> aparece dos veces en la fila 0, así que <code>log(dtf()+1) = 0.6931472 + 1 = <strong>1.6931472</strong></code></td>
</tr>
<tr>
<td>sumdft</td>
<td><q>special</q> aparece 2 veces en la fila 0, añadimos <code>log(2)+1</code><br />
<q>times</q> aparece 1 vez en la fila 0, añadimos <code>log(1)+1</code><br />
<q>require</q> aparece 1 vez en la fila 0, añadimos <code>log(1)+1</code><br />
<q>socks</q> aparece 1 vez en la fila 0, añadimos <code>log(1)+1</code><br />
el cálculo queda <code>sumdtf = log(2)+1 + (log(1)+1)*3 = <strong>4.6931472</strong></code></td>
</tr>
<tr>
<td>U</td>
<td>Hay 4 términos únicos en la fila 0, por lo que <code>U/(1+0.115*U) = 4/(1+0.0115*4) = <strong>3.824092</strong></code></td>
</tr>
</table>
<p><code>peso_local = 1.6931472 / 4.6931472 * 3.824092 = <strong>1.3796179</strong></code>. El mismo número que sale en el volcado del indice proporcionado por <code>myisam_ftdump</code>.</p>
<p>Para la segunda formula: <code>log((N-nf)/nf);</code></p>
<table style="font-family: verdana; font-size: 0.8em">
<tr>
<td>N</td>
<td>Hay 4 filas en la tabla <q>quotes</q></td>
</tr>
<tr>
<td>nf</td>
<td>El término <q>special</q> tiene ocurrencias en 1 fila.</td>
</tr>
</table>
<p><code>peso_global = log((N-nf)/nf) = log(3) = <strong>1.0986123</strong></code>. El mismo número que devuelve <code>myisam_ftdump</code> en el volcado de pesos por palabra.</p>
<p>Para la tercera fórmula: <code>peso_consulta = peso_local * peso_global * qf;</code></p>
<table style="font-family: verdana; font-size: 0.8em">
<tr>
<td>peso_local</td>
<td>1.3796179</td>
</tr>
<tr>
<td>peso_global</td>
<td>1.0986123</td>
</tr>
<tr>
<td>qf</td>
<td><q>special</q> aparece 1 vez en la consulta.</td>
</tr>
</table>
<p><code>peso_consulta = 1.3796179 * 1.0986123 * 1 = <strong>1.5156652</strong></code>. Finalmente esta es la relevancia de la consulta sobre la tabla de ejemplo.</p>
<p>Comprobemos el resultado con una consulta del tipo <code>MATCH</code> &#8230; <code>AGAINST</code>:</p>
<div class="dean_ch" style="white-space: wrap;">
<ol>
<li class="li1">
<div class="de1">&nbsp;</div>
</li>
<li class="li1">
<div class="de1"><span class="kw1">SELECT</span> ROUND<span class="br0">&#40;</span>MATCH<span class="br0">&#40;</span>quote<span class="br0">&#41;</span> AGAINST <span class="br0">&#40;</span><span class="st0">&#8217;special&#8217;</span><span class="br0">&#41;</span>,<span class="nu0">7</span><span class="br0">&#41;</span> <span class="kw1">AS</span> score <span class="kw1">FROM</span> quotes;</div>
</li>
</ol>
</div>
<pre>
+-------------------------------------------+
| score |
+-------------------------------------------+
|                                 1.5156652 |
|                                 0.0000000 |
|                                 0.0000000 |
|                                 0.0000000 |
+-------------------------------------------+

4 rows in set (0.00 sec)</pre>
<blockquote><p> NOTA: MySQL devuelve un número con mayor resolución que mi calculadora casio, así que me ha tocado redondear a 7 decimales para que salgan los cálculos <img src='http://quarkblog.org/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> </p></blockquote>
<p>La consulta devuelve una puntuación de 1.5156652 para la primera columna de la tabla, el mismo peso que sale de nuestros cálculos.</p>
<h4>Explicación de la fórmula</h4>
<p>Observad que el peso local depende de una constante que lo multiplica. Esta constante es un factor de corrección para tratar una alta frecuencia de términos dentro de la fila. Para aclararnos: <strong>si un término aparece muchas veces en una fila, el peso crece</strong>.</p>
<p>¿Por qué el peso local depende de las veces que el termino aparece en la fila? Piensa en este mismo artículo, los términos <q>MySQL</q> y <q>FULLTEXT</q> aparecen muchas veces. Esto es típico: <strong>si una serie de palabras aparecen muchas veces a la fuerza deben ser relevantes</strong>.</p>
<p>Observad que el peso global depende de la multiplicación de una inversa, el número de filas <strong>menos</strong> el número de filas en la que aparece el término. Para aclararnos: <strong>si un término aparece en muchas filas, el peso baja</strong>.</p>
<p>El peso global baja con la frecuencia de un término en todas las filas de la tabla, esto es así porque se considera un término común. Si una palabra aparece constantemente en todas las filas se convierte en un patrón de búsqueda inútil. Pensad en buscar cosas como artículos, pronombres, etc. son tan frecuentes que los resultados son inservibles.</p>
<p>Observad que el peso local, el peso global y el peso de la consulta es lo único que importa. MySQL no incrementará el peso si dos términos aparecen cerca uno de otro. MySQL tampoco entiende las raíces semánticas, los tiempos verbales ni los plurales, esto es, si buscas <q>weed</q> no te va a hacer los cálculos de relevancia para <q>weeds</q>.</p>
<blockquote><p> La búsqueda utilizando grafos de proximidad y raíces semánticas (<em>stemming</em>) son algunas de las novedades que están preparando de cara a la versión 5.2 de MySQL. Quizá no tardemos mucho en disfrutar de ellas.</p></blockquote>
<p>MySQL tiene muchas opciones, incluyendo <code>IN BOOLEAN MODE</code> donde las formulas de arriba se vuelven irrelevantes. El modo booleano básicamente realiza una búsqueda en el histograma de frecuencias de la tabla.</p>
<h4>La lista <em>stopword</em></h4>
<p>Algunos términos — los artículos, conjunciones y pronombres son un buen ejemplo — tendrán un peso global igual a cero. MySQL no los indexará ni los tendrá en cuenta en las consultas. Es lo que se conoce como <em>stopwords</em>. MySQL maneja una lista de <em>stopwords</em> por defecto para el idioma inglés, que puede ocasionar más problemas de los que resuelve.</p>
<p>Por ejemplo, supongamos el texto:</p>
<blockquote><p> Every word she writes is a lie, including &#8220;and&#8221; and &#8220;the&#8221;.<br />
It depends on what the meaning of the word &#8220;is&#8221; is.</p></blockquote>
<p>Es imposible buscar los términos <q>and and the</q> o <q>is is</q> porque son <em>stopwords</em>. Para devolver resultados deberíamos utilizar el operador <code>LIKE</code>, por ejemplo:</p>
<div class="dean_ch" style="white-space: wrap;">
<ol>
<li class="li1">
<div class="de1">&nbsp;</div>
</li>
<li class="li1">
<div class="de1"><span class="kw1">SELECT</span> * <span class="kw1">FROM</span> quotes <span class="kw1">WHERE</span> quote <span class="kw1">LIKE</span> <span class="st0">&#8216;%is&quot; is%&#8217;</span>;</div>
</li>
</ol>
</div>
<p>O bien, utilizar una frase de busqueda que no incluya un <em>stopword</em>, por ejemplo:</p>
<pre>
SELECT * FROM quotes WHERE MATCH(quote) AGAINST('including and and the');</pre>
<p>Otro inconveniente es que los <em>stopwords</em> por defecto pueden ser palabras válidas en otros idiomas distintos del inglés. Por ejemplo <q>Is</q> es el nombre de un río ruso y <q>the</q> es la palabra francesa para referirse al <q>té</q>. Definitivamente parece que tenemos que cambiar la dichosa lista &#8230;</p>
<p>Hay listas de <em>stopwords</em> en la <a href="http://www.unine.ch/info/clef/">Universidad de Neuchatel</a> provenientes del estudio de gramáticas y lenguajes del departamento de computación. Hay listas para los idiomas Finlandés, Francés, Alemán, Italiano, Ruso, Español y Sueco.</p>
<p>Para decirle a MySQL que utilice nuestra lista hay que levantar la base de datos con el siguiente parámetro:</p>
<pre>
#&gt; mysqld --ft_stopword_file=stopword.txt</pre>
<p>Podemos comprobar la lista que esta utilizando con una consulta <code>SHOW</code>:</p>
<div class="dean_ch" style="white-space: wrap;">
<ol>
<li class="li1">
<div class="de1"><span class="kw1">SHOW</span> <span class="kw1">VARIABLES</span> <span class="kw1">LIKE</span> <span class="st0">&#8216;ft_stopword_file&#8217;</span>;</div>
</li>
</ol>
</div>
<pre>
+------------------+--------------+
| Variable_name    | Value        |
+------------------+--------------+
| ft_stopword_file | stopword.txt |
+------------------+--------------+

1 row in set (0.00 sec)</pre>
<h4>Algunos trucos</h4>
<p>Debido a que la formula final depende del parámetro <em>qf</em> (la frecuencia de un término en la consulta), se puede otorgar un peso extra a un término tan sólo con ponerlo varias veces dentro de la consulta. Si preguntamos a la base de datos por <q>special special</q> en lugar de por <q>special</q> veremos como la relevancia cambia.</p>
<p>Suele ayudar bastante añadir una columna con los términos que uno considere relevantes y asignarle un indice <code>FULLTEXT</code>. Es una idea similar a la folcsonomía (ordenación por etiquetas o tags) que tan de moda está en estos días. Los <em>keywords</em> seleccionados otorgarán un mayor peso a la fila.</p>
<p>En ocasiones las consultas devuelven resultados muy extraños, ejecuta <code>myisam_ftdump</code>. Quizá no te salvará el culo pero los resultados te pueden ayudar a mejorar las estrategias de búsqueda la próxima vez.</p>
<p>Actualmente InnoDB no tiene soporte para los indices <code>FULLEXT</code>: deberás elegir entre tablas con indice <code>FULLEXT</code> o tablas con integridad referencial. Al menos hasta la próxima versión 5.2, dónde está previsto que todos los motores de datos de MySQL respeten la integridad referencial.</p>
<p>Los lenguajes ideográficos como el Chino, el Japonés y el Indú carecen de delimitadores entre palabras, escriben todo seguido sin espacios. Esto representa un impedimento enorme en el estudio de todo lo que se ha comentado en el articulo, por esa razón no hay soporte <code>FULLEXT</code> en estos idiomas.</p>
<p>Existe un motor de datos para MySQL que incorpora muchas más funcionalidades en el campo de las búsquedas <code>FULLEXT</code>, entre ellas el soporte para lenguajes ideográficos, grafos de proximidad y <em>stemming</em>. Actualmente está en intensivo desarrollo y no hay paquetes para instalarlo fácilmente: <a href="http://www.sphinxsearch.com/">Sphinx</a>.</p>
<p>Espero que este artículo os ayude a mejorar la precisión de vuestras búsquedas y se traduzca en mejores aplicaciones para los usuarios.</p>
]]></content:encoded>
			<wfw:commentRss>http://quarkblog.org/2007/01/14/mysql-full-text-para-humanos/feed/</wfw:commentRss>
		</item>
		<item>
		<title>Multiple &#8216;GROUP BY&#8217; en SQL</title>
		<link>http://quarkblog.org/2006/11/30/multiple-group-by-en-sql/</link>
		<comments>http://quarkblog.org/2006/11/30/multiple-group-by-en-sql/#comments</comments>
		<pubDate>Wed, 29 Nov 2006 22:45:11 +0000</pubDate>
		<dc:creator>Moisés Maciá</dc:creator>
		
		<category><![CDATA[Bases de datos]]></category>

		<category><![CDATA[MySQL]]></category>

		<category><![CDATA[Programación]]></category>

		<guid isPermaLink="false">http://quarkblog.org/2006/11/30/multiple-group-by-en-sql/</guid>
		<description><![CDATA[Os planteo una dudilla típica en el mundo de las bases de datos: tenemos una tabla con todos los empleados de una empresa, cada empleado tiene un único perfil asociado y además cada uno de ellos puede tener (o no) una serie de atributos.
Lo que quiero es obtener una tabla de frecuencias en la que [...]]]></description>
			<content:encoded><![CDATA[<p>Os planteo una dudilla típica en el mundo de las bases de datos: tenemos una tabla con todos los empleados de una empresa, cada empleado tiene un único perfil asociado y además cada uno de ellos puede tener (o no) una serie de atributos.</p>
<p>Lo que quiero es obtener una tabla de frecuencias en la que diga cuantos empleados pertenecientes a un perfil y con una serie de atributos, empiezan con la letra A, con la B, con la C y así hasta la Z.</p>
<p>Es decir agrupar los empleados por perfil/atributo y después por letra alfabetica; algo parecido a una nube de tags o un histográma.</p>
<p>La instrucción <code>GROUP BY</code> de SQL sólo nos permite agrupar por un campo, entonces ¿Se puede sacar con una sola consulta sin utilizar programación ni tablas temporales? <strong>Si</strong>.</p>
<p>Veamos el esquema de datos sobre MySQL:</p>
<p>Tabla <code>employees</code>, almacena los datos relativos a los empleados:</p>
<div class="dean_ch" style="white-space: wrap;">
<ol>
<li class="li1">
<div class="de1">&nbsp;</div>
</li>
<li class="li1">
<div class="de1"><span class="kw1">CREATE</span> <span class="kw1">TABLE</span> employees <span class="br0">&#40;</span></div>
</li>
<li class="li1">
<div class="de1">&nbsp; id int<span class="br0">&#40;</span><span class="nu0">10</span><span class="br0">&#41;</span> <span class="kw1">UNSIGNED</span> <span class="kw1">NOT</span> <span class="kw1">NULL</span> <span class="kw1">AUTO_INCREMENT</span>,</div>
</li>
<li class="li1">
<div class="de1">&nbsp; name varchar<span class="br0">&#40;</span><span class="nu0">50</span><span class="br0">&#41;</span>,</div>
</li>
<li class="li2">
<div class="de2">&nbsp; profile_id int<span class="br0">&#40;</span><span class="nu0">10</span><span class="br0">&#41;</span> <span class="kw1">UNSIGNED</span> <span class="kw1">NOT</span> <span class="kw1">NULL</span>,</div>
</li>
<li class="li1">
<div class="de1">&nbsp; <span class="kw1">PRIMARY</span> <span class="kw1">KEY</span> <span class="br0">&#40;</span>id<span class="br0">&#41;</span></div>
</li>
<li class="li1">
<div class="de1"><span class="br0">&#41;</span> ENGINE=MyISAM;</div>
</li>
</ol>
</div>
<p>Tabla <code>profile</code>, almacena el perfil de los empleados (uno por empleado):</p>
<div class="dean_ch" style="white-space: wrap;">
<ol>
<li class="li1">
<div class="de1">&nbsp;</div>
</li>
<li class="li1">
<div class="de1"><span class="kw1">CREATE</span> <span class="kw1">TABLE</span> profiles <span class="br0">&#40;</span></div>
</li>
<li class="li1">
<div class="de1">&nbsp; id int<span class="br0">&#40;</span><span class="nu0">10</span><span class="br0">&#41;</span> <span class="kw1">UNSIGNED</span> <span class="kw1">NOT</span> <span class="kw1">NULL</span> <span class="kw1">AUTO_INCREMENT</span>,</div>
</li>
<li class="li1">
<div class="de1">&nbsp; name varchar<span class="br0">&#40;</span><span class="nu0">50</span><span class="br0">&#41;</span>,</div>
</li>
<li class="li2">
<div class="de2">&nbsp; <span class="kw1">PRIMARY</span> <span class="kw1">KEY</span> <span class="br0">&#40;</span>id<span class="br0">&#41;</span></div>
</li>
<li class="li1">
<div class="de1"><span class="br0">&#41;</span> ENGINE=MyISAM;</div>
</li>
</ol>
</div>
<p>Tabla <code>attributes</code>, almacena todos los atributos:</p>
<div class="dean_ch" style="white-space: wrap;">
<ol>
<li class="li1">
<div class="de1">&nbsp;</div>
</li>
<li class="li1">
<div class="de1"><span class="kw1">CREATE</span> <span class="kw1">TABLE</span> attributes <span class="br0">&#40;</span></div>
</li>
<li class="li1">
<div class="de1">&nbsp; id int<span class="br0">&#40;</span><span class="nu0">10</span><span class="br0">&#41;</span> <span class="kw1">UNSIGNED</span> <span class="kw1">NOT</span> <span class="kw1">NULL</span> <span class="kw1">AUTO_INCREMENT</span>,</div>
</li>
<li class="li1">
<div class="de1">&nbsp; name varchar<span class="br0">&#40;</span><span class="nu0">50</span><span class="br0">&#41;</span>,</div>
</li>
<li class="li2">
<div class="de2">&nbsp; <span class="kw1">PRIMARY</span> <span class="kw1">KEY</span> <span class="br0">&#40;</span>id<span class="br0">&#41;</span></div>
</li>
<li class="li1">
<div class="de1"><span class="br0">&#41;</span> ENGINE=MyISAM;</div>
</li>
</ol>
</div>
<p>Tabla <code>employee_attributes</code>, almacena las relaciones entre empleados y atributos:</p>
<div class="dean_ch" style="white-space: wrap;">
<ol>
<li class="li1">
<div class="de1">&nbsp;</div>
</li>
<li class="li1">
<div class="de1"><span class="kw1">CREATE</span> <span class="kw1">TABLE</span> employee_attributes <span class="br0">&#40;</span></div>
</li>
<li class="li1">
<div class="de1">&nbsp; id int<span class="br0">&#40;</span><span class="nu0">10</span><span class="br0">&#41;</span> <span class="kw1">UNSIGNED</span> <span class="kw1">NOT</span> <span class="kw1">NULL</span> <span class="kw1">AUTO_INCREMENT</span>,</div>
</li>
<li class="li1">
<div class="de1">&nbsp; employee_id int<span class="br0">&#40;</span><span class="nu0">10</span><span class="br0">&#41;</span> <span class="kw1">UNSIGNED</span> <span class="kw1">NOT</span> <span class="kw1">NULL</span>,</div>
</li>
<li class="li2">
<div class="de2">&nbsp; attribute_id int<span class="br0">&#40;</span><span class="nu0">10</span><span class="br0">&#41;</span> <span class="kw1">UNSIGNED</span> <span class="kw1">NOT</span> <span class="kw1">NULL</span>,</div>
</li>
<li class="li1">
<div class="de1">&nbsp; <span class="kw1">PRIMARY</span> <span class="kw1">KEY</span> <span class="br0">&#40;</span>id<span class="br0">&#41;</span></div>
</li>
<li class="li1">
<div class="de1"><span class="br0">&#41;</span> ENGINE=MyISAM;</div>
</li>
</ol>
</div>
<p>Insertamos algunos datos de prueba:</p>
<div class="dean_ch" style="white-space: wrap;">
<ol>
<li class="li1">
<div class="de1">&nbsp;</div>
</li>
<li class="li1">
<div class="de1"><span class="kw1">INSERT</span> <span class="kw1">INTO</span> profiles <span class="br0">&#40;</span>name<span class="br0">&#41;</span> <span class="kw1">VALUES</span><span class="br0">&#40;</span><span class="st0">&#8216;perfil de programador&#8217;</span><span class="br0">&#41;</span>;</div>
</li>
<li class="li1">
<div class="de1"><span class="kw1">INSERT</span> <span class="kw1">INTO</span> profiles <span class="br0">&#40;</span>name<span class="br0">&#41;</span> <span class="kw1">VALUES</span><span class="br0">&#40;</span><span class="st0">&#8216;perfil de manager&#8217;</span><span class="br0">&#41;</span>;</div>
</li>
<li class="li1">
<div class="de1"><span class="kw1">INSERT</span> <span class="kw1">INTO</span> profiles <span class="br0">&#40;</span>name<span class="br0">&#41;</span> <span class="kw1">VALUES</span><span class="br0">&#40;</span><span class="st0">&#8216;perfil de tester&#8217;</span><span class="br0">&#41;</span>;</div>
</li>
<li class="li2">
<div class="de2">&nbsp;</div>
</li>
<li class="li1">
<div class="de1"><span class="kw1">INSERT</span> <span class="kw1">INTO</span> attributes <span class="br0">&#40;</span>name<span class="br0">&#41;</span> <span class="kw1">VALUES</span><span class="br0">&#40;</span><span class="st0">&#8216;jornada completa&#8217;</span><span class="br0">&#41;</span>;</div>
</li>
<li class="li1">
<div class="de1"><span class="kw1">INSERT</span> <span class="kw1">INTO</span> attributes <span class="br0">&#40;</span>name<span class="br0">&#41;</span> <span class="kw1">VALUES</span><span class="br0">&#40;</span><span class="st0">&#8216;empleado del mes&#8217;</span><span class="br0">&#41;</span>;</div>
</li>
<li class="li1">
<div class="de1"><span class="kw1">INSERT</span> <span class="kw1">INTO</span> attributes <span class="br0">&#40;</span>name<span class="br0">&#41;</span> <span class="kw1">VALUES</span><span class="br0">&#40;</span><span class="st0">&#8216;encargado de sección&#8217;</span><span class="br0">&#41;</span>;</div>
</li>
<li class="li1">
<div class="de1"><span class="kw1">INSERT</span> <span class="kw1">INTO</span> attributes <span class="br0">&#40;</span>name<span class="br0">&#41;</span> <span class="kw1">VALUES</span><span class="br0">&#40;</span><span class="st0">&#8216;le toca guardia&#8217;</span><span class="br0">&#41;</span>;</div>
</li>
<li class="li2">
<div class="de2">&nbsp;</div>
</li>
<li class="li1">
<div class="de1"><span class="kw1">INSERT</span> <span class="kw1">INTO</span> employees <span class="br0">&#40;</span>name,profile_id<span class="br0">&#41;</span> <span class="kw1">VALUES</span><span class="br0">&#40;</span><span class="st0">&#8216;pepito&#8217;</span>, <span class="nu0">1</span><span class="br0">&#41;</span>;</div>
</li>
<li class="li1">
<div class="de1"><span class="kw1">INSERT</span> <span class="kw1">INTO</span> employees <span class="br0">&#40;</span>name,profile_id<span class="br0">&#41;</span> <span class="kw1">VALUES</span><span class="br0">&#40;</span><span class="st0">&#8216;juanito&#8217;</span>, <span class="nu0">1</span><span class="br0">&#41;</span>;</div>
</li>
<li class="li1">
<div class="de1"><span class="kw1">INSERT</span> <span class="kw1">INTO</span> employees <span class="br0">&#40;</span>name,profile_id<span class="br0">&#41;</span> <span class="kw1">VALUES</span><span class="br0">&#40;</span><span class="st0">&#8216;manolito&#8217;</span>, <span class="nu0">1</span><span class="br0">&#41;</span>;</div>
</li>
<li class="li1">
<div class="de1"><span class="kw1">INSERT</span> <span class="kw1">INTO</span> employees <span class="br0">&#40;</span>name,profile_id<span class="br0">&#41;</span> <span class="kw1">VALUES</span><span class="br0">&#40;</span><span class="st0">&#8216;menganito&#8217;</span>, <span class="nu0">1</span><span class="br0">&#41;</span>;</div>
</li>
<li class="li2">
<div class="de2">&nbsp;</div>
</li>
<li class="li1">
<div class="de1"><span class="kw1">INSERT</span> <span class="kw1">INTO</span> employee_attributes <span class="br0">&#40;</span>employee_id,attribute_id<span class="br0">&#41;</span> <span class="kw1">VALUES</span><span class="br0">&#40;</span><span class="nu0">1</span>,<span class="nu0">1</span><span class="br0">&#41;</span>;</div>
</li>
<li class="li1">
<div class="de1"><span class="kw1">INSERT</span> <span class="kw1">INTO</span> employee_attributes <span class="br0">&#40;</span>employee_id,attribute_id<span class="br0">&#41;</span> <span class="kw1">VALUES</span><span class="br0">&#40;</span><span class="nu0">1</span>,<span class="nu0">3</span><span class="br0">&#41;</span>;</div>
</li>
<li class="li1">
<div class="de1"><span class="kw1">INSERT</span> <span class="kw1">INTO</span> employee_attributes <span class="br0">&#40;</span>employee_id,attribute_id<span class="br0">&#41;</span> <span class="kw1">VALUES</span><span class="br0">&#40;</span><span class="nu0">2</span>,<span class="nu0">1</span><span class="br0">&#41;</span>;</div>
</li>
<li class="li1">
<div class="de1"><span class="kw1">INSERT</span> <span class="kw1">INTO</span> employee_attributes <span class="br0">&#40;</span>employee_id,attribute_id<span class="br0">&#41;</span> <span class="kw1">VALUES</span><span class="br0">&#40;</span><span class="nu0">2</span>,<span class="nu0">2</span><span class="br0">&#41;</span>;</div>
</li>
<li class="li2">
<div class="de2"><span class="kw1">INSERT</span> <span class="kw1">INTO</span> employee_attributes <span class="br0">&#40;</span>employee_id,attribute_id<span class="br0">&#41;</span> <span class="kw1">VALUES</span><span class="br0">&#40;</span><span class="nu0">2</span>,<span class="nu0">3</span><span class="br0">&#41;</span>;</div>
</li>
<li class="li1">
<div class="de1"><span class="kw1">INSERT</span> <span class="kw1">INTO</span> employee_attributes <span class="br0">&#40;</span>employee_id,attribute_id<span class="br0">&#41;</span> <span class="kw1">VALUES</span><span class="br0">&#40;</span><span class="nu0">3</span>,<span class="nu0">1</span><span class="br0">&#41;</span>;</div>
</li>
<li class="li1">
<div class="de1"><span class="kw1">INSERT</span> <span class="kw1">INTO</span> employee_attributes <span class="br0">&#40;</span>employee_id,attribute_id<span class="br0">&#41;</span> <span class="kw1">VALUES</span><span class="br0">&#40;</span><span class="nu0">3</span>,<span class="nu0">4</span><span class="br0">&#41;</span>;</div>
</li>
<li class="li1">
<div class="de1"><span class="kw1">INSERT</span> <span class="kw1">INTO</span> employee_attributes <span class="br0">&#40;</span>employee_id,attribute_id<span class="br0">&#41;</span> <span class="kw1">VALUES</span><span class="br0">&#40;</span><span class="nu0">3</span>,<span class="nu0">3</span><span class="br0">&#41;</span>;</div>
</li>
<li class="li1">
<div class="de1"><span class="kw1">INSERT</span> <span class="kw1">INTO</span> employee_attributes <span class="br0">&#40;</span>employee_id,attribute_id<span class="br0">&#41;</span> <span class="kw1">VALUES</span><span class="br0">&#40;</span><span class="nu0">3</span>,<span class="nu0">2</span><span class="br0">&#41;</span>;</div>
</li>
</ol>
</div>
<p>La solución <em>Quick&amp;Dirt</em> empleada masivamente en estos casos es hacer una consulta para filtrar los empleados que nos interesan mediante la siguiente consulta:</p>
<div class="dean_ch" style="white-space: wrap;">
<ol>
<li class="li1">
<div class="de1">&nbsp;</div>
</li>
<li class="li1">
<div class="de1"><span class="kw1">SELECT</span> Employee.id, Employee.name</div>
</li>
<li class="li1">
<div class="de1"><span class="kw1">FROM</span> employees <span class="kw1">AS</span> Employee, employee_attributes <span class="kw1">AS</span> EmployeeAttr</div>
</li>
<li class="li1">
<div class="de1"><span class="kw1">WHERE</span> Employee.id = EmployeeAttr.employee_id</div>
</li>
<li class="li2">
<div class="de2">&nbsp; &nbsp; <span class="kw1">AND</span> EmployeeAttr.attribute_id <span class="kw1">IN</span> <span class="br0">&#40;</span><span class="nu0">1</span>,<span class="nu0">2</span>,<span class="nu0">3</span><span class="br0">&#41;</span></div>
</li>
<li class="li1">
<div class="de1">&nbsp; &nbsp; <span class="kw1">AND</span> Employee.profile_id = <span class="nu0">1</span></div>
</li>
<li class="li1">
<div class="de1"><span class="kw1">GROUP</span> <span class="kw1">BY</span> Employee.id</div>
</li>
</ol>
</div>
<p>Resultado:</p>
<pre>
  id    name
----------------
  1    pepito
  2    juanito
  3    manolito
  4    menganito</pre>
<p>&#8230; para después mediante programación, agruparlos y realizar el conteo.</p>
<p>¿Es óptimo? <strong>NO</strong>, hay que hacer un tratamiento a posteriori de los datos.</p>
<p>¿Es sencillo? <strong>NO</strong>, quizá sea más complicado y engorroso la programación del tratamiento posterior que hacerlo corréctamente.</p>
<p>¿Es elegante? <strong>NO</strong>, se mezcla la capa de negocio con la capa de datos.</p>
<p><strong>¿Entonces cómo lo hago?</strong></p>
<p>La mejor forma de hacer este tipo de consulta y mantener tu <em>karma</em> como programador es utilizar una <strong>Subquery</strong>. Una subquery es una manera especial de anidar sentencias SQL para componer consultas mas complejas que de otra forma no se podrían realizar.</p>
<p>Las subqueries se agrupan fundamentalmente en dos grandes grupos: <strong>escalares y correlacionadas</strong>, aunque la que voy a utilizar yo es un poco diferente puesto que el resultado de la subquery suplantará a una tabla; jugando con este efecto conseguimos el comportamiento de un <strong><code>GROUP BY</code> multiple</strong>.</p>
<ul>
<li>Más información en el apartado <a href="http://dev.mysql.com/doc/refman/5.0/en/subqueries.html">Subquery Syntax</a> de la documentación de MySQL.</li>
<li>Más infromación sobre Subqueries correlacionadas y escalares (con muchos ejemplos) en <a href="http://www.mysql-hispano.org/page.php?id=31&amp;pag=12">MySQL Hispano</a>.</li>
</ul>
<p>Veamos la consulta:</p>
<div class="dean_ch" style="white-space: wrap;">
<ol>
<li class="li1">
<div class="de1">&nbsp;</div>
</li>
<li class="li1">
<div class="de1"><span class="kw1">SELECT</span> <span class="kw1">LEFT</span><span class="br0">&#40;</span>Employee.name,<span class="nu0">1</span><span class="br0">&#41;</span> <span class="kw1">AS</span> alphacrum, COUNT<span class="br0">&#40;</span><span class="kw1">LEFT</span><span class="br0">&#40;</span>Employee.name,<span class="nu0">1</span><span class="br0">&#41;</span><span class="br0">&#41;</span> <span class="kw1">AS</span> total</div>
</li>
<li class="li1">
<div class="de1"><span class="kw1">FROM</span> <span class="br0">&#40;</span></div>
</li>
<li class="li1">
<div class="de1">&nbsp; <span class="kw1">SELECT</span> Employee.id, Employee.name</div>
</li>
<li class="li2">
<div class="de2">&nbsp; &nbsp; <span class="kw1">FROM</span> employees <span class="kw1">AS</span> Employee, employee_attributes <span class="kw1">AS</span> EmployeeAttr</div>
</li>
<li class="li1">
<div class="de1">&nbsp; &nbsp; <span class="kw1">WHERE</span> Employee.id = EmployeeAttr.employee_id</div>
</li>
<li class="li1">
<div class="de1">&nbsp; &nbsp; &nbsp; <span class="kw1">AND</span> EmployeeAttr.attribute_id <span class="kw1">IN</span> <span class="br0">&#40;</span><span class="nu0">1</span>,<span class="nu0">2</span>,<span class="nu0">3</span><span class="br0">&#41;</span></div>
</li>
<li class="li1">
<div class="de1">&nbsp; &nbsp; &nbsp; <span class="kw1">AND</span> Employee.profile_id = <span class="nu0">1</span></div>
</li>
<li class="li1">
<div class="de1">&nbsp; &nbsp; <span class="kw1">GROUP</span> <span class="kw1">BY</span> Employee.id</div>
</li>
<li class="li2">
<div class="de2">&nbsp; <span class="br0">&#41;</span> <span class="kw1">AS</span> Employee</div>
</li>
<li class="li1">
<div class="de1"><span class="kw1">GROUP</span> <span class="kw1">BY</span> alphacrum</div>
</li>
<li class="li1">
<div class="de1"><span class="kw1">ORDER</span> <span class="kw1">BY</span> alphacrum <span class="kw1">ASC</span>;</div>
</li>
</ol>
</div>
<p>Resultado:</p>
<pre>
  alphacrum    total
----------------------------
  j                    1
  m                  2
  p                   1</pre>
<p>Como podeis observar, la tabla <code>employees</code> de la consulta principal no es la original; es el resultado del filtrado previo según los parametros que nos interesen.</p>
<p>Una vez filtrados es muy sencillo reordenar los datos a nuestro gusto, únicamente agrupamos por letra y realizamos el conteo de los resultados con <code>COUNT</code>, devolviendonos los datos tal y como nos hacen falta.</p>
<p>De esta manera se utiliza efectivamente el cache del servidor de bases de datos y la programación de la aplicación no se satura con un montón funciones de tratamiento de datos. Simple y elegante.</p>
]]></content:encoded>
			<wfw:commentRss>http://quarkblog.org/2006/11/30/multiple-group-by-en-sql/feed/</wfw:commentRss>
		</item>
		<item>
		<title>Cosas a tener en cuenta cuando diseñamos un modelo de datos</title>
		<link>http://quarkblog.org/2006/11/22/cosas-a-tener-en-cuenta-cuando-disenamos-un-modelo-de-datos/</link>
		<comments>http://quarkblog.org/2006/11/22/cosas-a-tener-en-cuenta-cuando-disenamos-un-modelo-de-datos/#comments</comments>
		<pubDate>Tue, 21 Nov 2006 22:11:27 +0000</pubDate>
		<dc:creator>Moisés Maciá</dc:creator>
		
		<category><![CDATA[Bases de datos]]></category>

		<category><![CDATA[MySQL]]></category>

		<category><![CDATA[Programación]]></category>

		<guid isPermaLink="false">http://quarkblog.org/2006/11/22/cosas-a-tener-en-cuenta-cuando-disenamos-un-modelo-de-datos/</guid>
		<description><![CDATA[&#8230; y que la mayoría de los &#8220;programadores&#8221; ni siquiera saben que existen:

Crea indices para las columnas, pero hazlo con cabeza. Muchos indices pueden hacer que las inserciones, borrados y actualizaciones sean operaciones muy lentas.
Utiliza EXPLAIN para optimizar las consultas y observar como afectan los indices al rendimiento.
Minimiza los datos que devuelven las consultas. No [...]]]></description>
			<content:encoded><![CDATA[<p>&#8230; y que la mayoría de los &#8220;programadores&#8221; ni siquiera saben que existen:</p>
<ol>
<li>Crea indices para las columnas, pero hazlo con cabeza. Muchos indices pueden hacer que las inserciones, borrados y actualizaciones sean operaciones <strong>muy</strong> lentas.</li>
<li>Utiliza <strong>EXPLAIN</strong> para optimizar las consultas y observar como afectan los indices al rendimiento.</li>
<li>Minimiza los datos que devuelven las consultas. No utilices <strong>SELECT *</strong>, trocea los datos en páginas con <strong>LIMIT</strong> e intenta agrupar varias consultas en una sola con <strong>UNION</strong> o <strong>JOIN</strong>.</li>
<li>El mayor cuello de botella de las bases de datos suele ser el disco duro. Si necesitas rendimiento considera montar un <strong>RAID</strong> para datos y otro para los logs, así como dedicar una máquina únicamente para la BD. Manejar tablas temporales en memoria también mejora notablemente el rendimiento.</li>
<li><strong>LOAD DATA</strong> funciona <strong>mucho</strong> más rápido que <strong>INSERT</strong>: por cada inserción se actualiza el árbol de indices, así que si no te queda mas remedio que hacer muchos <strong>INSERT</strong> seguidos, desactiva los indices y actualizalos al terminar.</li>
<li>No selecciones columnas de tipo <strong>BLOB</strong> o <strong>TEXT</strong> directamente, hazlo siempre a través de una tabla relacionada. Cuando se borran filas con estos tipos de datos, las tablas tienden a fragmentarse. Analiza y reconstruye los indices al eliminar filas de este tipo.</li>
<li>Normaliza tu modelo, por lo menos hasta la tercera forma normal. En ocasiones se degradan las formas normales de alguna relación para ganar rendimiento, pero eso sólo es cierto en determinado tipo de situaciones muy específicas. Normaliza siempre.</li>
<li>Considera utilizar el tipo de datos <strong>ENUM</strong> en lugar de una tabla relacionada.</li>
<li>La integridad referencial y abusar de los triggers baja el rendimiento de las consultas. Activalos cuando los necesites pero no abuses.</li>
<li>Si puedes hacerlo con un procedimiento almacenado, hazlo.</li>
<li>Prepara benchmarks y mide el rendimiento de tus consultas sobre datos reales.</li>
</ol>
<p>Si todo esto te queda grande, contrata a un DBA.</p>
]]></content:encoded>
			<wfw:commentRss>http://quarkblog.org/2006/11/22/cosas-a-tener-en-cuenta-cuando-disenamos-un-modelo-de-datos/feed/</wfw:commentRss>
		</item>
	</channel>
</rss>

<!-- Dynamic Page Served (once) in 2.760 seconds -->
<!-- Cached page served by WP-Cache -->
