MySQL - Satırlardan Sütunlara


188

Mesajları aramaya çalıştım, ancak sadece SQL Server / Access için çözümler buldum. MySQL'de (5.X) bir çözüme ihtiyacım var.

Hostid, itemname, itemvalue: 3 sütun içeren bir tablo (geçmiş denir) var.
Bir select ( select * from history) yaparsam , geri döner

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

Nasıl bir şey döndürmek için veritabanını sorgulamak

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

@Rob, Soruyu tam sorguyu içerecek şekilde düzenleyebilir misiniz?
Johan


NOT: @ako'nun bağlantısı sadece MariaDB ile ilgilidir.
ToolmakerSteve

Bir pivotun otomatik oluşturulması ve çalıştırılması: mysql.rjweb.org/doc.php/pivot
Rick James

Yanıtlar:


277

Bu sorunu çözmek için atılacak adımlara biraz daha uzun ve daha ayrıntılı bir açıklama ekleyeceğim. Çok uzun olursa özür dilerim.


Verdiğiniz üs ile başlayacağım ve bu yazının geri kalanı için kullanacağım birkaç terimi tanımlamak için kullanacağım. Bu temel tablo olacaktır :

select * from history;

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

Bu bizim hedefimiz olacak, güzel pivot masa :

select * from history_itemvalue_pivot;

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

Değerler history.hostidsütun olacak y değerleri, pivot tabloda. İçindeki değerler history.itemnamesütununda olacak x değerleri (bilinen nedenlerle).


Bir pivot tablo oluşturma sorununu çözmek zorunda kaldığımda, üç aşamalı bir işlemle (isteğe bağlı dördüncü adımla) mücadele ediyorum:

  1. ilgilenilen sütunları seçin, yani y-değerleri ve x-değerleri
  2. taban tablosunu ekstra sütunlarla genişletme - her x değeri için bir tane
  3. genişletilmiş tabloyu gruplama ve toplama - her y değeri için bir grup
  4. (isteğe bağlı) toplu tabloyu önceden onaylama

Bu adımları probleminize uygulayalım ve ne elde ettiğimize bakalım:

1.Adım: İlgilendiğiniz sütunları seçin . İstenen sonuç olarak, hostidiçerir y-değerleri ve itemnameiçerir X-değerleri .

2.Adım: Temel tabloyu fazladan sütunlarla genişletin . Genellikle x-değeri başına bir sütuna ihtiyacımız vardır. X değeri sütunumuzun şunu hatırlayın 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 |
+--------+----------+-----------+------+------+------+

Satır sayısını değiştirmediğimizi unutmayın - fazladan sütun ekledik. Ayrıca, NULLs örüntüsünün itemname = "A"yeni sütun için null olmayan bir değere Ave diğer yeni sütunlar için null değerine sahip bir satıra dikkat edin .

3.Adım: Genişletilmiş tabloyu gruplandırın ve toplayın . Biz gerek group by hostido y-değerlerini sağladığından,:

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

(Şimdi her y değeri için bir satırımız olduğunu unutmayın.) Tamam, neredeyse geldik! Sadece bu çirkinlerden kurtulmamız gerekiyorNULL .

4.Adım: Prettify . Null değerleri sıfırlarla değiştireceğiz, böylece sonuç kümesi bakmak daha güzel:

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

Ve işimiz bitti - MySQL kullanarak güzel, güzel bir pivot tablo oluşturduk.


Bu prosedürü uygularken dikkat edilmesi gerekenler:

  • fazladan sütunlarda kullanılacak değer. Kullandığım itemvaluebu örnekte
  • ekstra sütunlarda ne "nötr" değeri kullanılır. Kullandım NULL, ama aynı zamanda 0veya ""tam durumunuza bağlı olarak da olabilir
  • gruplama sırasında kullanılacak toplama işlevi. Kullandığım sumama countvemax ayrıca sık (kullanılan maxbirçok satırlar arasında yayılmış olmuştu tek satır "nesneleri" oluştururken sıklıkla kullanılır)
  • y değerleri için birden çok sütun kullanma. Bu çözüm, y değerleri için tek bir sütun kullanmakla sınırlı değildir; yalnızca ekstra sütunları group byyan tümceye takın (ve selectbunları unutmayın )

Bilinen sınırlamalar:

  • bu çözüm pivot tabloda n sütuna izin vermez - temel tablo genişletilirken her pivot sütununun manuel olarak eklenmesi gerekir. Yani 5 veya 10 x-değerleri için bu çözüm güzel. 100 için, çok hoş değil. Bir sorgu üreten saklı yordamlar ile bazı çözümler vardır, ancak doğru çirkin ve zor. Şu anda pivot tablonun çok sayıda sütuna sahip olması gerektiğinde bu sorunu çözmek için iyi bir yol bilmiyorum.

25
+1 Bu, gördüğüm
MySQL'deki

6
Mükemmel açıklama, teşekkürler. Adım 4, IFNULL (toplam (A), 0) AS A kullanılarak adım 3 ile birleştirilebilir, size aynı sonucu verir, ancak başka bir tablo oluşturmaya gerek kalmadan
nealio82

1
Pivot için en şaşırtıcı çözümdü, ancak sadece x eksenini oluşturan sütun öğesi adında birden fazla değere sahip olup olmadığını merak ediyorum, burada olduğu gibi sadece üç değere sahibiz, yani A, B, C. B, C, D, E, AB, BC, AC, AD, H ..... n. o zaman bu durumda çözüm ne olurdu.
Deepesh

1
bu gerçekten kabul edilen cevap olmalı. Bu, daha ayrıntılı, kullanışlı ve şu anda kabul edilen gibi bir makaleye bağlantı vermek yerine onu nasıl anlayacağını açıklıyor
EdgeCaseBerg

2
@BeyazBig lütfen tarihlere bir göz atın - bu StackOverflow yanıtı bu blog gönderisinden 1.5 yıl önce yazılmıştır. Belki de blogun bana kredi vermesini istemelisiniz.
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;

'A', 'B', 'C' için üç farklı satır oluşturur
Palani

1
@Palani: Hayır, değil. Bkz group by.
ruakh

Teşekkürler, bu benim için çalıştı! Ancak, yıllık sadece bir Bilginize birkaç geç, kullanmak zorunda MAXyerine SUMbenim çünkü itemValuedizeleri, değil sayısal değerlerdir.
Merricat

33

Başka bir seçenek, özellikle pivot yapmanız gereken birçok öğeniz varsa, mysql'in sorguyu sizin için oluşturmasına izin vermektir:

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 Çalıştığını görmek için bazı ekstra değerler eklendi

GROUP_CONCAT varsayılan değeri 1000'dir, bu nedenle gerçekten büyük bir sorgunuz varsa bu parametreyi çalıştırmadan önce değiştirin

SET SESSION group_concat_max_len = 1000000;

Ölçek:

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 Belki bana yardım edebilirsin. Şuna bakın: stackoverflow.com/questions/51832979/…
Başarı Adamı

Basitleştirmek Can 'ifnull(SUM(case when itemname = ''',ile ''' then itemvalue end),0) AS karşı `' 'SUM(case when itemname = '''ile ''' then itemvalue else 0 end) AS ',. Bu gibi terimler çıkarır SUM(case when itemname = 'A' then itemvalue else 0 end) AS 'A'.
ToolmakerSteve

24

Matt Fenwick'in sorunu çözmeme yardımcı olan fikrinden yararlanarak (çok teşekkürler), bunu sadece bir sorguya indirgeyelim:

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

14

Agung Sagita'nın cevabını alt sorgudan katılmak için düzenliyorum . Bu 2 yol arasında ne kadar fark olduğundan emin değilim, sadece başka bir referans için.

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'

2
Muhtemelen, bu daha hızlı bir çözüm olabilir.
jave.web

Ben öyle düşünmüyorum. çünkü sol birleşmenin kendi gecikmesi var!
Abadis

10

alt sorgu kullan

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

ancak alt sorgu bir satırdan daha fazla sonuç veriyorsa, alt sorguda daha fazla toplama işlevi kullanın


4

Çözümüm :

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

Sunulan davada beklenen sonuçları üretir.


3

Ben içine Group By hostIdo zaman
gibi değerleri ile sadece ilk satırı gösterecektir yapmak :

A   B  C
1  10
2      3

3

Raporlarımı basit sorguları kullanarak satırları sütunlara neredeyse dinamik hale getirmenin bir yolunu buluyorum. Burada çevrimiçi görebilir ve test edebilirsiniz .

Sorgu sütunlarının sayısı sabittir, ancak değerler dinamiktir ve satırların değerlerini temel alır. Bunu oluşturabilirsiniz Yani, tablo başlığı oluşturmak için bir sorgu ve değerleri görmek için başka bir sorgu kullanın:

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;

Siz de özetleyebilirsiniz:

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

RexTester Sonuçları :

RexTester Sonuçları

http://rextester.com/ZSWKS28923

Gerçek bir kullanım örneği için bu rapor, teknenin / otobüsün kalkış saatlerini görsel bir programla sütunlarda göstermektedir. Görselleştirmeyi karıştırmadan son sütunda kullanılmayan bir sütun daha göreceksiniz: sistema venda de passagens çevrimiçi e tüketim ürünleri son e controle de frota - xsl teknoloji - xsl.com.br ** Biletleme sistemi online ve sunum biletini satma


3

MariaDB'yi kullanabiliyorsanız çok kolay bir çözüm var.

Yana mariadb-10,02 olarak adlandırılan yeni bir depolama motorunu orada eklendi CONNECT Sen bir göz olabilir: sadece ne istediğinizi gibi, pivot tabloya başka bir sorgu veya tablonun sonuçlarını dönüştürmek için bize yardımcı olabilir docs .

Her şeyden önce bağlantı depolama motorunu takın .

Şimdi tablonuzun pivot sütunu itemnameve her öğenin verileri itemvaluesütunda bulunur, bu nedenle bu sorguyu kullanarak sonuç pivot tablosuna sahip olabiliriz:

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

Şimdi aşağıdakilerden ne istediğimizi seçebiliriz pivot_table:

select * from pivot_table

Daha fazla ayrıntı burada


1

Bu aradığınız tam cevap değil, ancak projemde ihtiyaç duyduğum bir çözümdü ve umarım bu birine yardımcı olur. Bu, virgülle ayrılmış 1 ila n satır öğesini listeler. Group_Concat bunu MySQL'de mümkün kılar.

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

Bu mezarlığın iki ortak adı vardır, bu nedenle adlar tek bir kimlikle bağlanan farklı satırlarda listelenir, ancak iki ad kimliği ve sorgu bu gibi bir şey üretir.

    CemeteryID Cemetery_Name Latitude
    1 Appleton, Sulpher Springs


-2

Bunu söylediğim için üzgünüm ve belki de sorununuzu tam olarak çözmüyorum ama PostgreSQL MySQL'den 10 yıl daha eski ve MySQL'e kıyasla son derece gelişmiş ve bunu başarmanın birçok yolu var. PostgreSQL'i kurun ve bu sorguyu yürütün

CREATE EXTENSION tablefunc;

o zaman voila! Ve işte kapsamlı belgeler: PostgreSQL: Belgeler: 9.1: tablefunc veya bu sorgu

CREATE EXTENSION hstore;

sonra tekrar voila! PostgreSQL: Belgeler: 9.0: hstore

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.