sql: joins vs multiple selects

Pese a que el saber popular dice que hacer un select multiple*(1) versus un select con joins*(2) es mas lento.

*(1) SELECT link_id,sum(vote_value) FROM links,votes WHERE link_id=vote_link_id AND vote_type=’links’ AND vote_value>0 AND link_date > DATE_SUB(NOW(), INTERVAL 1 year) GROUP BY link_id
*(2) SELECT link_id,sum(vote_value) FROM links INNER JOIN votes ON (link_id=vote_link_id AND vote_type=’links’ AND vote_value>0) WHERE link_date > DATE_SUB(NOW(), INTERVAL 1 year) GROUP BY link_id

Para sacarme la duda cree una script que crea usuarios, enlaces, comentarios y votos (positivos y negativos) en meneame.

Luego tome las diferentes queries que usaban multiples tablas para el calculo del karma (el archivo /scripts/karma9.php) y las compare con las joins, en todas las consultas agregue el prefijo SQL_NO_CACHE

El resultado final contra todo lo que esperaba fue no concluyente…
Seletcs multiples total took: 587
Selects joins total took: 600

Como no me gusta rendirme facilmente elimine todos los indices y corri de nuevo los test. Obteniendo el siguiente resultado;
Seletcs multiples total took: 118023
Selects joins total took: 117221

A tener en cuenta, las consultas siempre fueron hechas entre dos tablas, si fueran de mas tablas segun la documentacion de sql: nested joins

Conclusion
Como conclusion podemos afirmar que los indices vamos a lograr consultas muy rapidas, pero esto se paga, los inserts, deletes y updates de una campo indice van a ser mas lentos, debido a que los indices deben ser regenerados. Tambien se paga con espacio ya que los indices ocupan memoria.

Analisis de codigo PHP

Hoy navegando me tope con algunas herramientas interesantes para analizar codigo.

Codigo duplicado

La primer herramienta con la que me tope es phpcpd, basicamente busca codigo duplicado, por ejemplo:

test.php

<?
class Test{
  function suma1($x,$y){ return $x+$y; }
  function suma2($a,$b){ return $a + $b; }
  function suma3($a,$b){ return $b+$a; }
}
function suma1($x,$y){ return $x+$y; }
function suma2($a,$b){ return $a + $b; }
function suma3($a,$b){ return $b+$a; }
eugenio@eugenio-desktop:/var/www$ phpcpd test.php
phpcpd 1.3.1 by Sebastian Bergmann.
0.00% duplicated lines out of 9 total lines of code.

WTF??? al parecer no es muy inteligente, para que funcione el codigo debe ser un copy & paste exacto de el codigo.

eugenio@eugenio-desktop:/var/www$ phpcpd ./wordpress/
phpcpd 1.3.1 by Sebastian Bergmann.
Found 27 exact clones with 472 duplicated lines in 7 files:
 - wp-includes/class-simplepie.php:2512-2520
 wp-includes/class-simplepie.php:3414-3422
...
 - wp-content/plugins/akismet/akismet.php:837-851
 wp-content/plugins/akismet/akismet.php:904-918
0.30% duplicated lines out of 157906 total lines of code.

A partir de esta herramienta encontre otras un poco mas complejas y inteligentes;

phpcs, pdepend y phpmd

phpcs

PHP_CodeSniffer es una script PHP5 que “sniffea” archivos PHP, JavaScript y CSS para detectar violaciones de un standard de codigo definido.

Esta herramienta nos indicara cuando no se siga el standard definido, dentro de la documentacion de phpcs en pear nos indican como hacer para definir un standard propio para cambiar el que viene por defecto.

Muestra de uso;

eugenio@eugenio-desktop:/var/www$ phpcs  ./test.php
FILE: /var/www/test.php
--------------------------------------------------------------------------------
FOUND 25 ERROR(S) AND 0 WARNING(S) AFFECTING 8 LINE(S)
--------------------------------------------------------------------------------
 1 | ERROR | Short PHP opening tag used. Found "<?" Expected "<?php".
 1 | ERROR | Missing file doc comment
 2 | ERROR | Missing class doc comment
 2 | ERROR | Opening brace of a class must be on the line after the definition
 3 | ERROR | Line indented incorrectly; expected 4 spaces, found 2
 3 | ERROR | Missing function doc comment
 3 | ERROR | Opening brace should be on a new line
 3 | ERROR | Closing brace must be on a line by itself
 4 | ERROR | Line indented incorrectly; expected 4 spaces, found 2
 4 | ERROR | Missing function doc comment
 4 | ERROR | Opening brace should be on a new line
 4 | ERROR | Closing brace must be on a line by itself
 5 | ERROR | Line indented incorrectly; expected 4 spaces, found 2
 5 | ERROR | Missing function doc comment
 5 | ERROR | Opening brace should be on a new line
 5 | ERROR | Closing brace must be on a line by itself
 7 | ERROR | Missing function doc comment
 7 | ERROR | Opening brace should be on a new line
 7 | ERROR | Closing brace must be on a line by itself
 8 | ERROR | Missing function doc comment
 8 | ERROR | Opening brace should be on a new line
 8 | ERROR | Closing brace must be on a line by itself
 9 | ERROR | Missing function doc comment
 9 | ERROR | Opening brace should be on a new line
 9 | ERROR | Closing brace must be on a line by itself
-----------------------------------------------------------------

Como veran se queja demasiado… es muy conveniente crear un standard propio.

pdepend

Pdepend realizara un informe sobre la metrica del software, analizara la complejidad de las funciones.

Metrica de software que analizara;
Cyclomatic Complexity
NPath Complexity
CodeRank
Lines Of Code

Y finalmente el que creo mas interesante phpmd

What PHPMD does is: It takes a given PHP source code base and look for several potential problems within that source. These problems can be things like:

  • Possible bugs
  • Suboptimal code
  • Overcomplicated expressions
  • Unused parameters, methods, properties
eugenio@eugenio-desktop:/var/www$ phpmd test.php text codesize,unusedcode,naming,naming
/var/www/test.php:12    Avoid variables with short names like $x
/var/www/test.php:12    Avoid variables with short names like $y
/var/www/test.php:12    Avoid variables with short names like $x
...
/var/www/test.php:31    Avoid variables with short names like $a
/var/www/test.php:31    Avoid variables with short names like $b
/var/www/test.php:31    Avoid variables with short names like $a
/var/www/test.php:31    Avoid variables with short names like $b

Analizemos el codigo de worpress…

eugenio@eugenio-desktop:/var/www$ time phpmd ./wordpress text codesize,unusedcode,naming,design>wordpress.doc
real	4m1.382s
user	3m56.543s
sys	0m2.624s

Descargar el informe de analisis al wordpress 3.0.1

eugenio@eugenio-desktop:/var/www$ time phpmd ./meneame4/ text codesize,unusedcode,naming,design>meneame.doc
real    0m42.966s
user    0m39.526s
sys    0m1.064s

Descargar el informe de analisis al meneame v4

Conclusion

De todas las herramientas que probe definitivamente me quedo con phpmd, esta herramienta nos alertara de cualquier codesmell de manera efectiva

cakePHP Bakery: publicaron mi articulo!!!

Hace alrededor de un mes estaba creando una herramienta que usaba consultas muy pesadas y realizaba complejas busquedas. Como con SQL era imposible conseguir ese tipo de consultas de forma eficiente utilice Sphinx

Sphinx viene con un API, de cuasualidad en la “panaderia” de cakePHP me encontre con un comportamiento para los modelos que hacia busquedas utilizando la api de sphinx.

Como todo esto no me bastaba para el proyecto, por cierto pueden verlo aqui; www. gastromap.com.ar es un mapa gastronomico

Yo necesitaba un componente ademas de hacer uso del comportamiento del modelo, por ello cree un componente y le agregue algunas cositas que le faltaban como poder hacer uso de la busqueda geografica de sphinx.

Como me parecio que podia ser util cree un articulo y lo publique en la panaderia, en los ultimos dias habia visto un incremento en el spam, por lo que crei que jamas lo publicarian.

Pero un mes despues… http://bakery.cakephp.org/articles/view/sphinx-component-and-behavior

Crea un acortador de url con cakephp en 10 minutos

Despues de leer este articulo decidi escribir mi propio tutorial, mientras pensaba en como escribir note que en meneame utilizan un acortador de urls con una implementacion similar a la que voy a mostrar.

La idea en si es simple, guardar un link en la base de datos y darle al usuario un string que identifica al link en la base de datos, la funcion para convertir el id (que es un numero) de la link a alfanumerico se llama base_convert.

Manos a la obra

CREATE DATABASE `shortener` ;
CREATE TABLE `shortener`.`links` ( `id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY ,
`url` VARCHAR( 1024 ) NOT NULL,
`checksum` VARCHAR( 32 ) NOT NULL UNIQUE
) ENGINE = MYISAM ;

Luego de crear la base de datos “horneamos” el nuevo proyecto;

eugenio@eugenio-desktop:/var/www$ cake bake /var/www/shortener

Si hiciste todo bien el proyecto ahora deberia estar creado en /var/www/shortener , puedes usar la ruta que quieras.

Comencemos creando los modelos;
#models/link.php

&lt;?php
class Link extends AppModel{
var $validate = array(
'url' =&gt; array(
'rule' =&gt; 'url',
'message' =&gt; 'La direccion que has enviado no es valida.'
)
);

function getEncodedId($id=null){
$id=($id) ? $id: $this-&gt;id;
return base_convert($id,10,36);
}

function getUrlFromEncodedId($encoded_id){
$id=(int)base_convert($encoded_id,36,10);

return ($id&gt;0)? $this-&gt;field('url',array('id'=&gt;$id)) : null;
}

function searchOrInsert($url){
$url=$this-&gt;getCorrectUrl($url);
$checksum=md5($url);

if($id=$this-&gt;field('id',array('checksum'=&gt;$checksum))){
return array('id'=&gt;$id,'url'=&gt;$url);
}

$this-&gt;create();
$this-&gt;set(array('url'=&gt;$url,'checksum'=&gt;$checksum));
if($this-&gt;save()){
return array('id'=&gt;$this-&gt;id,'url'=&gt;$url);
}
return false;
}

function getCorrectUrl($url){
if(!ereg('/^https*:\/\//', $url)) $url='http://'.$url;
$pos=strpos($url,'//')+2;
if(!$pos=strpos($url,'/',$pos)) $pos=strlen($url);
$url=strtolower(substr($url,0,$pos)).substr($url,$pos);
return $url;
}
}

Luego creemos el controllador

#controllers/links_controller.php
<?php
class LinksController extends AppController{

function view($encoded_id=null){
if($encoded_id && ($url=$this->Link->getUrlFromEncodedId($encoded_id))){
$this->redirect($url);
}

$this->Session->setFlash(_(‘La url acortada que ingresaste no existe’));
$this->redirect(array(‘action’=>’add’));
}

function add($url=null){
$url=(isset($this->params[‘data’][‘Link’][‘url’])) ? $this->params[‘data’][‘Link’][‘url’] : $url;

if($url){
if($data=$this->Link->searchOrInsert($url)){
$this->set(‘encoded_id’,$this->Link->getEncodedId($data[‘id’]));
$this->set(‘url’,$data[‘url’]);
$this->render(‘urlinfo’);
}
$this->Session->setFlash(_(‘La url que ingresaste no es valida’));

}
}
}

Luego las vistas;

#views/links/add.ctp
<?php
e(
$this->Form->create('Link',array('action'=>'add')).
$this->Form->input('Link.url').
$this->Form->submit(_('Acortar')).
$this->Form->end()
);
?>

#views/links/urlinfo.ctp
<?php
$shorted_url=Router::url(array('controller'=>'links','action'=>'view',$encoded_id),true);

$compress_ratio=floor((strlen($shorted_url)/strlen($url))*100);

?>
<h3>Tu url acortada es:</h3><input type=”text” readonly=”readonly” onclick=”select_text();” onkeyup=”select_text();” onselect=”select_text();” value=”<?=$shorted_url ?>”><br />
Url original: <?=$url ?>
<hr />
<b><?=($compress_ratio<100) ? ‘La url se ha comprimido un ‘.$compress_ratio.’%’ : ‘La url ya era demasiado corta y no pudimos acortarla mas' ?></b>

Por ultimo configuremos las rutas;

#config/routes.php
<?php
Router::connect('/', array('controller' => 'links', 'action' => 'add'));

/**
* …and connect the rest of ‘Pages’ controller’s urls.
*/
Router::connect(‘/pages/*’, array(‘controller’ => ‘pages’, ‘action’ => ‘display’));

Solo quedaria mejorar la configuracion para que conecte /links/view/LINK_ID con algo mas corto como /short/LINK_ID .

Alguien se anima?