Задавайте вопросы, мы ответим
Вы не зашли.
Добрый вечер,
Уже намучался с запросами... Задача такая: есть категории товарные, есть товары, есть свойства, есть значения свойств, есть таблица привязки. (Проще говоря, у каждой категории есть свойства, а у них есть вырианты выбора - selectvalues). Надо получить запросом список значений который есть у товаров этой папки, допустим
ID свойства ID выбранного значения кол-во товаров с этим значением
1 1 10
1 2 20
2 3 30
Вот структура таблиц (на ключи можно не смотреть, это уже создавалось что угодно для экспериментов)
товары (parent_id - ID папки):
CREATE TABLE `shop_products` (
`id` int(10) unsigned NOT NULL auto_increment,
`parent_id` smallint(2) NOT NULL default '0',
`name` varchar(255) NOT NULL default '',
PRIMARY KEY (`id`),
KEY `parent_id_2` (`parent_id`,`id`),
KEY `id` (`id`,`parent_id`)
) ENGINE=MyISAM AUTO_INCREMENT=159220 DEFAULT CHARSET=utf8
свойства (parent_id - ID папки):
CREATE TABLE `shop_properties` (
`id` int(10) unsigned NOT NULL auto_increment,
`parent_id` smallint(2) NOT NULL default '0',
`name` varchar(255) NOT NULL default '',
`is_flag5` tinyint(1) NOT NULL default '0',
`is_deleted` tinyint(1) NOT NULL default '0',
`ordering` int(11) NOT NULL default '0',
PRIMARY KEY (`id`),
KEY `parent_id` (`parent_id`,`is_deleted`,`is_flag5`),
KEY `parent_id_2` (`parent_id`,`is_deleted`,`is_flag5`,`name`)
) ENGINE=MyISAM AUTO_INCREMENT=3061 DEFAULT CHARSET=utf8
значения свойств:
CREATE TABLE `shop_selectvalues` (
`id` int(10) unsigned NOT NULL auto_increment,
`property_id` int(10) unsigned NOT NULL default '0',
`value` char(255) NOT NULL default '',
PRIMARY KEY (`id`),
KEY `del_prop_ord` (`is_deleted`,`property_id`,`ordering`),
KEY `property_id` (`property_id`,`value`)
) ENGINE=MyISAM AUTO_INCREMENT=82242 DEFAULT CHARSET=utf8
таблица связки (parent_id - добавлен в качестве эксперимента):
CREATE TABLE `shop_selected` (
`product_id` int(11) unsigned NOT NULL default '0',
`select_id` int(11) unsigned NOT NULL default '0',
`property_id` int(11) unsigned NOT NULL default '0',
`parent_id` smallint(6) NOT NULL default '0',
PRIMARY KEY (`product_id`,`select_id`),
KEY `product_id` (`product_id`,`property_id`),
KEY `product_id_2` (`product_id`,`select_id`,`property_id`),
KEY `parent_id` (`parent_id`,`select_id`,`property_id`),
KEY `select_id_2` (`select_id`,`product_id`),
KEY `parent_id_2` (`parent_id`,`product_id`,`select_id`,`property_id`),
KEY `product_id_3` (`product_id`,`property_id`,`select_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8
Вот к какому запросу я пришел в итоге:
SELECT SQL_NO_CACHE s.select_id, p.id, p.name, sl.value, count(*) FROM shop_selectvalues AS sl RIGHT JOIN shop_selected AS s ON (sl.id = s.select_id) LEFT JOIN shop_products AS g ON g.id = s.product_id AND g.parent_id = 103, shop_properties AS p WHERE p.id = s.property_id AND p.is_deleted = 0 AND p.is_flag5 = 1 GROUP BY s.select_id;
Выполняется за 0.50сек, в таблице shop_products 150тыс записей примерно, в shop_selected - 1.5млн, в остальных мало
Вот explain:
mysql> explain SELECT SQL_NO_CACHE s.select_id, p.id, p.name, sl.value, count(*) FROM shop_selectvalues AS sl RIGHT JOIN shop_selected AS s ON (sl.id = s.select_id) LEFT JOIN shop_products AS g ON g.id = s.product_id AND g.parent_id = 103, shop_properties AS p WHERE p.id = s.property_id AND p.is_deleted = 0 AND p.is_flag5 = 1 GROUP BY s.select_id;
+----+-------------+-------+--------+----------------------------------------------+--------------+---------+------------------------+---------+----------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+--------+----------------------------------------------+--------------+---------+------------------------+---------+----------------------------------------------+
| 1 | SIMPLE | s | index | NULL | product_id_2 | 12 | NULL | 1230452 | Using index; Using temporary; Using filesort |
| 1 | SIMPLE | p | eq_ref | PRIMARY,is_deleted,is_deleted_2,id | PRIMARY | 4 | ava_main.s.property_id | 1 | Using where |
| 1 | SIMPLE | sl | eq_ref | PRIMARY | PRIMARY | 4 | ava_main.s.select_id | 1 | |
| 1 | SIMPLE | g | eq_ref | PRIMARY,parent_id,parent_id_2,parent_id_3,id | PRIMARY | 4 | ava_main.s.product_id | 1 | |
+----+-------------+-------+--------+----------------------------------------------+--------------+---------+------------------------+---------+----------------------------------------------+
4 rows in set (0.04 sec)
Всё вроде хорошо, в принципе, но любой order работает больше 20 секунд, а очень желательно чтоб выводились результаты в порядке свойств... плюс тормозит таки если любой order поставить. уже всякие варианты с right join, left join, join, straight_join, use index, пробовал, результат никакой.. Уже 2 дня долблюсь с консолью)
Неактивен
Ух
Данных у меня нету, поэтому будем в интерактивном режиме пытаться разобраться
1. Понять, почему тормозит.
Тормозит, потому что нет нужных индексов, а есть какие-то ненужные. Индексы нужно сделать нужные.
2. Попытаться разобраться в том, что есть.
Для того, чтобы выбрать "товары этой папки", нужно ограничение на parent_id. Я его не вижу
3. Упростить задачу.
Попытайтесь делать выборки по методу приближений. Чем больше индекс отрезает ненужного, тем лучше.
Запишите структуру в простом виде, тогда легче будет понимать, что происходит. Я бы сделал это как-то так:
prod (id, parent, ...)
prop (id, parent, ...)
val (id, prop, ...)
seld (prod, val, prop)
Тут сразу видно, что лишнее поле val.prop
"Надо получить запросом список значений который есть у товаров этой папки", т.е. ограничить по parent.
+ мы выяснили, что табличка val содержит ненужное поле, соответственно, она вообще не нужна в запросе,
который вытаскивает id.
Итого как-то так:
SELECT prop.id, seld.val, count(*)
FROM prod, prop, seld
WHERE
(prod.id = seld.prod) AND (prop.id = seld.prop) AND -- связи таблиц
(prod.parent = 1) AND (prop.parent = 1) -- 1 - нужный id папки
GROUP BY prop.id, seld.val
Индексы нужно использовать по максимуму, поэтому я бы делал следующие индексы:
1. (id) на всех табличках с полем id
2. (parent) на prod
3. (parent,id) на prop
4. на seld отдельные ключики по всем трем полям
Неактивен
единственное что пришло в голову это промежуточная таблица в которой будут все данные которые мне надо выбрать, и в выборке делать просто связку shop_products и shop_thismegatable, а для её хранения делать триггер на все 3 таблицы чтоб данные править... вот
Неактивен
Нужно для начала выкинуть из запроса все лишнее, а потом уже смотреть, как денормализовать, если все равно данных
слишком много. Денормализовать изначально не нормализованные данные плохо, по опыту.
Неактивен
paulus написал:
Ух
Данных у меня нету, поэтому будем в интерактивном режиме пытаться разобраться
1. Понять, почему тормозит.
Тормозит, потому что нет нужных индексов, а есть какие-то ненужные. Индексы нужно сделать нужные.
2. Попытаться разобраться в том, что есть.
Для того, чтобы выбрать "товары этой папки", нужно ограничение на parent_id. Я его не вижу
3. Упростить задачу.
Попытайтесь делать выборки по методу приближений. Чем больше индекс отрезает ненужного, тем лучше.
Запишите структуру в простом виде, тогда легче будет понимать, что происходит. Я бы сделал это как-то так:
prod (id, parent, ...)
prop (id, parent, ...)
val (id, prop, ...)
seld (prod, val, prop)
Тут сразу видно, что лишнее поле val.prop
"Надо получить запросом список значений который есть у товаров этой папки", т.е. ограничить по parent.
+ мы выяснили, что табличка val содержит ненужное поле, соответственно, она вообще не нужна в запросе,
который вытаскивает id.
Итого как-то так:
SELECT prop.id, seld.val, count(*)
FROM prod, prop, seld
WHERE
(prod.id = seld.prod) AND (prop.id = seld.prop) AND -- связи таблиц
(prod.parent = 1) AND (prop.parent = 1) -- 1 - нужный id папки
GROUP BY prop.id, seld.val
Вот как получилось:
mysql> explain SELECT SQL_NO_CACHE p.id, s.select_id, count(*) FROM shop_products AS g, shop_properties AS p, shop_selected AS s WHERE (g.id = s.product_id) AND (p.id = s.property_id) AND (g.parent_id = 103) AND (p.parent_id = 103) GROUP BY p.id, s.select_id;
+----+-------------+-------+--------+----------------------------------------------------------+--------------+---------+------------------------+------+----------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+--------+----------------------------------------------------------+--------------+---------+------------------------+------+----------------------------------------------+
| 1 | SIMPLE | g | ref | PRIMARY,parent_id,parent_id_2,parent_id_3,id | parent_id_2 | 2 | const | 4551 | Using index; Using temporary; Using filesort |
| 1 | SIMPLE | s | ref | PRIMARY,product_id,product_id_2,product_id_3,property_id | product_id_2 | 4 | ava_main.g.id | 14 | Using index |
| 1 | SIMPLE | p | eq_ref | PRIMARY,parent_id,parent_id_2,id,parent_id_3 | PRIMARY | 4 | ava_main.s.property_id | 1 | Using where |
+----+-------------+-------+--------+----------------------------------------------------------+--------------+---------+------------------------+------+----------------------------------------------+
3 rows in set (0.00 sec)
Выполняется где-то 4 секунды
Ключи:
g.parent_id_2 - `parent_id_2` (`parent_id`,`id`)
s.product_id_2 - `product_id_2` (`product_id`,`select_id`,`property_id`)
p - берет зараза праймари
paulus написал:
Индексы нужно использовать по максимуму, поэтому я бы делал следующие индексы:
1. (id) на всех табличках с полем id
2. (parent) на prod
3. (parent,id) на prop
4. на seld отдельные ключики по всем трем полям
1. везде id - primary
2. есть (parent_id, id)
3. сделал, оно все равно берет PRIMARY, сейчас попробую еще принудительно ему индекс дать
4. тоже есть
а будет ли резон сделать таблицу типа?
id
product_id
property_id
property_name
select_id
select_name
чтоб обойтись одним join по g.id = product_id
Неактивен
еще забыл, если будет общая таблица, то там будет на порядок меньше строк, потому что свойства тоже не все нужны, а только их часть
Неактивен
вот что получилось после ковыряний
mysql> EXPLAIN SELECT SQL_NO_CACHE p.id, p.name, sl.value, s.select_id, count(*) FROM shop_products AS g, shop_properties AS p, shop_selected AS s, shop_selectvalues AS sl WHERE (p.id = s.property_id) AND (g.id = s.product_id) AND (sl.id = s.select_id) AND (g.parent_id = 103) AND (p.parent_id = 103) AND (p.is_deleted=0) AND (p.is_flag5=1) GROUP BY s.property_id, s.select_id ORDER BY p.id, sl.value;
+----+-------------+-------+--------+----------------------------------------------------------------------------------------------+---------------+---------+-----------------------+------+---------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+--------+----------------------------------------------------------------------------------------------+---------------+---------+-----------------------+------+---------------------------------+
| 1 | SIMPLE | p | ref | PRIMARY,parent_id,parent_id_2,id_2,is_deleted_3,id_3 | is_deleted_3 | 4 | const,const,const | 3 | Using temporary; Using filesort |
| 1 | SIMPLE | s | ref | PRIMARY,product_id,select_id,property_id,property_id_2 | property_id_2 | 4 | ava_main.p.id | 442 | Using index |
| 1 | SIMPLE | sl | eq_ref | PRIMARY,id | PRIMARY | 4 | ava_main.s.select_id | 1 | |
| 1 | SIMPLE | g | eq_ref | PRIMARY,parent_id,parent_id_2,parent_id_3,id | PRIMARY | 4 | ava_main.s.product_id | 1 | Using where |
+----+-------------+-------+--------+----------------------------------------------------------------------------------------------+---------------+---------+-----------------------+------+---------------------------------+
4 rows in set (0.00 sec)
Отредактированно elgato (21.11.2008 22:10:27)
Неактивен
А сколько выполняется по времени? 1200 строк - это нормально для такого запроса?
Неактивен
paulus написал:
А сколько выполняется по времени? 1200 строк - это нормально для такого запроса?
4500 товаров примерно, это самая обьемная папка с товарами. 0.30 сек выполняется запрос. сейчас всё множественными запросами реализовано, список свойств, для каждого взять вариант, соответственно при 5 свойствах и в сумме 50 значений это 55 запросов, вот и захотелось уменьшить.. но те запросы как бы и выполняются за сотые доли секунды, но их много.. в результате получается что-то вроде
свойство 1:
значение 1 - 10 товаров
значение 2 - 20 товаров
значение 3 - 25 товаров
свойство 2:
значение 4 - 5 товаров
значение 5 - 10 товаров
значение 6 - 20 товаров
значение 7 - 5 товаров
но если выбрано какое-то значение, то делается выборка та же самая но из товаров у которых выбрано уже это значение.. умной реализации не нашел и даже не знаю как искать, лишь бы работало - уже есть, но хочется как-то оптимизировать...
Неактивен
Ну, оптимизировать запрос, который достает все строки из индекса вряд ли получится.
0.3 секунды на 1200 строк - это не так плохо. Разве что Вас спасет LIMIT в конце,
потому как вряд ли Вам нужны все 1200 строк сразу.
Неактивен
paulus написал:
Ну, оптимизировать запрос, который достает все строки из индекса вряд ли получится.
0.3 секунды на 1200 строк - это не так плохо. Разве что Вас спасет LIMIT в конце,
потому как вряд ли Вам нужны все 1200 строк сразу.
как раз таки надо) построить блок где список всех фильтров, но там 1200 не будет конечно, но 100 - легко всё равно что-то не додумал я,, ведь если выбрано значение массовое, под которое подходит допустим 2000 товаров из 4000, то есть 2 варианта:
выбрать с учетов g.id IN () или сделать запрос по тому значению и програмно отделять, что тоже не ахти
Неактивен
Если будет выбор по g.id, то на него нужен ключик
100 значений будут отрабатывать быстрее, чем 1200 в среднем в 12 раз
Ну, конечно, бывают подковырки, но на Ваших запросах должно быть как-то так.
Неактивен