MySQL - Hàng đến cột


185

Tôi đã cố gắng tìm kiếm các bài đăng, nhưng tôi chỉ tìm thấy giải pháp cho SQL Server / Access. Tôi cần một giải pháp trong MySQL (5.X).

Tôi có một bảng (được gọi là lịch sử) với 3 cột: hostid, itemname, itemvalue.
Nếu tôi chọn select ( select * from history), nó sẽ trả về

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

Làm thế nào để tôi truy vấn cơ sở dữ liệu để trả về một cái gì đó như

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

@Rob, Bạn có thể vui lòng chỉnh sửa câu hỏi để bao gồm truy vấn chính xác không?
Johan

Câu trả lời:


274

Tôi sẽ thêm một lời giải thích dài hơn và chi tiết hơn về các bước cần thực hiện để giải quyết vấn đề này. Tôi xin lỗi nếu nó quá dài.


Tôi sẽ bắt đầu với cơ sở bạn đã đưa ra và sử dụng nó để xác định một vài thuật ngữ mà tôi sẽ sử dụng cho phần còn lại của bài đăng này. Đây sẽ là bảng cơ sở :

select * from history;

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

Đây sẽ là mục tiêu của chúng tôi, bảng xoay vòng đẹp :

select * from history_itemvalue_pivot;

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

Các giá trị trong history.hostidcột sẽ trở thành giá trị y trong bảng trụ. Các giá trị trong history.itemnamecột sẽ trở thành giá trị x (vì lý do rõ ràng).


Khi tôi phải giải quyết vấn đề tạo bảng xoay vòng, tôi giải quyết nó bằng quy trình ba bước (với bước thứ tư tùy chọn):

  1. chọn các cột quan tâm, tức là giá trị yx-giá trị
  2. mở rộng bảng cơ sở với các cột bổ sung - một cột cho mỗi giá trị x
  3. nhóm và tổng hợp bảng mở rộng - một nhóm cho mỗi giá trị y
  4. (tùy chọn) sắp xếp lại bảng tổng hợp

Hãy áp dụng các bước này cho vấn đề của bạn và xem những gì chúng tôi nhận được:

Bước 1: chọn các cột quan tâm . Trong kết quả mong muốn, hostidcung cấp các giá trị yitemnamecung cấp các giá trị x .

Bước 2: mở rộng bảng cơ sở với các cột phụ . Chúng tôi thường cần một cột cho mỗi giá trị x. Hãy nhớ rằng cột giá trị x của chúng tôi là 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 |
+--------+----------+-----------+------+------+------+

Lưu ý rằng chúng tôi đã không thay đổi số lượng hàng - chúng tôi chỉ thêm các cột bổ sung. Cũng lưu ý mẫu của NULLs - một hàng itemname = "A"có giá trị khác null cho cột mới Avà giá trị null cho các cột mới khác.

Bước 3: nhóm và tổng hợp bảng mở rộng . Chúng ta cần group by hostid, vì nó cung cấp các giá trị 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 |
+--------+------+------+------+

(Lưu ý rằng chúng tôi hiện có một hàng cho mỗi giá trị y.) Được rồi, chúng tôi sắp hoàn thành! Chúng ta chỉ cần thoát khỏi những NULLs xấu xí .

Bước 4: tô điểm . Chúng ta sẽ thay thế bất kỳ giá trị null nào bằng số 0 để tập kết quả đẹp hơn để xem xét:

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

Và chúng tôi đã hoàn thành - chúng tôi đã xây dựng một bảng trụ đẹp, đẹp bằng cách sử dụng MySQL.


Cân nhắc khi áp dụng thủ tục này:

  • giá trị nào để sử dụng trong các cột thêm. Tôi đã sử dụng itemvaluetrong ví dụ này
  • giá trị "trung tính" nào sẽ được sử dụng trong các cột thêm. Tôi đã sử dụng NULL, nhưng nó cũng có thể 0hoặc "", tùy thuộc vào tình huống chính xác của bạn
  • chức năng tổng hợp nào để sử dụng khi nhóm. Tôi đã sử dụng sum, nhưng countmaxcũng thường được sử dụng ( maxthường được sử dụng khi xây dựng các "đối tượng" một hàng được trải rộng trên nhiều hàng)
  • sử dụng nhiều cột cho giá trị y. Giải pháp này không giới hạn trong việc sử dụng một cột duy nhất cho các giá trị y - chỉ cần cắm các cột bổ sung vào group bymệnh đề (và đừng quên selectchúng)

Những hạn chế đã biết:

  • giải pháp này không cho phép n cột trong bảng trụ - mỗi cột cần phải được thêm thủ công khi mở rộng bảng cơ sở. Vì vậy, đối với 5 hoặc 10 giá trị x, giải pháp này là tốt đẹp. Đối với 100, không tốt đẹp. Có một số giải pháp với các thủ tục được lưu trữ tạo ra một truy vấn, nhưng chúng xấu và khó lấy đúng. Hiện tại tôi không biết một cách hay để giải quyết vấn đề này khi bảng trụ cần có nhiều cột.

25
+1 Đây là lời giải thích tốt nhất / rõ ràng nhất về các bảng trụ / tab chéo trong MySQL mà tôi đã thấy
cameron.bracken

6
Giải thích tuyệt vời, cảm ơn. Bước 4 có thể được hợp nhất vào bước 3 bằng cách sử dụng IFNULL (tổng (A), 0) NHƯ A, mang lại cho bạn kết quả tương tự nhưng không cần phải tạo một bảng khác
nealio82 20/03/13

1
Đó là giải pháp tuyệt vời nhất cho trục, nhưng tôi chỉ tò mò nếu trong tên mục cột tạo thành trục x có nhiều giá trị, như ở đây chúng ta chỉ có ba giá trị tức là A, B, C. Nếu các giá trị này được mở rộng thành A, B, C, D, E, AB, BC, AC, AD, H ..... n. sau đó trong trường hợp này điều gì sẽ là giải pháp.
Deepesh

1
đây thực sự nên là câu trả lời được chấp nhận ở đây Đó là cách chi tiết, hữu ích và giải thích cách hiểu nó hơn là chỉ liên kết đến một số bài viết như bài viết hiện đang được chấp nhận
EdgeCaseBerg

1
@WhiteBig vui lòng xem ngày - câu trả lời StackOverflow này đã được viết 1,5 năm trước bài đăng trên blog đó. Có lẽ thay vào đó bạn nên yêu cầu blog ghi có cho tôi.
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;

Tạo ba hàng khác nhau, cho 'A', 'B', 'C'
Palani

@Palani: Không, không. Xem group by.
ruakh 17/03 '

Cảm ơn, điều này đã làm việc cho tôi! Tuy nhiên, chỉ là một FYI muộn vài năm, tôi đã phải sử dụng MAXthay SUMvì bởi vì tôi itemValuelà các chuỗi, không phải là giá trị số.
Merricat

31

Một tùy chọn khác, đặc biệt hữu ích nếu bạn có nhiều mục bạn cần xoay vòng là để mysql xây dựng truy vấn cho bạn:

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 Đã thêm một số giá trị bổ sung để xem nó hoạt động

GROUP_CONCAT có giá trị mặc định là 1000, vì vậy nếu bạn có một truy vấn thực sự lớn, hãy thay đổi tham số này trước khi chạy nó

SET SESSION group_concat_max_len = 1000000;

Kiểm tra:

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 Có lẽ bạn có thể giúp tôi. Nhìn vào đây: stackoverflow.com/questions/51832979/ từ
Người thành công

24

Tận dụng ý tưởng của Matt Fenwick đã giúp tôi giải quyết vấn đề (rất nhiều lời cảm ơn), chúng ta hãy giảm nó xuống chỉ còn một truy vấn:

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

Tôi chỉnh sửa câu trả lời của Agung Sagita từ câu hỏi phụ để tham gia. Tôi không chắc có bao nhiêu sự khác biệt giữa 2 cách này, nhưng chỉ để tham khảo.

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
Có thể, đây có thể là một giải pháp nhanh hơn.
jave.web

Tôi không nghĩ vậy. bởi vì tham gia trái có độ trễ riêng của nó!
Abadis

10

sử dụng truy vấn con

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

nhưng nó sẽ là một vấn đề nếu truy vấn phụ dẫn đến nhiều hàng, sử dụng hàm tổng hợp tiếp theo trong truy vấn con


4

Giải pháp của tôi :

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

Nó tạo ra kết quả mong đợi trong trường hợp gửi.


3

Tôi thực hiện điều Group By hostIdđó sau đó nó sẽ chỉ hiển thị hàng đầu tiên với các giá trị,
như:

A   B  C
1  10
2      3

3

Tôi tìm ra một cách để làm cho các báo cáo của mình chuyển đổi các hàng thành các cột gần như động bằng các truy vấn đơn giản. Bạn có thể xem và kiểm tra nó trực tuyến tại đây .

Số lượng cột truy vấn là cố định nhưng các giá trị là động và dựa trên giá trị của các hàng. Bạn có thể xây dựng nó Vì vậy, tôi sử dụng một truy vấn để xây dựng tiêu đề bảng và một truy vấn khác để xem các giá trị:

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;

Bạn cũng có thể tóm tắt nó:

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

Kết quả của RexTester :

Kết quả của RexTester

http://rextester.com/ZSWKS28923

Đối với một ví dụ thực tế về việc sử dụng, báo cáo dưới đây hiển thị trong các cột số giờ khởi hành của thuyền / xe buýt với một lịch trình trực quan. Bạn sẽ thấy một cột bổ sung không được sử dụng ở cột cuối cùng mà không nhầm lẫn giữa trực quan hóa: sistema pursa de passagens trực tuyến e tiêu dùng cuối cùng e controle de frota - xsl tecnologia - xsl.com.br ** hệ thống bán vé để bán vé trực tuyến và có giá trị


3

Nếu bạn có thể sử dụng MariaDB, có một giải pháp rất dễ dàng.

Kể từ MariaDB-10.02 , đã có thêm một công cụ lưu trữ mới có tên CONNECT có thể giúp chúng tôi chuyển đổi kết quả của một truy vấn hoặc bảng khác thành bảng xoay vòng, giống như những gì bạn muốn: Bạn có thể xem tài liệu .

Trước hết cài đặt công cụ lưu trữ kết nối .

Bây giờ cột trục của bảng của chúng tôi là itemnamevà dữ liệu cho từng mục được đặt trong itemvaluecột, vì vậy chúng tôi có thể có bảng trục kết quả bằng truy vấn này:

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

Bây giờ chúng ta có thể chọn những gì chúng ta muốn từ pivot_table:

select * from pivot_table

Thêm chi tiết tại đây


1

Đây không phải là câu trả lời chính xác mà bạn đang tìm kiếm nhưng nó là một giải pháp mà tôi cần cho dự án của mình và hy vọng điều này sẽ giúp được ai đó. Điều này sẽ liệt kê các mục từ 1 đến n hàng được phân tách bằng dấu phẩy. Group_Concat làm cho điều này có thể có trong 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

Nghĩa trang này có hai tên phổ biến, vì vậy các tên được liệt kê trong các hàng khác nhau được kết nối bởi một id duy nhất nhưng hai id tên và truy vấn tạo ra một cái gì đó giống như

    Nghĩa trang Nghĩa
    trang_Name Latitude 1 Appleton, Sulpher Springs 35.4276242832293


-2

Tôi rất tiếc phải nói điều này và có lẽ tôi không giải quyết chính xác vấn đề của bạn nhưng PostgreSQL lớn hơn MySQL 10 năm và cực kỳ tiên tiến so với MySQL và có nhiều cách để đạt được điều này một cách dễ dàng. Cài đặt PostgreSQL và thực hiện truy vấn này

CREATE EXTENSION tablefunc;

rồi thì đấy! Và đây là tài liệu mở rộng: PostgreSQL: Tài liệu: 9.1: tablefunc hoặc truy vấn này

CREATE EXTENSION hstore;

rồi lại voila! PostgreSQL: Tài liệu: 9.0: hstore

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.