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.

Anuncios

Lograr que cakephp soporte subdominios

Hola a todos, por si algunos no lo sabian cakePHP no es muy bueno a la hora de manejar urls con subdominio, en este post voy a escribir sobre como hacer posible manejar subdominios haciendo la menor cantidad de modificaciones posibles.

Basicamente la idea es que pase el subdominio por el named parameter subdomain. Todo esto sin tocar el .htaccess

Para esta solucion necesitamos poner unas pocas lineas en el config/bootstrap.php , otras en el config/routes.php (ya que hay que agregar el domain a las rutas), luego agregamos una constante en webroot/index.php y finalmente usar un nuevo htmlhelper para hacer links correctamente que por supuesto extiende de htmlhelper

Primero:

En el archivo config/bootstrap.php agrega al final:
(Recuerda cambiar test.domain.com por el nombre de tu dominio ejemplo: miweb.com tambien puedes usar un subdominio, es importante que siempre lo escribas en minusculas)

// sub domain in lower
define('MYDOMAIN','test.larompe.com.ar');

if(!isset($_GET['url']))$_GET['url']='';

$token=parse_url($_SERVER['SERVER_NAME']);
if((($pos=strpos($token['path'],MYDOMAIN))>0)){
$subdomain=strtolower(substr($token['path'],0,$pos-1));

}else{
$subdomain='www'

}
$_GET['url']=$subdomain.'/'.$_GET['url'];

Segundo

Vas a tener que modificar las rutas de tu aplicacion, estas se encuentran en config/routes.php

Ejemplo;

La ruta;
Router::connect(‘/’, array(‘controller’ => ‘pages’, ‘action’ => ‘display’, ‘home’));

Pasa a llamarse;
Router::connect(‘/:subdomain/’, array(‘controller’ => ‘pages’, ‘action’ => ‘display’, ‘home’),array(‘subdomain’));

Finalmente tenemos que atrapar los /controller /controller/action;
Router::connect(‘/:subdomain/:controller/:action/*’, array(‘subdomain’));
Router::connect(‘/:subdomain/:controller/*’, array(‘subdomain’));

Recuerda que por defecto si el subdominio no esta seteado pone www, osea que las rutas para la aplicacion principal es;
Router::connect(‘/www/users/*’, array(‘controller’=>’users’,’action’=>’index’));

Tercero

Agregar la siguiente linea al principio de webroot/index.php

define(‘WEBROOT_DIR’, ‘/’);

Cuarto

Crea el helper HtmlSubdomain, para ello crea el archivo views/helpers/html_subdomain.php


<?php
class HtmlSubdomainHelper extends HtmlHelper{
function link($title, $url = null, $options = array(), $confirmMessage = false) {
$escapeTitle = true;
if ($url !== null) {
$url = $this->url($url);
}
return parent::link($title,$url,$options,$confirmMessage);
}
function url($url=null,$full=false){
if(is_array($url)){
if(!isset($url['subdomain'])) $url['subdomain'] = 'www';
$subdomain=$url['subdomain'];
$url=parent::url($url);
$pos=strpos($url,'/'.$subdomain.'/')+strlen('/'.$subdomain);
$url=substr($url,$pos);
return 'http://'.$subdomain.'.'.MYDOMAIN.$url;
}
return parent::url($url,$full);
}
}

Por ultimo

Debes recordar agregar el Helper HtmlSubdomain para crear los links en tu vista, ejemplo;

echo $this->HtmlSubdomain->link(‘hola’,array(‘controller’=>’pages’,’view’=>’display’,’home’,’subdomain’=>’subdom1′);

Que tan ineficiente puede ser incrementar un valor de un modelo

Estoy trabajando para refactorizar el codigo de meneame usando el framework cakephp.

Hoy me encontraba haciendo la logica para votar una noticia y me encontre con el problema de tener que hacer una query simple;
UPDATE `links` AS `Link` SET `Link`.`link_anonymous` = link_anonymous+1 WHERE `link_id` = 1

Esto se logra haciendo desde el modelo;
$this->updateAll(array(‘link_anonymous’=>’link_anonymous+1’),array(‘link_id’=>1));

Pero al hacer en vez de hacer el update simple que escribi mas arriba hace un update con todos los joins de los modelos;
UPDATE `links` AS `Link` LEFT JOIN `users` AS `User` ON (`Link`.`link_author` = `User`.`user_id`) LEFT JOIN `blogs` AS `Blog` ON (`Link`.`link_blog` = `Blog`.`blog_id`) LEFT JOIN `categories` AS `Category` ON (`Link`.`link_category` = `Category`.`category_id`) SET `Link`.`link_anonymous` = link_anonymous+1 WHERE `link_id` = 1 AND `limit` = ‘1’

Este update es muy costoso, intente usando las funciones del modelo saveField y save, pero no entienden el mensaje ‘link_anonymous+1’, lo transforman a 1 y yo quiero que hagan link_anonymous+1

Para solucionar este problemita agregue la funcion update, que elimina las asociaciones entre el modelo que queremos actualizar y luegos las agrega nuevamente, de esta forma el UPDATE vuelve a ser simple.

La funcion fue tomada de http://blog.pepa.info/php-html-css/cakephp/getting-rid-of-joins-in-updateall-query/

agregar a #app_model.php
/**
* A workaround for CakePHP lack of support for recursive
*/
function updateAll($fields, $conditions = true, $recursive = null) {
if (!isset($recursive)) {
$recursive = $this->recursive;
}

if ($recursive == -1) {
$this->unbindModel(array(
‘belongsTo’ => array_keys($this->belongsTo),
‘hasOne’ => array_keys($this->hasOne)
), true);
}

return parent::updateAll($fields, $conditions);
}

Luego hago;
$this->updateAll(array(‘link_anonymous’=>’link_anonymous+1’),array(‘link_id’=>1),-1);

y… voila!
UPDATE `links` AS `Link` SET `Link`.`link_anonymous` = link_anonymous+1 WHERE `link_id` = 1

EDITO: hoy agregue un ticket al equipo de desarrollo de cakephp… a ver que contestan

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?

Proyecto Euler: problema 1

Hola, siguiendo el notable ejemplo de Aureliano, me voy a poner a resolver problemas del proyecto euler con la restriccion de publicar las soluciones solo en php.

A continuacion el problema nro 1;
Encontrar la suma de todos los numeros naturales multiplos de 3 o 5 y menores de 1000.
Ejemplo; si sumaramos todos los multiplos de 3 o 5 menores de 10 nos daria 23. 3+5+6+9

<?
$sum=0;
for($i=1;$i<1000;$i++){
	if((!($i%3))||(!($i%5))) {
		$sum+=$i;
	}
}
echo $sum."\n";
?>

Finalmente, la respuesta es; 233168

Saludos