Comment puis-je retourner la sortie du tableau croisé dynamique dans MySQL?

312

Si j'ai une table MySQL ressemblant à ceci:

nom_entreprise action pagecount
-------------------------------
Société A PRINT 3
Société A PRINT 2
Société A PRINT 3
Société B EMAIL   
Société B PRINT 2
Société B PRINT 2
Société B PRINT 1
Société A PRINT 3

Est-il possible d'exécuter une requête MySQL pour obtenir une sortie comme celle-ci:

nom_entreprise EMAIL IMPRIMER 1 pages IMPRIMER 2 pages IMPRIMER 3 pages
-------------------------------------------------- -----------
EntrepriseA 0 0 1 3
SociétéB 1 1 2 0

L'idée est que pagecountpeut varier de sorte que le montant de la colonne de sortie doit refléter le fait que, une colonne pour chaque action/ pagecountpaire et nombre de visites par company_name. Je ne sais pas si cela s'appelle un tableau croisé dynamique, mais quelqu'un a suggéré cela?

peku
la source
3
Cela s'appelle le pivotement et c'est beaucoup, beaucoup plus rapide de faire cette transformation en dehors de SQL.
NB
1
Excel déchire des choses comme ça, c'est vraiment difficile dans MySQL car il n'y a pas d'opérateur "CROSSTAB" :(
Dave Rix
Oui, cela se fait actuellement à la main dans Excel et nous essayons de l'automatiser.
peku
3
Ici, j'ai trouvé un exemple étape par étape: comment automatiser les tableaux croisés dynamiques . et cela
Devid G
1
@giannischristofakis - cela dépend vraiment de ce que vous et vos collègues jugent plus simple. La technologie a un peu rattrapé depuis que j'ai posté le commentaire (4 ans), donc tout dépend de ce que vous ressentez le mieux - que ce soit en application ou en SQL. Par exemple, dans mon travail, nous traitons un problème similaire, mais nous combinons à la fois SQL et une approche in-app. Fondamentalement, je ne peux pas vous aider à part donner une réponse avisée et ce n'est pas ce dont vous avez besoin :)
NB

Réponses:

236

Il s'agit essentiellement d' un tableau croisé dynamique.

Un joli didacticiel sur la façon d'y parvenir peut être trouvé ici: http://www.artfulsoftware.com/infotree/qrytip.php?id=78

Je vous conseille de lire cet article et d'adapter cette solution à vos besoins.

Mettre à jour

Après que le lien ci-dessus ne soit plus disponible actuellement, je me sens obligé de fournir des informations supplémentaires pour vous tous à la recherche de réponses de pivot mysql ici. Il avait vraiment une grande quantité d'informations, et je ne mettrai pas tout à partir d'ici (d'autant plus que je ne veux tout simplement pas copier leurs vastes connaissances), mais je donnerai quelques conseils sur la façon de gérer le pivot tables la manière sql généralement avec l'exemple de peku qui a posé la question en premier lieu.

Peut-être que le lien reviendra bientôt, je garderai un œil dessus.

La manière tableur ...

De nombreuses personnes utilisent simplement un outil comme MSExcel, OpenOffice ou d'autres outils de tableur à cet effet. C'est une solution valide, copiez simplement les données là-bas et utilisez les outils proposés par l'interface graphique pour résoudre ce problème.

Mais ... ce n'était pas la question, et cela pourrait même entraîner des inconvénients, comme la façon de placer les données dans la feuille de calcul, une mise à l'échelle problématique, etc.

La manière SQL ...

Étant donné que sa table ressemble à ceci:

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;

Maintenant, regardez dans sa table désirée:

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

Les lignes ( EMAIL, PRINT x pages) ressemblent à des conditions. Le regroupement principal est par company_name.

Afin de mettre en place les conditions cela crie plutôt pour l'utilisation de la CASEdéclaration. Pour groupe par quelque chose, eh bien, l' utilisation ... GROUP BY.

Le SQL de base fournissant ce pivot peut ressembler à ceci:

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

Cela devrait fournir le résultat souhaité très rapidement. L'inconvénient majeur de cette approche, plus vous voulez de lignes dans votre tableau croisé dynamique, plus vous devez définir de conditions dans votre instruction SQL.

Cela peut également être traité, par conséquent, les gens ont tendance à utiliser des instructions, des routines, des compteurs et autres.

Quelques liens supplémentaires sur ce sujet:

Bjoern
la source
4
le lien semble fonctionner pour le moment ... s'il tombe à nouveau en panne, essayez-les: le cache de Google webcache.googleusercontent.com/… ou Internet Wayback Machine ( web.archive.org/web/20070303120558 * / artfulsoftware.com/ infotree / queries.php )
Lykegenes
le lien est accessible à cette url artfulsoftware.com/infotree/qrytip.php?id=78
MrPandav
1
Il existe une autre façon de générer un tableau croisé
dynamique
Vous pouvez supprimer le ELSE NULL de votre CASE car hat est le comportement par défaut (et l'agrégation conditionnelle est assez verbeuse)
Caius Jard
86

Ma solution est en T-SQL sans aucun 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
RRM
la source
2
Cela fonctionne pour moi même sur PostgreSQL. Je préfère cette méthode que d'utiliser l'extension de tableau croisé sur Postgres car c'est plus propre
itsols
2
"Ma solution est en T-SQL sans aucun pivot:" Non seulement SQL Server devrait fonctionner sur la plupart des fournisseurs de bases de données qui suivent les normes SQL ANSI. Notez que SUM()cela ne peut fonctionner avec des données numériques que si vous avez besoin de pivoter des chaînes que vous devrez utiliserMAX()
Raymond Nijland
1
Je pense que le CAS est inutile SUM(CASE WHEN (action='PRINT' AND pagecount=1) THEN 1 ELSE 0 END), vous pouvez simplement le faire SUM(action='PRINT' AND pagecount=1)car la condition sera convertie en 1vraie et 0en fausse
kajacx
1
@kajacx oui, bien qu'il soit nécessaire sur une base de données qui n'a pas ce genre de manipulation booléenne. Étant donné le choix entre une "syntaxe plus longue qui fonctionne sur tous les dB" et une "syntaxe plus courte qui ne fonctionne que sur ...", je choisirais l'ancienne
Caius Jard
66

Pour MySQL, vous pouvez directement mettre des conditions en SUM()fonction et elles seront évaluées comme booléennes 0ou1 et ainsi vous pouvez avoir votre compte en fonction de vos critères sans utiliser d' IF/CASEinstructions

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
la source
1
C'est vraiment bien. Savez-vous si cela est conforme aux normes sur d'autres plateformes (comme Postgres)?
itsols
3
@itsols No its for only Mysql specific
M Khalid Junaid
@itsols: j'ai ajouté une autre version SQL standard . Postgres a également une fonction dédiée crosstab().
Erwin Brandstetter
2
Fonctionne également pour SQLite
SBF
37

Pour un pivot dynamique, utilisez GROUP_CONCATavec CONCAT. La fonction GROUP_CONCAT concatène les chaînes d'un groupe en une chaîne avec différentes options.

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;

DÉMO ICI

Abhishek Gupta
la source
2
Pacerier, vrai homme mais pour le pivotement dynamique, c'est l'une des meilleures approches
Abhishek Gupta
2
Cela fonctionne bien si vous avez plusieurs valeurs dans la colonne "actions" ou si vous vous attendez à ce que cette liste s'allonge avec le temps, car l'écriture d'une déclaration de cas pour chaque valeur peut être longue et difficile à tenir à jour.
Patrick Murphy
23

Une version stardard-SQL utilisant la logique booléenne :

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.

Comment?

TRUE OR NULL rendements TRUE.
FALSE OR NULLrendements NULL.
NULL OR NULLrendements NULL.
Et COUNTne compte que les valeurs non nulles. Voilá.

Erwin Brandstetter
la source
@Erwin, mais comment sauriez-vous qu'il y a trois colonnes? Et s'il y en a 5? dix? 20?
Pacerier
@Pacerier: L'exemple de la question semble le suggérer. Dans tous les cas, SQL exige de connaître le type de retour. une requête complètement dynamique n'est pas possible. Si le nombre de colonnes de sortie peut varier, vous devez suivre deux étapes: 1ère génération de la requête, 2ème: exécution.
Erwin Brandstetter le
11

La bonne réponse est:

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
la source
1
Est-ce juste un exemple que vous aviez sous la main? Quelle est la structure de la other_valuetable?
Patrick Murphy
1
"La bonne réponse est:" Probablement pas car il manque la SETrequête pour augmenter la valeur par défaut qui est limitée à 1024 pour GROUP_CONCAT après 1024 GROUP_CONCAT tronque simplement la chaîne sans erreur, ce qui signifie que des résultats inattendus peuvent se produire ...
Raymond Nijland
désolé les gars ne me souviens plus de détails. Je fais des trucs pour le plaisir, puis j'oublie ou détruis tout le projet. Mais quand je tombe sur un défi, je partage comment je l'ai résolu. Je sais que mon exemple n'est pas très détaillé mais je suppose qu'il peut donner des indications à ceux qui savent ce qu'ils affrontent :)
Talha
9

Il existe un outil appelé générateur de tableaux croisés dynamiques MySQL, il peut vous aider à créer un tableau croisé dynamique basé sur le Web que vous pourrez ensuite exporter vers Excel (si vous le souhaitez). cela peut fonctionner si vos données sont dans une seule table ou dans plusieurs tables.

Tout ce que vous devez faire est de spécifier la source de données des colonnes (il prend en charge les colonnes dynamiques), les lignes, les valeurs dans le corps de la table et la relation de table (le cas échéant) Tableau croisé dynamique MySQL

La page d'accueil de cet outil est http://mysqlpivottable.net

Peter Green
la source
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
la source