2009-11-20 8 views
2

J'ai un insert qui utilise une vérification de condition pour un NOT IN. Il y a environ 230k lignes dans la sous-requête NOT IN.Aidez à optimiser cette requête avec une énorme instruction IN

INSERT INTO Validate.ItemError (ItemId, ErrorId, DateCreated) 
(
    SELECT ItemId, 10, GetUTCDate() 
    FROM Validate.Item 
    INNER JOIN Refresh.Company 
    ON Validate.Item.IMCompanyId = Refresh.Company.IMCompanyId 
    WHERE Refresh.Company.CompanyId = 14 
    AND 
    (
     IMAccountId IS NULL OR NOT IMAccountId IN 
     (
      SELECT RA.IMAccountId 
      FROM Refresh.Account RA 
      INNER JOIN Refresh.BalancePool BP 
      ON RA.BalancePoolId = BP.BalancePoolId 
      WHERE BP.CompanyId = 14 
     ) 
    ) 
) 

Lorsque je l'exécute tel quel, cela prend environ 30 minutes (yacks!). Le nombre de valeurs dans la table Validate.Item peut être de 150 lignes à plus de 200 Ko, ce qui vous permet de voir à quel point cela peut être douloureux.

Il existe des index sur tous les champs pertinents des tableaux, et aucun n'est trop fragmenté.

Ma première pensée était de faire en morceaux et le jeter dans une boucle while:

DECLARE @StartId int, @EndId int, @MaxId int 

SELECT @MaxId = MAX(AccountId) FROM Refresh.Account 
SET @StartId = 1 
SET @EndId = 1000 

WHILE (@StartId < @MaxId) 
BEGIN 
    INSERT INTO Validate.ItemError (ItemId, ErrorId, DateCreated) 
    (
     SELECT ItemId, 10, GetUTCDate() 
     FROM Validate.Item 
     INNER JOIN Refresh.Company 
     ON Validate.Item.IMCompanyId = Refresh.Company.IMCompanyId 
     WHERE Refresh.Company.CompanyId = 14 
     AND 
     (
      IMAccountId IS NULL 
      OR NOT IMAccountId IN 
      (
       SELECT RA.IMAccountId 
       FROM Refresh.Account RA 
       INNER JOIN Refresh.BalancePool BP 
       ON RA.BalancePoolId = BP.BalancePoolId 
       WHERE BP.CompanyId = 14 
       AND RA.AccountId BETWEEN @StartId AND @EndId 
      ) 
     ) 
    ) 
    SET @StartId = @StartId + 1000 
    SET @EndId = @EndId + 1000 
END 

En procédant ainsi, me filets un temps d'environ une minute par boucle; multipliez cela par 230 fois et nous avons un nombre encore plus ridicule.

Dites-moi si vous avez une meilleure idée de comment optimiser cela. Sans cette requête, le processus entier ne prend que 8 secondes; c'est juste la taille de la table Refresh.Account qui jette tout dans le chaos.

TIA!

Valkyrie

+1

Vous pourriez avoir mal optimisé/tables indexées que vous pourriez vouloir attaquer en premier. –

+0

@Kragen: Comment avez-vous indenté le code? un outil? – shahkalpesh

+1

Pourriez-vous s'il vous plaît poster le plan de requête tel qu'il est maintenant? – Quassnoi

Répondre

0

Est-ce que l'aide NOT EXISTS aide ici?

(SELECT ItemId, 10, GetUTCDate() 
FROM Validate.Item INNER JOIN Refresh.Company ON 
Validate.Item.IMCompanyId = Refresh.Company.IMCompanyId 
WHERE Refresh.Company.CompanyId = 14 
AND (IMAccountId IS NULL OR NOT EXISTS (SELECT TOP 1 RA.IMAccountId FROM 
Refresh.Account RA INNER JOIN Refresh.BalancePool BP 
ON RA.BalancePoolId = BP.BalancePoolId WHERE BP.CompanyId = 14 AND 
RA.IMAcccountID = Validate.Item.IMAccountId))) 

Je ne suis pas sûr, si la requête est correcte. Mais j'utilise NOT EXISTS avec TOP 1 dans la sous-requête. De plus, la sous-requête limite l'enregistrement en ajoutant AND RA.IMAcccountID = Validate.Item.IMAccountId supplémentaire.

EDIT: J'espère que vous aurez l'idée de ce que j'essaie de faire.
Au lieu de vérifier toutes les lignes dans Refresh.Account, je limite les lignes et essaye de trouver au moins 1 ligne correspondante avec IMAccountID correspondant - qui ne devrait pas exister selon votre requête originale (qui utilise NOT IN ...).

+0

Vous étiez plus rapide que moi :) – Erlock

+0

TOUS vous rock! Cela m'a conduit à une requête de moins d'une seconde, et est exactement ce dont j'avais besoin. Merci beaucoup! – Valkyrie

1

Utilisez NOT EXISTS à la place:

...OR NOT EXISTS (SELECT 1 FROM 
Refresh.Account RA INNER JOIN Refresh.BalancePool BP 
ON RA.BalancePoolId = BP.BalancePoolId WHERE BP.CompanyId = 14 AND RA.IMAccountId = xxx.IMAccountId))) 

La sous-requête suivante figure ne renverra le premier critère satisfaisant record. (N'oubliez pas de remplacer xxx par l'alias de la table de droite)

+0

Pratiquement la même chose que shahkalpesh, mais "TOP 1" n'est pas nécessaire. –

1

Plutôt que de faire un "pas dans", pourriez-vous simplement faire une jointure gauche à la table pertinente et vérifier les clés NULL? Je ne sais pas si la requête est 100% correct:

INSERT INTO Validate.ItemError (ItemId, ErrorId, DateCreated) 
SELECT ItemId, 10, GetUTCDate() 
FROM Validate.Item 
INNER JOIN Refresh.Company ON Validate.Item.IMCompanyId = Refresh.Company.IMCompanyId 
LEFT JOIN Refresh.Account 
    INNER JOIN Refresh.BalancePool BP ON BP.BalancePoolId = RA.BalancePoolId 
ON Refresh.Account.IMAccountId = Validate.Item.IMAccountId 
WHERE Refresh.Company.CompanyId = 14 
AND Validate.Item.IMAccountId IS NULL OR Refresh.Account.IMAccountId IS NULL 
2

Débarrassez-vous de la condition OR.

Il ajoute un fullscan et empêche l'optimiseur d'utiliser un ANTI JOIN qu'il utiliserait autrement.

Cette requête renvoie la même:

SELECT ItemId, 10, GetUTCDate() 
FROM Validate.Item 
INNER JOIN 
     Refresh.Company 
ON  Validate.Item.IMCompanyId = Refresh.Company.IMCompanyId 
WHERE Refresh.Company.CompanyId = 14 
     AND NOT EXISTS 
     (
     SELECT RA.IMAccountId 
     FROM Refresh.Account RA 
     INNER JOIN 
       Refresh.BalancePool BP 
     ON  RA.BalancePoolId = BP.BalancePoolId 
     WHERE BP.CompanyId = 14 
       AND RA.IMAccounID = Validate.Item.IMAccountId 
     ) 
Questions connexes