Otro blog más de diseño
En ocasiones nos es necesario crear una lista (<ul>) de un determinado número de registros de una base de datos. Y para rizar el rizo, esos datos están entrelazados, es decir, que unos registros dependen de otros.
Lo más fácil es crear un recordset, y dentro del bucle crear otro con los parámetros necesarios, pero ay!! que suele suceder que no nos resulta posible el saber cuantos registros habrán ni cuantos niveles de profundidad tendremos.
Vamos a partir de una base de datos llamada por ejemplo “registros”. “Registros” tendrá podrÃa tener 3 campos (y añadir más no serÃa complicado).
CREATE TABLE `registros` ( `id` int(11) NOT NULL auto_increment, `sub_id` int(11) NOT NULL default '0', `registro` varchar(80) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=21 ;
Como podemos comprobar tenemos un campo “id” autonumérico, un campo “sub_id” por el cuál relacionaremos un registro con otro, y un último campo llamado “registro” ( en original no me gana nadie) y como decÃa antes, para muchos casos será necesario el añadir más campos, lo cuál se puede hacer con total normalidad.
La idea es relacionar una serie de ciudades, con sus provincias, y a estas con sus comunidades y paÃses.
Y como buen mago, guardo en el bolsillo todos esos registros.
INSERT INTO `registros` VALUES (1, 0, 'España');
INSERT INTO `registros` VALUES (2, 0, 'Portugal');
INSERT INTO `registros` VALUES (3, 1, 'Galicia');
INSERT INTO `registros` VALUES (4, 1, 'Asturias');
INSERT INTO `registros` VALUES (5, 2, 'Oporto');
INSERT INTO `registros` VALUES (6, 2, 'Lisboa');
INSERT INTO `registros` VALUES (7, 2, 'Chaves');
INSERT INTO `registros` VALUES (8, 3, 'Pontevedra');
INSERT INTO `registros` VALUES (9, 3, 'La Coruña');
INSERT INTO `registros` VALUES (10, 3, 'Ourense');
INSERT INTO `registros` VALUES (11, 3, 'Lugo');
INSERT INTO `registros` VALUES (12, 4, 'Oviedo');
INSERT INTO `registros` VALUES (13, 8, 'Pontevedra');
INSERT INTO `registros` VALUES (14, 8, 'Vigo');
INSERT INTO `registros` VALUES (15, 8, 'Cangas');
INSERT INTO `registros` VALUES (16, 9, 'La Coruña');
INSERT INTO `registros` VALUES (17, 9, 'Santiago
de Compostela');
INSERT INTO `registros` VALUES (18, 9, 'Ferrol');
INSERT INTO `registros` VALUES (19, 12, 'Oviedo');
INSERT INTO `registros` VALUES (20, 12, 'Gijón');
Bueno, tenemos nuestra base de datos, la tabla y también los datos. Ahora solo nos queda plasmar la idea. Y como todo, es muy sencilla.Crearemos una función con un recordset cuya consulta vaya filtrada por su “sub_id” que es el que relaciona a un registro con otro y dentro de los resultados que nos devuelva la consulta, llamamos a la función que estamos creando, pasándole como parámetro el “id” de ese registro.
Y como es más fácil mostrarlo que contarlo, nos ponemos a ello.
<?php
// Creamos la conexion
$hostname_conn = "localhost";
$database_conn = "pruebas";
$username_conn = "root";
$password_conn = "";
$conn = mysql_pconnect($hostname_conn,
$username_conn, $password_conn) or
trigger_error(mysql_error(),E_USER_ERROR);
function arbol($id=0)
{
// Convertimos a globales el nombre de la bd y la conexion
global $database_conn, $conn;
// Creamos el recordset
mysql_select_db($database_conn, $conn);
$query_rsRegistro = 'SELECT * from registros
WHERE sub_id = '.$id;
$rsRegistro = mysql_query($query_rsRegistro, $conn)
or die(mysql_error());
$row_rsRegistro = mysql_fetch_assoc($rsRegistro);
$totalRows_rsRegistro = mysql_num_rows($rsRegistro);
// Comprobamos que no esta vacia
if($totalRows_rsRegistro > 0)
{
// Inicializamos la lista
echo '<ul>';
// Mostramos todos los resultados
do
{
echo '<li>'.$row_rsRegistro['registro'];
// Ejecutamos la funcion dentro de si misma
// Y le pasamos el id del registro actual
arbol($row_rsRegistro['id']);
echo '</li>';
}
while($row_rsRegistro = mysql_fetch_assoc($rsRegistro));
echo '</ul>';
}
}
?>
Ahora solo nos queda realizar la llamada en el lugar que consideremos oportuno, haciéndolo de la siguiente manera
<?php arbol();?>
Fácil, no ? Esto nos devuelve algo similar a esto.
* España
* Galicia
* Pontevedra
* Pontevedra
* Vigo
* Cangas
* La Coruña
* La Coruña
* Santiago de Compostela
* Ferrol
* Ourense
* Lugo
* Asturias
* Oviedo
* Oviedo
* Gijón
* Portugal
* Oporto
* Lisboa
* Chaves
Qué puedo decir de mi que no se haya dicho ya antes.
Skeku
Agosto 1st, 2008 el 10:52 am
Vale vale, pero esto en Photoshop cómo se hace? xD
cmacias
Agosto 1st, 2008 el 11:57 am
Es muy fácil, copias el texto y lo pegas en una capa en tu proyecto y el modo de fusión como “Sobreexposición lineal” y relleno al “30%”. No te aseguro que funcione al 100%, pero pasarás el tiempo
Skeku
Agosto 1st, 2008 el 1:25 pm
Touché!
Por cierto, aprovecho para comentarte algo que me pasa cuando quiero dejarte algún comentario.
Si hago clic sobre el textarea, automáticamente se abre una etiqueta strong. Si vuelvo a hacer clic, se cierra. Y asà sucesivas veces.
Curioso, sin duda
cmacias
Agosto 1st, 2008 el 2:26 pm
puessiqueloes. A ver si en un ratico le echo un vistazo y lo arreglo, como buen chico que soy
marcos
Agosto 4th, 2008 el 9:48 pm
Yo lo único que no veo bien del ejemplo, es la base de datos, que está mal construida desde el punto de vista teórico.
Sin duda en el caso de enfrentarse a una tabla como la que comentas, el post está muy interesante, pero una tabla como la explicada en el post es una tabla no correcta ya que alberga 2 entidades diferentes.
En mi opinión deberÃan ser 2 tablas, una para cada entidad, o dependiendo de la situación y objetivos hasta 3 tablas, una para cada entidad y otra de relación.
Saludos!
P.D. A mi no me pasa lo del sr. skeku
cmacias
Agosto 4th, 2008 el 10:43 pm
Pués resultarÃa interesante hablar sobre ello. Mi punto de vista es el del diseñador/programador (?). Y tu punto de vista quizá más acertado es el del programador de verdad.
Aún asà el uso de dos/tres tablas, crees que vale la pena viendo el objetivo ?
marcos
Agosto 4th, 2008 el 11:02 pm
Bueno, el problema en las bases de datos es que no es bueno pensar si vale la pena “en un caso concreto”, sino sobre qué podrÃa ocurrir si el dÃa de mañana nos piden alguna cosa adicional.
Relacionar registros de una misma tabla, mediante un campo de la misma tabla conlleva muchos problemas de escalabilidad. ¿Qué ocurre si un registro en el futuro tuviera que estar relacionado con otros dos y no solo con uno? HabrÃa que cambiar la estructura de la tabla, para permitir otro campo de relación, y asà sucesivamente.
Evidentemente no soy un purista, y si va bien para un caso, pues va bien. Pero teóricamente no es muy correcto. Además estás incrementando el número de registros de una tabla que conceptualmente no deberÃa contener esa mezcla de tipos de lugar, con lo que en teorÃa las consultas futuras se verÃan afectadas.
Lo dicho, si para el caso va bien, pues no pasa nada, pero yo personalmente siempre procuro cubrirme las espaldas de cara al futuro, y trato de hacer el diseño de la base de datos lo más independiente posible, para poder añadirle cosas (que pudieran pedir) en el futuro.
No me he parado a pensar la consulta en cuestión (estoy muy desentrenado), pero hacer una función recursiva con peticiones a la base de datos no me parece como norma general una buena práctica, y con un diseño de una sola tabla, quizás no sea posible hacerlo de otra manera…
Tecnorama
Agosto 5th, 2008 el 1:17 am
Definitivamente, Marcos y Carlos, os hacen falta unas vacaciones: Os veo muy tensos. Yo he vuelto como una malva. Para ponerme al dÃa: ¿En que lenguaje has escrito el ejemplo?
cmacias
Agosto 5th, 2008 el 7:13 am
En ActionScript 3.1 y access
marcos
Agosto 5th, 2008 el 8:07 am
Si es en AS3.1 y access no digo nada, pensé que estábamos jugando con cosas de verdad… XD bienvenido andrés, por cierto tengo que pasarme por llanera un dia.
Óscar
Agosto 5th, 2008 el 12:55 pm
¡¡Carlos vuelve!!. De verdad tÃo, cambiar Lugo por Vigo te ha sentado fatal
TÃo… dale un descanso a tu mente, te veo muy estresado. Vente por aquà y nos tomamos una tapita de pulpo y unos ribeiros… y déjate de tablas. Las mejores tablas son las de embutidos, vente que catamos unas…
Venga tÃo, fuera coñas, eres un fenómeno. Un saludete y nos vemos…
MarcosBL
Agosto 5th, 2008 el 8:18 pm
De acuerdo con el tema de que este diseño no escala, aunque en este caso concreto es extrañÃsimo que te surja la necesidad de escalar. Este diseño, para categorÃas o provincias, lo veo correcto, porque además es el tipico de “rellenar y olvidar”, sobre el que no suele haber rediseños jamás.
De ese lado no le quito la razón a Marcos, porque la tiene, pero el ejemplo de Carlos para ese caso es perfectamente válido desde mi punto de vista.
Ahora, en la implementación… lo mataria con mis propias manos si no se hubiese largado a Vigo
Si bien funciona, pobre de ti como tengas 10000 visitas al dia en una web con ese diseño, o si tienes 400 categorias entre abuelas/hijas/nietas… estarás haciendo consultas a la BD a los bestia.
¿ Cómo solucionarlo ? Marcos, con un diseño de una sola tabla SI puede hacerse, además perfectamente y a velocidad de escándalo. Basta con hacer un select de TODAS las categorÃas (en este caso pocos datos, no es pesado) y ordenarlas por código, creando un array recursivo desde PHP, no lanzando consultas recursivamente a MySQL como está haciendo Carlos (de ahi el.. me lo cargo!)
cmacias
Agosto 5th, 2008 el 10:07 pm
Señor Juez, me declaro culpable xD
@Oscar, una de las cosas que ya he echado de menos es el “Baviera”. Pedazo de bocadillos, que cosa rica
lonnrot
Agosto 5th, 2008 el 11:26 pm
Coincido con MarcosBl en todo, incluÃdo lo de matar a Carlos si lo tengo delante, asà que no añadiré ningún punto a eso.
Con respecto al comentario de Marcos, o se ha liado, o no se ha explicado bien:
El número de registros es el mismo, lo que cambia es el número de campos. Lo de que
pues no lo entiendo, la verdad, será que tengo el dÃa tonto,nada raro en mi. Conceptualmente el diseño está bien, si le llamas a “sub_id” “id_padre” tal vez lo veas mejor.
Optimizar las consultas es algo complejo, depende de los Ãndices, del tipo de las tablas, de los campos, de la cantidad de consultas que se hagan, pero piensa que por regla general es mejor hacer una consulta a una sola tabla que un join anidado a dos, ¡¡¡¡ y aún asà se pueden encontrar casos en que esto último no sea cierto!!!
cmacias
Agosto 6th, 2008 el 12:01 am
Mmmm, espero que eso de las ganas de matarme no se vaya a poner de moda
Tecnorama
Agosto 6th, 2008 el 12:48 am
Lo de la escalabilidad es buen moivo para un post aparte…
En este tema, siempre me he encontrado en mitad de la lucha entre dos refranes:
“Más vale un por si acaso que cien si lo hubiera sabido”
“Tanto peca lo mucho como lo poco”
Me explico: Siempre he sido partidario de diseñar las BD pensando en su ampliación e intentando respetar la normas de formalización. Ahora bien:
Imaginemos que el cliente NUNCA va a vender un producto fuera de España: ¿Es necesario realmente diseñar la BD de forma que algún dÃa pueda hacerlo (por ejemplo, poder añadir un campo de paises)?
Este artÃculo de Coding Horror me pareció revelador:
http://www.codinghorror.com/blog/archives/001152.html
Tecnorama
Agosto 6th, 2008 el 12:49 am
Donde dije formalización querÃa, obviamente, decir normalización…
marcos
Agosto 6th, 2008 el 8:25 am
@lonnrot: Si se distribuyen tanto provincias como ciudades como poblaciones en una misma tabla, el numero de registros es mayor por tabla que si se separan. A eso me referÃa. Esto con pocos registros puede parecer poca diferencia, pero hablando de poblaciones la cosa puede cambiar, y si están relacionados más aun.
Para mi conceptualmente el diseño está mal porque una entidad serÃa paÃs, otra entidad serÃa provincia, y otra entidad serÃa población. Que no quita que depende que es lo que quieras, pues puedas juntarlo todo en una tabla, pero teniendo en cuenta que son 3 niveles, la cosa puede crecer muy deprisa.
La solución de hacer el volcado a array y luego hacer la relacion trabajando sobre el array me parece desde luego mejor que la de carlos, pero hay que tener en cuenta que la capacidad y velocidad de php no es lo mismo que la de una consulta SELECT MySQL. De hecho eso puede ya ser un sÃntoma de que la base de datos no está bien hecha del todo. Si con una consulta se puede obtener lo mismo que con calculos y recorridos sobre array de PHP, siempre se deberÃa tratar por norma general de que sea con la consulta.
anda que no ha dado de si este post con tanta teoria chorras. Libre albedrÃo con las BBDD, deberÃan funcionar solas!
marcos
Agosto 6th, 2008 el 8:26 am
@lonnrot: quise decir paises, provincias y poblaciones en una misma tabla
cmacias
Agosto 6th, 2008 el 8:53 am
Igual lo hago a mano, en html directamente
marcos
Agosto 6th, 2008 el 8:56 am
si va a tener pocas actualizaciones, sin duda es la mejor opcion XD
MarcosBL
Agosto 6th, 2008 el 9:06 am
@marcos Conceptualmente no puedo menos que darte la razón, en este ejemplo probablemente la normalización en 3 tablas seria lo suyo, yo iba más por el ejemplo mio de las categorÃas. Indudablemente, una sóla consulta con Ãndices bien definidos es siempre mejor que tener que cargarle el trabajo a php, en este caso lo que intento evitar no es la consulta MySQL, sino un número N indeterminado de consultas, algo inevitable con el método de bucle que se está empleando. Si bien es cierto que las consultas bien hechas son rapidÃsimas, del orden de milisegundos, realizar 200 consultas (conexión, consulta, cierre de conexión) es un gasto importante de recursos tanto de tiempo de ejecución como de memoria consumida, por el proceso de PHP y por la bd MySQL, de ahi mi intención de reducir el tema a una consulta y hacer trabajar a PHP sólo dentro del bucle, no a MySQL + PHP.
@cmacias
De hecho lo que yo suelo hacer es leer esa estructura es un array de PHP, guardarlo serializado en disco y utilizarlo a posteriori como un include. El acceso a MySQL, si es rápido, es mucho más eficiente que el acceso a disco, pero esta solución libera bastante la BD (en el ejemplo de las categorias, por cada pageview se suelen leer todas una y otra vez) que suele ser el cuello de botella del rendimiento.
marcos
Agosto 6th, 2008 el 9:47 am
Efectivamente, pero sigo diciendo que con 3 tablas 1 consulta y un único recorrido del recordset resultado se puede hacer. De hecho hablándolo con Jorge (mi experto en php / mysql) creemos que es hasta bastante sencillo.
Select sobre la combinacion de las 3 tablas, ordenadas por pais, provincia y ciudad (en ese orden de criterios). Recorrido secuencial y cuando se detecta que cambia el valor de provincia o de pais, se procede a poner la etiqueta correspondiente en HTML.
Saludos!
MarcosBL
Agosto 6th, 2008 el 10:18 am
Ya me ha picado el gusanillo cojonero, voy a hacer unas pruebas xD
cmacias
Agosto 6th, 2008 el 10:32 am
Anda que no os ha dado vidilla el post xD
marcos
Agosto 6th, 2008 el 10:59 am
quien te iba a decir a ti que un código “problemático” iba a dar tanto juego en el blog, ya te veo escribiendo código erróneo para generar polémica XD… çpor cierto es una idea interesante, estarÃa bien un blog con código no demasiado bueno para que la gente aportara sus mejoras… mola!
cmacias
Agosto 6th, 2008 el 11:06 am
anda que no he pensado yo en lo mismo. El proximo tutorial como hacer un login para una zona de administración xD
Tecnorama
Agosto 6th, 2008 el 12:02 pm
Vamos a echar más leña al fuego:
Aunque Carlos ha realizado un volcado a un array, es posible obtener lo mismo directamente desde la consulta utilizando aliases: Por esta parte tenemos al menos opciones si deseamos mejorar el rendimiento (PHP vs SQL).
En cualquier caso, coincido con ambos Marcos en que es mejor el diseño a 3 tablas ¿Por rendimiento, por normalización? NOOOOO, ¡por simple comodidad nuestra!
En cualquiera de las circunstancias, la mejora en el rendimiento (salvo que hagamos alguna burrada) puede ser de unas centésimas por consulta: Si el precio para que el servidor trabaje menos (siendo el único de la ecuación que no cobra por horas) es que nosotros trabajemos el doble (creando las consultas, manteniendo las tablas…), no veo el beneficio por ninguna parte.
Siempre he creido que las BD tienen que ser legibles: Tomemos por ejemplo “Ferrol”: ¿Podemos decir a primera vista si es un pais, una provincia o una localidad? Con el diseño a 3 tablas no tendriamos ese problema.
Siguiendo el comentario de Marcos, esto me recuerda la eterna polémica entre utilizar echo o no usarlo (y anidar el html entre bloques php) para generar html (él dice que sà y yo digo que no)
MarcosBL
Agosto 6th, 2008 el 1:12 pm
Listo, si no probaba un ejemplo reventaba
Aqui os dejo con tu permiso, Carlos, la explanation y el ejemplo, con 771 categorÃas anidadas y un (creo) buen rendimiento http://www.propiedadprivada.com/lab/unitree/
cmacias
Agosto 6th, 2008 el 2:59 pm
Pués a mi me parece que el resultado es óptimo, no ? aunque creo que este post aún va a dar para algo más
Tecnorama
Agosto 7th, 2008 el 12:17 am
Es decir:
¿Estamos hablando de ahorrar milésimas de segundo a cambio de nuestro sudor?
¿que alguien cierre este post inmediatamente, por favor!
Por simple curiosidad: ¿Alguien se anima a relizar el mismo test de MarcosBL (simplemente excelente y esclarecedor de su posición) con tablas inpendientes?
marcos
Agosto 7th, 2008 el 8:32 am
Yo no XD
MarcosBL
Agosto 9th, 2008 el 3:37 am
Cabroneh ! No me dejeis pringar sólo ! xD
cmacias
Agosto 9th, 2008 el 10:09 am
En un concurso, ante la falta de otras iniciativas, mi idea serÃa la segunda clasificada, aunque primera en sencillez xDDDD