web-dev-qa-db-fra.com

Améliorer les performances avec la clause de sous-sélectionnée

Dans la requête suivante, je dois compter des transactions pour chaque client. [Modifier] Cependant, je dois exclure du résultat entièrement défini, les clients ayant une transaction de plus d'un an.

L'optimiseur de requête ne devrait-il pas être suffisamment intelligent pour évaluer une fois l'existence une fois pour chaque client?

--Count transactions on customers that are less than 1 year old

  SELECT t1.CUSTID,COUNT(*)
  FROM CUST_TRX t1
  WHERE NOT EXISTS ( 
    SELECT FIRST 1 1 
    FROM CUST_TRX t2 
    WHERE 
      t2.CUSTID=t1.CUSTID AND
      t2.DATED<CURRENT_DATE-365
    GROUP BY t2.CUSTID
  )
  GROUP BY t1.CUSTID

Il n'y a pas de natural dans mon plan de requête. Cette requête effectue comme si la base de données exécute la clause d'existence pour chaque transaction au lieu de l'exécuter pour chaque client. La performance est la même si je supprime le groupe par la sous-requête.

Y a-t-il une meilleure façon de le faire afin d'obtenir une meilleure performance de la base de données? J'espère qu'une simple requête de sélection fonctionnera en évitant une CTE si possible (cela introduirait d'autres défis). En raison d'un autre groupe par critère (non indiqué ici), je ne suis pas capable de simplement vérifier Min (daté), j'ai vraiment besoin d'effectuer une autre requête.

3
jcalfee314

Avec des requêtes comme celle-ci, c'est souvent plus efficace d'effectuer un LEFT OUTER JOIN à la place du NOT EXISTS Vérification de style, cela implique souvent une analyse complète de l'index (ou une numérisation de table sans les bons indices en place) mais avec de nombreuses lignes dans la ou les tableaux principaux, cela est moins coûteux que le grand nombre d'index recherchent ( Un sur la table de référence pour chaque ligne renvoyée de la table principale) qui autrement résulterait. Certaines planificateurs de requête sont assez brillants à propos de la repérage de cette équivalence et de l'utilisation du plan alternatif où il s'agit du meilleur choix, mais cela ne semble pas que cela se produise dans votre cas.

Essayez quelque chose comme:

SELECT t1.CUSTID, COUNT(*)
FROM   CUST_TRX t1
LEFT OUTER JOIN
       CUST_TRX t2 
ON     t2.CUSTID=t1.CUSTID 
AND    t2.DATED<CURRENT_DATE-365
WHERE  t2.CUSTID IS NULL
GROUP BY t1.CUSTID

(Remarque: Je ne connais pas Firebird, la syntaxe ci-dessus peut donc nécessiter des modifications, mais il faut illustrer le point)

Sans le WHERE t2.CUSTID IS NULL chaque rangée de t1 avec des allumettes dans t2 sera émis une fois pour chaque match trouvé dans t2 et ceux qui n'ont aucun match dans t2 sera sortie une fois mais avec des colonnes sélectionnées à partir de cet objet défini sur NULL. La clause WHERE puis écrase les matchs.

Selon les capacités du moteur DB, surtout si la quantité de données dans l'objet de référence (CUST_TRX Avec un filtre appliqué ici) est énorme, cela peut être significativement moins efficace que le WHERE <something> NOT IN ou alors WHERE NOT EXISTS Options, de sorte que la référence de données réalistes est d'abord avant d'utiliser la méthode. Cela fonctionne souvent beaucoup plus efficace avec MS SQL Server dans les cas où le planificateur de requête ne remarque pas que le WHERE NOT IN arrangement peut être effectué de cette manière plus efficace.

Aussi, si vous le faites, laissez un commentaire dans le code (et/ou la documentation de support) pour dire que vous le faites comme équivalent à WHERE <something> NOT IN ou alors WHERE NOT EXISTS que vous attendez-vous à être plus efficace. Vous vous en souviendrez et une personne SQL expérimentée reconnaîtra le modèle, mais d'autres personnes qui recherchent le code ne comprennent pas immédiatement l'intention/la raison et le retourner à l'aide de WHERE NOT EXISTS Pour plus de clarté, ce qui se lit mieux comme sur la phrase anglaise.

4
David Spillett

Lorsque vous dites "Compter les transactions sur les clients âgés de moins d'un an" Voulez-vous dire:

  1. Comptez toutes les transactions client de moins d'un an?
  2. Compter toutes les transactions pour les nouveaux clients de moins d'un an?

Depuis le code de l'échantillon, je comprends que # 1 est ce que vous voulez. Dans ce cas, avez-vous vraiment besoin d'un endroit où il n'existe pas? Pourriez-vous juste faire quelque chose comme:

SELECT t1.CUSTID, COUNT(*)
FROM CUST_TRX t1
WHERE t1.DATED>=CURRENT_DATE-365
GROUP BY t1.CUSTID
HAVING COUNT(*) > 0

Je ne suis pas un utilisateur de Firebird, mais j'ai regardé le groupe par/ayant la syntaxe.

[Modifier] Exclure des résultats des résultats qui ont une transaction de plus d'un an.

OK, il y a ici d'autres tâches d'agrégation des lignes pour éliminer le client de la sélection.

SELECT A.CUSTID, A.HowMany
FROM (SELECT t1.CUSTID, COUNT(*) HowMany, MIN(t1.DATED) OldestTran
    FROM CUST_TRX t1
    GROUP BY t1.CUSTID
    HAVING COUNT(*) > 0 AND MIN(t1.DATED) >=CURRENT_DATE-365) AS A

[EDIT] OK, la requête est donc plus complexe qui peut être incarnée dans une seule requête.

Cela signifie que vous aurez probablement besoin d'utiliser un motif beaucoup comme celui que vous avez publié pour la première fois. Notez que cela existe implique un distinct et souvent plus rapide que celui d'une jointure d'une sélection distincte. Mais vous pouvez essayer différentes approches et comparer le comportement, le timing, etc. puis choisissez celui que vous aimez le mieux.

0
RLF