2009-11-25 3 views
0

J'ai une requête où je sélectionne quelques colonnes de chacune des 5 tables jointes extérieures gauche. J'ai fait un plan d'exécution dans SQL Server 2008, et il y a essentiellement des scans de table sur toutes les tables jointes, mais le coût est de 0% pour eux - je suppose qu'il n'y a pas beaucoup d'enregistrements dans ces tables les tables. Ensuite, lors des deux dernières étapes du plan d'exécution (la dernière fusion de toutes les tables et de l'instruction SELECT), 55% du coût correspond à la jointure de fusion, et 45% du coût correspond à sélectionner.Pourquoi une instruction SELECT représenterait-elle 45% du coût du plan d'exécution dans SQL Server 2008?

Cela me semble étrange ... pourquoi le coût de ces deux dernières étapes du «tout rassembler» est-il si élevé? Je pensais que tous ces scans de table ou de tri auraient des coûts plus importants.

J'essaie d'obtenir un enregistrement "résumé" de tous ces tableaux ... peut-être que je prends la mauvaise approche en quittant tout rejoindre à gauche?

mis à jour avec SQL

SELECT 
/* Names */ 
NM.EMPLID, NM.NAME_PREFIX, NM.LAST_NAME, NM.FIRST_NAME, NM.MIDDLE_NAME, NM.NAME_SUFFIX, 
/* Directory Info */ 
DIR_PERSON.BIRTH_DT, 
/* PERSDATA */ 
PERS.SEX, PERS.HIGHEST_EDUC_LVL, 
/* DIVERS.ETHNIC */ 
ETHNIC.ETHNIC_GRP_CD, 
/* TENURE */ 
TENURE.EMPLID, TENURE.TENURE_STATUS, TENURE.EG_GRANTED_DT, TENURE.EG_TENURE_HOME, 
TENURE.EG_TRACK_HIRE_DT, TENURE.EG_MAND_REVW_DT, TENURE.CODE, 
/* VISA */ 
VISA.VISA_PERMIT_TYPE 

FROM NAMES NM 

/* ----- Table Joins ----- */ 
/* Directory Join */ 
LEFT OUTER JOIN DIR_PERSON ON DIR_PERSON.ID = NM.EMPLID 

/* PERS_DATA Join */ 
LEFT OUTER JOIN PERS ON PERS.EMPLID = NM.EMPLID 
AND PERS.EFFDT =( SELECT MAX(PERS_CURRENT.EFFDT) FROM PERS_CURRENT 
        WHERE PERS.EMPLID = PERS_CURRENT.EMPLID 
        AND PERS_CURRENT.EFFDT <= GETDATE()) 
/* ETHNIC Join */      
LEFT OUTER JOIN ETHNIC ON ETHNIC.EMPLID = NM.EMPLID 
AND ETHNIC.PRIMARY_INDICATOR = 'Y' 

/* TENURE Join */ 
LEFT OUTER JOIN TENURE ON TENURE.EMPLID = NM.EMPLID 

/* VISA Join */ 
LEFT OUTER JOIN VISA ON VISA.EMPLID = NM.EMPLID 
AND VISA.EFFDT = ( SELECT MAX(VISA_CURRENT.EFFDT) FROM VISA_CURRENT 
        WHERE VISA.EMPLID = VISA_CURRENT.EMPLID 
        AND VISA_CURRENT.EFFDT <= GETDATE()) 

/* ----- End Table Joins ----- */  

WHERE NM.NAME_TYPE = 'PRI' 
    AND NM.EFFDT = (SELECT MAX(NM_CURRENT.EFFDT) FROM NM_CURRENT 
        WHERE NM.EMPLID = NM_CURRENT.EMPLID 
        AND NM.NAME_TYPE = NM_CURRENT.NAME_TYPE 
        AND NM_CURRENT.EFFDT <= GETDATE()); 
+0

Pouvez-vous poster le .sqlplan (à savoir le plan d'exécution exporté pour la requête)? – chadhoc

+0

SET SHOWPLAN_XML n'est pas pris en charge sur le serveur que je désigne (je suppose que c'est avant 2005?), Donc je ne pouvais pas obtenir un plan exporté qui me paraissait bien. – chucknelson

Répondre

2

ACCÉLÉRER IDEA

Je refondus votre requête (je ne l'ai pas testé donc il pourrait y avoir des fautes de frappe) pour se débarrasser des sous-requêtes. Ici, vous obtenez d'abord tous les éléments max (1 x nombre d'empl), puis exécutez la sélection principale (1 x num d'empl). Cela change votre requête d'un O (N^3) à O (N), donc ça devrait être plus rapide.

Je ne ai fait deux d'entre eux, le troisième devrait être clair de cet exemple:

WITH mVisa AS 
(
SELECT MAX(VISA_CURRENT.EFFDT) as max, VISA_CURRENT.EMPID as EMPLID 
FROM VISA_CURRENT 
WHERE VISA_CURRENT.EFFDT <= GETDATE() 
GROUP BY VISA_CURRENT.EMPLID 
), mPers AS 
(
SELECT MAX(PERS_CURRENT.EFFDT) as max, PERS_CURRENT.EMPLID 
FROM PERS_CURRENT 
AND PERS_CURRENT.EFFDT <= GETDATE()) 
GROUP BY PERS_CURRENT.EMPLID 
) 
SELECT 
/* Names */ 
NM.EMPLID, NM.NAME_PREFIX, NM.LAST_NAME, NM.FIRST_NAME, NM.MIDDLE_NAME, NM.NAME_SUFFIX, 
/* Directory Info */ 
DIR_PERSON.BIRTH_DT, 
/* PERSDATA */ 
PERS.SEX, PERS.HIGHEST_EDUC_LVL, 
/* DIVERS.ETHNIC */ 
ETHNIC.ETHNIC_GRP_CD, 
/* TENURE */ 
TENURE.EMPLID, TENURE.TENURE_STATUS, TENURE.EG_GRANTED_DT, TENURE.EG_TENURE_HOME, 
TENURE.EG_TRACK_HIRE_DT, TENURE.EG_MAND_REVW_DT, TENURE.CODE, 
/* VISA */ 
VISA.VISA_PERMIT_TYPE 

FROM NAMES NM 

/* ----- Table Joins ----- */ 
/* Directory Join */ 
LEFT OUTER JOIN DIR_PERSON ON DIR_PERSON.ID = NM.EMPLID 

/* PERS_DATA Join */ 
LEFT JOIN mPers ON NM.EMPLID = mPers.EMPLID 
LEFT OUTER JOIN PERS ON PERS.EMPLID = NM.EMPLID 
AND PERS.EFFDT = mPers.max 
/* ETHNIC Join */          
LEFT OUTER JOIN ETHNIC ON ETHNIC.EMPLID = NM.EMPLID 
AND ETHNIC.PRIMARY_INDICATOR = 'Y' 

/* TENURE Join */ 
LEFT OUTER JOIN TENURE ON TENURE.EMPLID = NM.EMPLID 

/* VISA Join */ 
LEFT JOIN mVisa ON NM.EMPLID = mVisa.EMPLID 
LEFT OUTER JOIN VISA ON VISA.EMPLID = NM.EMPLID 
AND VISA.EFFDT = mVisa.max 

/* ----- End Table Joins ----- */  

WHERE NM.NAME_TYPE = 'PRI' 
     AND NM.EFFDT = (SELECT MAX(NM_CURRENT.EFFDT) FROM NM_CURRENT 
             WHERE NM.EMPLID = NM_CURRENT.EMPLID 
             AND NM.NAME_TYPE = NM_CURRENT.NAME_TYPE 
             AND NM_CURRENT.EFFDT <= GETDATE()); 
+0

Merci Hogan - le serveur I ' m pointant vers ne semble pas supporter SHOWPLAN_XML, donc maintenant je suis inquiet qu'il ne supporte pas non plus CTE. Je vais essayer dès que possible lundi ... sinon je vais essayer de parler aux gens qui maintiennent cette base de données et j'espère optimiser certaines choses ... – chucknelson

+0

SQL 2008? Ensuite, cela fonctionnera - showplan_xml nécessite dbo_owner ou un autre droit. Les CTE font partie du langage maintenant, ça devrait aller. – Hogan

+1

Si vous êtes avant 2005, vous pouvez insérer un insert dans #tempname à la place et créer un lien vers ces tables. – Hogan

0

45% de quelque chose de petit est encore 45%. C'est difficile à dire sans voir plus de détails, mais j'ai trouvé les dernières étapes très onéreuses lors de l'insertion dans une table d'index clusterisée (sur des colonnes non-IDENTITY) ou dans une table avec beaucoup d'index.

Avec tous ces balayages de table - n'y a-t-il aucun index?

+0

Il s'agit juste d'une sélection directe, combinant les données de ces tables en une ligne résumée pour une clé (dans ce cas, un employé). Je n'ai pas de droits d'administrateur sur la base de données, et je n'étais pas là quand elle a été créée, mais quand je regarde toutes ces tables via SQL Server, je ne vois aucune clé ou index sur ces tables ... Je ne suis pas sûr si SQL Server me montre la vérité. – chucknelson

+0

J'ai été totalement là. Vous pourriez ne pas avoir de bonnes statistiques, donc le plan d'exécution pourrait ne pas être précis - obtenir toutes vos informations ensemble et obtenir avec le DBA. –

0

Il serait utile que vous incluiez le code, mais si vous avez un GROUP BY ou ORDER BY, par exemple, cela ajoutera beaucoup à la requête. Si la sélection finale est une grande table, et les autres sont non seulement minuscules mais pas vraiment utilisées beaucoup dans la table principale, alors vous devez atteindre 100% dans une partie de la requête, même si c'est une partie simple.

0
WHERE NM.NAME_TYPE = 'PRI' 
     AND NM.EFFDT = (SELECT MAX(NM_CURRENT.EFFDT) FROM NM_CURRENT 
             WHERE NM.EMPLID = NM_CURRENT.EMPLID 
             AND NM.NAME_TYPE = NM_CURRENT.NAME_TYPE 
             AND NM_CURRENT.EFFDT <= GETDATE()); 

Vos 45% est ici. Si vous créez un index sur MN.NAME_TYPE et sur NM.EFFDT, vous verrez cette baisse de 45%.

Il peut inclure ou non cette sous-requête dans le coût de la sélection principale - si c'est là que réside réellement votre problème. N'OUBLIEZ PAS qu'il doit réexécuter cette requête pour chaque ligne.

Voir les autres commentaires pour un refactor à une jointure.

+0

Plus je regarde cela, plus je me demande - que font les sous-requêtes ... vous en avez 3 - elles courent sur toutes les lignes ... cela DOIT être ce qui ralentit la requête. – Hogan

+0

Toute sous-requête sur une jointure ou le WHERE est parce que j'ai besoin du dernier enregistrement hors de ces tables pour une personne spécifique. – chucknelson

1

Comme l'a dit Cade, vérifiez d'abord vos index.

Si les indices sont en place, vérifiez que vos statistiques sont à jour.

Si ces deux problèmes se vérifient, pensez à refactoriser vos sous-requêtes en un ou plusieurs CTE, puis joignez-les aux critères pertinents. Ce n'est pas une solution miracle, mais selon mon expérience, les CTE sont souvent meilleurs que les sous-requêtes.

+0

Exactement! Voir mon commentaire Brook - O (N) au lieu de O (N^2) ou pire – Hogan

+0

+ 1 pour ne pas être aussi paresseux que moi et refactoring réellement la requête :) – Brook

+0

lol, merci Brook – Hogan

Questions connexes