За чистый и ясный код!

Статьи на тему программирования под веб, используя PHP, MySQL, Jquery и многое другое

Работа с функцией GROUP_CONCAT()

Июнь15

Всем привет!

Хочу рассказать о работу с функцией GROUP_CONCAT.

Помнится меня на собеседовании в одну компанию спросили, как получить данные при группировке в строку…. ответа я не знал, конечно в тот же вечер я все узнал и понял как это легко))

Недавно мне надо было написать запрос как раз с использованием этой функции, и решил написать об этом статью.

И так, представим две таблицы, author(авторы) и books(книги авторов).

Заполним данные, ниже представлены две эти таблицы:

Таблица авторов:

CREATE TABLE `author` (
	`id` INT(10) NOT NULL AUTO_INCREMENT,
	`name` VARCHAR(200) NULL DEFAULT NULL COLLATE 'utf8_unicode_ci',
	`fam` VARCHAR(200) NULL DEFAULT NULL COLLATE 'utf8_unicode_ci',
	`birthday` DATE NULL DEFAULT NULL,
	PRIMARY KEY (`id`)
)
COLLATE='utf8_unicode_ci'
ENGINE=InnoDB
ROW_FORMAT=DEFAULT
AUTO_INCREMENT=3;

Заполняем таблицу:

INSERT INTO `author` (`id`, `name`, `fam`, `birthday`) VALUES (1, 'Николай Николаевич', 'Носов', '2008-11-23');
INSERT INTO `author` (`id`, `name`, `fam`, `birthday`) VALUES (2, 'Артур Конан', 'Дойль', '1859-05-22');

Таблица книг авторов:

CREATE TABLE `books` (
	`id` INT(10) NOT NULL AUTO_INCREMENT,
	`id_author` INT(10) NULL DEFAULT NULL,
	`book` VARCHAR(500) NULL DEFAULT NULL COLLATE 'utf8_unicode_ci',
	PRIMARY KEY (`id`),
	INDEX `id_author` (`id_author`)
)
COLLATE='utf8_unicode_ci'
ENGINE=InnoDB
ROW_FORMAT=DEFAULT
AUTO_INCREMENT=8;

Заполняем таблицу:

INSERT INTO `books` (`id`, `id_author`, `book`) VALUES (1, 1, 'Незнайка учится');
INSERT INTO `books` (`id`, `id_author`, `book`) VALUES (2, 1, 'Незнайка-путешественник');
INSERT INTO `books` (`id`, `id_author`, `book`) VALUES (3, 1, 'Винтик, Шпунтик и пылесос');
INSERT INTO `books` (`id`, `id_author`, `book`) VALUES (4, 2, 'Затерянный мир');
INSERT INTO `books` (`id`, `id_author`, `book`) VALUES (5, 2, 'Шерлок Холмс');
INSERT INTO `books` (`id`, `id_author`, `book`) VALUES (6, 2, 'Шерлок Холмс');
INSERT INTO `books` (`id`, `id_author`, `book`) VALUES (7, 2, NULL);

Сделаем выборки :

mysql> select * from author;
+----+-------------------------------------+------------+------------+
| id | name                                | fam        | birthday   |
+----+-------------------------------------+------------+------------+
|  1 | Николай Николаевич                  | Носов      | 2008-11-23 |
|  2 | Артур Конан                         | Дойль      | 1859-05-22 |
+----+-------------------------------------+------------+------------+
2 rows in set (0.00 sec)

mysql> select * from books;
+----+-----------+------------------------------------------------+
| id | id_author | book                                           |
+----+-----------+------------------------------------------------+
|  1 |         1 | Незнайка учится                                |
|  2 |         1 | Незнайка-путешественник                        |
|  3 |         1 | Винтик, Шпунтик и пылесос                      |
|  4 |         2 | Затерянный мир                                 |
|  5 |         2 | Шерлок Холмс                                   |
|  6 |         2 | Шерлок Холмс                                   |
|  7 |         2 | NULL                                           |
+----+-----------+------------------------------------------------+
7 rows in set (0.00 sec)

Обратите внимание, во второй выборке я специально добавил дублирующееся поле (Шерлок Холмс) и также поле с NULL записью, для чего это я сделал объясню позже.

Теперь нам надо выбрать всех авторов и их книги, если я напишу запрос

SELECT a.id, a.name, a.fam,  b.book  as books
FROM `author` a
LEFT JOIN `books` b ON a.id=b.id_author;

В итоге мы получил все книги по всем авторам:

+----+-----------------------+----------+----------------------------+
| id | name                  | fam      | books                      |
+----+-----------------------+----------+----------------------------+
|  1 | Николай Николаевич    | Носов    | Незнайка учится            |
|  1 | Николай Николаевич    | Носов    | Незнайка-путешественник    |
|  1 | Николай Николаевич    | Носов    | Винтик, Шпунтик и пылесос  |
|  2 | Артур Конан           | Дойль    | Затерянный мир             |
|  2 | Артур Конан           | Дойль    | Шерлок Холмс               |
|  2 | Артур Конан           | Дойль    | Шерлок Холмс               |
|  2 | Артур Конан           | Дойль    | NULL                       |
+----+-----------------------+----------+----------------------------+
7 rows in set (0.00 sec)

А мне бы получить записи авторов, т.е. две записи, в нашем случае, и чтобы в поле books были все книги автора…

Это можно сделать применив как раз ф-цию GROUP_CONCAT().
Схема ф-ции:

GROUP_CONCAT([DISTINCT] expr [,expr ...]
             [ORDER BY {unsigned_integer | col_name | expr}
                 [ASC | DESC] [,col_name ...]]
             [SEPARATOR str_val])

Для того, чтобы расписать каждую из команд, я напишу пример:

mysql> SELECT a.id, a.name, a.fam, GROUP_CONCAT(DISTINCT b.book ORDER BY b.book ASC SEPARATOR ', ') AS books
    -> FROM `author` a
    -> LEFT JOIN `books` b ON a.id=b.id_author
    -> GROUP BY a.id;
+----+--------------------+-------+---------------------------------------------------------------------+
| id | name               | fam   | books                                                               |
+----+--------------------+-------+---------------------------------------------------------------------+
|  1 | Николай Николаевич | Носов | Винтик, Шпунтик и пылесос, Незнайка учится, Незнайка-путешественник |
|  2 | Артур Конан        | Дойль | Затерянный мир, Шерлок Холмс                                        |
+----+--------------------+-------+---------------------------------------------------------------------+
2 rows in set (0.05 sec)

Как видно после использование этой ф-ции я получил то, что хотел — дву строчки и в поле books все книги автора.

Теперь распишем каждую команду.
Начну с того, что группировке поддается только поле отличные от NULL, т.е. записи с содержимыми типа NULL не будет в поле books.

DISTINCT — позволяет выбрать не повторяющиеся значения, т.е. книга с дублирующим названием будет откинута, для этого я ввел дважды название книги Шерлок Холмс;
ORDER BY col_name ASC | DESC — позволяет сортировать данные;
SEPARATOR — позволяет разделить данные через нужный вам делитель, по-умолчанию это запятая «,» (без кавычек)

Также эта функция относится к групповым функциям, поэтому использование её без оператора GROUP BY не желательно, иначе в нашем примере получим одну строку с первым автором в таблице и всеми книгами которые есть в таблице книг.

Есть еще одно ограничение. Кол-во символов которые будут отображены в сгруппированном поле по-умолчанию 1024, если надо больше, то использую переменную group_concat_max_len устанавливаем своё значение:

синатксис:
SET [GLOBAL | SESSION] group_concat_max_len = val;

пример:
SET group_concat_max_len = 2048;

Всем спасибо, в пользу тебе %username%.

Статья просмотренна 268408 раз, зашло посетителей 96853

рубрика: MySQL

Комментарии (51)

Загружаем... Выполняем авторизацию...
Спасибо, познавательно!
Действительно полезно. Не знал об этой функции...
5 ответов · последнее действие 604 недель назад
Есть еще частая проблема с GROUP_CONCAT, когда данные возвращаются в BLOB. Решается так:
GROUP_CONCAT(CAST(id AS CHAR)

Подробности: http://pyha.ru/forum/topic/4308.msg91137#msg91137
1 ответ · последнее действие менее 1 минуты назад
Спасибо, очень доходчиво всё объяснили. Я подобную задачу решал с помощью php (рекурсия -> массив -> implode), но sql-решение в разы элегантнее.
Вы не проверяли производительность таких запросов на проектах с высокой посещаемостью и большой базой?
1 ответ · последнее действие менее 1 минуты назад
Спасибо, за описание! Очень долго гуглил внятного решения не мог найти.
Как раз разбирался с этой функцией - запрос делал выборку и со временем страница сломалась, так как проект подрос, добавил выполнение SET group_concat_max_len = 4048; и заработало
1 ответ · последнее действие менее 1 минуты назад
Отличная новость! Уже спешу к вам!
"Помниться", "храниться"... Ухо режет.
1 ответ · последнее действие 651 недель назад
А как можно вывести в php в виде таблицы эти данные,в примере "А мне бы получить записи авторов, т.е. две записи, в нашем случае, и чтобы в поле books были все книги автора…",там,где данные берутся из нескольких таблиц?Вывести так,как показано в примере?
1 ответ · последнее действие 626 недель назад
Спасибо, оч доступно. Помогло.

з.ы. to "bondyara" доступно и понятно а ухи не "орехи" переживут))
1 ответ · последнее действие 626 недель назад
Спасибо за статью.

Благодаря ей быстро нагуглила ответ. :)
1 ответ · последнее действие 613 недель назад
Спасибо! Ваша статья отправная точка в решении моей проблемы.
Подскажите пожалуйста, как можно усовершенствовать запрос для получения идентификаторов к каждой книге? Конечно можно точно также сгруппировать идентификаторы и в скрипте уже обрабатывать, но хотелось бы более лучший способ :) Возможно с использованием алиасов?
Спасибо! То, что нужно было! А то нашелся в гугле какой-то жуткий вариант, а у вас все ясно и просто!
1 ответ · последнее действие 591 недель назад
большое спасибо! пример отличный, очень доходчиво объясняете
1 ответ · последнее действие 561 недель назад
Спасибо, иногда видимо может пригодиться (искал вовсе не это).
по поводу производительности: кажется в результатах все видно 0.00sec против 0.05sec - в лучшем случае в 10раз (0.006 округлится до 0.01)
2 ответа · последнее действие 545 недель назад
супер!! спасибо
1 ответ · последнее действие 544 недель назад
Спасибо за статью.

Не знал как такое реализовать, приходилось использовать кучу переменных для сравнивания "одинаков ли id прошлой записи" :)

Буду переписывать на нормальный код!
1 ответ · последнее действие 526 недель назад
очень помогла статья, спасибо тебе, добрый человек)
1 ответ · последнее действие 508 недель назад
а зачем там left join? оно ведь и без этого работает
1 ответ · последнее действие 459 недель назад
Большое спасибо за подробную и познавательную статью! Очень помогла.
Хотелось бы еще добавить что в PhpMyadmin запрос увеличения лимита надо делать вместе с основным запросом. Иначе лимит не увеличится.
Типа:
SET SESSION group_concat_max_len = 16384;
UPDATE ....
1 ответ · последнее действие 459 недель назад
Спасибо тебе, кто бы ты ни был! Эта функция решит много проблем с которыми раньше сталкивался.
Благодарен за твою работу!
1 ответ · последнее действие 379 недель назад
Огромное спасибо Вы только что спасили меня вот от такого монстра:

SELECT t1.id as ID, t1.post_name as URL, t1.post_title as NAME, t2.meta_value as BEGIN, t1.post_content as DETAIL, t3.meta_value as END, t4.meta_value as LOGO, t4.meta_value as CNT_ACTIONS, t4.meta_value as COST_ACTION, t7.meta_value as LANG, t8.meta_value as VENUE
FROM wp_posts t1
LEFT JOIN wp_postmeta t2 ON t1.id = t2.post_id AND t2.meta_key = '_EventStartDate'
LEFT JOIN wp_postmeta t3 ON t1.id = t3.post_id AND t3.meta_key = '_EventEndDate'
LEFT JOIN wp_postmeta t4 ON t1.id = t4.post_id AND t4.meta_key = '_thumbnail_id'
LEFT JOIN wp_postmeta t5 ON t1.id = t5.post_id AND t5.meta_key = 'cnt_actions'
LEFT JOIN wp_postmeta t6 ON t1.id = t6.post_id AND t6.meta_key = '_EventCost'
LEFT JOIN wp_postmeta t7 ON t1.id = t7.post_id AND t7.meta_key = 'language'
LEFT JOIN wp_postmeta t8 ON t1.id = t8.post_id AND t8.meta_key = '_EventVenueID'
WHERE t1.post_status = 'publish'
AND t1.post_type='tribe_events'
I've been browsing online more than 10 hours today

for window 7 starter snpc oa download & Работа с функцией

GROUP_CONCAT(), yet I never found any interesting article like yours.

It is pretty worth enough for me. In my opinion, if all website owners and bloggers

made good content as you did, the internet will be much more useful than ever before.
Дорогой Автор Maxnag! Спасибо тебе огромное. Благодаря твоей статье решил задачу экспортаимпорта на своем сайте.

Можно вопрос? Прошу еще помочь с таким вопросом.
В моем случае у меня не книги а имена файлов. И мне нужно перед началом каждого файла ( у тебя books) добавить урл. Если взять твой пример то в финале столбец books должен содержать:

www.mydomain/Винтик, www.mydomain/Шпунтик, www.mydomain/незнайка

Не подскажешь как добавить строку "www.mydomain/" в начале?
1 ответ · последнее действие 367 недель назад

Опубликовать новый комментарий

Comments by

  

Облако тегов

cli csv dump events form Kohana locale models MySQL mysqldump orm PHP tools trigger validate газ газовый счетчик итоги кеширование переменные

Облако тегов плагина WP Cumulus для WordPress требует для просмотра Flash Player 9 или выше.

Я на твиттере!

  • у твиттера тоже бывают перерывы...

Календарь

Июнь 2010
Пн Вт Ср Чт Пт Сб Вс
« Май   Июл »
 123456
78910111213
14151617181920
21222324252627
282930  

Сейчас на сайте