Come posso restituire l'output della tabella pivot in MySQL?


312

Se ho una tabella MySQL simile a questa:

pagecount azione nome_azienda
-------------------------------
Azienda A PRINT 3
Azienda A PRINT 2
Azienda A PRINT 3
Azienda B EMAIL   
Azienda B STAMPA 2
Azienda B STAMPA 2
Azienda B STAMPA 1
Azienda A PRINT 3

È possibile eseguire una query MySQL per ottenere un output in questo modo:

company_name EMAIL STAMPA 1 pagine STAMPA 2 pagine STAMPA 3 pagine
-------------------------------------------------- -----------
Azienda A 0 0 1 3
AziendaB 1 1 2 0

L'idea è che pagecountpuò variare, quindi la quantità della colonna di output dovrebbe riflettere quella, una colonna per ogni action/ pagecountcoppia e quindi il numero di hit per company_name. Non sono sicuro se questo si chiama tabella pivot ma qualcuno l'ha suggerito?


3
Si chiama pivot ed è molto, molto più veloce eseguire questa trasformazione al di fuori di SQL.
NB

1
Excel strappa cose come questa, è davvero difficile in MySQL in quanto non esiste un operatore "CROSSTAB" :(
Dave Rix,

Sì, attualmente è fatto a mano in Excel e stiamo cercando di automatizzarlo.
peku,

3
Qui ho trovato un esempio passo passo: come automatizzare le tabelle pivot . e questo
Devid G

1
@giannischristofakis - dipende davvero da cosa tu e i tuoi colleghi ritenete più semplici. La tecnologia ha recuperato un bel po 'da quando ho pubblicato il commento (4 anni), quindi dipende totalmente da ciò che ritieni sia migliore, sia nell'applicazione che in SQL. Ad esempio, nel mio lavoro ci occupiamo di un problema simile, ma stiamo combinando sia l'approccio SQL che quello in-app. Fondamentalmente, non posso aiutarti se non dare una risposta supponente e non è quello che ti serve :)
NB

Risposte:


236

Questa è fondamentalmente una tabella pivot.

Un bel tutorial su come raggiungere questo obiettivo può essere trovato qui: http://www.artfulsoftware.com/infotree/qrytip.php?id=78

Vi consiglio di leggere questo post e di adattare questa soluzione alle vostre esigenze.

Aggiornare

Dopo che il link qui sopra non è più disponibile, mi sento obbligato a fornire alcune informazioni aggiuntive per tutti voi alla ricerca di risposte pivot mysql qui. Aveva davvero una grande quantità di informazioni e non inserirò tutto da lì (anche di più, dato che non voglio copiare la loro vasta conoscenza), ma darò alcuni consigli su come gestire il pivot tabelle in modo sql generalmente con l'esempio di peku che ha posto la domanda in primo luogo.

Forse il link ritorna presto, lo terrò d'occhio.

Il modo di foglio di calcolo ...

Molte persone usano solo uno strumento come MSExcel, OpenOffice o altri strumenti per fogli di calcolo a questo scopo. Questa è una soluzione valida, basta copiare i dati laggiù e utilizzare gli strumenti offerti dalla GUI per risolverlo.

Ma ... questa non era la domanda, e potrebbe anche portare ad alcuni svantaggi, come come ottenere i dati nel foglio di calcolo, ridimensionamento problematico e così via.

Il modo SQL ...

Dato che il suo tavolo è simile al seguente:

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;

Ora guarda nella sua tabella desiderata:

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

Le righe ( EMAIL, PRINT x pages) assomigliano alle condizioni. Il gruppo principale è di company_name.

Al fine di impostare le condizioni, questo grida piuttosto per l'utilizzo della CASEdichiarazione. Al fine di gruppo da qualcosa, beh, l'uso ... GROUP BY.

L'SQL di base che fornisce questo perno può assomigliare a questo:

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

Questo dovrebbe fornire il risultato desiderato molto velocemente. Il principale svantaggio di questo approccio, più righe vuoi nella tua tabella pivot, più condizioni devi definire nell'istruzione SQL.

Anche questo può essere affrontato, quindi le persone tendono ad usare dichiarazioni preparate, routine, contatori e simili.

Alcuni link aggiuntivi su questo argomento:


4
il link sembra funzionare per ora ... se mai si interrompe di nuovo, prova questi: cache di Google webcache.googleusercontent.com/… o Internet Wayback Machine ( web.archive.org/web/20070303120558 * / artfulsoftware.com/ infotree / queries.php )
Lykegenes,

Il link è accessibile a questo URL artfulsoftware.com/infotree/qrytip.php?id=78
MrPandav

1
Esiste un altro modo per generare una tabella pivot senza utilizzare "if", "case" o "GROUP_CONCAT": en.wikibooks.org/wiki/MySQL/Pivot_table
user2513149

Puoi rimuovere ELSE NULL dal CASO in quanto il cappello è il comportamento predefinito (e l'aggregazione condizionale è abbastanza prolissa)
Caius Jard

86

La mia soluzione è in T-SQL senza pivot:

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

2
Questo funziona per me anche su PostgreSQL. Preferisco questo metodo piuttosto che usare l'estensione a campi incrociati su Postgres in quanto è più pulito
itols

2
"La mia soluzione è in T-SQL senza pivot:" Non solo SQL Server dovrebbe funzionare sulla maggior parte dei fornitori di database che segue gli standard ANSI SQL. Nota che SUM()può funzionare solo con dati numerici se devi utilizzare stringhe pivot che dovrai usareMAX()
Raymond Nijland

1
Penso che il CASE non sia preoccupante SUM(CASE WHEN (action='PRINT' AND pagecount=1) THEN 1 ELSE 0 END), puoi semplicemente farlo SUM(action='PRINT' AND pagecount=1)poiché la condizione verrà convertita in 1quando vera e 0quando falsa
kajacx

1
@kajacx sì, anche se è necessario su database che non hanno quel tipo di manipolazione booleana. Data la scelta tra una "sintassi più lunga che funziona su tutti i dB" e una "sintassi più breve che funziona solo su ..." Sceglierei la prima
Caius Jard

66

Per MySQL puoi mettere direttamente in SUM()funzione le condizioni e verrà valutato come booleano 0o,1 quindi, potrai contare su criteri basati su criteri senza usare IF/CASEdichiarazioni

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


1
È davvero pulito. Sai se questo è conforme agli standard su altre piattaforme (come Postgres)?
itsols

3
@itsols No è ​​solo per Mysql specifico
M Khalid Junaid

@itsols: ho aggiunto un'altra versione SQL standard . Postgres ha anche una funzione dedicata crosstab().
Erwin Brandstetter,

2
Funziona anche con SQLite
SBF

37

Per pivot dinamico, utilizzare GROUP_CONCATcon CONCAT. La funzione GROUP_CONCAT concatena le stringhe di un gruppo in una stringa con varie opzioni.

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 QUI


2
Pacerier, vero uomo ma per la dinamica che ruota attorno al suo uno dei migliori approcci
Abhishek Gupta

2
Funziona bene se hai molti valori nella colonna "azioni" o ti aspetti che l'elenco cresca nel tempo, poiché scrivere un'istruzione case per ogni valore può richiedere molto tempo e essere difficile da aggiornare.
Patrick Murphy,

23

Una versione stardard-SQL che utilizza la logica 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.

Come?

TRUE OR NULL rese TRUE.
FALSE OR NULLrese NULL.
NULL OR NULLrese NULL.
E COUNTconta solo valori non nulli. Ecco.


@Erwin, ma come faresti a sapere che ci sono tre colonne? E se ce ne fossero 5? 10? 20?
Pacerier,

@Pacerier: l'esempio nella domanda sembra suggerirlo. In entrambi i casi, SQL richiede di conoscere il tipo restituito. una query completamente dinamica non è possibile. Se il numero di colonne di output può variare, sono necessari due passaggi: 1o creare la query, 2o: eseguirlo.
Erwin Brandstetter,

11

La risposta corretta è:

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

1
È solo un esempio che hai avuto a portata di mano? Qual è la struttura del other_valuetavolo?
Patrick Murphy,

1
"La risposta corretta è:" Molto probabilmente non manca la SETquery per aumentare il valore defualt che è limitato a 1024 per GROUP_CONCAT dopo 1024 GROUP_CONCAT tronca semplicemente la stringa senza un errore, il che significa che possono verificarsi risultati imprevisti ..
Raymond Nijland

scusate ragazzi non ricordo altri dettagli. Faccio cose per divertimento e poi dimentico o distruggo l'intero progetto. Ma quando inciampo in una sfida condivido come l'ho risolto. So che il mio esempio non è molto dettagliato, ma immagino che possa dare indicazioni a coloro che sanno cosa stanno affrontando :)
Talha,

9

Esiste uno strumento chiamato Generatore di tabelle pivot MySQL, che può aiutarti a creare una tabella pivot basata sul web che puoi esportare in seguito in Excel (se lo desideri). può funzionare se i tuoi dati sono in una singola tabella o in più tabelle.

Tutto quello che devi fare è specificare l'origine dati delle colonne (supporta colonne dinamiche), le righe, i valori nel corpo della tabella e la relazione della tabella (se ce ne sono) Tabella pivot di MySQL

La home page di questo strumento è http://mysqlpivottable.net


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 ;
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.