2009-09-29 8 views
1

J'ai une requête SQL assez longue et complexe exécutée sur PostgreSQL 8.3. Une partie de la requête implique le filtrage sur une plage de dates qui se terminent aujourd'hui, comme ceci:La requête Postgres est très lente avec current_date :: date au lieu de la date codée en dur

where ... 
    and sp1.price_date between current_date::date - '1 year'::interval and current_date::date 
    and sp4.price_date between current_date::date - '2 weeks'::interval and current_date::date 
    and sp5.price_date = (select sp6.price_date 
          from stock_prices sp6 
         where sp6.stock_id = s.stock_id 
          and sp6.price_date < current_date::date 
         order by sp6.price_date desc 
         limit 1) 
    ... 

Cette requête prend près de 5 minutes pour exécuter (la première fois) et environ 1,5 minutes, la seconde fois. En regardant la sortie EXPLAIN ANALYZE, il semble que current_date est le problème. J'ai donc essayé de le remplacer par une date hardcoded, comme ceci:

where ... 
    and sp1.price_date between '2009-09-30'::date - '1 year'::interval and '2009-09-30'::date 
    and sp4.price_date between '2009-09-30'::date - '2 weeks'::interval and '2009-09-30'::date 
    and sp5.price_date = (select sp6.price_date 
          from stock_prices sp6 
         where sp6.stock_id = s.stock_id 
          and sp6.price_date < '2009-09-30'::date 
         order by sp6.price_date desc 
         limit 1) 
    ... 

La requête puis a couru dans une demi-seconde! C'est génial, sauf que la date se produit dans un total de 10 endroits dans la requête et, bien sûr, je ne veux pas que l'utilisateur doive le changer manuellement à 10 endroits. Dans MS SQL Server, je déclarerais simplement une variable avec la valeur de la date courante et l'utiliserais, mais apparently cela n'est pas possible en langage SQL simple dans Postgres.

Que puis-je faire pour que cette requête s'exécute rapidement tout en utilisant automatiquement la date actuelle?

+1

current_date :: date C'est très étrange, pourquoi avez-vous besoin de la partie :: date, si elle est déjà de type date? De plus, si vous ajoutez/soustrayez des intervalles de date, le résultat sera de toute façon un horodatage! – Jamol

Répondre

2

Tout d'abord, postez EXPLAIN ANALYZE sur les deux variantes afin que nous puissions voir. Première étape dans la détermination de notre pourquoi l'un est plus lent que l'autre. Peut-être utile de voir toute la requête aussi.

La première variante devrait être optimisable. Pour ne pas demander à votre utilisateur de modifier votre requête à plusieurs endroits, pensez à écrire un stored procedure, ou si/quand votre première variante est optimisée, un view. Edit: Remarqué que votre current__date - '...' :: interval retournerait un horodatage sans timezone. Je suppose que vous voulez lancer à la place à la place: (current_date - '2 weeks' :: interval) :: date

+0

Désolé, j'ai fait "EXPLAIN ANALYZE", pas "EXPLAIN" (édité). Cependant, je ne suis pas sûr de savoir comment l'optimiser. J'ai essayé d'écrire une fonction qui "RETURNS SETOF RECORD" pour faire la requête et utiliser un paramètre dans cette fonction, mais la fonction n'est pas revenue après 10 minutes, à quel point je l'ai annulé. – EMP

+0

@Evgeny: Exécuter expliquer analyser sur lui-même n'aide pas. Vous devez lire la sortie de celui-ci et trouver le problème. –

+0

@Evgeny: Peut-être que j'étais un peu flou. Veuillez poster les résultats de l'analyse d'explication ici. – tommym

1

EDIT: ce qui suit a été testé mais il était encore plus lent que la requête originale !. La leçon à ce sujet est peut-être que le coup de performance est encouru par tous les typecasting (:: date, :: intervalle etc.). Peut-être que ces conversions explicites peuvent être remplacées par quelque chose d'autre, et qu'une partie de l'expression telle que 'D.RightNow :: date -' 1 an ':: interval' soit pré-calculée.

- original reply--
Vous pouvez insérer la date dans une table vide autrement et se joindre à cette table ...

En d'autres termes, si une telle table est créée et nommée tblNow, la requête (s) avec des filtres liés à la date pourrait devenir quelque chose comme ceci:

UPDATE tblNow SET RightNow = TIMEOFDAY(); 
-- note: above could use CURRENT_DATE or whatever date function matches the 
-- semantics of the date fields in other tables. 

-- and now the original query can become 

from ... 
join tblNow AS D ON 1=1 -- added join 
        -- then all current_date::date below changed to D.RightNow 

where ... 
    and sp1.price_date between D.RightNow::date - '1 year'::interval and D.RightNow::date 
    and sp4.price_date between D.RightNow::date - '2 weeks'::interval and D.RightNow::date 
    and sp5.price_date = (select sp6.price_date 
          from stock_prices sp6 
         where sp6.stock_id = s.stock_id 
          and sp6.price_date < D.RightNow::date 
         order by sp6.price_date desc 
         limit 1) 
    ... 

nécessitant efficacement aucune modification à la requête chaque fois que nous voulons exécuter pour le moment en cours. Je ne suis pas familier avec postgreSQL, mais cette approche serait une solution de contournement naturelle pour toute limitation sur l'utilisation de variables dans les instructions SELECT.

+0

Comment cela pourrait-il l'aider? Au lieu de changer la requête, il doit ajouter/mettre à jour des lignes dans une table séparée? – tommym

+0

@etlerant: l'OP mentionne avoir à changer la date à 10+ endroits dans la ou les requêtes, cette approche permettrait 1) un emplacement unique et 2) Aucun changement à la requête du tout, si une requête UPDATE utilisant timeofday () précède les requêtes liées au travail. – mjv

+0

Merci, j'ai essayé, mais cela n'a pas aidé - la requête a duré 10 minutes avant que j'abandonne et l'annule. – EMP

Questions connexes