2010-05-12 41 views
2

J'ai une requête qui récupère les données dont j'ai besoin, mais malheureusement, elle est douloureusement lente (plus de 3 minutes). J'ai des index en place, mais je pense que le problème est les sous-requêtes dépendantes multiples. J'ai essayé de réécrire la requête à l'aide de jointures, mais je n'arrive pas à la faire fonctionner. Toute aide serait grandement appréciée.Sous-requêtes dépendantes multiples MySQL, douloureusement lente

Les tables:

Fondamentalement, j'ai 2 tables. Le premier (prix) détient les prix des articles dans un magasin. Chaque ligne correspond au prix d'un article ce jour-là et de nouvelles lignes sont ajoutées chaque jour avec un prix actualisé.

La seconde table (watches_US) contient les informations sur l'élément (nom, description, etc.).

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; 

La requête récupère les 10 dernières modifications de prix sur une période de 30 heures, commandés par la taille du changement de prix. J'ai donc des sous-requêtes pour obtenir le prix le plus récent, le prix le plus ancien dans les 30 heures, puis pour calculer le changement de prix.

est ici la requête:

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 

Comment puis-je réécrire cette option pour utiliser à la place se joint, ou autrement optimiser cette façon, il ne prend pas plus de 3 minutes pour obtenir un résultat? Toute aide serait grandement appréciée!

Merci beaucoup.

MISE À JOUR

En utilisant les réponses en bas, je suis arrivé à la requête à ceci, qui prend 2 secondes pour exécuter:

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 

Il pourrait probablement faire encore avec un peu de travail, mais il est utilisable tel quel. Merci à tous pour votre aide!

Répondre

0

Voici une idée partielle:

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 

Les changements sont la deuxième jointure sur les prix qui est utilisé pour obtenir new_price et new_price_date avec la clause WHERE pour sélectionner uniquement l'entrée la plus récente. Vous pourriez probablement le nettoyer un peu mais je voulais le faire sortir.

+0

On dirait qu'il travaillerait, malheureusement MySQL ne permettra pas à l'current_ID alias dans la clause where. – matt80

+0

Je viens de l'éditer pour intégrer current_ID – sblundy

0

Il y a plusieurs problèmes avec ce SQL:

  • Vous effectuez la même requête plusieurs fois:

    (SELECT prices_am des prix OU prices_watches_ID = current_ID ET prices_locale = 'US' ORDER BY prices_date DESC LIMIT 1) as new_price, (SELECT prices_date FROM prix where_watches_ID = current_ID AND prices_locale = 'US' COMMANDER PAR prices_date DESC LIMIT 1) as new_price_date,

Vous devez exécuter la requête une seule fois, lui donner un nom et en sélectionner plusieurs colonnes, par ex. SELECT ... sub1.prices_am, sub1.prices_date FROM ... SELECT() sub1 si je ne me trompe pas.

  • Ne pas utiliser pour aucune raison HAVING. Il tue vos performances, car il permet à la base de données de récupérer toutes les lignes de votre requête, puis de filtrer certaines d'entre elles comme décrit dans la clause HAVING.
0

Je commencerais par m'assurer que vous avez des valeurs numériques où vous faites des comparaisons et des expressions. Tout index impliquant une conversion de type sera non fonctionnel.Vos prix sont varchars.