![]() |
Задавайте вопросы, мы ответим
Вы не зашли.
Дано: 2 таблицы - towns(id, title), vacancies(id, town_id).
Надо получить: список названий всех городов из towns с количеством вакансий в этом городе.
Следующий запрос выбирает только города, для которых есть записи в vacancies.
SELECT towns.title, COUNT(vacancies.id) FROM towns, vacancies WHERE towns.id = vacancies.town_id GROUP BY towns.title
Подскажите, пожалуйста, как получить ВСЕ города? Заранее очень признателен.
Неактивен

используйте left join
http://dev.mysql.com/doc/refman/5.1/en/join.html
Неактивен
Vasya, был у меня вариант с left join, не устроила скорость работы (10000 записей за 1,5 сек)
Неактивен

Да, группировать по имени города — идея ужасная. Но подзапрос
в данном случае тоже лишний.
Неактивен
paulus, каким именно на ваш взгяд должен быть запрос в данной ситуации?
Неактивен

Подойдет тот, что у Вас написан, но GROUP BY towns.id.
Нужны ключики на vacancies (town_id, id) и towns (id).
Неактивен
paulus, насколько я Вас понял запрос должен выглядеть так:
Неактивен

А EXPLAIN от обоих вариантов покажите?
Неактивен
Количество записей:
vacancies 14406
towns 25
Сгенерил новый тестовый набор записей для vacancies(более равномерный). Теперь примерно по 600 записей на каждый город (раньше все записи приходились на 5 городов).
Индексы towns: id(PRIMARY).
Индексы vacancies: id(PRIMARY), tonw_id(INDEX).
Время выполнения:
без вложенного запроса 0,035 сек.
с вложенным запросом 0,007 сек.
Результаты EXPLAIN в прикрепленном файле.
Отредактированно mihim (27.09.2010 11:55:21)
Неактивен

А теперь посмотрите, какой индекс я предложил сделать на vacancies ![]()
И, если после добавления, не будет лопать индекс целиком, я бы
добавил ORDER BY vacancies.town_id.
Неактивен
paulus, насчет индексов сначала не так Вас понял. Сделал индекс по двум полям (vacancies.town_id, vacancies.id). Время выполнения уменьшилось с 0,035 сек до 0,02 сек. ORDER BY vacancies.town_id на ситуацию никак не повлиял.
При этом вариант с вложенным запросом все равно работает в 3 раза быстрее (0,007 сек).
Отредактированно mihim (27.09.2010 14:18:01)
Неактивен

Не повлиял — это Вы судите по EXPLAIN?
Неактивен
И по EXPLAIN (картинка прицеплена) и по скорости работы.
Неактивен

И правда, он странно выбирает ![]()
Неактивен
да все нормально работает.
в первом запросе из-за того что count и group by оперируют данными из разных таблиц и появляется using temporary.. и using filesort.
во втором запросе те самые using temporary и filesort появляются из-за требования отсортировать выборку по vacancies.town_id. на мой взгляд лишнее требование, т.к. group by по умолчанию обеспечит сортировку
Неактивен
Спасибо за помощь всем откликнувшимся!
И все же кто-нибудь может объяснить почему вариант с вложенным запросом, имея далеко неидеальный EXPLAIN, работает в 3 раза быстрее обычного запроса с оптимизированным EXPLAIN?
Неактивен
Неактивен

Ррр, ну почему приходится делать тестовые данные, чтобы показать, что
на самом деле всё хорошо? ![]()
mysql> SHOW CREATE TABLE towns\G
*************************** 1. row ***************************
Table: towns
Create Table: CREATE TABLE `towns` (
`id` int(11) DEFAULT NULL,
`title` varchar(20) DEFAULT NULL,
KEY `id` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
1 row in set (0.00 sec)
mysql> SHOW CREATE TABLE vacancies\G
*************************** 1. row ***************************
Table: vacancies
Create Table: CREATE TABLE `vacancies` (
`id` int(11) DEFAULT NULL,
`town_id` int(11) DEFAULT NULL,
KEY `town_id` (`town_id`,`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
1 row in set (0.00 sec)
mysql> SELECT * FROM towns;
+------+-------------------------------+
| id | title |
+------+-------------------------------+
| 1 | Москва |
| 2 | Санкт-Петербург |
| 3 | Владимир |
| 4 | Новгород |
+------+-------------------------------+
4 rows in set (0.00 sec)
mysql> SELECT * FROM vacancies;
+------+---------+
| id | town_id |
+------+---------+
| 1 | 1 |
| 2 | 1 |
| 3 | 2 |
| 4 | 3 |
+------+---------+
4 rows in set (0.00 sec)
mysql> EXPLAIN SELECT title, COUNT(v.id) FROM towns t LEFT JOIN vacancies v ON t.id = v.town_id GROUP BY t.id ORDER BY NULL;
+----+-------------+-------+------+---------------+---------+---------+-----------+------+-----------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+---------+---------+-----------+------+-----------------+
| 1 | SIMPLE | t | ALL | NULL | NULL | NULL | NULL | 4 | Using temporary |
| 1 | SIMPLE | v | ref | town_id | town_id | 5 | test.t.id | 1 | Using index |
+----+-------------+-------+------+---------------+---------+---------+-----------+------+-----------------+
2 rows in set (0.00 sec)
mysql> SELECT title, COUNT(v.id) FROM towns t LEFT JOIN vacancies v ON t.id = v.town_id GROUP BY t.id ORDER BY NULL;
+-------------------------------+-------------+
| title | COUNT(v.id) |
+-------------------------------+-------------+
| Москва | 2 |
| Санкт-Петербург | 1 |
| Владимир | 1 |
| Новгород | 0 |
+-------------------------------+-------------+
4 rows in set (0.00 sec)Неактивен
а как же using temporary...? вообще говоря это не очень хорошо.
Неактивен
попробуйте загнать в vacancies сильно больше данных(сотни тысяч, миллионы) и "Using temporary" обязательно окажет свое пагубное влияние на время исполнения
Неактивен

Попробуйте загнать сотни тысяч данных в выборку без WHERE, и Вы поймете,
почему Ваше требование не важно в данном случае ![]()
Но раз уж Вы придираетесь, то вот так Вас должно устроить:
mysql> ALTER TABLE towns DROP KEY id, ADD PRIMARY KEY(id); Query OK, 4 rows affected (0.38 sec) Records: 4 Duplicates: 0 Warnings: 0 mysql> EXPLAIN SELECT title, COUNT(v.id) FROM towns t LEFT JOIN vacancies v ON v.town_id = t.id GROUP BY t.id ORDER BY NULL; +----+-------------+-------+-------+---------------+---------+---------+-----------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+-------+---------------+---------+---------+-----------+------+-------------+ | 1 | SIMPLE | t | index | NULL | PRIMARY | 4 | NULL | 4 | | | 1 | SIMPLE | v | ref | town_id | town_id | 5 | test.t.id | 1 | Using index | +----+-------------+-------+-------+---------------+---------+---------+-----------+------+-------------+ 2 rows in set (0.00 sec)
Неактивен
да нет, так тоже не особо весело. я бы даже сказал совсем не весело. подзапрос качественно лучше.
Неактивен

Вы правда в это верите? ![]()
Но если Вы автор решения с подзапросом — то, разумеется, лучше: это
решение было получено куда быстрее ![]()
Неактивен
paulus, в EXPLAIN все красиво, но хоть убейте меня, скорость работы варианта с подзапросом выше в 3 раза. На 130 000 записей в vacancies и 25 городах - 0,07 против 0,19 сек
В теории с подзапросом должно работать медленнее, но на практике всё иначе...
Отредактированно mihim (29.09.2010 22:58:50)
Неактивен

Я ж не против, я же давно уже говорю, что раз рабочее решение есть —
надо использовать его. В теории нигде не сказано, что подзапросы
должны работать медленнее. На практике же это почти всегда так:
MySQL определяет подзапрос зависимым, и выполняет его много раз
вместо того, чтобы сделать это единожды. В Вашем же случае это не так,
поэтому скорости с подзапросом и без должны быть сравнимы. Что мы,
собственно, и получили.
Неактивен