web-dev-qa-db-ja.com

MySQL-行から列へ

投稿を検索しようとしましたが、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 |
   +--------+------+-----+-----+
157
Bob Rivers

この問題を解決するための手順について、もう少し長く詳細な説明を追加します。長すぎるとおIびします。


あなたが与えたベースから始め、このポストの残りの部分で使用するいくつかの用語を定義するために使用します。これはベーステーブルになります。

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-valuesになります。 history.itemname列の値はx-valuesになります(明らかな理由により)。


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

  1. 目的の列、つまりy-valuesおよびx-valuesを選択します
  2. 基本テーブルを追加の列で拡張します-それぞれに1つx-value
  3. 拡張テーブルをグループ化し、集計します-各グループに1つy-value
  4. (オプション)集約されたテーブルをきれいにします

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

ステップ1:対象の列を選択する。目的の結果では、hostidy-valuesを提供し、itemnamex-valuesを提供します。

ステップ2:余分な列でベーステーブルを拡張する。通常、x値ごとに1つの列が必要です。 x-value列は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以外の値を持ち、他の新しい列にNULL値を持ちます。

ステップ3:拡張テーブルをグループ化して集約する。 y値を提供するため、group by hostidが必要です。

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:prettify。ヌル値をゼロに置き換えるだけなので、結果セットは見やすくなります。

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のために、それほど素敵ではありません。クエリを生成するストアドプロシージャにはいくつかのソリューションがありますが、それらはくて正しいものにするのが困難です。現在、ピボットテーブルに多くの列が必要な場合、この問題を解決する良い方法がわかりません。
230
Matt Fenwick
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;
41
shantanuo

ピボットする必要のあるアイテムが多数ある場合に特に便利な別のオプションは、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
25
Mihai

問題を解決するのに役立ったMatt Fenwickのアイデアを利用して(多くの感謝)、それを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
20
jalber

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'
11
haudoing

サブクエリを使用する

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

しかし、サブクエリが行よりも多い場合は問題になります。サブクエリでさらに集計関数を使用します

7
Agung Sagita

これをGroup By hostIdにすると、値を持つ最初の行のみが表示されます。
like:

A   B  C
1  10
2      3
3
arpit

私の解決策:

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

提出されたケースで期待される結果を生成します。

2
André Wéber

単純なクエリを使用して、レポートを行から列にほぼ動的に変換する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 の結果:

Results of RexTester

http://rextester.com/ZSWKS2892

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

2
lynx_74

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

CREATE EXTENSION tablefunc;

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

CREATE EXTENSION hstore;

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

1
gdarcan

これはあなたが探している正確な答えではありませんが、私のプロジェクトで必要なソリューションであり、これが誰かの助けになることを願っています。これにより、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

1
James Humphrey

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

詳細はこちら

0
ako