Bagaimana saya bisa mengembalikan output tabel pivot di MySQL?

312

Jika saya memiliki tabel MySQL yang terlihat seperti ini:

company_name action pagecount
-------------------------------
Perusahaan A PRINT 3
Perusahaan A PRINT 2
Perusahaan A PRINT 3
Perusahaan B EMAIL   
Perusahaan B PRINT 2
Perusahaan B PRINT 2
Perusahaan B PRINT 1
Perusahaan A PRINT 3

Apakah mungkin menjalankan query MySQL untuk mendapatkan output seperti ini:

company_name EMAIL PRINT 1 halaman PRINT 2 halaman PRINT 3 halaman
-------------------------------------------------- -----------
PerusahaanA 0 0 1 3
Perusahaan B 1 1 2 0

Idenya adalah bahwa pagecountdapat bervariasi sehingga jumlah kolom output harus mencerminkan itu, satu kolom untuk masing-masing action/ pagecountpasangan dan kemudian jumlah klik per company_name. Saya tidak yakin apakah ini disebut tabel pivot tetapi seseorang menyarankan itu?

peku
sumber
3
Ini disebut pivoting dan jauh, lebih cepat untuk melakukan transformasi ini di luar SQL.
NB
1
Excel merobek hal-hal seperti ini, sangat sulit di MySQL karena tidak ada operator "CROSSTAB" :(
Dave Rix
Ya itu saat ini dilakukan dengan tangan di Excel dan kami mencoba untuk mengotomatiskannya.
peku
3
Di sini saya menemukan contoh langkah demi langkah: cara mengotomatiskan tabel pivot . dan ini
Devid G
1
@ giannischristofakis - itu benar-benar tergantung pada apa yang Anda dan rekan kerja Anda anggap lebih sederhana. Teknologi sudah cukup sibuk sejak saya memposting komentar (4 tahun) sehingga benar-benar tergantung pada apa yang Anda rasa lebih baik - baik itu dalam aplikasi atau SQL. Misalnya, di pekerjaan saya, kami menangani masalah yang sama tetapi kami menggabungkan pendekatan SQL dan dalam aplikasi. Pada dasarnya, saya tidak dapat membantu Anda selain memberikan jawaban dan bukan itu yang Anda butuhkan :)
NB

Jawaban:

236

Ini pada dasarnya adalah tabel pivot.

Sebuah tutorial yang bagus tentang cara mencapai ini dapat ditemukan di sini: http://www.artfulsoftware.com/infotree/qrytip.php?id=78

Saya menyarankan membaca posting ini dan menyesuaikan solusi ini dengan kebutuhan Anda.

Memperbarui

Setelah tautan di atas saat ini tidak tersedia lagi, saya merasa berkewajiban untuk memberikan beberapa informasi tambahan untuk Anda semua yang mencari jawaban pivot mysql di sini. Itu benar-benar memiliki sejumlah besar informasi, dan saya tidak akan meletakkan semuanya dari sana di sini (bahkan lebih karena saya hanya tidak ingin menyalin pengetahuan mereka yang luas), tetapi saya akan memberikan saran tentang cara menangani pivot tabel cara sql umumnya dengan contoh dari peku yang mengajukan pertanyaan di tempat pertama.

Mungkin tautannya segera kembali, saya akan mengawasinya.

Cara spreadsheet ...

Banyak orang hanya menggunakan alat seperti MSExcel, OpenOffice atau alat spreadsheet lainnya untuk tujuan ini. Ini adalah solusi yang valid, cukup salin data di sana dan gunakan alat yang ditawarkan GUI untuk menyelesaikannya.

Tapi ... ini bukan pertanyaannya, dan bahkan mungkin menyebabkan beberapa kerugian, seperti bagaimana memasukkan data ke dalam spreadsheet, penskalaan yang bermasalah, dan sebagainya.

Cara SQL ...

Mengingat mejanya terlihat seperti ini:

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;

Sekarang lihat ke meja yang diinginkan:

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

Baris ( EMAIL, PRINT x pages) menyerupai kondisi. Pengelompokan utama adalah dengan company_name.

Untuk mengatur kondisi ini lebih baik berteriak untuk menggunakan CASEpernyataan. Untuk kelompok oleh sesuatu, baik, penggunaan ... GROUP BY.

SQL dasar yang menyediakan pivot ini dapat terlihat seperti ini:

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

Ini harus memberikan hasil yang diinginkan dengan sangat cepat. Kelemahan utama untuk pendekatan ini, semakin banyak baris yang Anda inginkan dalam tabel pivot Anda, semakin banyak kondisi yang perlu Anda tetapkan dalam pernyataan SQL Anda.

Ini dapat diatasi juga, oleh karena itu orang cenderung menggunakan pernyataan, rutinitas, penghitung dan semacamnya.

Beberapa tautan tambahan tentang topik ini:

Bjoern
sumber
4
tautan tampaknya berfungsi untuk saat ini ... jika pernah turun lagi, coba ini: cache Google webcache.googleusercontent.com/... atau Internet Wayback Machine ( web.archive.org/web/20070303120558 * / artfulsoftware.com/ infotree / queries.php )
Lykegenes
tautan dapat diakses di url artfulsoftware.com/infotree/qrytip.php?id=78 ini
MrPandav
1
Ada cara lain untuk menghasilkan tabel pivot tanpa menggunakan "jika", "case", atau "GROUP_CONCAT": en.wikibooks.org/wiki/MySQL/Pivot_table
user2513149
Anda dapat menghapus ELSE NULL dari KASUS karena topi adalah perilaku default (dan agregasi bersyarat cukup bertele-tele)
Caius Jard
86

Solusi saya adalah dalam T-SQL tanpa 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
sumber
2
Ini berfungsi bahkan untuk saya di PostgreSQL. Saya lebih suka metode ini daripada menggunakan ekstensi
tab
2
"Solusi saya adalah dalam T-SQL tanpa pivot:" Tidak hanya SQL Server, ia harus bekerja pada kebanyakan vendor basis data yang mengikuti standar SQL ANSI. Perhatikan bahwa SUM()hanya dapat bekerja dengan data numerik jika Anda ingin string pivot yang harus Anda gunakanMAX()
Raymond Nijland
1
Saya pikir KASUS tidak diketahui SUM(CASE WHEN (action='PRINT' AND pagecount=1) THEN 1 ELSE 0 END), Anda hanya dapat melakukannya SUM(action='PRINT' AND pagecount=1)karena kondisinya akan dikonversi menjadi 1ketika benar dan 0ketika salah
kajacx
1
@ kajacx ya, meskipun itu diperlukan pada database yang tidak memiliki manipulasi Boolean semacam itu. Diberi pilihan antara "sintaks yang lebih panjang yang bekerja pada semua dB" dan "sintaks yang lebih pendek yang hanya bekerja pada ..." Saya akan memilih yang sebelumnya
Caius Jard
66

Untuk MySQL Anda dapat langsung memasukkan kondisi dalam SUM()fungsi dan itu akan dievaluasi sebagai Boolean 0atau1 dan karenanya Anda dapat menghitung berdasarkan kriteria Anda tanpa menggunakan IF/CASEpernyataan

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
sumber
1
Itu sangat rapi. Apakah Anda tahu apakah ini sesuai standar di semua platform lain (seperti Postgres)?
itsol
3
@itsols Tidak hanya untuk spesifik Mysql
M Khalid Junaid
@itsols: Saya menambahkan versi SQL standar lain . Postgres juga memiliki fungsi khusus crosstab().
Erwin Brandstetter
2
Juga berfungsi untuk SQLite
SBF
37

Untuk pivot dinamis, gunakan GROUP_CONCATdengan CONCAT. Fungsi GROUP_CONCAT menggabungkan string dari grup menjadi satu string dengan berbagai opsi.

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 DI SINI

Abhishek Gupta
sumber
2
Pacerier, pria sejati tetapi karena dinamis berputar salah satu pendekatan terbaiknya
Abhishek Gupta
2
Ini bekerja dengan baik jika Anda memiliki banyak nilai di kolom "tindakan" atau berharap daftar itu bertambah seiring berjalannya waktu, karena menulis pernyataan kasus untuk setiap nilai dapat memakan waktu dan sulit untuk tetap up to date.
Patrick Murphy
23

Versi stardard-SQL menggunakan logika boolean :

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.

Bagaimana?

TRUE OR NULL hasil panen TRUE.
FALSE OR NULLhasil panen NULL.
NULL OR NULLhasil panen NULL.
Dan COUNThanya menghitung nilai yang bukan nol. Voila.

Erwin Brandstetter
sumber
@ Erwin, Tapi bagaimana Anda tahu ada tiga kolom? Bagaimana jika ada 5? 10? 20?
Pacerier
@Pacerier: Contoh dalam pertanyaan sepertinya menyarankan itu. Either way, SQL menuntut untuk mengetahui tipe pengembalian. a benar-benar query dinamis tidak mungkin. Jika jumlah kolom output dapat bervariasi, Anda memerlukan dua langkah: pertama membangun kueri, 2: jalankan.
Erwin Brandstetter
11

Jawaban yang benar adalah:

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
sumber
1
Apakah ini hanya contoh yang Anda miliki? Bagaimana struktur other_valuetabelnya?
Patrick Murphy
1
"Jawaban yang benar adalah:" Kemungkinan besar tidak karena tidak ada SETpermintaan untuk meningkatkan nilai defualt yang terbatas pada 1024 untuk GROUP_CONCAT setelah 1024 GROUP_CONCAT hanya memotong string tanpa kesalahan yang berarti hasil yang tidak terduga dapat terjadi ..
Raymond Nijland
maaf guys tidak ingat detail lebih lanjut. Saya melakukan hal-hal untuk bersenang-senang dan kemudian melupakan atau menghancurkan seluruh proyek. Tetapi ketika saya menemukan sebuah tantangan, saya membagikan bagaimana saya memperbaikinya. Saya tahu contoh saya tidak terlalu detail, tetapi saya kira itu dapat memberikan arahan kepada mereka yang tahu apa yang mereka hadapi :)
Talha
9

Ada alat yang disebut generator tabel MySQL Pivot, ini dapat membantu Anda membuat tabel pivot berbasis web yang nantinya dapat Anda ekspor ke excel (jika diinginkan). ini bisa berfungsi jika data Anda berada dalam satu tabel atau dalam beberapa tabel.

Yang perlu Anda lakukan adalah menentukan sumber data kolom (mendukung kolom dinamis), baris, nilai-nilai di tubuh tabel dan hubungan tabel (jika ada) Tabel MySQL Pivot

Halaman muka alat ini adalah http://mysqlpivottable.net

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