Beruflich Dokumente
Kultur Dokumente
PHP-Hispano.net
Comunidad hispana de desarrollo web
Inicio / Articulos / Diseo y uso de bases de datos relacionales
41953 lecturas
ndice
Diseando la BD Normalizando la BD Normalizando la BD: primera forma normal (1FN) Normalizando la BD: segunda forma normal (2FN) Normalizando la BD: tercera forma normal (3FN) Diseando la BD sobre la marcha Consultando la BD: SELECT Consultando la BD: INNER JOIN Consultando la BD: LEFT JOIN Consultando la BD: GROUP BY Consultando la BD: optimizar consultas
Diseando la BD
Para disear una base de datos se parte de la recoleccin de atributos o campos que va a tener, y de la definicin de sus tipos de dato. La manera ms profesional es realizando el anlisis de requisitos con todas las personas que van a hacer uso de los datos. Pero por experiencia ya sabis que esto se hace muy a ojo: os piden realizar una aplicacin y segn los requisitos de la aplicacin hacis el diseo de la BD. El primer mtodo est ms estandarizado, y suele ser ms lento pero a cambio es improbable que el diseo salga mal. El segundo es ms rpido porque directamente se piensa en las tablas y sus datos sobre la marcha. Se utiliza principalmente en la metodologa de programacin conocida como "programacin extrema" y en las dems de la familia "desarrollo gil de software"; y es ms propenso a fallos de diseo , proporcionalmente inversos al tiempo que se dedique a su definicin y valoracin (ms tiempo, menos probabilidad de fallos).
Normalizando la BD
La normalizacin es un mtodo de anlisis de BD para c onseguir una BD relac ional, que respete la integridad referenc ial, y que no tenga redundanc ia de datos. Se divide en formas normales, y aunque hay un montn y es toda una ciencia, explicar por encima las 3 primeras ya que el nivel 3 es sufic iente para la mayora de casos. Hay que destacar que la normalizacin se puede hacer a nivel completo de la BD, o a nivel de tablas o esquemas. La tcnica es la misma: analizar el conjunto de campos y en base a eso designar una clave inicial que identifique a un grupo de datos. Por ejemplo si estamos normalizando todo un esquema de facturacin podemos partir de los datos del cliente aadiendo la clave del cliente, y segn vayamos normalizando nos saldrn todas las tablas y les iremos dando claves primarias nuevas. Si lo que normalizamos es una tabla, el procedimiento es el mismo y ya irn saliendo otras tablas subordinadas si acaso.
www.php-hispano.net/articulos/modelo-uso-bases-datos-relacionales.html?print=true
1/13
26/09/13
Ejemplo
Inc orrec to clientes IDCliente 45 275 Correc to clientes IDCliente 45 275 Nombre Francisco Miguel Nombre Francisco Miguel Telefono 444444444 555555555,666666666
No se permiten grupos repetidos en varias c olumnas Esto es una variante de lo anterior: separamos los campos de un mismo dominio en varias columnas, haciendo un grupo difcilmente procesable a la hora de consultarlo. En el ejemplo anterior sera tener el campo telefono1, telefono2... y as. Es evidente que este fallo del diseo es incluso peor que el anterior pues habr muc hos c ampos nulos, y en caso de necesitar ms tendramos que redimensionar la tabla con un nuevo campo (telefono3). Pero la solucin es sencilla: la misma que en el anterior caso.
Ejemplo
Inc orrec to clientes IDCliente 45 275 Correc to clientes IDCliente 45 275 Nombre Francisco Miguel Nombre Francisco Miguel Telefono 444444444 555555555 Telefono2 NULL 666666666 Telefono3 NULL NULL
No se permiten c ampos nulos Esta regla es algo discutible, pero tiene su lgica. Para empezar, si un campo va a tener valores nulos, qu proporcin de registros tendrn
www.php-hispano.net/articulos/modelo-uso-bases-datos-relacionales.html?print=true
2/13
26/09/13
Ejemplo
Inc orrec to productos IDProducto 147 155 221 Correc to productos IDProducto 147 155 221 ropas IDProducto 147 joyas IDProducto 155 electricos IDProducto 221 Potencia 1500 Kilates 24 Talla 44 Nombre Blusa fashion Broche duquesa Subwoofer extreme Nombre Blusa fashion Broche duquesa Subwoofer extreme Talla 44 NULL NULL Kilates NULL 24 NULL Potencia NULL NULL 1500
Ejemplo
Inc orrec to lineas_pedido IDCliente 29 46 204 144 Correc to lineas_pedido IDCliente 29 46 204 144 productos IDProducto 9 Nombre_producto Baln reglamentario de baloncesto IDProducto 42 9 42 10 Cantidad 1 5 1 1 IDProducto 42 9 42 10 Cantidad Nombre_producto 1 5 1 1 Zapatillas deportivas de tenis Baln reglamentario de baloncesto Zapatillas deportivas de tenis Zapatillas deportivas de rugby
www.php-hispano.net/articulos/modelo-uso-bases-datos-relacionales.html?print=true
3/13
26/09/13
10 42
Como vemos en la tabla "lineas_pedido" del ejemplo incorrecto, la nica clave candidata es IDCliente + IDProducto, ya que en c onjunto son nic as en la tabla (podramos tener un IDLinea_pedido nico tambin, pero an as esos dos campos seguran siendo una clave candidata). El campo Cantidad es dependiente de la clave candidata, pues el cliente ha pedido de ese producto una cantidad determinada de artculos, pero el nombre en cambio es dependiente slo del produc to, no del c liente . Si dejaramos esa tabla como est, tendramos por una parte una redundanc ia de datos innec esaria pues el nombre del producto lo podemos sacar uniendo la tabla de productos, y adems podran darse inc onsistenc ias de datos si cambiamos el nombre del producto en un registro... cul sera el nombre real del producto 42 si en varios registros tiene un nombre distinto?
Conclusiones
Por lo tanto los pasos para aplicar la segunda forma normal son muy sencillos: enc ontrar las c laves c andidatas (compuestas), que identifican de manera nica el registro; comprobar que los campos que no forman parte de la clave candidata y no son parte de ella (en el ejemplo de antes ni IDCliente ni IDProducto deben ser analizados) dependen totalmente de la c lave c andidata. Para el segundo paso puede ayudar preguntarse lo siguiente: puedo saber el valor del campo X sabiendo el valor del campo Y (siendo Y parte de la clave candidata y X no siendo parte de ella)? Pero como todo lo relacionado con el anlisis esto requiere un mnimo de agudeza, pues puede que casualmente el valor de un campo se repita para una parte de la clave (por casualidad todos los que compran unas pelotas de tenis lo hacen en cantidades de 5) pero sabemos que no es dependiente de ella. Por ltimo, aclarar que hay ocasiones en las que el anlisis no tiene que ser tan c errado , ya que a veces las apariencias engaan. Un ejemplo de ello es una tabla de facturas que tiene el nombre, direccin, NIF, y dems datos del cliente: a simple vista esos datos estn duplic ados y dependen del c liente y no de la fac tura, pero resulta que esos datos deben permanecer ah pues fiscalmente debemos saber a qu datos se emiti una factura; esos datos son realmente dependientes de la fac tura, no del c liente . Si no los incluyramos en la tabla de facturas, al modificar el registro del cliente en la tabla de clientes no sabramos a qu datos fiscales se emiti la factura. As que una vez ms, hay que utilizar un poco de ingenio y no aplic ar normas c omo una mquina y sin pensar.
Ejemplo
Inc orrec to carga_diaria IDServidor 21 21 21 34 34 34 66 66 66 Correc to carga_diaria IDServidor 21 21 21 34 34 34 66 66 66 servicios Fecha 2009-01-14 2009-01-15 2009-01-16 2009-01-14 2009-01-15 2009-01-16 2009-01-14 2009-01-15 2009-01-16 IDServicio 1 9 22 3 22 22 9 22 1 Carga 100 100 85 74 58 67 98 94 84 Fecha 2009-01-14 2009-01-15 2009-01-16 2009-01-14 2009-01-15 2009-01-16 2009-01-14 2009-01-15 2009-01-16 IDServicio 1 9 22 3 22 22 9 22 1 Nombre_servicio Oracle MySQL Apache PostgreSQL Apache Apache MySQL Apache Oracle 10g Carga 100 100 85 74 58 67 98 94 84
www.php-hispano.net/articulos/modelo-uso-bases-datos-relacionales.html?print=true
4/13
26/09/13
IDServicio 1 9 22 3 22 22 9 22 1 Nombre_servicio Oracle MySQL Apache PostgreSQL Apache Apache MySQL Apache Oracle 10g
Imaginad que una tabla se encarga de registrar el primer servicio que ms carga los servidores cada da. Del ejemplo incorrecto deducimos que el IDServidor y la Fecha son la clave candidata, pues identific an de manera nic a los registros. Analizando vemos que el IDServicio, que no es un campo primario, depende nicamente de la clave candidata, y que la carga tambin. Pero resulta que el Nombre_servicio depende de esa clave candidata pero tambin depende del IDServicio, pues c on el IDServic io podemos averiguar qu Nombre_servic io tiene el registro . Para solucionar esto sacamos el campo Nombre_ servicio de la tabla, y nos llevamos el IDServic io para que sea la clave principal pues es el c ampo del que depende . Y con este ejemplo vemos qu fcil es librarnos de las inconsistencias de no cumplir la tercera forma normal, y de la redundancia de datos. Si no hubieramos normalizado tendramos que en un registro el IDServicio 22 es Apache y nadie nos asegura que en otro el IDServicio 22 tambin lo sea pues puede haberse modificado el campo Nombre_servicio. Y si resulta que la tabla fuese un histrico de 500 servidores durante 1000 das, tendramos 500 mil registros con un c ampo innec esario que estara duplic ado muchsimas veces.
Primero, documentarse
Lo ms importante para disear la BD sobre la marcha es tener la mente amplia, c onoc er las bases de la normalizac in, y dejarse ac onsejar por los expertos. Todo esto de las BD relacionales no es nuevo y hay muchos gurs (Codd, Edgar Frank Codd, es el padre de todos) que os pueden ayudar a entender qu caractersticas debe cumplir una BD para ser relacional. De modo que si no sabis del tema, lo mejor es que os olvidis de las malas enseanazas que tengis imbuidas: una BD con muchas tablas no est mal diseada (una con pocas es ms probable que s lo est); llevarse el cdigo del cliente a todas las tablas (lo necesiten o no) no es la forma de tener una BD relac ional; guardar los datos serializados en un campo no te hac e la vida ms fc il; tener un campo que hace referencia a una tabla unas veces y a otra en otras ocasiones no es un buen diseo (un c ampo referenc ial slo puede referenc iar a una tabla, as que usad la generalizacin en esas situaciones).
Errores habituales
Un error habitual a la hora de usar este mtodo de diseo es tener un campo referencial que admite valores nulos o vacos (tpico clave_referencia 0 cuando no referencia a nada). Si la BD es relacional, un c ampo referenc ial tiene que apuntar a otro registro en la BD. A veces tenemos un campo IDPadre que hace referencia a un mismo registro de la tabla, o vale 0 cuando el registro es padre en s... pero lo c orrec to en una relac in reflexiva (una tabla relacionada consigo misma) que da lugar a otro tabla que contiene el IDHijo y el IDPadre; consultando esa tabla podemos saber si un registro es hijo (tiene entradas con su ID en IDHijo) o padre (su ID est en algn IDPadre) y sacar todos los hijos de un padre.
Consejos
Otro buen consejo, que no est limitado a este mtodo de disear, es tener en c uenta el tamao de las tablas en cuanto a longitud de fila (en bytes). De hecho recuerdo que me hablaron de una regla de diseo de BD que deca que una tabla no deba contener ms de X campos... y bueno, siempre es discutible pero es ms ptimo que la longitud de la fila no sea muy larga, sobre todo en las tablas que se c onsultan c on frec uenc ia. Es una prctica muy recomendada que si tenemos campos grandes, tipo TEXT o BLOB, saquemos esos datos a otra tabla para que las bsquedas y JOIN generales que no necesiten ese campo sean ms rpidas. Hay que tener en cuenta que el sistema de gestin de BD (SGBD) en ocasiones no puede optimizar las consultas y necesita esc anear por c ompleto la tabla, o tiene que volcarla a la memoria fsica o virtual. En definitiva: recordad que una BD relacional no puede ser dependiente de la aplic ac in , sino al revs. As que olvidaros de disearla pensando qu es lo mejor para la aplicacin, y pensad qu es lo c orrec to para que sea ms ptima y senc illa de manejar independientemente del cliente que la maneje (aplicacin, consultas directas, herramientas de informe...).
www.php-hispano.net/articulos/modelo-uso-bases-datos-relacionales.html?print=true
5/13
26/09/13
Analizando la sintxis tenemos opc iones: segn el SGBD que usemos tendremos opciones para establecer la prioridad de la consulta, obtener resultados no duplicados, y opciones tiles como el SQL_CALC_FOUND_ROWS de MySQL, que calcula cantos registros devuelve la consulta sin aplicarle un LIMIT (as podemos obtener por ejemplo 20 registros, ideal para una paginacin, y saber cuntos registros devolvera sin el LIMIT lanzando despus un SELECT FOUND_ROWS()). valores: son las columnas con valores que devolver la consulta. Aqu podemos indicar de todo: nombres de campos, operaciones aritmticas, valores constantes, funciones, subconsultas... y tambin as, podemos asignarles un alias para que se devuelvan con ese nombre de columna ("SELECT campo1 c1 FROM tabla" nos devolvera el campo1 con el nombre de columna c1). En los alias, dependiendo del SGBD, se puede usar el keyword opcional AS, que es algo ms claro a la vista: SELECT campo1 AS c1 FROM tabla tablas: aqu van todas las tablas que participarn en la consulta. Pueden ir tanto tablas (y vistas), como subconsultas, y se pueden usar alias tambin (el keyword AS es menos soportado aqu, ya que por ejemplo Oracle no lo admite). Una subconsulta significa poner una consulta entre parntesis y usar su resultado como una tabla normal:
S E L E C Tc a m p o 1 ,s c 1 ,s c 2F R O Mt a b l a ,( S E L E C Tc a m p o 2A Ss c 1 ,c a m p o 2A Ss c 2F R O Mt a b l a 2 )W H E R E c a m p o 1=s c 1
Algunos SGBD como MySQL permiten varias opciones en este parte, como indicar qu ndice queremos que se use para recorrer la tabla. Esto ltimo es muy raramente til pues el optimizador de c onsultas no suele errar en el ndic e a usar; es ms comn que se equivoque en el orden en el que une las tablas, pero para eso hay opciones como STRAIGHT_JOIN en el SELECT que obligan al optimizador a unir las tablas en el orden en el que las hemos puesto nosotros. Las JOIN van en este lugar tambin pero ms adelante las veremos. c ondic iones: serie de condiciones que en conjunto tienen que evaluar a TRUE para que el registro sea devuelto. Debido al orden en que se procesan las consultas, aqu no se pueden usar los alias de los c ampos que hayamos indicado en la parte de "valores" (pero s los alias de tablas), as que tenemos que usar el nombre del campo o poner de nuevo la operacin entera si se trata de una operacin. Son estndares expresiones como IS NULL/IS NOT NULL (devuelve TRUE si lo evaluado es/no es nulo), NOT (niega la expresin, de modo que si evala a TRUE no ser devuelto el registro), BETWEEN valor1 AND valor2 (el operando precedente tiene que estar entre los valores indicados, inclusive), LIKE 'patrn' (el operando, que debe ser una cadena, debe coincidir con el patrn, que admite, comodines como el '%' para cualquier carcter de 0 a N veces y el '_' para cualquier carcter una sola vez)... y como no, tambin podemos usar funciones del SGBD y operaciones de cualquier tipo, adems de los operadores >, >=, <>, <, <=. Como operadores lgicos tenemos el NOT, AND y OR (con ese orden de preferencia), y para no marearnos en las expresiones complicadas podemos meter entre parntesis las expresiones:
. . .W H E R E( c o n d i c i o n 1O Rc o n d i c i o n 2 )A N Dc o n d i c i o n 3A N Dc o n d i c i o n 4
Aqu es donde filtraremos los resultados para obtener lo que nos interese , como los usuarios que tienen el nombre Miguel, los trabajadores que tienen ms de 30 aos, las lneas de pedido de un pedido concreto... ORDER BY : aqu especificamos el orden de los registros segun el campo que queramos. Aqu s se puede usar alias de c ampos, o el nmero de posicin que ocupa el valor dentro de la fila:
S E L E C Tc a m p o 1A Sc 1 ,c a m p o 2F R O Mt a b l aO R D E RB Y2
En el ejemplo anterior se ordenaran los registros por campo2, que es la columna nmero 2 del resultado Tambin se puede ordenar por varios campos, y en ascendente o descendente:
. . .O R D E RB Yc a m p o 1D E S C ,c a m p o 2A S C
El ORDER BY en ocasiones puede hac er que el resultado tarde ms en ser devuelto pues el SGBD puede necesitar realizar otra pasada para ordenarlo.
www.php-hispano.net/articulos/modelo-uso-bases-datos-relacionales.html?print=true
6/13
26/09/13
S E L E C T. . .F R O Mt a b l a 1I N N E RJ O I Nt a b l a 2O Nt a b l a 1 . c a m p o=t a b l a 2 . c a m p oW H E R E. . .
Cuando realizamos una INNER JOIN lo que hacemos es unir dos tablas mediante una o varias c ondic iones, habitualmente porque estn relac ionadas por un c ampo c lave . Se pueden concatenar tantas JOIN como queramos; de hecho es la manera habitual de seguir las relac iones de las tablas al realizar consultas (una tabla se relaciona con otra, que a su vez se relaciona con otra...). INNER JOIN es exc lusivo , de modo que la relacin debe c umplirse siempre o sino no se devolver el registro. As, si consultamos todos los clientes y unimos los pedidos mediante el ID de cliente obtendremos slo los que tengan pedidos. Si hacemos:
S E L E C Tc a m p o 1F R O Mt a b l atI N N E RJ O I Nt a b l a 2t 2O Nt . i d=t 2 . i d _ t
S E L E C Tc a m p o 1F R O Mt a b l at ,t a b l a 2t 2W H E R Et . i d=t 2 . i d _ t
De hecho, los optimizadores de consultas (query optimizer) suelen cambiar las JOINS por consultas as. No es que sea ms ptimo realizarlo as, pero para operar sobre las tablas para optimizar la consulta se manejarn mejor as. Pero del lado del programador de BD, es ms fc il de ver las uniones mediante JOINs que mirando las tablas del FROM y luego las condiciones del WHERE (al menos para m).
es mejor traducirlo a:
Por lo dems, c ualquier c ondic in que no sea ligar las tablas por ID es mejor ponerla en el W HERE. Tambin se puede omitir el ON con la condicin, pero en ese caso el resultado ser en la mayora de los casos catastrfico, pues si usamos 2 tablas sin relac ionarlas se realizar el produc to c artesiano entre ellas, o sea que por cada registro de la tabla 1 se devolvern todos los registros de la tabla 2. Si son tablas de 10 y 5 registros no es mucho problema, se devolveran 50 registros, pero si son de 10000 y 50000..., adems no slo ser un resultado grande, tambin muy lento y pesado en recursos para el servidor de BD.
Ejemplo
Vamos a suponer una BD con clientes, pedidos y productos (1 cliente tiene N pedidos, 1 pedido es de un nico cliente, y 1 pedido tiene N productos que a su vez pueden estar en M pedidos). De la relacin pedidos - productos nos sale la entidad lineas_pedidos.
www.php-hispano.net/articulos/modelo-uso-bases-datos-relacionales.html?print=true
7/13
26/09/13
2 10 13 John Edward Rose
Tabla pedidos: id_pedido 1 2 3 4 5 6 7 8 9 id_cliente 2 2 10 2 10 10 10 2 10 fecha 2009-01-24 08:00:15 2009-01-24 09:22:08 2009-01-27 13:10:22 2009-01-27 17:54:15 2009-01-29 08:13:01 2009-01-29 10:28:57 2009-01-29 18:26:59 2009-01-30 12:30:47 2009-01-30 12:31:18
Tabla productos: id_producto 1 2 3 4 5 nombre frigorfico ACME televisor plasma minicadena HI-FI aspirador ACME climatizador ACME precio_base 749.00 799.00 129.00 89.00 1499.00 stock 25 87 19 28 32
Tabla lineas_pedido: id_pedido 1 1 2 3 4 4 4 5 6 7 8 9 id_producto 1 4 2 2 2 3 4 2 5 4 2 5 precio_unidad cantidad 709.00 89.00 729.00 699.00 729.00 129.00 89.00 699.00 1399.00 89.00 729.00 1399.00 1 2 1 1 1 1 2 7 9 5 1 8
id_cliente 2 2 10 2 10 10 10 2 10
id_pedido 1 2
fecha 2009-01-24 08:00:15 2009-01-24 09:22:08 2009-01-27 13:10:22 2009-01-27 17:54:15 2009-01-29 08:13:01 2009-01-29 10:28:57 2009-01-29 18:26:59 2009-01-30 12:30:47 2009-01-30 12:31:18
Edward 3 John 4
Edward 9
Como se puede observar no se han devuelto registros del cliente 13 porque no tiene pedidos. Y tambin se ve que por cada cliente se han devuelto tantos registros como coincidencias tiene en la tabla de pedidos. Si quiseramos obtener slo un registro por cliente usaramos GROUP BY para
www.php-hispano.net/articulos/modelo-uso-bases-datos-relacionales.html?print=true
8/13
26/09/13
S E L E C Tp r . n o m b r e ,l . p r e c i o _ u n i d a d ,l . c a n t i d a d ,( l . p r e c i o _ u n i d a d*l . c a n t i d a d )A St o t a l _ l i n e a , R O U N D ( 1 0 0-( ( l . p r e c i o _ u n i d a d*1 0 0 )/p r . p r e c i o _ b a s e ) ,2 )A Sd e s c u e n t o F R O Mp e d i d o spI N N E RJ O I Nl i n e a s _ p e d i d olO Np . i d _ p e d i d o=l . i d _ p e d i d o I N N E RJ O I Np r o d u c t o sp rO Nl . i d _ p r o d u c t o=p r . i d _ p r o d u c t o W H E R Ep . i d _ c l i e n t e=2A N Dp . i d _ p e d i d o=4
Lo que hemos hecho ahora es, dado un cliente y un pedido (para asegurarnos de que el pedido es de un cliente en concreto), sacar el detalle de los produc tos solicitados en dicho pedido. As podramos preparar el detalle de la fac tura con el nombre del producto, su precio, su cantidad, su precio total y el % de descuento que ha tenido sobre el precio base.
S E L E C T. . .F R O Mt a b l a 1L E F TJ O I Nt a b l a 2O Nt a b l a 1 . c a m p o=t a b l a 2 . c a m p oW H E R E. . .
La sintaxis de LEFT JOIN es idntica a INNER JOIN, pero su comportamiento es muy distinto. Con LEFT JOIN obtenemos todos los registros de la izquierda de la expresin ("tabla1" en el ejemplo de arriba) y luego se uniran c on la parte derec ha, si hubiera c oinc idenc ias. Por ejemplo, si unimos clientes con pedidos y un cliente no tiene pedidos, se devolvera el cliente en el resultado pero con todos los campos de la tabla pedidos a NULL. As es muy fcil obtener todos los clientes que no tienen pedidos:
Por lo dems la LEFT JOIN tiene las mismas caractersticas que una INNER JOIN en cuanto a las condiciones que se pueden poner en el ON, y opciones que el SGBD permita.
Ejemplo
Tabla clientes: id_cliente 2 10 13 nombre John Edward Rose
Tabla pedidos: id_pedido 1 2 3 4 5 6 7 8 9 id_cliente 2 2 10 2 10 10 10 2 10 fecha 2009-01-24 08:00:15 2009-01-24 09:22:08 2009-01-27 13:10:22 2009-01-27 17:54:15 2009-01-29 08:13:01 2009-01-29 10:28:57 2009-01-29 18:26:59 2009-01-30 12:30:47 2009-01-30 12:31:18
www.php-hispano.net/articulos/modelo-uso-bases-datos-relacionales.html?print=true
9/13
26/09/13
id_cliente 2 2 10 2 10 10 10 2 10 13
id_pedido 1 2
fecha 2009-01-24 08:00:15 2009-01-24 09:22:08 2009-01-27 13:10:22 2009-01-27 17:54:15 2009-01-29 08:13:01 2009-01-29 10:28:57 2009-01-29 18:26:59 2009-01-30 12:30:47 2009-01-30 12:31:18 NULL
Edward 3 John 4
A diferencia del INNER JOIN, hemos obtenido tambin un registro del cliente 13 aunque no tiene coincidencias en la tabla de pedidos. Ahora, para obtener los clientes que no tienen pedidos simplemente hacemos:
id_cliente 13
nombre Rose
id_pedido NULL
fecha NULL
S E L E C T. . .F R O Mt a b l a 1I N N E RJ O I Nt a b l a 2O Nt a b l a 1 . c a m p o=t a b l a 2 . c a m p oW H E R E. . .G R O U PB Yc a m p o 1 [ H A V I N Gc o n d i c i o n e s ]
Con GROUP BY podemos agrupar, en torno a uno o varios c ampos (algunos SGBD admiten alias en vez de campos), los registros devueltos. Opcionalmente podemos usar condiciones del agrupamiento en el HAVING, algo tpicamente usado con las funciones de agregado.
S E L E C TC O U N T ( * )F R O Mt a b l a
pero no:
S E L E C TC O U N T ( * ) ,c a m p o 1F R O Mt a b l a
Es algo ilgico que se pueda usar una funcin de agregado sin agrupar, pero lo que ocurre es que por simplic idad los SGBD lo permiten para cuando se quiere obtener el nmero de registros, o el valor mayor de un campo, cosas bastante frecuentes. En estos casos es el propio SGBD el que se enc arga de agrupar y generalmente de optimizar estas c onsultas. Pero cuidado con el parmetro que ponemos en las funciones de agregado, porque si indicamos un valor que sea nulo no se aplicar; y tened en cuenta que COUNT(*) es una exc epc in , pues * no es ningn campo o valor, pero los SGBD lo admiten como atajo para simplemente contar todos los registros de la consulta... no intentis hacer SUM(*) porque eso fallar.
www.php-hispano.net/articulos/modelo-uso-bases-datos-relacionales.html?print=true
10/13
26/09/13
Ejemplo
Tabla clientes: id_cliente 2 10 13 nombre John Edward Rose
Ahora vamos a aadir el importe del pedido a la tabla de pedidos. El importe de un pedido es, generalmente, propiedad del pedido pues la suma de los productos que contiene podra variar y el precio del pedido no debera variar. Es discutible pues se puede argumentar que el cambio de precio de uno de los productos que pidi el cliente no debe implicar el cambio del precio del pedido, y tambin se puede argumentar que si se considera inviolable el precio de un pedido tambin lo tienen que ser los precios de los productos que contiene. id_pedido 1 2 3 4 5 6 7 8 9 id_cliente 2 2 10 2 10 10 10 2 10 fecha 2009-01-24 08:00:15 2009-01-24 09:22:08 2009-01-27 13:10:22 2009-01-27 17:54:15 2009-01-29 08:13:01 2009-01-29 10:28:57 2009-01-29 18:26:59 2009-01-30 12:30:47 2009-01-30 12:31:18 importe 940.00 100.00 64.00 85.00 540.00 50.00 35.00 1420.00 680.00
S E L E C Tc . i d _ c l i e n t e ,C O U N T ( * )n u m _ p e d i d o s ,S U M ( p . i m p o r t e )t o t a lF R O Mc l i e n t e scI N N E RJ O I Np e d i d o s pO Nc . i d _ c l i e n t e=p . i d _ c l i e n t eG R O U PB Yc . i d _ c l i e n t e
id_cliente 2 10
num_pedidos 4 5
Como vemos, el cliente 2 tiene 4 pedidos y el 10 tiene 5. El 13 no aparece porque no tiene ningn pedido y hemos usado INNER JOIN, pero si hubieramos usado LEFT JOIN s aparecera con num_pedidos a 0 (ojo, no NULL, pues es una funcin, no un campo). Tambin hemos sacado, a la vez, la suma de importes de sus pedidos. Si queremos saber cuntos pedidos al da hace un cliente, y de cunto es su pedido con importe ms pequeo, hacemos (MySQL):
id_cliente 2 2 10 10 2 10
num_pedidos 2 1 1 3 1 1
www.php-hispano.net/articulos/modelo-uso-bases-datos-relacionales.html?print=true
11/13
26/09/13
Consejos
La primera regla de oro es que el optimizador usar los ndic es para busc ar los registros segn los filtros que le hay amos dado en el W HERE o el ON de las JOINs. As, buscar por un nombre de cliente implicar que no usar ningn ndice si el campo nombre no es ndice. Esto no tiene por qu ser lento, pero relativamente lo ser porque si ese campo fuera un ndice la bsqueda sera mucho ms rpida. Algunos SGBD no tienen penalizaciones por indizar muchos campos de una tabla, pero en MySQL s que la hay ya que el tamao del ndice en general crece haciendo que sea inmensamente mayor y por lo tanto se pierde la ventaja de un ndice ligero. La gestin de los ndices es todo un mundo para los administradores de BD, eso cualquier DBA de Oracle os lo puede decir; as que ante la duda es mejor que consultis Internet o a vuestro admin a ver qu os puede recomendar. El tamao de fila es un valor muy importante en las c onsultas pues, si no hay ndice a usar, se rec orrer toda la fila para busc ar los c ampos del W HERE y as filtrar los resultados. Este valor se puede calcular con los tipos de dato de la tabla, o consultando el esquema INFORMATION_SCHEMA que contiene informacin sobre los meta-datos de todas las BD a las que tenemos acceso. Tambin suele haber herramientas tipo SHOW que pueden devolvernos esa informacin, todo es cuestin de buscar en la documentacin del SGBD. Es importante que los c ampos c lave externa estn indizados pues las JOIN los usan para realizar las uniones de registros con sus relacionados. Si tenemos en cuenta que por cada registro de una tabla primaria en una subordinada puede haber N registros relacionados, ralentizar mucho la consulta no tener un ndice en ese campo referencial. Las subc onsultas son lentas. Esto no es cierto al 100% pero mejor si creemos que s, pues para las subconsultas los SGBD suelen hac er tablas temporales, en memoria o en disco segn pueda o decida qu es lo mejor. No hay que subestimar al optomizador en estos casos pues suele ser muy listo a la hora de dec idir el c amino ms rpido para seguir. ORDER BY y GROUP BY suelen ser enemigos. Ordenar un resultado que se ha agrupado es tarea que los SGBD suelen hacer por ellos mismos, de modo que al agrupar por un campo casi seguro que aprovecha para ordenarlo (ascendementemente) por l. Si intentamos ordenar el resultado por otro campo, MySQL por ejemplo tendr que dar otra vuelta sobre el resultado para re-ordenarlo (apostad porque otros SGBD lo harn tambin). Asmismo, se suelen usar los ndices para el orden del resultado e indicar un orden distinto suele suponer una penalizacin en el tiempo de respuesta. De modo que usad el ORDER BY con la cabeza y cuando sea necesario, porque a vec es ordenamos sin nec esidad . No usis el HAV ING c omo sustituto del W HERE. El HAVING puede servir de WHERE (pero no a la inversa), pero no significa que convenga suplantarlo pues recordad que el filtro HAVING se aplica una vez satisfecho todo lo dems de la consulta: aqu y a no valen las ventajas de los ndic es. Evidentemente si queremos agrupar por un campo y obtener slo los grupos que tengan ms de X elementos o que la suma de uno de sus campos sea menor que Y, no queda otra que usar el HAVING, pues en el tiempo del WHERE no hay manera de saber cunto vale el resultado de una funcin de agregado. El orden de las tablas del JOIN s es importante . Cuando relacionamos tablas en una consulta, ya sea con JOINs o mediante condiciones del WHERE, es cierto que los optimizadores las unirn en el orden que ellos crean conveniente, pero a vec es se equivoc an . Sobre todo MySQL, que parece que cuando usamos JOIN su orden de unin es el que le damos... pero tenemos varias herramientas para solventar este problema: el consejo general es indic ar los JOIN en el orden ms lgic o que se nos oc urra, y si an as el optimizador lo cambia, el SGBD nos suele proporcionar herramientas para indicar en la consulta que se use el ndice que le digamos o se use el orden de unin que explcitamente le indiquemos. Es un buen consejo separar los c ampos c onsultados c on frec uenc ia de los c ampos grandes apenas usados. A veces tenemos en la tabla de clientes un montn de informacin que no se usa casi nunca, pero resulta que esa tabla de clientes es usada con frecuencia. Cuando las consultas no usen un ndice la penalizacin viene dada por el tamao de fila as que tener filas ms pequeas y cortas nos ahorrara muc ho tiempo de respuesta. La solucin es dividir la tabla en dos, con los campos ms frecuenemente consultados en una tabla y los dems en otra (los TEXT, BLOB, y grupos de un muchos campos tipo CHAR/VARCHAR); para esto en la tabla que apenas usaremos nos llevaremos el id de la otra y lo dejaremos c omo c lave primaria y fornea (externa) a su vez, en una relacin 1 a 1 donde la usada con frecuencia sera la fuerte y la otra la subordinada. El tamao de fila del ndic e mejor si es c onstante . Tener campos de tamao variable, como VARCHAR, en un ndice no suele ser buena idea si usas MySQL (sobre todo con el motor MyISAM) por cuestiones de rendimiento e incluso de consistencia. No sera la primera vez en la que yo haya visto que una tabla se corrompe por estas cosas... podemos echarle la culpa al SGBD, al sistema de ficheros del sistema o a lo que sea, pero es mejor que lo tengis en cuenta sobre todo si se rompe con frecuencia una tabla (en mi caso cambiar el engine a InnoDB era posible y solucion el problema). Las transac c iones seguras masivas son ms rpidas que las no seguras. Es habitual que tengamos que hacer cambios masivos en la BD, y especialmente notaremos una buena lentitud cuando sean inserciones. Los SGBD rara vez nos permiten usar distintos motores de almacenamiento de los datos, pero MySQL es un servidor que s nos da a elegir. Es bien sabido que una insercin masiva es ms rpida en MyISAM que en InnoDB, y la razn es la caracterstica de transacciones seguras de InnoDB. MyISAM en contra bloquea las tablas, que es en cierto modo peor si queremos que la tabla siga en uso mientras insertamos, pero la gran demora de InnoDB puede ser un problema... si no usamos transacciones seguras. Un buen truco es iniciar la transaccin segura, START TRANSACTION, realizar los INSERT, y luego finalizar la transaccin segura con COMMIT. Sinceramente no recuerdo si esto es aplicable a otros SGBD como SQL Server u Oracle, pero si os va lento podis probarlo.
www.php-hispano.net/articulos/modelo-uso-bases-datos-relacionales.html?print=true
12/13
26/09/13
Artculo realizado por Eloy Bote Falc n para PHP-Hispano.net Se permite la reproduccin total o parcial del contenido del artculo siempre que se mantenga el autor y una referencia a la fuente original.
www.php-hispano.net/articulos/modelo-uso-bases-datos-relacionales.html?print=true
13/13