Como posso retornar a saída da tabela dinâmica no MySQL?

312

Se eu tiver uma tabela MySQL parecida com esta:

número de página da ação company_name
-------------------------------
Empresa A PRINT 3
Empresa A PRINT 2
Empresa A PRINT 3
Empresa B EMAIL   
Empresa B PRINT 2
Empresa B PRINT 2
Empresa B PRINT 1
Empresa A PRINT 3

É possível executar uma consulta MySQL para obter uma saída como esta:

company_name EMAIL PRINT 1 páginas PRINT 2 páginas PRINT 3 páginas
-------------------------------------------------- -----------
EmpresaA 0 0 1 3
EmpresaB 1 1 2 0

A idéia é que pagecountpode variar, de modo que o valor da coluna de saída reflita isso, uma coluna para cada action/ pagecountpar e, em seguida, número de ocorrências por company_name. Não tenho certeza se isso é chamado de tabela dinâmica, mas alguém sugeriu isso?

peku
fonte
3
É chamado de pivot e é muito, muito mais rápido fazer essa transformação fora do SQL.
NB
1
O Excel rasga coisas assim, é realmente difícil no MySQL, pois não há operador "CROSSTAB" :(
Dave Rix
Sim, atualmente é feito manualmente no Excel e estamos tentando automatizá-lo.
peku
3
Aqui encontrei um exemplo passo a passo: como automatizar tabelas dinâmicas . e isso #
Devid G
1
@giannischristofakis - isso realmente depende do que você e seus colegas de trabalho consideram mais simples. A tecnologia alcançou um pouco desde que eu publiquei o comentário (4 anos), então depende totalmente do que você sente ser melhor - seja no aplicativo ou no SQL. Por exemplo, no meu trabalho, lidamos com problemas semelhantes, mas estamos combinando a abordagem SQL e no aplicativo. Basicamente, eu não posso ajudá-lo além de dar resposta opinativo e não é isso que você precisa :)
NB

Respostas:

236

Basicamente, essa é uma tabela dinâmica.

Um bom tutorial sobre como conseguir isso pode ser encontrado aqui: http://www.artfulsoftware.com/infotree/qrytip.php?id=78

Aconselho a leitura deste post e adapto esta solução às suas necessidades.

Atualizar

Depois que o link acima não está mais disponível no momento, sinto-me obrigado a fornecer algumas informações adicionais para todos vocês que procuram por respostas dinâmicas do mysql aqui. Ele realmente tinha uma grande quantidade de informações e não colocarei tudo aqui (ainda mais porque não quero copiar seu vasto conhecimento), mas darei alguns conselhos sobre como lidar com o pivô tabelas da maneira sql geralmente com o exemplo de peku que fez a pergunta em primeiro lugar.

Talvez o link volte em breve, eu vou ficar de olho nele.

A maneira da planilha ...

Muitas pessoas usam apenas uma ferramenta como MSExcel, OpenOffice ou outras ferramentas de planilha para esse fim. Esta é uma solução válida, basta copiar os dados e usar as ferramentas que a GUI oferece para resolver isso.

Mas ... essa não era a questão, e pode até levar a algumas desvantagens, como como inserir os dados na planilha, dimensionamento problemático e assim por diante.

O jeito SQL ...

Dada a tabela dele, é algo como isto:

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;

Agora olhe para a tabela desejada:

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

As linhas ( EMAIL, PRINT x pages) se assemelham às condições. O agrupamento principal é por company_name.

Para configurar as condições, isso significa que você usa a CASEinstrução-. A fim de grupo por alguma coisa, bem, uso ... GROUP BY.

O SQL básico que fornece esse pivô pode se parecer com isso:

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`;

Isso deve fornecer o resultado desejado muito rapidamente. A principal desvantagem dessa abordagem, quanto mais linhas você deseja na sua tabela dinâmica, mais condições você precisa definir na sua instrução SQL.

Isso também pode ser resolvido, portanto, as pessoas tendem a usar declarações, rotinas, contadores preparados e outros.

Alguns links adicionais sobre este tópico:

Bjoern
fonte
4
o link parece funcionar por enquanto ... se ele cair novamente, tente o seguinte: cache do Google, webcache.googleusercontent.com/… ou Internet Wayback Machine ( web.archive.org/web/20070303120558 * / artfulsoftware.com/ infotree / queries.php )
Lykegenes
o link está acessível neste URL artfulsoftware.com/infotree/qrytip.php?id=78
MrPandav
1
Há outra maneira de gerar uma tabela dinâmica sem usar "if", "case" ou "GROUP_CONCAT": en.wikibooks.org/wiki/MySQL/Pivot_table
user2513149
Você pode remover o ELSE NULL do seu CASE, pois hat é o comportamento padrão (e a agregação condicional é suficiente)
Caius Jard
86

Minha solução está no T-SQL sem nenhum pivô:

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
fonte
2
Isso funciona para mim mesmo no PostgreSQL. Eu prefiro este método de usar a extensão de referência cruzada no Postgres como este é mais limpo
itsols
2
"Minha solução está no T-SQL sem pivôs:" Não apenas o SQL Server deve funcionar na maioria dos fornecedores de bancos de dados que seguem os padrões ANSI SQL. Observe que SUM()só pode funcionar com dados numéricos se você precisar dinamizar seqüências de caracteres que você precisará usar #MAX()
Raymond Nijland
1
Eu acho que o CASE é anti-indesejável SUM(CASE WHEN (action='PRINT' AND pagecount=1) THEN 1 ELSE 0 END), você pode fazer isso, SUM(action='PRINT' AND pagecount=1)já que a condição será convertida em 1quando for verdadeira e 0quando for falsa
kajacx
1
@kajacx sim, embora seja necessário no banco de dados que não tenha esse tipo de manipulação booleana. Dada a escolha entre uma "sintaxe mais longa que funcione em todos os dB" e uma "sintaxe mais curta que só funciona em ..." eu escolheria a primeira
Caius Jard
66

Para o MySQL, você pode colocar diretamente as condições em SUM()função e elas serão avaliadas como booleanas 0ou,1 portanto, você poderá ter sua contagem com base em seus critérios sem usar IF/CASEinstruções

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
fonte
1
Essa é realmente legal. Você sabe se isso é compatível com os padrões em outras plataformas (como o Postgres)?
itsols
3
@itsols No seu por apenas específico Mysql
M Khalid Junaid
@itsols: adicionei outra versão padrão do SQL . O Postgres também possui uma função dedicada crosstab().
Erwin Brandstetter
2
Trabalha também para SQLite
SBF
37

Para dinâmica dinâmica, use GROUP_CONCATcom CONCAT. A função GROUP_CONCAT concatena seqüências de caracteres de um grupo em uma sequência com várias opções.

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 AQUI

Abhishek Gupta
fonte
2
Pacerier, verdadeiro homem, mas para dinâmica girando seu um dos a melhor abordagem
Abhishek Gupta
2
Isso funciona bem se você tiver muitos valores na coluna "ações" ou esperar que a lista cresça ao longo do tempo, pois escrever uma instrução de caso para cada valor pode ser demorado e difícil de manter atualizado.
Patrick Murphy
23

Uma versão stardard-SQL usando 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.

Quão?

TRUE OR NULL rendimentos TRUE.
FALSE OR NULLrendimentos NULL.
NULL OR NULLrendimentos NULL.
E COUNTconta apenas valores não nulos. Voilá.

Erwin Brandstetter
fonte
@ Erwin, mas como você saberia que existem três colunas? E se houver 5? 10? 20?
Pacerier
@ Pacerier: O exemplo na pergunta parece sugerir isso. De qualquer maneira, o SQL exige saber o tipo de retorno. uma consulta completamente dinâmica não é possível. Se o número de colunas de saída puder variar, você precisará de duas etapas: 1º construa a consulta, 2º: execute-a.
Erwin Brandstetter
11

A resposta correta é:

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
fonte
1
Este é apenas um exemplo que você tinha em mãos? Qual é a estrutura da other_valuetabela?
Patrick Murphy
1
"A resposta correta é:" Provavelmente não porque está faltando a SETconsulta para aumentar o valor padrão que é limitado a 1024 para GROUP_CONCAT após 1024 GROUP_CONCAT simplesmente trunca a string sem um erro, o que significa que resultados inesperados podem acontecer ..
Raymond Nijland
desculpe, os caras não conseguem se lembrar de mais detalhes. Faço coisas por diversão e depois esqueço ou destruo o projeto inteiro. Mas quando me deparei com um desafio, compartilho como o consertei. Eu sei que o meu exemplo não é muito detalhado, mas eu acho que pode dar instruções para aqueles que sabem o que está acima de encontro :)
Talha
9

Existe uma ferramenta chamada MySQL Pivot table generator, que pode ajudá-lo a criar uma tabela dinâmica baseada na Web que você pode exportar posteriormente para o Excel (se desejar). pode funcionar se seus dados estiverem em uma única tabela ou em várias tabelas.

Tudo o que você precisa fazer é especificar a fonte de dados das colunas (ela suporta colunas dinâmicas), linhas, valores no corpo da tabela e relacionamento da tabela (se houver) Tabela Dinâmica MySQL

A página inicial desta ferramenta é http://mysqlpivottable.net

Peter Green
fonte
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
fonte