MySQL - Baris ke Kolom

188

Saya mencoba mencari posting, tetapi saya hanya menemukan solusi untuk SQL Server / Access. Saya butuh solusi di MySQL (5.X).

Saya punya tabel (disebut histori) dengan 3 kolom: hostid, itemname, itemvalue.
Jika saya melakukan select ( select * from history), itu akan kembali

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

Bagaimana saya meminta basis data untuk mengembalikan sesuatu seperti

   +--------+------+-----+-----+
   | hostid |   A  |  B  |  C  |
   +--------+------+-----+-----+
   |   1    |  10  |  3  |  0  |
   +--------+------+-----+-----+
   |   2    |   9  |  0  |  40 |
   +--------+------+-----+-----+
Bob Rivers
sumber
@Rob, Bisakah Anda mengedit pertanyaan untuk memasukkan permintaan yang tepat?
Johan
CATATAN: Tautan @ako hanya relevan dengan MariaDB.
ToolmakerSteve
Generasi otomatis dan berjalannya pivot: mysql.rjweb.org/doc.php/pivot
Rick James

Jawaban:

276

Saya akan menambahkan penjelasan yang agak lebih panjang dan lebih rinci tentang langkah yang harus diambil untuk menyelesaikan masalah ini. Saya minta maaf jika terlalu lama.


Saya akan mulai dengan pangkalan yang Anda berikan dan menggunakannya untuk menentukan beberapa istilah yang akan saya gunakan untuk sisa posting ini. Ini akan menjadi tabel dasar :

select * from history;

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

Ini akan menjadi tujuan kami, tabel pivot cantik :

select * from history_itemvalue_pivot;

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

Nilai dalam history.hostidkolom akan menjadi nilai-y dalam tabel pivot. Nilai dalam history.itemnamekolom akan menjadi nilai x (untuk alasan yang jelas).


Ketika saya harus menyelesaikan masalah membuat tabel pivot, saya mengatasinya menggunakan proses tiga langkah (dengan langkah keempat opsional):

  1. pilih kolom yang diminati, yaitu nilai-y dan x
  2. memperpanjang tabel dasar dengan kolom tambahan - satu untuk masing-masing nilai x
  3. kelompokkan dan agregat tabel diperpanjang - satu kelompok untuk masing-masing nilai-y
  4. (opsional) cantikkan tabel agregat

Mari kita terapkan langkah-langkah ini untuk masalah Anda dan lihat apa yang kami dapatkan:

Langkah 1: pilih kolom yang diminati . Dalam hasil yang diinginkan, hostidmenyediakan y-nilai dan itemnamemenyediakan x-nilai .

Langkah 2: perpanjang tabel dasar dengan kolom tambahan . Kami biasanya membutuhkan satu kolom per nilai x. Ingat bahwa kolom x-value kami adalah 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 |
+--------+----------+-----------+------+------+------+

Perhatikan bahwa kami tidak mengubah jumlah baris - kami baru saja menambahkan kolom tambahan. Perhatikan juga pola NULLs - baris dengan itemname = "A"nilai bukan nol untuk kolom baruA , dan nilai nol untuk kolom baru lainnya.

Langkah 3: kelompokkan dan agregat tabel diperpanjang . Kita perlu group by hostid, karena memberikan nilai-y:

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

(Perhatikan bahwa kita sekarang memiliki satu baris per nilai-y.) Oke, kita hampir sampai! Kita hanya perlu menyingkirkan yang jelek NULLitu.

Langkah 4: cantikkan . Kami hanya akan mengganti nilai nol dengan nol sehingga set hasil lebih baik untuk dilihat:

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

Dan kami selesai - kami telah membangun tabel pivot yang bagus dan cantik menggunakan MySQL.


Pertimbangan saat menerapkan prosedur ini:

  • nilai apa yang digunakan di kolom tambahan. Saya menggunakan itemvaluecontoh ini
  • apa nilai "netral" untuk digunakan dalam kolom tambahan. Saya menggunakan NULL, tetapi bisa juga 0atau "", tergantung pada situasi Anda yang sebenarnya
  • fungsi agregat apa yang digunakan saat pengelompokan. Saya menggunakan sum, tetapi countdan maxjuga sering digunakan (max sering digunakan ketika membangun "objek" satu baris yang telah tersebar di banyak baris)
  • menggunakan beberapa kolom untuk nilai-y. Solusi ini tidak terbatas pada menggunakan satu kolom untuk nilai-y - cukup tancapkan kolom tambahan ke group byklausa (dan jangan lupa untuk selectitu)

Keterbatasan yang diketahui:

  • solusi ini tidak mengizinkan n kolom dalam tabel pivot - setiap kolom pivot perlu ditambahkan secara manual saat memperluas tabel dasar. Jadi untuk 5 atau 10 nilai x, solusi ini bagus. Untuk 100, tidak begitu baik. Ada beberapa solusi dengan prosedur tersimpan menghasilkan kueri, tetapi mereka jelek dan sulit untuk diperbaiki. Saat ini saya tidak tahu cara yang baik untuk menyelesaikan masalah ini ketika tabel pivot perlu memiliki banyak kolom.
Matt Fenwick
sumber
25
+1 Sejauh ini ini adalah penjelasan terbaik / paling jelas tabel pivot / tab silang di MySQL yang saya lihat
cameron.bracken
6
Penjelasan yang bagus, terima kasih. Langkah 4 dapat digabungkan menjadi langkah 3 dengan menggunakan IFNULL (jumlah (A), 0) AS A, memberi Anda hasil yang sama tetapi tanpa perlu membuat tabel lain
nealio82
1
Itu adalah solusi yang paling luar biasa untuk pivot, tapi saya hanya ingin tahu jika dalam kolom itemname yang membentuk sumbu x memiliki beberapa nilai, seperti di sini kita hanya memiliki tiga nilai yaitu A, B, C. Jika nilai-nilai ini diperluas ke A, B, C, D, E, AB, BC, AC, AD, H ..... n. maka dalam hal ini apa yang akan menjadi solusinya.
Deepesh
1
ini seharusnya jawaban yang diterima di sini. Ini jauh lebih rinci, berguna, dan menjelaskan cara memahaminya daripada hanya menautkan ke beberapa artikel seperti yang diterima saat ini
EdgeCaseBerg
2
@WhiteBig tolong lihat tanggalnya - jawaban StackOverflow ini ditulis 1,5 tahun sebelum posting blog itu. Mungkin Anda seharusnya meminta blog untuk memberikan kredit kepada saya.
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
sumber
Menciptakan tiga baris berbeda, untuk 'A', 'B', 'C'
Palani
1
@Palani: Tidak, tidak. Lihat group by.
ruakh
Terima kasih, ini berhasil untuk saya! Namun, hanya FYI beberapa tahun yang terlambat, saya harus menggunakan MAXbukan SUMkarena itemValuestring saya , bukan nilai numerik.
Merricat
33

Opsi lain, terutama berguna jika Anda memiliki banyak item yang perlu Anda pivot adalah membiarkan mysql membuat kueri untuk Anda:

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 Menambahkan beberapa nilai tambahan agar berfungsi

GROUP_CONCAT memiliki nilai default 1000 jadi jika Anda memiliki kueri yang sangat besar ubah parameter ini sebelum menjalankannya

SET SESSION group_concat_max_len = 1000000;

Uji:

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
sumber
@Mihai Mungkin Anda bisa membantu saya. Lihat ini: stackoverflow.com/questions/51832979/…
Success Man
Dapat disederhanakan 'ifnull(SUM(case when itemname = ''',dengan ''' then itemvalue end),0) AS ', `untuk 'SUM(case when itemname = '''dengan ''' then itemvalue else 0 end) AS ',. Ini menghasilkan istilah seperti SUM(case when itemname = 'A' then itemvalue else 0 end) AS 'A'.
ToolmakerSteve
24

Mengambil keuntungan dari ide Matt Fenwick yang membantu saya untuk menyelesaikan masalah (terima kasih banyak), mari kita kurangi menjadi hanya satu permintaan:

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
sumber
14

Saya mengedit jawaban Agung Sagita dari subquery untuk bergabung. Saya tidak yakin tentang seberapa besar perbedaan antara 2 cara ini, tetapi hanya untuk referensi lain.

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
sumber
2
Mungkin, ini bisa menjadi solusi yang lebih cepat.
jave.web
Saya rasa tidak. karena gabung kiri memiliki latensi sendiri!
Abadis
10

gunakan subquery

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

tetapi akan menjadi masalah jika sub kueri menghasilkan lebih dari satu baris, gunakan fungsi agregat lebih lanjut dalam subquery

Agung Sagita
sumber
4

Solusi saya:

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

Ini menghasilkan hasil yang diharapkan dalam kasus yang diajukan.

André Wéber
sumber
3

Saya membuatnya menjadi Group By hostIdmaka hanya akan menampilkan baris pertama dengan nilai-nilai,
seperti:

A   B  C
1  10
2      3
arpit
sumber
3

Saya menemukan satu cara untuk membuat laporan saya mengonversi baris ke kolom hampir dinamis menggunakan kueri sederhana. Anda dapat melihat dan mengujinya secara online di sini .

Jumlah kolom kueri tetap tetapi nilainya dinamis dan didasarkan pada nilai baris. Anda dapat membangunnya Jadi, saya menggunakan satu kueri untuk membangun tajuk tabel dan yang lainnya untuk melihat nilainya:

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;

Anda juga dapat meringkasnya:

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

Hasil RexTester :

Hasil RexTester

http://rextester.com/ZSWKS28923

Untuk satu contoh nyata penggunaan, laporan ini di bawah menunjukkan dalam kolom jam keberangkatan kedatangan kapal / bus dengan jadwal visual. Anda akan melihat satu kolom tambahan yang tidak digunakan pada kolom terakhir tanpa membingungkan visualisasi: sistema online passagens online dan konsumer final dan kontrol - xsl teknologi - xsl.com.br ** sistem tiket untuk penjualan tiket online dan presentasi

lynx_74
sumber
3

Jika Anda bisa menggunakan MariaDB ada solusi yang sangat sangat mudah.

Sejak MariaDB-10.02 telah ditambahkan mesin penyimpanan baru bernama CONNECT yang dapat membantu kami mengubah hasil kueri atau tabel lain menjadi tabel pivot, seperti yang Anda inginkan: Anda dapat melihat dokumen .

Pertama-tama instal mesin penyambung koneksi .

Sekarang kolom pivot dari tabel kami adalah itemnamedan data untuk setiap item terletak di itemvaluekolom, sehingga kami dapat memiliki tabel pivot hasil menggunakan kueri ini:

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

Sekarang kita dapat memilih apa yang kita inginkan dari pivot_table:

select * from pivot_table

Lebih detail di sini

ako
sumber
1

Ini bukan jawaban yang tepat yang Anda cari tetapi itu adalah solusi yang saya butuhkan pada proyek saya dan berharap ini membantu seseorang. Ini akan mencantumkan 1 hingga n item baris yang dipisahkan oleh koma. Group_Compat memungkinkan ini di MySQL.

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

Kuburan ini memiliki dua nama umum sehingga nama-nama tersebut terdaftar dalam baris yang berbeda yang terhubung oleh satu id tetapi dua id nama dan permintaan menghasilkan sesuatu seperti

    CemeteryIDame_Name Latitude
    1 Appleton ini, Sulpher Springs 35.4276242832293

James Humphrey
sumber
-2

Saya minta maaf untuk mengatakan ini dan mungkin saya tidak memecahkan masalah Anda persis tetapi PostgreSQL 10 tahun lebih tua dari MySQL dan sangat maju dibandingkan dengan MySQL dan ada banyak cara untuk mencapai ini dengan mudah. Instal PostgreSQL dan jalankan kueri ini

CREATE EXTENSION tablefunc;

lalu voila! Dan inilah dokumentasi yang lengkap: PostgreSQL: Dokumentasi: 9.1: tablefunc atau permintaan ini

CREATE EXTENSION hstore;

sekali lagi voila! PostgreSQL: Dokumentasi: 9.0: hstore

gdarcan
sumber