¿Cómo puedo devolver la salida de la tabla dinámica en MySQL?

312

Si tengo una tabla MySQL que se parece a esto:

recuento de páginas de acción nombre_empresa
-------------------------------
Empresa A PRINT 3
Empresa A PRINT 2
Empresa A PRINT 3
Empresa B CORREO ELECTRÓNICO   
Empresa B IMPRIMIR 2
Empresa B IMPRIMIR 2
Empresa B IMPRESIÓN 1
Empresa A PRINT 3

¿Es posible ejecutar una consulta MySQL para obtener resultados como este:

nombre_empresa EMAIL IMPRIMIR 1 páginas IMPRIMIR 2 páginas IMPRIMIR 3 páginas
-------------------------------------------------- -----------
Empresa A 0 0 1 3
EmpresaB 1 1 2 0

La idea es que pagecountpuede variar de modo que la cantidad de columna de salida debería reflejar que, una columna para cada action/ pagecountpar y luego el número de visitas por company_name. No estoy seguro de si esto se llama una tabla dinámica, pero alguien sugirió eso.

peku
fuente
3
Se llama pivotar y es mucho, mucho más rápido hacer esta transformación fuera de SQL.
NB
1
Excel analiza cosas como esta, es realmente difícil en MySQL ya que no hay un operador "CROSSTAB" :(
Dave Rix
Sí, actualmente se hace a mano en Excel y estamos tratando de automatizarlo.
peku
3
Aquí encontré un ejemplo paso a paso: cómo automatizar tablas dinámicas . y esto
Devid G
1
@giannischristofakis: realmente depende de lo que usted y sus compañeros de trabajo consideren más simple. La tecnología se puso bastante al día desde que publiqué el comentario (4 años), por lo que depende totalmente de lo que consideres mejor, ya sea en la aplicación o en SQL. Por ejemplo, en mi trabajo lidiamos con un problema similar, pero estamos combinando tanto el enfoque SQL como en la aplicación. Básicamente, no puedo ayudarte más que dar una respuesta obstinada y eso no es lo que necesitas :)
NB

Respuestas:

236

Esto es básicamente una tabla dinámica.

Un buen tutorial sobre cómo lograr esto se puede encontrar aquí: http://www.artfulsoftware.com/infotree/qrytip.php?id=78

Aconsejo leer esta publicación y adaptar esta solución a sus necesidades.

Actualizar

Después de que el enlace anterior ya no esté disponible, me siento obligado a proporcionar información adicional para todos ustedes que buscan respuestas de mysql pivot aquí. Realmente tenía una gran cantidad de información, y no pondré todo desde allí (incluso más, ya que no quiero copiar su vasto conocimiento), pero daré algunos consejos sobre cómo lidiar con pivot Tablas de la manera sql generalmente con el ejemplo de peku que hizo la pregunta en primer lugar.

Tal vez el enlace vuelva pronto, estaré atento.

La forma de la hoja de cálculo ...

Muchas personas simplemente usan una herramienta como MSExcel, OpenOffice u otras herramientas de hoja de cálculo para este propósito. Esta es una solución válida, solo copie los datos allí y use las herramientas que ofrece la GUI para resolver esto.

Pero ... esta no era la pregunta, e incluso podría dar lugar a algunas desventajas, como cómo ingresar los datos en la hoja de cálculo, la escala problemática, etc.

La forma SQL ...

Dada su mesa se ve algo así:

CREATE TABLE `test_pivot` (
  `pid` bigint(20) NOT NULL AUTO_INCREMENT,
  `company_name` varchar(32) DEFAULT NULL,
  `action` varchar(16) DEFAULT NULL,
  `pagecount` bigint(20) DEFAULT NULL,
  PRIMARY KEY (`pid`)
) ENGINE=MyISAM;

Ahora mire en su mesa deseada:

company_name    EMAIL   PRINT 1 pages   PRINT 2 pages   PRINT 3 pages
-------------------------------------------------------------
CompanyA        0       0               1               3
CompanyB        1       1               2               0

Las filas ( EMAIL, PRINT x pages) se parecen a las condiciones. La agrupación principal es por company_name.

Para configurar las condiciones, esto grita más bien por usar la CASEdeclaración. Con el fin de agrupar por algo, así, el uso ... GROUP BY.

El SQL básico que proporciona este pivote puede verse así:

SELECT  P.`company_name`,
    COUNT(
        CASE 
            WHEN P.`action`='EMAIL' 
            THEN 1 
            ELSE NULL 
        END
    ) AS 'EMAIL',
    COUNT(
        CASE 
            WHEN P.`action`='PRINT' AND P.`pagecount` = '1' 
            THEN P.`pagecount` 
            ELSE NULL 
        END
    ) AS 'PRINT 1 pages',
    COUNT(
        CASE 
            WHEN P.`action`='PRINT' AND P.`pagecount` = '2' 
            THEN P.`pagecount` 
            ELSE NULL 
        END
    ) AS 'PRINT 2 pages',
    COUNT(
        CASE 
            WHEN P.`action`='PRINT' AND P.`pagecount` = '3' 
            THEN P.`pagecount` 
            ELSE NULL 
        END
    ) AS 'PRINT 3 pages'
FROM    test_pivot P
GROUP BY P.`company_name`;

Esto debería proporcionar el resultado deseado muy rápido. El principal inconveniente de este enfoque, cuantas más filas desee en su tabla dinámica, más condiciones deberá definir en su declaración SQL.

Esto también puede tratarse, por lo tanto, las personas tienden a usar declaraciones preparadas, rutinas, contadores y demás.

Algunos enlaces adicionales sobre este tema:

Bjoern
fuente
44
el enlace parece funcionar por ahora ... si alguna vez vuelve a fallar , intente estos: el caché de Google webcache.googleusercontent.com/… o Internet Wayback Machine ( web.archive.org/web/20070303120558 * / artfulsoftware.com/ infotree / queries.php )
Lykegenes
Se puede acceder al enlace en esta url artfulsoftware.com/infotree/qrytip.php?id=78
MrPandav
1
Hay otra forma de generar una tabla dinámica sin usar "if", "case" o "GROUP_CONCAT": en.wikibooks.org/wiki/MySQL/Pivot_table
user2513149
Puede eliminar ELSE NULL de su CASE ya que ese es el comportamiento predeterminado (y la agregación condicional es lo suficientemente prolija)
Caius Jard
86

Mi solución está en T-SQL sin ningún pivote:

SELECT
    CompanyName,  
    SUM(CASE WHEN (action='EMAIL') THEN 1 ELSE 0 END) AS Email,
    SUM(CASE WHEN (action='PRINT' AND pagecount=1) THEN 1 ELSE 0 END) AS Print1Pages,
    SUM(CASE WHEN (action='PRINT' AND pagecount=2) THEN 1 ELSE 0 END) AS Print2Pages,
    SUM(CASE WHEN (action='PRINT' AND pagecount=3) THEN 1 ELSE 0 END) AS Print3Pages
FROM 
    Company
GROUP BY 
    CompanyName
RRM
fuente
2
Esto funciona para mí incluso en PostgreSQL. Prefiero este método que usar la extensión de tabla de
referencias
2
"Mi solución está en T-SQL sin ningún pivote:" No solo SQL Server debería funcionar en la mayoría de los proveedores de bases de datos que siguen los estándares ANSI SQL. Tenga en cuenta que SUM()solo puede funcionar con datos numéricos si necesita girar cadenas que tendrá que usarMAX()
Raymond Nijland el
1
Creo que el CASO no es necesario SUM(CASE WHEN (action='PRINT' AND pagecount=1) THEN 1 ELSE 0 END), puedes hacerlo SUM(action='PRINT' AND pagecount=1)ya que la condición se convertirá 1en verdadera y 0falsa
kajacx
1
@kajacx sí, aunque es necesario en una base de datos que no tiene ese tipo de manipulación booleana. Dada la opción entre una "sintaxis más larga que funciona en todos los dB" y una "sintaxis más corta que solo funciona en ...", elegiría la primera
Caius Jard,
66

Para MySQL, puede poner directamente condiciones en SUM()función y se evaluará como booleano 0o,1 por lo tanto, puede tener su recuento en función de sus criterios sin usar IF/CASEdeclaraciones

SELECT
    company_name,  
    SUM(action = 'EMAIL')AS Email,
    SUM(action = 'PRINT' AND pagecount = 1)AS Print1Pages,
    SUM(action = 'PRINT' AND pagecount = 2)AS Print2Pages,
    SUM(action = 'PRINT' AND pagecount = 3)AS Print3Pages
FROM t
GROUP BY company_name

DEMO

M Khalid Junaid
fuente
1
Esa es una muy buena. ¿Sabes si esto cumple con los estándares en otras plataformas (como Postgres)?
itsols
3
@itsols No es solo para Mysql específico
M Khalid Junaid
@itsols: agregué otra versión estándar de SQL . Postgres también tiene una función dedicada crosstab().
Erwin Brandstetter
2
Funciona también para SQLite
SBF
37

Para pivote dinámico, use GROUP_CONCATcon CONCAT. La función GROUP_CONCAT concatena cadenas de un grupo en una cadena con varias opciones.

SET @sql = NULL;
SELECT
    GROUP_CONCAT(DISTINCT
    CONCAT(
      'SUM(CASE WHEN action = "',
      action,'"  AND ', 
           (CASE WHEN pagecount IS NOT NULL 
           THEN CONCAT("pagecount = ",pagecount) 
           ELSE pagecount IS NULL END),
      ' THEN 1 ELSE 0 end) AS ',
      action, IFNULL(pagecount,'')

    )
  )
INTO @sql
FROM
  t;

SET @sql = CONCAT('SELECT company_name, ', @sql, ' 
                  FROM t 
                   GROUP BY company_name');

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

DEMO AQUÍ

Abhishek Gupta
fuente
2
Pacerier, verdadero hombre pero por pivote dinámico es uno de los mejores enfoques
Abhishek Gupta
2
Esto funciona bien si tiene muchos valores en la columna "acciones" o espera que la lista crezca con el tiempo, ya que escribir una declaración de caso para cada valor puede llevar mucho tiempo y ser difícil de mantener actualizado.
Patrick Murphy
23

Un stardard-SQL versión usa lógica booleana :

SELECT company_name
     , COUNT(action = 'EMAIL' OR NULL) AS "Email"
     , COUNT(action = 'PRINT' AND pagecount = 1 OR NULL) AS "Print 1 pages"
     , COUNT(action = 'PRINT' AND pagecount = 2 OR NULL) AS "Print 2 pages"
     , COUNT(action = 'PRINT' AND pagecount = 3 OR NULL) AS "Print 3 pages"
FROM   tbl
GROUP  BY company_name;

SQL Fiddle.

¿Cómo?

TRUE OR NULL rendimientos TRUE.
FALSE OR NULLrendimientos NULL.
NULL OR NULLrendimientos NULL.
Y COUNTsolo cuenta valores no nulos. Voilá

Erwin Brandstetter
fuente
@ Erwin, ¿pero cómo sabrías que hay tres columnas? ¿Qué pasa si hay 5? 10? 20?
Pacerier
@Pacerier: El ejemplo en la pregunta parece sugerir eso. De cualquier manera, SQL exige saber el tipo de retorno. Una consulta completamente dinámica no es posible. Si el número de columnas de salida puede variar, necesita dos pasos: primero compilar la consulta, segundo: ejecutarla.
Erwin Brandstetter
11

La respuesta correcta es:

select table_record_id,
group_concat(if(value_name='note', value_text, NULL)) as note
,group_concat(if(value_name='hire_date', value_text, NULL)) as hire_date
,group_concat(if(value_name='termination_date', value_text, NULL)) as termination_date
,group_concat(if(value_name='department', value_text, NULL)) as department
,group_concat(if(value_name='reporting_to', value_text, NULL)) as reporting_to
,group_concat(if(value_name='shift_start_time', value_text, NULL)) as shift_start_time
,group_concat(if(value_name='shift_end_time', value_text, NULL)) as shift_end_time
from other_value
where table_name = 'employee'
and is_active = 'y'
and is_deleted = 'n'
GROUP BY table_record_id
Talha
fuente
1
¿Es esto solo un ejemplo que tenías a mano? ¿Cuál es la estructura de la other_valuemesa?
Patrick Murphy
1
"La respuesta correcta es:" Lo más probable es que no falte la SETconsulta para aumentar el valor de defualt que está limitado a 1024 para GROUP_CONCAT después de 1024 GROUP_CONCAT simplemente trunca la cadena sin un error, lo que significa que pueden ocurrir resultados inesperados ...
Raymond Nijland
lo siento chicos no puedo recordar más detalles. Hago cosas por diversión y luego olvido o destruyo todo el proyecto. Pero cuando me encuentro con un desafío, comparto cómo lo solucioné. Sé que mi ejemplo no es muy detallado, pero creo que puede dar instrucciones a aquellos que saben a qué se enfrentan :)
Talha
9

Existe una herramienta llamada generador de tabla dinámica de MySQL, que puede ayudarlo a crear una tabla dinámica basada en web que luego puede exportar a Excel (si lo desea). puede funcionar si sus datos están en una sola tabla o en varias tablas.

Todo lo que necesita hacer es especificar la fuente de datos de las columnas (admite columnas dinámicas), filas, los valores en el cuerpo de la tabla y la relación de la tabla (si hay alguna) Tabla pivote de MySQL

La página de inicio de esta herramienta es http://mysqlpivottable.net

Peter Green
fuente
3
select t3.name, sum(t3.prod_A) as Prod_A, sum(t3.prod_B) as Prod_B, sum(t3.prod_C) as    Prod_C, sum(t3.prod_D) as Prod_D, sum(t3.prod_E) as Prod_E  
from
(select t2.name as name, 
case when t2.prodid = 1 then t2.counts
else 0 end  prod_A, 

case when t2.prodid = 2 then t2.counts
else 0 end prod_B,

case when t2.prodid = 3 then t2.counts
else 0 end prod_C,

case when t2.prodid = 4 then t2.counts
else 0 end prod_D, 

case when t2.prodid = "5" then t2.counts
else 0 end prod_E

from 
(SELECT partners.name as name, sales.products_id as prodid, count(products.name) as counts
FROM test.sales left outer join test.partners on sales.partners_id = partners.id
left outer join test.products on sales.products_id = products.id 
where sales.partners_id = partners.id and sales.products_id = products.id group by partners.name, prodid) t2) t3

group by t3.name ;
irba
fuente