Форум: Форум PHPФорум ApacheФорум Регулярные ВыраженияФорум MySQLHTML+CSS+JavaScriptФорум FlashРазное
Новые темы: 0000000
MySQL на примерах. Авторы: Кузнецов М.В., Симдянов И.В. Программирование. Ступени успешной карьеры. Авторы: Кузнецов М.В., Симдянов И.В. PHP. Практика создания Web-сайтов (второе издание). Авторы: Кузнецов М.В., Симдянов И.В. Самоучитель PHP 5 / 6 (3 издание). Авторы: Кузнецов М.В., Симдянов И.В. PHP на примерах (2 издание). Авторы: Кузнецов М.В., Симдянов И.В.
ВСЕ НАШИ КНИГИ
Консультационный центр SoftTime

Форум MySQL

Выбрать другой форум

 

Здравствуйте, Посетитель!

вид форума:
Линейный форум Структурный форум

тема: Можно ли GROUP_CONCAT заменить вложенным селектом?
 
 автор: kosta_in_net   (14.12.2011 в 18:25)   письмо автору
 
 

Вот структура:
CREATE TABLE `applicants`  

   `app_id` INT(11) NOT NULL DEFAULT NULL AUTO_INCREMENT, 
   `app_name` VARCHAR(100), 
   `birthday` DATE, 
   PRIMARY KEY (`app_id`) 
); 

CREATE TABLE `education`  

   `id_app` INT(11) NOT NULL, 
   `descr`  VARCHAR(255) NOT NULL 
);

Запрос:
SELECT a.app_name, GROUP_CONCAT(e.descr SEPARATOR '; ')  
  FROM applicants a  
    LEFT JOIN education e ON e.id_app = a.app_id 
  GROUP BY a.app_id

Можно ли, с тем же результатом, вместо GROUP_CONCAT применить вложенный селект?

  Ответить  
 
 автор: cheops   (14.12.2011 в 19:31)   письмо автору
 
   для: kosta_in_net   (14.12.2011 в 18:25)
 

Можно, но запрос будет выполняться дольше.

  Ответить  
 
 автор: kosta_in_net   (15.12.2011 в 12:42)   письмо автору
 
   для: cheops   (14.12.2011 в 19:31)
 

Дольше - это понятно, так как потребуются вложенные запросы. Но всвязи с ограничением длины GROUP_CONCAT, все таки хотелось бы разобраться, можно ли заменить его подзапросом, и, если можно, то как?

  Ответить  
 
 автор: cheops   (15.12.2011 в 13:02)   письмо автору
 
   для: kosta_in_net   (15.12.2011 в 12:42)
 

Кстати, длину можно менять при помощи серверной переменной group_concat_max_len, в том числе и лишь для текущей сессии.

  Ответить  
 
 автор: cheops   (15.12.2011 в 13:11)   письмо автору
 
   для: kosta_in_net   (15.12.2011 в 12:42)
 

Тут штука в том, что в одну строку не получится, т.е. вернуть результат можно, но вам его к виду GROUP_CONCAT придется в приложении приводить.

  Ответить  
 
 автор: kosta_in_net   (15.12.2011 в 16:06)   письмо автору
 
   для: cheops   (15.12.2011 в 13:11)
 

про group_concat_max_len все понятно. Но не хочется туда целый мегабайт отдавать (возможно это требует дополнительнве расходы памяти), а заранее требуемый объем не определишь.
Если не в одну строку, то это уже... как бы и не вложенный селект. Левым джоном не в одну строку сделать не проблема. Проблема возникает, если нужен постраничный вывод. Одно дело сгруппировать все записи по главной таблице и взять десяток в нужном диапазоне, другое дело брать десяток несгруппированных записей.

Кстати, по моим тестам левым джоном и фильтрацией в приложении выходит в 2 раза быстрее, чем через GROUP_CONCAT... Но это без постраничного деления, в котором потребуется вначале сделать запрос только к главной таблице на определение интервала требуемых записей (к примеру, для определения что в диапазон попадают записи с id от... до..., а потом уже получение связанных данных с id от и до). При чем, такое определение интервала невозможно, если сортировка идет не по уникальному полю (товары с ценой в 100 рублей могут занимать несколько страниц и поделить их на страницы не удастся)

  Ответить  
 
 автор: kosta_in_net   (22.12.2011 в 17:02)   письмо автору
 
   для: kosta_in_net   (15.12.2011 в 16:06)
 

Вот данные для теста:

CREATE TABLE `parent` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(20) NOT NULL,
  PRIMARY KEY (`id`)
);

INSERT INTO `parent` (`id`, `name`) VALUES
(1, 'Вася'),
(2, 'Петр');

CREATE TABLE `education` (
  `parentid` int(11) NOT NULL,
  `id` int(11) NOT NULL,
  `name` varchar(255) NOT NULL,
  PRIMARY KEY (`parentid`,`id`)
);

INSERT INTO `education` (`parentid`, `id`, `name`) VALUES
(1, 1, 'детский сад №1'),
(1, 2, 'школа №5'),
(2, 1, 'детский сад №2'),
(2, 2, 'школа №2'),
(2, 3, 'институт какой-то');

CREATE TABLE `work` (
  `parentid` int(11) NOT NULL,
  `id` int(11) NOT NULL,
  `name` varchar(255) NOT NULL,
  PRIMARY KEY (`parentid`,`id`)
);

INSERT INTO `work` (`parentid`, `id`, `name`) VALUES
(1, 1, 'Плотник'),
(1, 2, 'Слесарь'),
(1, 3, 'Токарь'),
(2, 1, 'Мастер цеха'),
(2, 2, 'Главный инженер'),
(2, 3, 'Зам. директора'),
(2, 4, 'Главный инженер');


Если выбирать данные так:

SELECT SQL_CALC_FOUND_ROWS p.id,w.id AS w_id,e.id AS e_id,p.name AS person,w.name AS work,e.name AS education
FROM parent p 
LEFT JOIN work w ON w.parentid=p.id 
LEFT JOIN education e ON e.parentid=p.id 

Будет выбрано 18 строк, которые придется дальше обрабатывать программно. Деление на страницы станет проблематичным.
Поэтому записи требуется группировать. Но обратите внимание: у второго человека четвертая запись в таблице work такая же, как и вторая. Это пример того, что после зам. директора человек вернулся на предыдущую должность. Возникает проблема: без DISTINCT появляется куча дублей, не только грозящих переполнить допустимую память, но и создающих неудобства в обработке

SELECT SQL_CALC_FOUND_ROWS p.id,p.name
,GROUP_CONCAT(w.id SEPARATOR "|") AS w_id,GROUP_CONCAT(w.name SEPARATOR "|") AS work
,GROUP_CONCAT(e.id SEPARATOR "|") AS e_id,GROUP_CONCAT(e.name SEPARATOR "|") AS education
FROM parent p 
LEFT JOIN work w ON w.parentid=p.id 
LEFT JOIN education e ON e.parentid=p.id 
GROUP BY p.id

А с DISTINCT

SELECT SQL_CALC_FOUND_ROWS p.id,p.name
,GROUP_CONCAT(DISTINCT w.id SEPARATOR "|") AS w_id,GROUP_CONCAT(DISTINCT w.name SEPARATOR "|") AS work
,GROUP_CONCAT(DISTINCT e.id SEPARATOR "|") AS e_id,GROUP_CONCAT(DISTINCT e.name SEPARATOR "|") AS education
FROM parent p 
LEFT JOIN work w ON w.parentid=p.id 
LEFT JOIN education e ON e.parentid=p.id 
GROUP BY p.id

Теряется вся повторяющаяся информация. В том числе повторная работа на том же месте.
Решить проблему удается внедрением CONCAT_WS в данные GROUP_CONCAT:

SELECT SQL_CALC_FOUND_ROWS p.id,p.name
,GROUP_CONCAT(DISTINCT CONCAT_WS("|",w.id,w.name) SEPARATOR "<br>") AS work
,GROUP_CONCAT(DISTINCT CONCAT_WS("|",e.id,e.name) SEPARATOR "<br>") AS education
FROM parent p 
LEFT JOIN work w ON w.parentid=p.id 
LEFT JOIN education e ON e.parentid=p.id 
GROUP BY p.id

Такой запрос дает более скоростной код и требует меньше обращений к SQL-серверу, чем запросы к базам education и work по результатам выборки из базы parent. Хотя и медленнее, чем выборка всех 18 строк (пример первого запроса в этом сообщении). Надеясь увеличить скорость и уменьшить вероятность переполнения, я все же сконструировал запрос с вложенными селектами:

SELECT SQL_CALC_FOUND_ROWS p.id,p.name AS person,w_id,work,e_id,education 
FROM parent p 
LEFT JOIN (SELECT GROUP_CONCAT(id SEPARATOR "|") AS w_id,GROUP_CONCAT(name SEPARATOR "|") AS work,parentid FROM '.work.' GROUP BY parentid) AS w ON w.parentid=p.id 
LEFT JOIN (SELECT GROUP_CONCAT(id SEPARATOR "|") AS e_id,GROUP_CONCAT(name SEPARATOR "|") AS education,parentid FROM '.education.' GROUP BY parentid) AS e ON e.parentid=p.id
GROUP BY p.id

На мое удивление скорость выполнения была в несколько раз медленнее. Даже медленнее, чем дополнительные запросы по цыклу результатов выборки основной базы. В 5 раз медленнее при однократном проходе и сто раз медленнее при стократных проходах!
Вывод такой: вложенные селекты - это зло, которое следует избегать любыми способами.

  Ответить  
Rambler's Top100
вверх

Rambler's Top100 Яндекс.Метрика Яндекс цитирования