MySQL - แถวไปยังคอลัมน์


188

ฉันพยายามค้นหาโพสต์ แต่ฉันพบวิธีแก้ปัญหาสำหรับ SQL Server / Access เท่านั้น ฉันต้องการโซลูชันใน MySQL (5.X)

ฉันมีตาราง (เรียกว่าประวัติ) ที่มี 3 คอลัมน์: hostid, itemname, itemvalue
ถ้าฉันเลือก ( select * from history) มันจะกลับมา

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

ฉันจะสืบค้นฐานข้อมูลเพื่อส่งคืนสิ่งที่ต้องการได้อย่างไร

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

@Rob คุณสามารถแก้ไขคำถามเพื่อรวมคำค้นหาที่แน่นอนได้หรือไม่
Johan


หมายเหตุ: ลิงค์ของ @ako นั้นเกี่ยวข้องกับ MariaDB เท่านั้น
ToolmakerSteve

การสร้างอัตโนมัติและการทำงานของ pivot: mysql.rjweb.org/doc.php/pivot
Rick James

คำตอบ:


276

ฉันจะเพิ่มคำอธิบายที่ยาวขึ้นและมีรายละเอียดมากขึ้นเกี่ยวกับขั้นตอนในการแก้ไขปัญหานี้ ฉันขอโทษถ้ามันยาวเกินไป


ฉันจะเริ่มด้วยพื้นฐานที่คุณให้และใช้เพื่อกำหนดคำศัพท์สองสามคำที่ฉันจะใช้สำหรับส่วนที่เหลือของโพสต์นี้ นี่จะเป็นตารางฐาน :

select * from history;

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

นี่จะเป็นเป้าหมายของเราตารางสาระสำคัญ :

select * from history_itemvalue_pivot;

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

ค่าในhistory.hostidคอลัมน์จะกลายเป็นค่า yในตารางเดือย ค่าในhistory.itemnameคอลัมน์จะกลายเป็นค่า x (สำหรับเหตุผลที่ชัดเจน)


เมื่อฉันต้องแก้ปัญหาในการสร้างตารางสาระสำคัญฉันจัดการมันโดยใช้กระบวนการสามขั้นตอน (ด้วยขั้นตอนที่สี่ที่เป็นตัวเลือก):

  1. เลือกคอลัมน์ที่สนใจเช่นค่า yและค่าx
  2. ขยายตารางฐานด้วยคอลัมน์เพิ่มเติม - หนึ่งคอลัมน์สำหรับแต่ละค่า x
  3. กลุ่มและรวมตารางเสริม - หนึ่งกลุ่มสำหรับแต่ละค่า y
  4. (ไม่บังคับ) ให้ใส่ข้อมูลตารางรวมไว้ล่วงหน้า

ลองใช้ขั้นตอนเหล่านี้กับปัญหาของคุณและดูว่าเราได้รับอะไรบ้าง:

ขั้นตอนที่ 1: เลือกคอลัมน์ที่น่าสนใจ ในผลที่ต้องการhostidให้ค่า Yและitemnameให้ค่า x

ขั้นตอนที่ 2: ขยายตารางฐานด้วยคอลัมน์เพิ่มเติม โดยทั่วไปเราต้องการหนึ่งคอลัมน์ต่อค่า x โปรดจำไว้ว่าคอลัมน์ค่า x ของเราคือ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 |
+--------+----------+-----------+------+------+------+

โปรดทราบว่าเราไม่ได้เปลี่ยนจำนวนแถว - เราเพิ่งเพิ่มคอลัมน์เพิ่มเติม นอกจากนี้โปรดสังเกตรูปแบบของNULLs - แถวที่itemname = "A"มีค่าที่ไม่เป็นศูนย์สำหรับคอลัมน์ใหม่Aและค่า Null สำหรับคอลัมน์ใหม่อื่น ๆ

ขั้นตอนที่ 3: กลุ่มและการรวมตารางขยาย เราต้องการgroup by hostidเนื่องจากมันมีค่า 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 |
+--------+------+------+------+

(โปรดทราบว่าตอนนี้เรามีหนึ่งแถวต่อค่า y) โอเคเราเกือบจะถึงแล้ว! เราแค่ต้องกำจัดสิ่งที่น่าเกลียดเหล่านั้นNULLออกไป

ขั้นตอนที่ 4: เสริมสวย เราจะแทนที่ค่าว่างใด ๆ ด้วยค่าศูนย์ดังนั้นชุดผลลัพธ์จะดูดีกว่า:

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

และเราก็ทำเสร็จแล้ว - เราได้สร้างตารางเดือยที่ดีและสวยโดยใช้ MySQL


ข้อควรพิจารณาเมื่อใช้ขั้นตอนนี้:

  • มูลค่าที่จะใช้ในคอลัมน์เพิ่มเติม ฉันใช้itemvalueในตัวอย่างนี้
  • ค่า "เป็นกลาง" ที่ใช้ในคอลัมน์เพิ่มเติม ฉันใช้NULLแต่อาจเป็น0หรือ""ขึ้นอยู่กับสถานการณ์ที่แน่นอนของคุณ
  • ฟังก์ชั่นรวมที่จะใช้เมื่อจัดกลุ่ม ฉันใช้sumแต่countและmaxมักจะใช้ ( maxมักใช้เมื่อสร้าง "วัตถุ" แถวเดียวที่แพร่กระจายข้ามหลายแถว)
  • ใช้หลายคอลัมน์สำหรับค่า y วิธีแก้ปัญหานี้ไม่ได้ จำกัด อยู่แค่การใช้คอลัมน์เดียวสำหรับค่า y เพียงแค่เสียบคอลัมน์เพิ่มเติมลงในgroup byข้อ (และอย่าลืมselectพวกเขา)

ข้อ จำกัด ที่รู้จัก:

  • วิธีนี้ไม่อนุญาตให้มีคอลัมน์ n คอลัมน์ในตารางสาระสำคัญ - แต่ละคอลัมน์สาระสำคัญต้องเพิ่มด้วยตนเองเมื่อขยายตารางฐาน ดังนั้นสำหรับค่า 5 หรือ 10 ค่า x วิธีนี้ดี สำหรับ 100 ไม่ใช่คนดี มีวิธีแก้ปัญหาบางอย่างที่มีขั้นตอนการจัดเก็บสร้างแบบสอบถาม แต่มันน่าเกลียดและยากที่จะเข้าใจ ขณะนี้ฉันยังไม่รู้วิธีที่ดีในการแก้ปัญหานี้เมื่อตารางเดือยจำเป็นต้องมีคอลัมน์จำนวนมาก

25
+1 นี่คือคำอธิบายที่ดีที่สุด / ชัดเจนที่สุดของตารางเดือย / แท็บไขว้ใน MySQL ที่ฉันเคยเห็น
cameron.bracken

6
คำอธิบายที่ยอดเยี่ยมขอบคุณ ขั้นตอนที่ 4 สามารถผสานเข้ากับขั้นตอนที่ 3 โดยใช้ IFNULL (ผลรวม (A), 0) เป็น A ให้ผลลัพธ์เดียวกัน แต่ไม่จำเป็นต้องสร้างอีกตารางหนึ่ง
nealio82

1
มันเป็นทางออกที่น่าทึ่งที่สุดสำหรับเดือย แต่ฉันแค่อยากรู้ว่าในคอลัมน์ชื่อรายการซึ่งเป็นแกน x มีหลายค่าเช่นที่นี่เรามีเพียงสามค่าคือ A, B, C หากค่าเหล่านี้ขยายเป็น A B, C, D, E, AB, BC, AC, AD, H ..... n แล้วในกรณีนี้สิ่งที่จะแก้ปัญหา
Deepesh

1
นี่ควรเป็นคำตอบที่ยอมรับได้ที่นี่จริงๆ เป็นวิธีที่มีรายละเอียดมีประโยชน์และอธิบายวิธีทำความเข้าใจมากกว่าเพียงแค่เชื่อมโยงไปยังบทความบางอย่างเช่นที่ยอมรับในปัจจุบัน
EdgeCaseBerg

2
@ WhiteBig โปรดดูวันที่ - คำตอบ StackOverflow นี้เขียนขึ้น 1.5 ปีก่อนโพสต์บล็อกนั้น บางทีคุณควรจะขอบล็อกให้เครดิตฉันแทน
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'
Palani

1
@Palani: ไม่มันไม่ได้ group byดู
ruakh

ขอบคุณสิ่งนี้ใช้ได้สำหรับฉัน! อย่างไรก็ตามเพียงแค่ช่วงปลายปีสองสามปีที่ผ่านมาฉันต้องใช้MAXแทนSUMเพราะฉันitemValueเป็นสตริงไม่ใช่ค่าตัวเลข
Merricat

33

ตัวเลือกอื่นมีประโยชน์อย่างยิ่งหากคุณมีหลายรายการที่คุณต้องใช้ในการหมุนเดือยคือให้ mysql สร้างคิวรีให้คุณ:

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 เพิ่มค่าพิเศษบางอย่างเพื่อให้มันทำงานได้

GROUP_CONCAT มีค่าเริ่มต้นที่ 1,000 ดังนั้นหากคุณมีคิวรีขนาดใหญ่จริงๆให้เปลี่ยนพารามิเตอร์นี้ก่อนเรียกใช้

SET SESSION group_concat_max_len = 1000000;

ทดสอบ:

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 บางทีคุณสามารถช่วยฉันได้ ดูที่นี่: stackoverflow.com/questions/51832979/…
คนที่ประสบความสำเร็จ

สามารถลดความซับซ้อน'ifnull(SUM(case when itemname = ''',กับ''' then itemvalue end),0) AS ', `ไปด้วย'SUM(case when itemname = ''' ''' then itemvalue else 0 end) AS ',เอาท์พุตคำศัพท์เช่นSUM(case when itemname = 'A' then itemvalue else 0 end) AS 'A'นี้
ToolmakerSteve

24

การใช้ประโยชน์จากความคิดของ Matt Fenwick ที่ช่วยให้ฉันแก้ปัญหาได้ (ขอขอบคุณมาก) มาลดลงเหลือเพียงหนึ่งข้อความค้นหา:

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จากแบบสอบถามย่อยเพื่อเข้าร่วม ฉันไม่แน่ใจเกี่ยวกับความแตกต่างระหว่าง 2 ทางนี้ แต่สำหรับการอ้างอิงอื่น

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
อาจเป็นวิธีแก้ปัญหาที่เร็วกว่านี้
jave.web

ฉันไม่คิดอย่างนั้น เนื่องจากการเข้าร่วมที่เหลือมีเวลาแฝงของตัวเอง!
Abadis

10

ใช้แบบสอบถามย่อย

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

แต่มันจะเป็นปัญหาหากเคียวรีย่อยส่งผลมากกว่าแถวให้ใช้ฟังก์ชันการรวมเพิ่มเติมในเคียวรีย่อย


4

ทางออกของฉัน:

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

มันสร้างผลลัพธ์ที่คาดหวังในกรณีที่ส่ง


3

ฉันทำGroup By hostIdมันแล้วมันจะแสดงเฉพาะแถวแรกที่มีค่า
เช่น:

A   B  C
1  10
2      3

3

ฉันหาวิธีหนึ่งในการทำให้รายงานของฉันแปลงแถวเป็นคอลัมน์เกือบเป็นแบบไดนามิกโดยใช้แบบสอบถามง่ายๆ คุณสามารถดูและทดสอบออนไลน์ได้ที่นี่

จำนวนคอลัมน์ของแบบสอบถามได้รับการแก้ไขแต่ค่าเป็นแบบไดนามิกและขึ้นอยู่กับค่าของแถว คุณสามารถสร้างได้ดังนั้นฉันใช้หนึ่งแบบสอบถามเพื่อสร้างส่วนหัวของตารางและอีกหนึ่งรายการเพื่อดูค่า:

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;

คุณสามารถสรุปได้เช่นกัน:

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 :

ผลลัพธ์ของ RexTester

http://rextester.com/ZSWKS28923

สำหรับตัวอย่างการใช้งานจริงอย่างใดอย่างหนึ่งรายงานตะโกนแสดงในคอลัมน์ชั่วโมงของการเดินทางมาถึงของเรือ / รถบัสพร้อมตารางการมองเห็น คุณจะเห็นคอลัมน์เพิ่มเติมหนึ่งคอลัมน์ที่ไม่ได้ใช้ที่คอลัมน์สุดท้ายโดยไม่สร้างความสับสนให้กับภาพ: sistema venda de passagens ออนไลน์และอุปกรณ์สิ้นเปลืองสุดท้ายและควบคุม frota - xsl tecnologia - xsl.com.br ** ระบบจองตั๋วไปที่ขายตั๋วออนไลน์และงานนำเสนอ


3

หากคุณสามารถใช้MariaDBก็มีวิธีแก้ปัญหาที่ง่ายมาก

เนื่องจากMariaDB-10.02มีการเพิ่มเอนจิ้นการจัดเก็บใหม่ชื่อCONNECTที่สามารถช่วยเราแปลงผลลัพธ์ของการสืบค้นหรือตารางอื่นให้เป็นตารางสาระสำคัญได้เหมือนกับที่คุณต้องการ: คุณสามารถดูเอกสารได้ได้

แรกของทั้งหมดที่ติดตั้งเครื่องมือเก็บการเชื่อมต่อ

ตอนนี้คอลัมน์ pivot ของตารางของเราคือitemnameและข้อมูลสำหรับแต่ละรายการอยู่ในitemvalueคอลัมน์ดังนั้นเราสามารถมีตารางสาระสำคัญผลลัพธ์โดยใช้แบบสอบถามนี้:

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

ตอนนี้เราสามารถเลือกสิ่งที่เราต้องการจากpivot_table:

select * from pivot_table

รายละเอียดเพิ่มเติมที่นี่


1

นี่ไม่ใช่คำตอบที่คุณต้องการ แต่มันเป็นทางออกที่ฉันต้องการในโครงการของฉันและหวังว่าจะช่วยคนได้ รายการนี้จะแสดงรายการ 1 ถึง n แถวโดยคั่นด้วยเครื่องหมายจุลภาค Group_Concat ทำให้สิ่งนี้เป็นไปได้ใน 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

สุสานนี้มีชื่อสามัญสองชื่อดังนั้นชื่อจะแสดงรายการในแถวต่าง ๆ ที่เชื่อมต่อด้วย id เดียว แต่ ID ชื่อสองชื่อและข้อความค้นหาสร้างสิ่งนี้เช่น

    CemeteryID Cemetery_Name Latitude
    1 Appleton, Sulpher Springs 35.4276242832293


-2

ฉันขอโทษที่จะพูดแบบนี้และบางทีฉันก็ไม่ได้แก้ปัญหาของคุณอย่างแน่นอน แต่ PostgreSQL นั้นเก่ากว่า MySQL 10 ปีและก้าวหน้าไปมากเมื่อเทียบกับ MySQL และมีหลายวิธีที่จะทำให้สิ่งนี้ประสบความสำเร็จได้อย่างง่ายดาย ติดตั้ง PostgreSQL และดำเนินการค้นหานี้

CREATE EXTENSION tablefunc;

ถ้าอย่างนั้น voila! และนี่คือเอกสารที่ครอบคลุม: PostgreSQL: เอกสารประกอบ: 9.1: tablefuncหรือแบบสอบถามนี้

CREATE EXTENSION hstore;

อีกแล้ว voila! PostgreSQL: เอกสาร: 9.0: hstore

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.