Toutes les réponses existantes (fonctionnelles) présentent l'un des deux problèmes suivants :
- Ils ignoreront les index de la colonne recherchée
- Il sélectionnera (potentiellement) des données non prévues, corrompant silencieusement vos résultats.
Pour la plupart, lorsqu'une colonne recherchée a une fonction appelée dessus (y compris implicitement, comme pour CAST
), l'optimiseur doit ignorer les index de la colonne et rechercher dans chaque enregistrement. Voici un exemple rapide :
Nous avons affaire à des horodatages, et la plupart des SGBDR ont tendance à stocker ces informations sous la forme d'une valeur croissante, généralement un long
ou BIGINTEGER
nombre de milli-/nanosecondes. L'heure actuelle ressemble/est stockée comme suit :
1402401635000000 -- 2014-06-10 12:00:35.000000 GMT
Vous ne voyez pas la valeur 'Année' ('2014'
) là-dedans, n'est-ce pas ? En fait, il y a pas mal de calculs compliqués à traduire dans les deux sens. Donc, si vous appelez l'une des fonctions d'extraction/de partie de date sur la colonne recherchée, le serveur doit effectuer tous ces calculs juste pour déterminer si vous pouvez l'inclure dans les résultats. Sur les petites tables, ce n'est pas un problème, mais à mesure que le pourcentage de lignes sélectionnées diminue, cela devient un drain de plus en plus important. Ensuite, dans ce cas, vous le faites une deuxième fois pour demander à propos de MONTH
... eh bien, vous obtenez l'image.
Selon la version particulière de SQL Server et les types de données de colonne, en utilisant BETWEEN
(ou plages supérieures inclusives similaires :<=
) peut entraîner la sélection de mauvaises données. Essentiellement, vous finissez potentiellement par inclure des données à partir de minuit du jour "suivant", ou exclure une partie des enregistrements du jour "en cours".
Ce que vous devriez faire :
Nous avons donc besoin d'un moyen sûr pour nos données et qui utilisera des indices (si viables). Le chemin correct est alors de la forme :
WHERE date_created >= @startOfPreviousMonth AND date_created < @startOfCurrentMonth
Étant donné qu'il n'y a qu'un mois, @startOfPreviousMonth
peut être facilement substitué/dérivé par :
DATEADD(month, -1, @startOCurrentfMonth)
Si vous avez besoin de dériver le début du mois en cours sur le serveur, vous pouvez le faire via ce qui suit :
DATEADD(month, DATEDIFF(month, 0, CURRENT_TIMESTAMP), 0)
Un petit mot d'explication ici. Le DATEDIFF(...)
initial obtiendra la différence entre le début de l'ère actuelle (0001-01-01
- AD, CE, peu importe), renvoyant essentiellement un grand entier. C'est le nombre de mois jusqu'au début du courant mois. Nous ajoutons ensuite ce nombre au début de l'ère, qui correspond au début du mois donné.
Ainsi, votre script complet pourrait/devrait ressembler à ceci :
DECLARE @startOfCurrentMonth DATETIME
SET @startOfCurrentMonth = DATEADD(month, DATEDIFF(month, 0, CURRENT_TIMESTAMP), 0)
SELECT *
FROM Member
WHERE date_created >= DATEADD(month, -1, @startOfCurrentMonth) -- this was originally misspelled
AND date_created < @startOfCurrentMonth
Toutes les opérations de date ne sont donc effectuées qu'une seule fois, sur une valeur; l'optimiseur est libre d'utiliser des index et aucune donnée incorrecte ne sera incluse.