MySQL несколько зависимых подзапросов, мучительно медленно - PullRequest
2 голосов
/ 13 мая 2010

У меня есть рабочий запрос, который извлекает нужные мне данные, но, к сожалению, он мучительно медленный (выполняется более 3 минут). У меня есть индексы, но я думаю, что проблема в нескольких зависимых подзапросах. Я пытался переписать запрос с помощью объединений, но я не могу заставить его работать. Любая помощь будет принята с благодарностью.

Таблицы:

В основном у меня есть 2 таблицы. Первый (цены) содержит цены товаров в магазине. Каждая строка - это цена товара в этот день, и новые строки добавляются каждый день с обновленной ценой.

Вторая таблица (watches_US) содержит информацию об элементе (имя, описание и т. Д.).

CREATE TABLE `prices` (
`prices_id` int(11) NOT NULL auto_increment,
`prices_locale` enum('CA','DE','FR','JP','UK','US') NOT NULL default 'US',
`prices_watches_ID` char(10) NOT NULL,
`prices_date` datetime NOT NULL,
`prices_am` varchar(10) default NULL,
`prices_new` varchar(10) default NULL,
`prices_used` varchar(10) default NULL,
PRIMARY KEY  (`prices_id`),
KEY `prices_am` (`prices_am`),
KEY `prices_locale` (`prices_locale`),
KEY `prices_watches_ID` (`prices_watches_ID`),
KEY `prices_date` (`prices_date`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=61764 ;

CREATE TABLE `watches_US` (
`watches_ID` char(10) NOT NULL,
`watches_date_added` datetime NOT NULL,
`watches_last_update` datetime default NULL,
`watches_title` varchar(255) default NULL,
`watches_small_image_height` int(11) default NULL,
`watches_small_image_width` int(11) default NULL,
`watches_description` text,
PRIMARY KEY  (`watches_ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;   

Запрос извлекает последние 10 изменений цен за период в 30 часов, упорядоченных по размеру изменения цены. Итак, у меня есть подзапросы, чтобы получить самую новую цену, самую старую цену в течение 30 часов, а затем рассчитать изменение цены.

Вот запрос:

SELECT watches_US.*, prices.*, watches_US.watches_ID as current_ID,
    ( SELECT prices_am FROM prices WHERE prices_watches_ID = current_ID AND prices_locale = 'US' ORDER BY prices_date DESC LIMIT 1 ) as new_price, 
    ( SELECT prices_date FROM prices WHERE prices_watches_ID = current_ID AND prices_locale = 'US' ORDER BY prices_date DESC LIMIT 1 ) as new_price_date, 
    ( SELECT prices_am FROM prices WHERE ( prices_watches_ID = current_ID AND prices_locale = 'US') AND ( prices_date >= DATE_SUB(new_price_date,INTERVAL 30 HOUR) ) ORDER BY prices_date ASC LIMIT 1 ) as old_price,
    ( SELECT ROUND(((new_price - old_price)/old_price)*100,2) ) as percent_change,
    ( SELECT (new_price - old_price) ) as absolute_change
FROM watches_US 
LEFT OUTER JOIN prices ON prices.prices_watches_ID = watches_US.watches_ID 
WHERE ( prices_locale = 'US' )
AND ( prices_am IS NOT NULL )
AND ( prices_am != '' )
HAVING ( old_price IS NOT NULL )
AND ( old_price != 0 )
AND ( old_price != '' )
AND ( absolute_change < 0 )
AND ( prices.prices_date = new_price_date )
ORDER BY absolute_change ASC
LIMIT 10

Как бы я переписал это, чтобы вместо этого использовать объединения, или как-то иначе оптимизировать это, чтобы результат не занимал более 3 минут? Любая помощь будет принята с благодарностью!

Спасибо, любезно.

UPDATE

Используя ответы снизу, я получил запрос, который занимает 2 секунды:

SELECT watches_US.*, prices.*,
    ( SELECT prices_am FROM prices prices2 WHERE ( prices2.prices_watches_ID = watches_US.watches_ID AND prices2.prices_locale = 'US') AND ( prices2.prices_date >= DATE_SUB(prices.prices_date,INTERVAL 30 HOUR) ) ORDER BY prices2.prices_date ASC LIMIT 1 ) as old_price,
    ( SELECT ROUND(((prices.prices_am - old_price)/old_price)*100,2) ) as percent_change,
    ( SELECT (prices.prices_am - old_price) ) as absolute_change
FROM watches_US 
LEFT OUTER JOIN prices ON prices.prices_watches_ID = watches_US.watches_ID AND prices.prices_locale = 'US'
WHERE ( prices.prices_am IS NOT NULL )
AND ( prices.prices_am != '' )
AND ( prices.prices_date IN (SELECT MAX(prices_date) FROM prices WHERE prices_watches_ID = watches_US.watches_ID AND prices_locale = 'US' ) )
HAVING ( old_price IS NOT NULL )
AND ( old_price != 0 )
AND ( old_price != '' )
AND ( absolute_change < 0 )
ORDER BY absolute_change ASC
LIMIT 10

Возможно, это все еще можно сделать с какой-то работой, но это можно использовать как есть. Спасибо всем за помощь!

Ответы [ 3 ]

0 голосов
/ 13 мая 2010

Я бы начал с того, чтобы убедиться, что у вас есть числовые значения, где вы делаете сравнения и выражения. Любой индекс, который включает преобразование типа, будет нефункциональным. Ваши цены указаны в долларах.

0 голосов
/ 13 мая 2010

Вот частичная идея:

SELECT watches_US.*, prices.*, watches_US.watches_ID as current_ID,
    prices2.prices_am as new_price, 
    prices2.prices_date as new_price_date, 
    ( SELECT prices_am FROM prices WHERE ( prices_watches_ID = current_ID AND prices_locale = 'US') AND ( prices_date >= DATE_SUB(new_price_date,INTERVAL 30 HOUR) ) ORDER BY prices_date ASC LIMIT 1 ) as old_price,
    ( SELECT ROUND(((new_price - old_price)/old_price)*100,2) ) as percent_change,
    ( SELECT (new_price - old_price) ) as absolute_change
FROM watches_US 
LEFT OUTER JOIN prices ON prices.prices_watches_ID = watches_US.watches_ID 
LEFT OUTER JOIN prices prices2 ON prices2.prices_watches_ID = watches_US.watches_ID 
WHERE ( prices_locale = 'US' )
AND ( prices_am IS NOT NULL )
AND ( prices_am != '' )
AND ( prices2.prices_date IN (SELECT MAX(price_date) FROM prices WHERE prices_watches_ID = watches_US.watches_ID AND prices_locale = 'US' )
HAVING ( old_price IS NOT NULL )
AND ( old_price != 0 )
AND ( old_price != '' )
AND ( absolute_change < 0 )
AND ( prices.prices_date = new_price_date )
ORDER BY absolute_change ASC
LIMIT 10

Изменения являются вторым объединением цен, которое используется для получения new_price и new_price_date с предложением WHERE для выбора только самой последней записи. Вероятно, вы могли бы немного почистить его, но я хотел получить его там.

0 голосов
/ 13 мая 2010

Есть несколько проблем с этим SQL:

  • Вы выполняете один и тот же запрос несколько раз:

    (ВЫБРАТЬ ЦЕНЫ_am ОТ ЦЕН, ГДЕ values_watches_ID = current_ID AND values_locale = 'US' ORDER BY Prices_date DESC LIMIT 1) как new_price, (ВЫБРАТЬ ЦЕНЫ_ДАТА ОТ ЦЕНЫ, ГДЕ values_watches_ID = current_ID AND values_locale = 'US' ORDER BY values_date DESC LIMIT 1) как new_price_date,

Вы должны выполнить запрос только один раз, дать ему имя и выбрать из него несколько столбцов, например. SELECT ... sub1.prices_am, sub1.prices_date FROM ... SELECT () sub1 если я не ошибаюсь.

  • Не по какой-либо причине использовать HAVING. Это снижает вашу производительность, поскольку позволяет базе данных извлекать все строки в вашем запросе, а затем отфильтровывать некоторые из них, как описано в предложении HAVING.
...