MySQL - рядки до стовпців


187

Я намагався шукати публікації, але знайшов рішення тільки для SQL Server / Access. Мені потрібно рішення в MySQL (5.X).

У мене є таблиця (звана історія) з 3 стовпцями: hostid, name item, itemvalue.
Якщо я виберу select ( 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, чи можете ви відредагувати питання, щоб включити точний запит?
Йохан


ПРИМІТКА: Посилання @ako стосується лише MariaDB.
ToolmakerSteve

Автоматичне генерування і запуску шарніра
Рік Джеймс

Відповіді:


275

Я збираюся додати дещо більш тривале та детальне пояснення кроків, які потрібно вжити для вирішення цієї проблеми. Прошу вибачення, якщо це занадто довго.


Почну з основи, яку ви надали, і використаю її, щоб визначити пару термінів, які я використовуватиму для решти цієї публікації. Це буде основна таблиця :

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) AS A, даючи такий же результат, але без необхідності створення ще однієї таблиці
nealio82

1
Це було найдивовижніше рішення для півота, але мені просто цікаво, якщо в назві елемента стовпця, який утворює вісь x, є декілька значень, наприклад, у нас є лише три значення, тобто A, B, C. Якщо ці значення поширюються на A, B, C, D, E, AB, BC, AC, AD, H ..... n. то в цьому випадку, що було б рішенням.
Діепеш

1
це справді має бути прийнятою відповіддю. Це спосіб більш детальний, корисний і пояснює, як його зрозуміти, а не просто посилатися на якусь статтю, наприклад, прийняту на даний момент
EdgeCaseBerg

2
@WhiteBig, будь ласка, подивіться на дати - ця відповідь StackOverflow була написана за 1,5 року до цього повідомлення в блозі. Можливо, вам слід замість того, щоб просити блог мені кредитувати.
Метт Фенвік

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"
Палані

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 має значення за замовчуванням 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 Можливо, ти можеш мені допомогти. Подивіться на це: 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

Скориставшись ідеєю Метта Фенвіка, яка допомогла мені вирішити проблему (велика подяка), давайте зведемо її лише до одного запиту:

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

Я редагую відповідь Агунга Сагіти з підзапиту, щоб приєднатися. Я не впевнений у тому, яка різниця між цим двома способами, але лише для іншої довідки.

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

Я не думаю, що так. тому що лівий приєднання має свою затримку!
Абадіс

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

В якості одного реального прикладу використання, цей звіт нижче показує в колонках години відправлень прибуття човна / автобуса з візуальним графіком. Ви побачите один додатковий стовпець, не використаний на останньому колі, не плутайте візуалізацію: система venda de passagens онлайн та остаточний контроль споживачів та frota 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

Це кладовище має дві загальні назви, тому імена перераховані в різні рядки, з'єднані одним ідентифікатором, але два ідентифікаційні імена, і запит створює щось подібне до цього

    CemeteryID Cemetery_Name Latitude
    1 Appleton, Sulpher Springs 35.4276242832293


-2

Пробачте, кажу це, і, можливо, я точно не вирішую вашу проблему, але PostgreSQL на 10 років старший за MySQL і надзвичайно просунутий у порівнянні з MySQL, і існує безліч способів цього легко досягти. Встановіть PostgreSQL та виконайте цей запит

CREATE EXTENSION tablefunc;

то вуаля! І ось велика документація: PostgreSQL: Документація: 9.1: tablefunc або цей запит

CREATE EXTENSION hstore;

потім знову вуаля! PostgreSQL: Документація: 9.0: hstore

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.