Foros del Web » Programación para mayores de 30 ;) » Bases de Datos General » Mysql »

Listar registros con atributos en columnas

Estas en el tema de Listar registros con atributos en columnas en el foro de Mysql en Foros del Web. Hola a todos, resulta que tengo unas tablas con los siguientes campos: - libros id_libro,titulolibro 1,Odisea 2,Soledad - atributos id_atributo,nombreatributo 1,Precio 2,Fecha 3,Volumen 4,Empaste - ...
  #1 (permalink)  
Antiguo 27/06/2012, 22:53
Avatar de aovalle  
Fecha de Ingreso: junio-2002
Ubicación: Bogotá, Colombia
Mensajes: 54
Antigüedad: 21 años, 10 meses
Puntos: 0
Listar registros con atributos en columnas

Hola a todos, resulta que tengo unas tablas con los siguientes campos:

- libros
id_libro,titulolibro
1,Odisea
2,Soledad

- atributos
id_atributo,nombreatributo
1,Precio
2,Fecha
3,Volumen
4,Empaste

- atributoslibros
id_libro,id_atributo,valor_atributo
1,1,10000
1,2,1980
1,3,1
1,4,Rustico
2,1,0
2,2,1945
2,3,NULL
2,4,Tapa dura

Los atributos son unos pocos (precio, fecha, volumen, empaste, etc.) cada uno con su id y previamente creados en la tabla atributos.

Lo que deseo obtener en la consulta es una tabla en la que cada libro sea una fila con sus atributos como columnas, ejemplo:

id_libro,titulo,precio,fecha,volumen,empaste
1,Odisea,10000,1980,1,Rustico
2,Soledad,0,1945,NULL,Tapa dura

¿Es esto posible?, he probado con INNER JOIN, LEFT JOIN pero el problema es que me duplica los resultados.

De antemano gracias por sus aportes.
  #2 (permalink)  
Antiguo 28/06/2012, 01:54
Colaborador
 
Fecha de Ingreso: marzo-2008
Ubicación: Sabadell
Mensajes: 4.897
Antigüedad: 16 años, 1 mes
Puntos: 574
Respuesta: Listar registros con atributos en columnas

Tan variables son los atributos de la ficha de un libro para no ponerlos como campos de la tabla libros.... creo que te estas complicando la vida....

Código MySQL:
Ver original
  1. SELECT Sbc.id_libro,Sbc.titulolibro,
  2.              MAX(Sbc.APrecio) Precio,
  3.              MAX(Sbc.AFecha) Fecha,
  4.              ...
  5. (SELECT l.id_libro,l.titulolibro,
  6.              if(a.nombreatributo="Precio",al.valor_atributo,0) APrecio,
  7.              if(a.nombreatributo="Fecha",al.valor_atributo,0) AFecha,
  8.              ...
  9. FROM (libros l INNER JOIN atributoslibros al ON l.id_libro=al.id_libro)
  10.                       INNER JOIN atributos a ON a.id_atributo=al.id_atributo) Sbc
  11. GROUP BY Sbc.id_libro,Sbc.titulolibro;

Esto creo que te lo dará.... no se si usando GROUP_CONCAT se puede hacer (no me gusta).

Si tienes atributos con valor negativo twendrás que cambiar algo...

Pero en cualquier caso si pretendias hacer un sistema en que la lista de atributos fuera dinamica, en el momento que quieres hacer esto deja de serlo ya que la debes conocer ... con programacion externa si se podria construir un TRANSFORM dinamico..... pero el tema es que el diseño para la ficha de un libro no debe ser este....
__________________
Quim
--------------------------------------------------
Ayudar a ayudar es una buena práctica!!! Y da buenos resultados.
  #3 (permalink)  
Antiguo 28/06/2012, 10:37
Colaborador
 
Fecha de Ingreso: enero-2007
Ubicación: México
Mensajes: 2.097
Antigüedad: 17 años, 3 meses
Puntos: 447
Respuesta: Listar registros con atributos en columnas

Hola aovalle:

Complementando el comentario de quimfv, el utilizar funciones de agrupación (MAX, MIN, SUM) es una manera "tramposa" de hacerlo, ya que de entrada puedo suponer que el campo valor_atributo es de tipo VARCHAR, ya que almacenas tanto valores como cadenas y valores nulos... de tal forma que el resultado puede ser un tanto impredecible... hay otra forma de obtener el mismo resultado utilizando LEFT JOIN's sobre la tabla de atributoslibros, utilizando alias, de esta manera:

Código MySQL:
Ver original
  1. mysql> SELECT * FROM libros;
  2. +----------+-------------+
  3. | id_libro | titulolibro |
  4. +----------+-------------+
  5. |        1 | Odisea      |
  6. |        2 | Soledad     |
  7. +----------+-------------+
  8. 2 rows in set (0.00 sec)
  9.  
  10. mysql> SELECT * FROM atributoslibros;
  11. +----------+-------------+----------------+
  12. | id_libro | id_atributo | valor_atributo |
  13. +----------+-------------+----------------+
  14. |        1 |           1 | 10000          |
  15. |        1 |           2 | 1980           |
  16. |        1 |           3 | 1              |
  17. |        1 |           4 | Rustico        |
  18. |        2 |           1 | 0              |
  19. |        2 |           2 | 1945           |
  20. |        2 |           3 | NULL           |
  21. |        2 |           4 | Tapa dura      |
  22. +----------+-------------+----------------+
  23. 8 rows in set (0.01 sec)
  24.  
  25. mysql> SELECT
  26.     ->   l.id_libro,
  27.     ->   l.titulolibro,
  28.     ->   alPrecio.valor_atributo precio,
  29.     ->   alFecha.valor_atributo fecha,
  30.     ->   alVolumen.valor_atributo volumen,
  31.     ->   alEmpaste.valor_atributo empaste
  32.     -> FROM libros l
  33.     -> LEFT JOIN atributoslibros alPrecio
  34.     ->    ON l.id_libro = alPrecio.id_libro AND alPrecio.id_atributo = 1
  35.     -> LEFT JOIN atributoslibros alFecha
  36.     ->    ON l.id_libro = alFecha.id_libro AND alFecha.id_atributo = 2
  37.     -> LEFT JOIN atributoslibros alVolumen
  38.     ->    ON l.id_libro = alVolumen.id_libro AND alVolumen.id_atributo = 3
  39.     -> LEFT JOIN atributoslibros alEmpaste
  40.     ->    ON l.id_libro = alEmpaste.id_libro AND alEmpaste.id_atributo = 4;
  41. +----------+-------------+--------+-------+---------+-----------+
  42. | id_libro | titulolibro | precio | fecha | volumen | empaste   |
  43. +----------+-------------+--------+-------+---------+-----------+
  44. |        1 | Odisea      | 10000  | 1980  | 1       | Rustico   |
  45. |        2 | Soledad     | 0      | 1945  | NULL    | Tapa dura |
  46. +----------+-------------+--------+-------+---------+-----------+
  47. 2 rows in set (0.00 sec)

Tal como te menciona quimfv en su post, el problema es que la asignación de los alias para cada columna lo estoy haciendo de manera arbitraria, es decir sin tomar en cuenta el contenido del catálogo de atributos. Finalmente, puedes simplificar la consulta de quimfv sin necesidad de utilizar subconsultas, asi:


Código MySQL:
Ver original
  1. mysql> SELECT
  2.     ->   l.id_libro,
  3.     ->   l.titulolibro,
  4.     ->   MAX(IF(a.id_atributo = 1, a.valor_atributo, NULL)) precio,
  5.     ->   MAX(IF(a.id_atributo = 2, a.valor_atributo, NULL)) fecha,
  6.     ->   MAX(IF(a.id_atributo = 3, a.valor_atributo, NULL)) volumen,
  7.     ->   MAX(IF(a.id_atributo = 4, a.valor_atributo, NULL)) empaste
  8.     -> FROM libros l INNER JOIN atributoslibros a ON l.id_libro = a.id_libro
  9.     -> GROUP BY l.id_libro, l.titulolibro;
  10. +----------+-------------+--------+-------+---------+-----------+
  11. | id_libro | titulolibro | precio | fecha | volumen | empaste   |
  12. +----------+-------------+--------+-------+---------+-----------+
  13. |        1 | Odisea      | 10000  | 1980  | 1       | Rustico   |
  14. |        2 | Soledad     | 0      | 1945  | NULL    | Tapa dura |
  15. +----------+-------------+--------+-------+---------+-----------+
  16. 2 rows in set (0.00 sec)

Repitiendo, que el uso de la función MAX sobre un campo tipo varchar puede traer problemas.

Finalmente, podrías optar por cambiar el modelo de BD, después de todo no creo que los atributos de un libro sean muchos aparte de estos... podrías cambiar a un modelo con sólo dos tablas:

Código MySQL:
Ver original
  1. mysql> SELECT * FROM libros;
  2. +----------+-------------+
  3. | id_libro | titulolibro |
  4. +----------+-------------+
  5. |        1 | Odisea      |
  6. |        2 | Soledad     |
  7. +----------+-------------+
  8. 2 rows in set (0.00 sec)
  9.  
  10. mysql> select * from atributoslibros;
  11. +----------+--------+-------+---------+-----------+
  12. | id_libro | precio | fecha | volumen | empaste   |
  13. +----------+--------+-------+---------+-----------+
  14. |        1 |  10000 |  1980 |       1 | Rustico   |
  15. |        2 |      0 |  1945 |    NULL | Tapa dura |
  16. +----------+--------+-------+---------+-----------+
  17. 2 rows in set (0.07 sec)

Podrías agregar una columna de comentarios para agregar atributos adicionales... podrías definir tus campos precio, fecha y volumen de tipo numérico, lo que te permitiría obtener consultas por ejemplo de rangos de precios o de años en específicos (cosa que con tu modelo no lo podrías hacer).

Saludos
Leo.

Última edición por leonardo_josue; 28/06/2012 a las 10:48
  #4 (permalink)  
Antiguo 28/06/2012, 14:13
Avatar de aovalle  
Fecha de Ingreso: junio-2002
Ubicación: Bogotá, Colombia
Mensajes: 54
Antigüedad: 21 años, 10 meses
Puntos: 0
Respuesta: Listar registros con atributos en columnas

Hola,

Me ha servido de mucho la ayuda de sus respuestas, he logrado sacar la consulta sin ningun dificultad.

Realmente el planteamiento de la base creo que está bien, lo que pasa es que el sistema (es un sistema bibliotecario) tiene unos atributos definidos, y la posibilidad de crear atributos adicionales es una opción cuando la biblioteca tiene mas información para registrar que no está cubierta por los atributos principales.

Efectivamente los valores de los atributos se almacenan en un campo varchar, por lo que me queda la inquietud de que tipo de problemas podrían surgir al usar la función MAX sobre dicho campo.

Muchas gracias de nuevo!
  #5 (permalink)  
Antiguo 29/06/2012, 01:05
Colaborador
 
Fecha de Ingreso: marzo-2008
Ubicación: Sabadell
Mensajes: 4.897
Antigüedad: 16 años, 1 mes
Puntos: 574
Respuesta: Listar registros con atributos en columnas

Pues no habia caido pero tiene razon leonardo_josue, intenta ordenar estos valores guardados en un varchar

"1"
"1000000"
"2"

El maximo si es varchar seria 2!!!!

Luego la solución con join es la correcta.
__________________
Quim
--------------------------------------------------
Ayudar a ayudar es una buena práctica!!! Y da buenos resultados.

Etiquetas: atributos, columnas, join, registros, tabla, campos
Atención: Estás leyendo un tema que no tiene actividad desde hace más de 6 MESES, te recomendamos abrir un Nuevo tema en lugar de responder al actual.
Respuesta




La zona horaria es GMT -6. Ahora son las 14:19.