Comme mes compétences en réglage des performances ne semblent jamais suffisantes, je me demande toujours s'il y a plus d' optimisation que je peux effectuer contre certaines requêtes. La situation à laquelle cette question se rapporte est une fonction Windowed MAX imbriquée dans une sous-requête.
Les données que je fouille sont une série de transactions sur divers groupes d'ensembles plus importants. J'ai 4 champs d'importance, l'ID unique d'une transaction, l'ID de groupe d'un lot de transactions et les dates associées à la transaction unique ou au groupe de transactions respectif. La plupart du temps, la date de groupe correspond à la date de transaction unique maximale pour un lot, mais il y a des moments où des ajustements manuels passent par notre système et une opération de date unique se produit après la capture de la date de transaction de groupe. Cette modification manuelle n'ajuste pas la date du groupe par conception.
Ce que j'identifie dans cette requête, ce sont les enregistrements où la date unique tombe après la date de groupe. L'exemple de requête suivant crée un équivalent approximatif du scénario my et l'instruction SELECT renvoie les enregistrements que je recherche, mais est-ce que j'aborde cette solution de la manière la plus efficace? Cela prend un certain temps à s'exécuter pendant le chargement de ma table de faits car mon enregistrement compte le nombre dans les 9 chiffres supérieurs, mais surtout mon dédain pour les sous-requêtes me fait me demander s'il y a une meilleure approche ici. Je ne suis pas aussi préoccupé par les indices que je suis convaincu qu'ils sont déjà en place; ce que je recherche, c'est une approche de requête alternative qui permettra d'atteindre la même chose, mais encore plus efficacement. Toute rétroaction est la bienvenue.
CREATE TABLE #Example
(
UniqueID INT IDENTITY(1,1)
, GroupID INT
, GroupDate DATETIME
, UniqueDate DATETIME
)
CREATE CLUSTERED INDEX [CX_1] ON [#Example]
(
[UniqueID] ASC
)
SET NOCOUNT ON
--Populate some test data
DECLARE @i INT = 0, @j INT = 5, @UniqueDate DATETIME, @GroupDate DATETIME
WHILE @i < 10000
BEGIN
IF((@i + @j)%173 = 0)
BEGIN
SET @UniqueDate = GETDATE()+@i+5
END
ELSE
BEGIN
SET @UniqueDate = GETDATE()+@i
END
SET @GroupDate = GETDATE()+(@j-1)
INSERT INTO #Example (GroupID, GroupDate, UniqueDate)
VALUES (@j, @GroupDate, @UniqueDate)
SET @i = @i + 1
IF (@i % 5 = 0)
BEGIN
SET @j = @j+5
END
END
SET NOCOUNT OFF
CREATE NONCLUSTERED INDEX [IX_2_4_3] ON [#Example]
(
[GroupID] ASC,
[UniqueDate] ASC,
[GroupDate] ASC
)
INCLUDE ([UniqueID])
-- Identify any UniqueDates that are greater than the GroupDate within their GroupID
SELECT UniqueID
, GroupID
, GroupDate
, UniqueDate
FROM (
SELECT UniqueID
, GroupID
, GroupDate
, UniqueDate
, MAX(UniqueDate) OVER (PARTITION BY GroupID) AS maxUniqueDate
FROM #Example
) calc_maxUD
WHERE maxUniqueDate > GroupDate
AND maxUniqueDate = UniqueDate
DROP TABLE #Example
dbfiddle ici
la source
Réponses:
Je suppose qu'il n'y a pas d'index, car vous n'en avez fourni aucun.
Dès le départ, l'index suivant éliminera un opérateur de tri dans votre plan, qui autrement consommerait potentiellement beaucoup de mémoire:
La sous-requête n'est pas un problème de performances dans ce cas. Si quoi que ce soit, je chercherais des moyens d'éliminer la fonction de fenêtre (MAX ... OVER) pour éviter la construction Nested Loop et Table Spool.
Avec le même index, la requête suivante peut à première vue sembler moins efficace, et elle passe de deux à trois analyses sur la table de base, mais elle élimine un grand nombre de lectures en interne car elle manque d'opérateurs de spoule. Je suppose qu'il fonctionnera toujours mieux, en particulier si vous avez suffisamment de cœurs de processeur et de performances d'E / S sur votre serveur:
(Remarque: j'ai ajouté un
MERGE JOIN
indice de requête, mais cela devrait probablement se produire automatiquement si vos statistiques sont en ordre. La meilleure pratique consiste à laisser des indices comme ceux-ci si vous le pouvez.)la source
Lorsque et si vous êtes en mesure de mettre à niveau de SQL Server 2012 vers SQL Server 2016, vous pourrez peut-être profiter des performances nettement améliorées (en particulier pour les agrégats de fenêtres sans cadre) fournies par le nouvel opérateur d'agrégation de fenêtres en mode batch.
Presque tous les grands scénarios de traitement de données fonctionnent mieux avec le stockage columnstore qu'avec rowstore. Même sans passer au magasin de colonnes pour vos tables de base, vous pouvez toujours profiter des avantages de la nouvelle exécution de l'opérateur 2016 et du mode de traitement par lots en créant un index filtré vide non clustered columnstore sur l'une des tables de base, ou par une jointure externe redondante à un magasin de colonnes organisé table.
En utilisant la deuxième option, la requête devient:
db <> violon
Notez que la seule modification apportée à la requête d'origine consiste à créer une table temporaire vide et à ajouter la jointure gauche. Le plan d'exécution est le suivant:
Pour plus d'informations et d'options, consultez l'excellente série d'Itzik Ben-Gan, Ce que vous devez savoir sur l'opérateur d'agrégation de fenêtres en mode batch dans SQL Server 2016 (en trois parties).
la source
Je vais juste jeter la vieille croix Appliquer là-bas:
Avec quelques index, ça marche plutôt bien.
Le temps des statistiques et io ressemblent à ceci (votre requête est le premier résultat)
Les plans de requête sont ici (encore une fois, le vôtre est le premier):
https://www.brentozar.com/pastetheplan/?id=BJYJvqAal
Pourquoi je préfère cette version? J'évite les bobines. Si ceux-ci commencent à se répandre sur le disque, ça va devenir laid.
Mais vous voudrez peut-être essayer aussi.
S'il s'agit d'un grand fichier DW, vous préférerez peut-être la jointure par hachage et le filtrage des lignes dans la jointure plutôt qu'à la fin de la
TOP 1
requête en tant qu'opérateur de filtre.Le plan est ici: https://www.brentozar.com/pastetheplan/?id=BkUF55ATx
Stats temps et io ici:
J'espère que cela t'aides!
Une modification, basée sur l'idée de @ ypercube, et un nouvel index.
Voici le temps des statistiques et io:
Voici le plan:
https://www.brentozar.com/pastetheplan/?id=SJv8foR6g
la source
Je regarderais
top with ties
Si
GroupDate
est le même parGroupId
alors:Sinon: utilisation
top with ties
dans une expression de table communedbfiddle: http://dbfiddle.uk/?rdbms=sqlserver_2016&fiddle=c058994c2f5f3d99b212f06e1dae9fd3
Requête d'origine
vs
top with ties
dans une expression de table communela source
J'ai donc fait une analyse des différentes approches publiées jusqu'à présent, et dans mon environnement, il semble que l'approche de Daniel l'emporte systématiquement sur les temps d'exécution. Étonnamment (pour moi), la troisième approche CROSS APPLY de sp_BlitzErik n'était pas si loin derrière. Voici les sorties si quelqu'un est intéressé, mais merci à TON pour toutes les approches alternatives. J'ai appris plus en fouillant dans les réponses à cette question que je n'en ai depuis longtemps!
la source
top with ties
boucles avec autant de lignes. dbfiddle.uk/…