MySQL-行から列へ


188

投稿を検索しようとしましたが、SQL Server / Accessのソリューションしか見つかりませんでした。MySQL(5.X)のソリューションが必要です。

hostid、itemname、itemvalueの3つの列を持つテーブル(履歴と呼ばれる)があります。
選択(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 |
   +--------+------+-----+-----+

@ロブ、質問を編集して正確なクエリを含めることができますか?
ヨハン


注:@akoのリンクはMariaDBにのみ関連しています。
ToolmakerSteve

ピボットの自動生成と実行: 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値になります(明らかな理由による)。


ピボットテーブルを作成する問題を解決する必要がある場合は、3つのステップのプロセス(オプションで4番目のステップを使用)を使用してそれに取り組みます。

  1. 対象の列、つまりy値x を選択する
  2. ベーステーブルを追加の列で拡張します-各x値に 1つ
  3. 拡張テーブルのグループ化と集計-各y値に 1つのグループ
  4. (オプション)集約されたテーブルをきれいにする

これらの手順を問題に適用して、何が得られるかを見てみましょう。

ステップ1:対象の列を選択します。望ましい結果でhostidは、y値x itemname提供します

手順2:追加の列でベーステーブルを拡張します。通常、x値ごとに1つの列が必要です。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 hostidy値を提供するため、が必要です。

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値ごとに1つの行ができることに注意してください。) わかりました、もうすぐ完了です!これらの醜いNULLs を取り除く必要があるだけです。

ステップ4:きれいにする。null値をゼロに置き換えるだけなので、結果セットが見やすくなります。

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しましたがcountmax頻繁に使用されています(max多くの行全体で普及していた1行「オブジェクト」を構築する際によく使用されます)
  • y値に複数の列を使用する。このソリューションは、y値に単一の列を使用することに限定されていません-余分な列をgroup by句に接続するだけです(そしてselectそれらを忘れないでください)。

既知の制限:

  • このソリューションでは、ピボットテーブルにn列を使用できません。ベーステーブルを拡張するときに、各ピボット列を手動で追加する必要があります。したがって、5または10のx値の場合、このソリューションは適切です。100の場合、あまり良くありません。クエリを生成するストアドプロシージャを使用したいくつかのソリューションがありますが、それらは醜く、正しく行うのが困難です。現在、ピボットテーブルに多数の列が必要な場合に、この問題を解決する適切な方法を知りません。

25
+1これは、私が見たMySQLのピボットテーブル/クロス
集計の断然

6
素晴らしい説明、ありがとう。IFNULL(sum(A)、0)AS Aを使用してステップ4をステップ3にマージすると、同じ結果が得られますが、さらに別のテーブルを作成する必要はありません
nealio82

1
これはピボットの最もすばらしい解決策でしたが、x軸を形成する列itemnameに複数の値がある場合、ちょうどA、B、Cの3つの値しかない場合に興味があります。これらの値がAに拡張される場合、 B、C、D、E、AB、BC、AC、AD、H ..... n。次に、この場合の解決策は何でしょうか。
Deepesh

1
これは本当にここで受け入れられる答えになるはずです。これは、より詳細で便利な方法であり、現在受け入れられている記事などの記事にリンクするだけではなく、理解する方法を説明しています
EdgeCaseBerg

2
@WhiteBigで日付を確認してください-このStackOverflowの回答は、ブログ投稿の1.5年前に書かれました。おそらくあなたは代わりにブログに私を信用するように頼むべきです。
Matt Fenwick 2017

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」の3つの異なる行を作成します
Palani

1
@パラニ:いいえ、違います。を参照してくださいgroup by
ruakh

ありがとう、これでうまくいきました!ただし、数年後の参考までに、数値ではなく文字列なので、MAX代わりに使用する必要がありましSUMitemValue
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 デフォルト値は1000なので、非常に大きなクエリがある場合は、実行する前にこのパラメーターを変更してください。

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多分あなたは私を助けることができます。これを見てください
Success Man

簡素化することができます'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

問題を解決するのに役立つマットフェンウィックのアイデアを利用して(大いに感謝します)、クエリを1つだけに減らしましょう。

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

簡単なクエリを使用して、レポートを行から列に変換する方法をほぼ動的にする1つの方法を見つけました。ここオンラインで確認およびテストできます

クエリは固定ですが、値は動的で、行の値に基づいています。それを作成することができるので、1つのクエリを使用してテーブルヘッダーを作成し、別のクエリを使用して値を表示します。

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

実際の使用例の1つとして、このレポートは、ボート/バスの出発時間と到着時間を視覚的なスケジュールで列に示しています。視覚化を混乱させることなく、最後の列で使用されていない1つの追加の列が表示されます。 オンラインsistema venda de Passagens e e consumidor final e controle de frota-xsl tecnologia-xsl.com.br **オンラインおよびプレゼン用にチケットを販売するチケットシステム


3

MariaDBを使用できれば、非常に簡単な解決策があります。

MariaDB-10.02から、CONNECTと呼ばれる新しいストレージエンジンが追加されました。これは、別のクエリまたはテーブルの結果を、希望どおりのピボットテーブルに変換するのに役立ちます。ドキュメントをご覧ください

まず、接続ストレージエンジンをインストールします。

これで、テーブルのピボット列が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

この墓地には2つの一般的な名前があるため、名前は単一のIDで接続された別の行にリストされますが、2つの名前IDがあり、クエリは次のようなものを生成します

    CemeteryID Cemetery_Name Latitude
    1 Appleton、Sulpher Springs 35.4276242832293


-2

申し訳ありませんが、問題を正確に解決できていない可能性がありますが、PostgreSQLはMySQLより10年古く、MySQLに比べて非常に高度であり、これを簡単に実現する方法はたくさんあります。PostgreSQLをインストールしてこのクエリを実行する

CREATE EXTENSION tablefunc;

その後出来上がり!そしてここに広範なドキュメントがあります:PostgreSQL:ドキュメント:9.1:tablefuncまたはこのクエリ

CREATE EXTENSION hstore;

その後再び出来上がり!PostgreSQL:ドキュメント:9.0:hstore

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.