MySQL - Zeilen zu Spalten

185

Ich habe versucht, Beiträge zu suchen, aber ich habe nur Lösungen für SQL Server / Access gefunden. Ich brauche eine Lösung in MySQL (5.X).

Ich habe eine Tabelle (genannt Verlauf) mit 3 Spalten: Host-ID, Elementname, Elementwert.
Wenn ich select ( select * from history) mache , wird es zurückgegeben

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

Wie frage ich die Datenbank ab, um so etwas zurückzugeben?

   +--------+------+-----+-----+
   | hostid |   A  |  B  |  C  |
   +--------+------+-----+-----+
   |   1    |  10  |  3  |  0  |
   +--------+------+-----+-----+
   |   2    |   9  |  0  |  40 |
   +--------+------+-----+-----+
Bob Rivers
quelle
@Rob, können Sie bitte die Frage so bearbeiten, dass sie die genaue Abfrage enthält?
Johan

Antworten:

274

Ich werde eine etwas längere und detailliertere Erläuterung der Schritte zur Lösung dieses Problems hinzufügen. Ich entschuldige mich, wenn es zu lang ist.


Ich beginne mit der Basis, die Sie angegeben haben, und definiere damit einige Begriffe, die ich für den Rest dieses Beitrags verwenden werde. Dies wird die Basistabelle sein :

select * from history;

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

Dies wird unser Ziel sein, der hübsche Pivot-Tisch :

select * from history_itemvalue_pivot;

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

Die Werte in der history.hostidSpalte werden zu y-Werten in der Pivot-Tabelle. Die Werte in der history.itemnameSpalte werden (aus offensichtlichen Gründen) zu x-Werten .


Wenn ich das Problem der Erstellung einer Pivot-Tabelle lösen muss, gehe ich es in drei Schritten an (mit einem optionalen vierten Schritt):

  1. Wählen Sie die gewünschten Spalten aus, dh y-Werte und x-Werte
  2. Erweitern Sie die Basistabelle um zusätzliche Spalten - eine für jeden x-Wert
  3. Gruppieren und aggregieren Sie die erweiterte Tabelle - eine Gruppe für jeden y-Wert
  4. (optional) Verschönern Sie die aggregierte Tabelle

Lassen Sie uns diese Schritte auf Ihr Problem anwenden und sehen, was wir bekommen:

Schritt 1: Wählen Sie die gewünschten Spalten aus . Liefert im gewünschten Ergebnis hostiddie y-Werte und itemnameliefert die x-Werte .

Schritt 2: Erweitern Sie die Basistabelle um zusätzliche Spalten . Wir benötigen normalerweise eine Spalte pro x-Wert. Denken Sie daran, dass unsere x-Wert-Spalte lautet 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 |
+--------+----------+-----------+------+------+------+

Beachten Sie, dass wir die Anzahl der Zeilen nicht geändert haben - wir haben nur zusätzliche Spalten hinzugefügt. Beachten Sie auch das Muster von NULLs - eine Zeile mit itemname = "A"hat einen Wert ungleich Null für eine neue Spalte Aund Nullwerte für die anderen neuen Spalten.

Schritt 3: Gruppieren und aggregieren Sie die erweiterte Tabelle . Wir müssen group by hostid, da es die y-Werte liefert:

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 |
+--------+------+------+------+

(Beachten Sie, dass wir jetzt eine Zeile pro y-Wert haben.) Okay, wir sind fast da! Wir müssen nur diese hässlichen NULLs loswerden .

Schritt 4: verschönern . Wir werden nur alle Nullwerte durch Nullen ersetzen, damit die Ergebnismenge besser aussieht:

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 |
+--------+------+------+------+

Und wir sind fertig - wir haben mit MySQL eine schöne, hübsche Pivot-Tabelle erstellt.


Überlegungen bei der Anwendung dieses Verfahrens:

  • Welchen Wert soll in den zusätzlichen Spalten verwendet werden? Ich habe itemvaluein diesem Beispiel verwendet
  • Welchen "neutralen" Wert soll in den zusätzlichen Spalten verwendet werden? Ich habe verwendet NULL, aber es könnte auch sein, 0oder "", abhängig von Ihrer genauen Situation
  • Welche Aggregatfunktion soll beim Gruppieren verwendet werden? Ich habe verwendet sum, aber countund werde maxauch oft verwendet ( maxwird oft verwendet, wenn einzeilige "Objekte" erstellt werden, die über viele Zeilen verteilt waren)
  • Verwenden mehrerer Spalten für y-Werte. Diese Lösung beschränkt sich nicht nur auf die Verwendung einer einzelnen Spalte für die y-Werte. Fügen Sie einfach die zusätzlichen Spalten in die group byKlausel ein (und vergessen Sie selectsie nicht).

Bekannte Einschränkungen:

  • Diese Lösung erlaubt keine n Spalten in der Pivot-Tabelle. Jede Pivot-Spalte muss beim Erweitern der Basistabelle manuell hinzugefügt werden. Für 5 oder 10 x-Werte ist diese Lösung also gut. Für 100 nicht so schön. Es gibt einige Lösungen mit gespeicherten Prozeduren, die eine Abfrage generieren, aber sie sind hässlich und schwer zu finden. Ich kenne derzeit keinen guten Weg, um dieses Problem zu lösen, wenn die Pivot-Tabelle viele Spalten haben muss.
Matt Fenwick
quelle
25
+1 Dies ist bei weitem die beste /
klarste
6
Hervorragende Erklärung, danke. Schritt 4 könnte mit IFNULL (Summe (A), 0) AS A in Schritt 3 zusammengeführt werden, wodurch Sie das gleiche Ergebnis erhalten, ohne jedoch eine weitere Tabelle
erstellen zu müssen
1
Es war die erstaunlichste Lösung für Pivot, aber ich bin nur neugierig, ob in der Spalte Elementname, der die x-Achse bildet, mehrere Werte vorhanden sind. Wie hier haben wir nur drei Werte, dh A, B, C. Wenn diese Werte auf A erweitert werden, B, C, D, E, AB, BC, AC, AD, H ..... n. dann in diesem Fall, was wäre die Lösung.
Deepesh
1
Dies sollte hier wirklich die akzeptierte Antwort sein. Es ist viel detaillierter, nützlicher und erklärt, wie man es versteht, anstatt nur auf einen Artikel wie den derzeit akzeptierten zu
verlinken
1
@WhiteBig Bitte schauen Sie sich die Daten an - diese StackOverflow-Antwort wurde 1,5 Jahre vor diesem Blog-Beitrag geschrieben. Vielleicht solltest du stattdessen den Blog bitten, mich gutzuschreiben.
Matt Fenwick
55
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;
Shantanuo
quelle
Erstellt drei verschiedene Zeilen für 'A', 'B', 'C'
Palani
@ Palani: Nein, das tut es nicht. Siehe group by.
Ruakh
Danke, das hat bei mir funktioniert! Allerdings nur ein FYI ein paar Jahre zu spät, musste ich MAXstattdessen verwenden, SUMweil meine itemValueZeichenfolgen sind, keine numerischen Werte.
Merricat
31

Eine weitere Option, die besonders nützlich ist, wenn Sie viele Elemente zum Schwenken benötigen, besteht darin, MySQL die Abfrage für Sie erstellen zu lassen:

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 Es wurden einige zusätzliche Werte hinzugefügt, damit es funktioniert

GROUP_CONCAT hat einen Standardwert von 1000. Wenn Sie also eine wirklich große Abfrage haben, ändern Sie diesen Parameter, bevor Sie ihn ausführen

SET SESSION group_concat_max_len = 1000000;

Prüfung:

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
Mihai
quelle
@ Mihai Vielleicht kannst du mir helfen. Schauen Sie sich das an: stackoverflow.com/questions/51832979/…
Success Man
24

Lassen Sie uns die Idee von Matt Fenwick nutzen, die mir bei der Lösung des Problems geholfen hat (vielen Dank), und reduzieren Sie sie auf nur eine Abfrage:

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
Jalber
quelle
14

Ich bearbeite Agung Sagitas Antwort von der Unterabfrage, um beizutreten. Ich bin mir nicht sicher, wie viel Unterschied zwischen diesen beiden Möglichkeiten besteht, aber nur als weitere Referenz.

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'
haudoing
quelle
2
Möglicherweise könnte dies eine schnellere Lösung sein.
jave.web
Das glaube ich nicht. weil left join seine eigene Latenz hat!
Abadis
10

Unterabfrage verwenden

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

Es ist jedoch ein Problem, wenn eine Unterabfrage, die mehr als eine Zeile ergibt, eine weitere Aggregatfunktion in der Unterabfrage verwendet

Agung Sagita
quelle
4

Meine Lösung :

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

Es liefert die erwarteten Ergebnisse im eingereichten Fall.

André Wéber
quelle
3

Ich mache das Group By hostIddann zeigt es nur die erste Zeile mit Werten
wie:

A   B  C
1  10
2      3
Arpit
quelle
3

Ich finde eine Möglichkeit, meine Berichte mithilfe einfacher Abfragen fast dynamisch in Zeilen umzuwandeln. Sie können es hier online sehen und testen .

Die Anzahl der Abfragespalten ist festgelegt, aber die Werte sind dynamisch und basieren auf Zeilenwerten. Sie können es erstellen Also verwende ich eine Abfrage, um den Tabellenkopf zu erstellen, und eine andere, um die Werte anzuzeigen:

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;

Sie können es auch zusammenfassen:

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 |
+--------+------+------+------+

Ergebnisse von RexTester :

Ergebnisse von RexTester

http://rextester.com/ZSWKS28923

Für ein reales Anwendungsbeispiel zeigt dieser Bericht in Spalten die Ankunftszeiten von Booten / Bussen mit einem visuellen Zeitplan. Sie werden eine zusätzliche Spalte sehen, die in der letzten Spalte nicht verwendet wird, ohne die Visualisierung zu verwirren: sistema venda de passagens online e verbrauchen final e controle de frota - xsl tecnologia - xsl.com.br. ** Ticketingsystem, um Tickets online und präsentativ zu verkaufen

lynx_74
quelle
3

Wenn Sie MariaDB verwenden könnten, gibt es eine sehr sehr einfache Lösung.

Seit MariaDB-10.02 wurde eine neue Speicher-Engine namens CONNECT hinzugefügt , mit deren Hilfe wir die Ergebnisse einer anderen Abfrage oder Tabelle in eine Pivot-Tabelle konvertieren können, genau wie Sie es möchten: Sie können sich die Dokumente ansehen .

Zunächst einmal installieren Sie die connect - Speicher - Engine .

Jetzt ist die Pivot-Spalte unserer Tabelle itemnameund die Daten für jedes Element befinden sich in der itemvalueSpalte, sodass wir die Ergebnis-Pivot-Tabelle mithilfe dieser Abfrage haben können:

create table pivot_table
engine=connect table_type=pivot tabname=history
option_list='PivotCol=itemname,FncCol=itemvalue';

Jetzt können wir auswählen, was wir wollen aus pivot_table:

select * from pivot_table

Weitere Details hier

ako
quelle
1

Dies ist nicht die genaue Antwort, die Sie suchen, aber es war eine Lösung, die ich für mein Projekt brauchte und hoffe, dass dies jemandem hilft. Dadurch werden 1 bis n durch Kommas getrennte Zeilenelemente aufgelistet. Group_Concat macht dies in MySQL möglich.

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

Dieser Friedhof hat zwei gebräuchliche Namen, daher werden die Namen in verschiedenen Zeilen aufgelistet, die durch eine einzelne ID, aber zwei Namens-IDs verbunden sind, und die Abfrage erzeugt so etwas wie diese

    CemeteryID Cemetery_Name Latitude
    1 Appleton, Sulpher Springs 35.4276242832293

James Humphrey
quelle
-2

Es tut mir leid, das zu sagen, und vielleicht löse ich Ihr Problem nicht genau, aber PostgreSQL ist 10 Jahre älter als MySQL und im Vergleich zu MySQL extrem fortgeschritten. Es gibt viele Möglichkeiten, dies einfach zu erreichen. Installieren Sie PostgreSQL und führen Sie diese Abfrage aus

CREATE EXTENSION tablefunc;

dann voila! Und hier ist eine umfangreiche Dokumentation: PostgreSQL: Dokumentation: 9.1: tablefunc oder diese Abfrage

CREATE EXTENSION hstore;

dann wieder voila! PostgreSQL: Dokumentation: 9.0: hstore

gdarcan
quelle