|
|
|
| Вот структура:
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 применить вложенный селект? | |
|
|
|
|
|
|
|
для: kosta_in_net
(14.12.2011 в 18:25)
| | Можно, но запрос будет выполняться дольше. | |
|
|
|
|
|
|
|
для: cheops
(14.12.2011 в 19:31)
| | Дольше - это понятно, так как потребуются вложенные запросы. Но всвязи с ограничением длины GROUP_CONCAT, все таки хотелось бы разобраться, можно ли заменить его подзапросом, и, если можно, то как? | |
|
|
|
|
|
|
|
для: kosta_in_net
(15.12.2011 в 12:42)
| | Кстати, длину можно менять при помощи серверной переменной group_concat_max_len, в том числе и лишь для текущей сессии. | |
|
|
|
|
|
|
|
для: kosta_in_net
(15.12.2011 в 12:42)
| | Тут штука в том, что в одну строку не получится, т.е. вернуть результат можно, но вам его к виду GROUP_CONCAT придется в приложении приводить. | |
|
|
|
|
|
|
|
для: cheops
(15.12.2011 в 13:11)
| | про group_concat_max_len все понятно. Но не хочется туда целый мегабайт отдавать (возможно это требует дополнительнве расходы памяти), а заранее требуемый объем не определишь.
Если не в одну строку, то это уже... как бы и не вложенный селект. Левым джоном не в одну строку сделать не проблема. Проблема возникает, если нужен постраничный вывод. Одно дело сгруппировать все записи по главной таблице и взять десяток в нужном диапазоне, другое дело брать десяток несгруппированных записей.
Кстати, по моим тестам левым джоном и фильтрацией в приложении выходит в 2 раза быстрее, чем через GROUP_CONCAT... Но это без постраничного деления, в котором потребуется вначале сделать запрос только к главной таблице на определение интервала требуемых записей (к примеру, для определения что в диапазон попадают записи с id от... до..., а потом уже получение связанных данных с id от и до). При чем, такое определение интервала невозможно, если сортировка идет не по уникальному полю (товары с ценой в 100 рублей могут занимать несколько страниц и поделить их на страницы не удастся) | |
|
|
|
|
|
|
|
для: 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 раз медленнее при однократном проходе и сто раз медленнее при стократных проходах!
Вывод такой: вложенные селекты - это зло, которое следует избегать любыми способами. | |
|
|
|
|