2009-08-06 31 views
118

Intenté buscar publicaciones, pero solo encontré soluciones para SQL Server/Access. Necesito una solución en MySQL (5.X).MySQL - Filas a columnas

Tengo una tabla (llamada historia) con 3 columnas: hostid, itemname, itemvalue.
Si hago un selecto (select * from history), volverá

+--------+----------+-----------+ 
    | hostid | itemname | itemvalue | 
    +--------+----------+-----------+ 
    | 1 | A  | 10  | 
    +--------+----------+-----------+ 
    | 1 | B  |  3  | 
    +--------+----------+-----------+ 
    | 2 | A  |  9  | 
    +--------+----------+-----------+ 
    | 2 | c  | 40  | 
    +--------+----------+-----------+ 

¿Cómo consultar la base de datos para devolver algo así como

+--------+------+-----+-----+ 
    | hostid | A | B | C | 
    +--------+------+-----+-----+ 
    | 1 | 10 | 3 | 0 | 
    +--------+------+-----+-----+ 
    | 2 | 9 | 0 | 40 | 
    +--------+------+-----+-----+ 
+0

@ Rob, ¿Puede por favor editar la pregunta para incluir la consulta exacta? – Johan

Respuesta

28
SELECT 
    hostid, 
    sum(if(itemname = 'A', itemvalue, 0)) AS A, 
    sum(if(itemname = 'B', itemvalue, 0)) AS B, 
    sum(if(itemname = 'C', itemvalue, 0)) AS C 
FROM 
    bob 
GROUP BY 
    hostid; 
+0

Crea tres filas diferentes, para 'A', 'B', 'C' – Palani

+0

@Palani: No, no es así. Consulte ['group by'] (https://www.google.com/search?q=group+by). – ruakh

+0

gracias budy ayuda –

3

hago eso en Group By hostId continuación, se mostrará solo primera fila con valores,
como:

A B C 
1 10 
2  3 
5

uso subconsulta

SELECT hostid, 
    (SELECT VALUE FROM TableTest WHERE ITEMNAME='A' AND hostid = t1.hostid) AS A, 
    (SELECT VALUE FROM TableTest WHERE ITEMNAME='B' AND hostid = t1.hostid) AS B, 
    (SELECT VALUE FROM TableTest WHERE ITEMNAME='C' AND hostid = t1.hostid) AS C 
FROM TableTest AS T1 
GROUP BY hostid 

pero va a ser un problema si la consulta sub dando como resultado más de una fila, utilice la función adicional agregado en la subconsulta

177

Voy a añadir un poco más largo y más detallado explicación de los pasos a seguir para resolver este problema. Me disculpo si es demasiado largo.


Voy a empezar a cabo con la base que has dado y lo utilizan para definir un par de términos que voy a utilizar para el resto de este post. Esta será la tabla base de:

select * from history; 

+--------+----------+-----------+ 
| hostid | itemname | itemvalue | 
+--------+----------+-----------+ 
|  1 | A  |  10 | 
|  1 | B  |   3 | 
|  2 | A  |   9 | 
|  2 | C  |  40 | 
+--------+----------+-----------+ 

Este será nuestro objetivo, los tabla dinámica bastante:

select * from history_itemvalue_pivot; 

+--------+------+------+------+ 
| hostid | A | B | C | 
+--------+------+------+------+ 
|  1 | 10 | 3 | 0 | 
|  2 | 9 | 0 | 40 | 
+--------+------+------+------+ 

Los valores en la columna de la history.hostid se convertirán en y-valores en el tabla dinámica. Los valores en la columna history.itemname se convertirán en x-values ​​ (por razones obvias).


Cuando tengo que resolver el problema de la creación de una tabla dinámica, I abordarlo utilizando un proceso de tres pasos (con un cuarto paso opcional):

  1. seleccionar las columnas de interés, es decir,y-valores y valores x
  2. extienden la tabla base con columnas adicionales - una para cada uno x-valor
  3. grupo y el agregado de la mesa extendida - un grupo para cada valor de y
  4. (opcional) prettify la tabla agregada

vamos a aplicar estas medidas para su problema y ver lo que obtenemos:

Paso 1: seleccione columnas de interés. En el resultado deseado, hostid proporciona y-values ​​ y itemname proporciona x-values ​​.

Paso 2: extienda la tabla base con columnas adicionales. Por lo general, necesitamos una columna por valor x. Recordemos que nuestra columna valor de x es itemname:

create view history_extended as (
    select 
    history.*, 
    case when itemname = "A" then itemvalue end as A, 
    case when itemname = "B" then itemvalue end as B, 
    case when itemname = "C" then itemvalue end as C 
    from history 
); 

select * from history_extended; 

+--------+----------+-----------+------+------+------+ 
| hostid | itemname | itemvalue | A | B | C | 
+--------+----------+-----------+------+------+------+ 
|  1 | A  |  10 | 10 | NULL | NULL | 
|  1 | B  |   3 | NULL | 3 | NULL | 
|  2 | A  |   9 | 9 | NULL | NULL | 
|  2 | C  |  40 | NULL | NULL | 40 | 
+--------+----------+-----------+------+------+------+ 

Tenga en cuenta que no hemos cambiado el número de filas - que acaba de agregar columnas adicionales. También tenga en cuenta el patrón de NULL s: una fila con itemname = "A" tiene un valor no nulo para la nueva columna A, y valores nulos para las otras columnas nuevas.

Paso 3: agrupe y agregue la tabla extendida. Necesitamos group by hostid, ya que proporciona los valores de y: (. Tenga en cuenta que ahora tenemos una fila por cada valor de y)

create view history_itemvalue_pivot as (
    select 
    hostid, 
    sum(A) as A, 
    sum(B) as B, 
    sum(C) as C 
    from history_extended 
    group by hostid 
); 

select * from history_itemvalue_pivot; 

+--------+------+------+------+ 
| hostid | A | B | C | 
+--------+------+------+------+ 
|  1 | 10 | 3 | NULL | 
|  2 | 9 | NULL | 40 | 
+--------+------+------+------+ 

bien, estamos casi allí! Solo tenemos que deshacernos de esos feos NULL s.

Paso 4: pretifique. Sólo vamos a reemplazar los valores nulos con ceros por lo que el conjunto de resultados es más agradable a la vista:

create view history_itemvalue_pivot_pretty as (
    select 
    hostid, 
    coalesce(A, 0) as A, 
    coalesce(B, 0) as B, 
    coalesce(C, 0) as C 
    from history_itemvalue_pivot 
); 

select * from history_itemvalue_pivot_pretty; 

+--------+------+------+------+ 
| hostid | A | B | C | 
+--------+------+------+------+ 
|  1 | 10 | 3 | 0 | 
|  2 | 9 | 0 | 40 | 
+--------+------+------+------+ 

Y hemos terminado - hemos construido una tabla dinámica agradable, bonita utilizando MySQL.


Consideraciones al aplicar este procedimiento:

  • que valor usar en las columnas adicionales. Usé itemvalue en este ejemplo
  • qué valor "neutral" usar en las columnas adicionales. Usé NULL, pero también podría ser 0 o "", dependiendo de su situación exacta
  • qué función agregada utilizar al agrupar. Usé sum, pero también se usan a menudo count y max (max se usa a menudo al construir "objetos" de una fila que se han distribuido en muchas filas)
  • usando múltiples columnas para valores y.Esta solución no se limita a la utilización de una sola columna para los valores de y - sólo tiene que enchufar las columnas adicionales en la cláusula group by (y no se olvide de ellos select)

limitaciones conocidas:

  • esta solución no permite n columnas en la tabla dinámica; cada columna dinámica debe agregarse manualmente al extender la tabla base. Entonces, para 5 o 10 valores x, esta solución es agradable. Para 100, no tan agradable. Hay algunas soluciones con procedimientos almacenados que generan una consulta, pero son feas y difíciles de corregir. Actualmente no sé de una buena manera de resolver este problema cuando la tabla pivote necesita tener muchas columnas.
+13

+1 Esta es de lejos la mejor/más clara explicación de las tablas pivote/pestañas cruzadas en MySQL que he visto –

+4

Excelente explicación, gracias. El paso 4 podría combinarse en el paso 3 utilizando IFNULL (suma (A), 0) como A, obteniendo el mismo resultado pero sin la necesidad de crear otra tabla – nealio82

+0

Mejor explicación que nunca. Impresionante – Matarishvan

19

Aprovechando la idea de Matt Fenwick que me ayudó a resolver el problema (una gran cantidad de gracias), vamos a reducirlo a una sola consulta:

select 
    history.*, 
    coalesce(sum(case when itemname = "A" then itemvalue end), 0) as A, 
    coalesce(sum(case when itemname = "B" then itemvalue end), 0) as B, 
    coalesce(sum(case when itemname = "C" then itemvalue end), 0) as C 
from history 
group by hostid 
+0

Gracias Nik por las correcciones, necesito clases de inglés :) – jalber

9

edito Agung Sagita 's respuesta de subconsulta para unirse. No estoy seguro de la cantidad de diferencia entre esta 2 vía, pero solo para otra referencia.

SELECT hostid, T2.VALUE AS A, T3.VALUE AS B, T4.VALUE AS C 
FROM TableTest AS T1 
LEFT JOIN TableTest T2 ON T2.hostid=T1.hostid AND T2.ITEMNAME='A' 
LEFT JOIN TableTest T3 ON T3.hostid=T1.hostid AND T3.ITEMNAME='B' 
LEFT JOIN TableTest T4 ON T4.hostid=T1.hostid AND T4.ITEMNAME='C' 
+1

Posiblemente, esta podría ser una solución más rápida. –

1

Esta no es la respuesta exacta que está buscando pero era una solución que necesitaba en mi proyecto y espero que esto ayude a alguien. Esto mostrará una lista de n elementos de fila separados por comas. Group_Concat hace esto posible en MySQL.

select 
cemetery.cemetery_id as "Cemetery_ID", 
GROUP_CONCAT(distinct(names.name)) as "Cemetery_Name", 
cemetery.latitude as Latitude, 
cemetery.longitude as Longitude, 
c.Contact_Info, 
d.Direction_Type, 
d.Directions 

    from cemetery 
    left join cemetery_names on cemetery.cemetery_id = cemetery_names.cemetery_id 
    left join names on cemetery_names.name_id = names.name_id 
    left join cemetery_contact on cemetery.cemetery_id = cemetery_contact.cemetery_id 

    left join 
    (
     select 
      cemetery_contact.cemetery_id as cID, 
      group_concat(contacts.name, char(32), phone.number) as Contact_Info 

       from cemetery_contact 
       left join contacts on cemetery_contact.contact_id = contacts.contact_id 
       left join phone on cemetery_contact.contact_id = phone.contact_id 

      group by cID 
    ) 
    as c on c.cID = cemetery.cemetery_id 


    left join 
    (
     select 
      cemetery_id as dID, 
      group_concat(direction_type.direction_type) as Direction_Type, 
      group_concat(directions.value , char(13), char(9)) as Directions 

       from directions 
       left join direction_type on directions.type = direction_type.direction_type_id 

      group by dID 


    ) 
    as d on d.dID = cemetery.cemetery_id 

group by Cemetery_ID 

Este cementerio tiene dos nombres comunes por lo que los nombres figuran en diferentes filas conectadas por un único ID, pero dos identificadores de nombre y la consulta produce algo como esto

        CemeteryID         Cemetery_Name                         Latitud
                                        Appleton, Sulpher resortes     35,4276242832293

1

Mi solución:

select h.hostid, sum(ifnull(h.A,0)) as A, sum(ifnull(h.B,0)) as B, sum(ifnull(h.C,0)) as C from (
select 
hostid, 
case when itemName = 'A' then itemvalue end as A, 
case when itemName = 'B' then itemvalue end as B, 
case when itemName = 'C' then itemvalue end as C 
    from history 
) h group by hostid 

Produce los resultados esperados en el caso enviado.

15

Otra opción, especialmente útil si tiene muchos elementos que necesita para girar es dejar que MySQL elabora la consulta para usted:

SELECT 
    GROUP_CONCAT(DISTINCT 
    CONCAT(
     'ifnull(SUM(case when itemname = ''', 
     itemname, 
     ''' then itemvalue end),0) AS `', 
     itemname, '`' 
    ) 
) INTO @sql 
FROM 
    history; 
SET @sql = CONCAT('SELECT hostid, ', @sql, ' 
        FROM history 
        GROUP BY hostid'); 

PREPARE stmt FROM @sql; 
EXECUTE stmt; 
DEALLOCATE PREPARE stmt; 

FIDDLE añadido algunos valores adicionales para ver su funcionamiento

GROUP_CONCAT tiene un valor predeterminado de 1000 por lo que si tiene una consulta realmente grande, cambie este parámetro antes de ejecutarlo

SET SESSION group_concat_max_len = 1000000; 

prueba:

DROP TABLE IF EXISTS history; 
CREATE TABLE history 
(hostid INT, 
itemname VARCHAR(5), 
itemvalue INT); 

INSERT INTO history VALUES(1,'A',10),(1,'B',3),(2,'A',9), 
(2,'C',40),(2,'D',5), 
(3,'A',14),(3,'B',67),(3,'D',8); 

    hostid A  B  C  D 
    1  10  3  0  0 
    2  9  0 40  5 
    3  14  67  0  8 
+0

Esto es simplemente demasiado bueno ..... Y me ayudó a obtener resultados en mi consulta .... – Madhura

+0

Fiddle tiene un error –

1

que descubra una manera de hacer que mis informes convertir filas a columnas casi dinámicos utilizando querys simples. Puedes verlo y probarlo online here.

El número de columnas de consulta se fija pero los valores son dinámicos y en base a los valores de las filas. Se puede construir Así, utilizo una consulta para construir la cabecera de la tabla y otro para ver los valores:

SELECT distinct concat('<th>',itemname,'</th>') as column_name_table_header FROM history order by 1; 

SELECT 
    hostid 
    ,(case when itemname = (select distinct itemname from history a order by 1 limit 0,1) then itemvalue else '' end) as col1 
    ,(case when itemname = (select distinct itemname from history a order by 1 limit 1,1) then itemvalue else '' end) as col2 
    ,(case when itemname = (select distinct itemname from history a order by 1 limit 2,1) then itemvalue else '' end) as col3 
    ,(case when itemname = (select distinct itemname from history a order by 1 limit 3,1) then itemvalue else '' end) as col4 
FROM history order by 1; 

se puede resumir, también:

SELECT 
    hostid 
    ,sum(case when itemname = (select distinct itemname from history a order by 1 limit 0,1) then itemvalue end) as A 
    ,sum(case when itemname = (select distinct itemname from history a order by 1 limit 1,1) then itemvalue end) as B 
    ,sum(case when itemname = (select distinct itemname from history a order by 1 limit 2,1) then itemvalue end) as C 
FROM history group by hostid order by 1; 
+--------+------+------+------+ 
| hostid | A | B | C | 
+--------+------+------+------+ 
|  1 | 10 | 3 | NULL | 
|  2 | 9 | NULL | 40 | 
+--------+------+------+------+ 

Resultados de RexTester:

Results of RexTester

http://rextester.com/ZSWKS28923

Para un ejemplo real de uso, este informe a continuación muestra en columnas las horas de salidas de salidas de barco/autobús con un horario visual. Verá una columna adicional no utilizado en el último col sin confundir a la visualización: sistema venda de passagens online e consumidor final e controle de frota - xsl tecnologia - xsl.com.br ** de sistema de boletos de venta de entradas en línea y presencial