SQL Server 2008 et versions ultérieures
Dans SQL Server 2008 et versions ultérieures, bien sûr, le moyen le plus rapide est Convert(date, @date)
. Cela peut être renvoyé à un datetime
ou datetime2
si nécessaire.
Qu'est-ce qui est vraiment meilleur dans SQL Server 2005 et versions antérieures ?
J'ai vu des affirmations incohérentes sur ce qui est le plus rapide pour tronquer l'heure d'une date dans SQL Server, et certaines personnes ont même dit qu'elles avaient fait des tests, mais mon expérience a été différente. Faisons donc des tests plus rigoureux et laissons tout le monde avoir le script afin que si je fais des erreurs, les gens puissent me corriger.
Les conversions flottantes ne sont pas exactes
Tout d'abord, je m'abstiendrais de convertir datetime
pour float
, car il ne se convertit pas correctement. Vous pouvez vous en sortir avec précision, mais je pense que c'est une mauvaise idée de l'utiliser car cela indique implicitement aux développeurs qu'il s'agit d'une opération sûre et ce n'est pas le cas . Jetez un œil :
declare @d datetime;
set @d = '2010-09-12 00:00:00.003';
select Convert(datetime, Convert(float, @d));
-- result: 2010-09-12 00:00:00.000 -- oops
Ce n'est pas quelque chose que nous devrions enseigner aux gens dans notre code ou dans nos exemples en ligne.
De plus, ce n'est même pas le moyen le plus rapide !
Preuve – Tests de performance
Si vous souhaitez effectuer vous-même des tests pour voir comment les différentes méthodes s'empilent réellement, vous aurez besoin de ce script de configuration pour exécuter les tests plus loin :
create table AllDay (Tm datetime NOT NULL CONSTRAINT PK_AllDay PRIMARY KEY CLUSTERED);
declare @d datetime;
set @d = DateDiff(Day, 0, GetDate());
insert AllDay select @d;
while @@ROWCOUNT != 0
insert AllDay
select * from (
select Tm =
DateAdd(ms, (select Max(DateDiff(ms, @d, Tm)) from AllDay) + 3, Tm)
from AllDay
) X
where Tm < DateAdd(Day, 1, @d);
exec sp_spaceused AllDay; -- 25,920,000 rows
Veuillez noter que cela crée une table de 427,57 Mo dans votre base de données et que son exécution prendra entre 15 et 30 minutes. Si votre base de données est petite et définie sur une croissance de 10 %, cela prendra plus de temps que si vous avez d'abord une taille suffisamment grande.
Passons maintenant au script de test de performances réel. Veuillez noter qu'il est utile de ne pas renvoyer de lignes au client, car cela coûterait très cher sur 26 millions de lignes et masquerait les différences de performances entre les méthodes.
Résultats de performances
set statistics time on;
-- (All queries are the same on io: logical reads 54712)
GO
declare
@dd date,
@d datetime,
@di int,
@df float,
@dv varchar(10);
-- Round trip back to datetime
select @d = CONVERT(date, Tm) from AllDay; -- CPU time = 21234 ms, elapsed time = 22301 ms.
select @d = CAST(Tm - 0.50000004 AS int) from AllDay; -- CPU = 23031 ms, elapsed = 24091 ms.
select @d = DATEDIFF(DAY, 0, Tm) from AllDay; -- CPU = 23782 ms, elapsed = 24818 ms.
select @d = FLOOR(CAST(Tm as float)) from AllDay; -- CPU = 36891 ms, elapsed = 38414 ms.
select @d = CONVERT(VARCHAR(8), Tm, 112) from AllDay; -- CPU = 102984 ms, elapsed = 109897 ms.
select @d = CONVERT(CHAR(8), Tm, 112) from AllDay; -- CPU = 103390 ms, elapsed = 108236 ms.
select @d = CONVERT(VARCHAR(10), Tm, 101) from AllDay; -- CPU = 123375 ms, elapsed = 135179 ms.
-- Only to another type but not back
select @dd = Tm from AllDay; -- CPU time = 19891 ms, elapsed time = 20937 ms.
select @di = CAST(Tm - 0.50000004 AS int) from AllDay; -- CPU = 21453 ms, elapsed = 23079 ms.
select @di = DATEDIFF(DAY, 0, Tm) from AllDay; -- CPU = 23218 ms, elapsed = 24700 ms
select @df = FLOOR(CAST(Tm as float)) from AllDay; -- CPU = 29312 ms, elapsed = 31101 ms.
select @dv = CONVERT(VARCHAR(8), Tm, 112) from AllDay; -- CPU = 64016 ms, elapsed = 67815 ms.
select @dv = CONVERT(CHAR(8), Tm, 112) from AllDay; -- CPU = 64297 ms, elapsed = 67987 ms.
select @dv = CONVERT(VARCHAR(10), Tm, 101) from AllDay; -- CPU = 65609 ms, elapsed = 68173 ms.
GO
set statistics time off;
Quelques analyses décousues
Quelques notes à ce sujet. Tout d'abord, si vous effectuez simplement un GROUP BY ou une comparaison, il n'est pas nécessaire de reconvertir en datetime
. Vous pouvez donc économiser du CPU en évitant cela, sauf si vous avez besoin de la valeur finale à des fins d'affichage. Vous pouvez même GROUP BY la valeur non convertie et mettre la conversion uniquement dans la clause SELECT :
select Convert(datetime, DateDiff(dd, 0, Tm))
from (select '2010-09-12 00:00:00.003') X (Tm)
group by DateDiff(dd, 0, Tm)
Voyez également comment les conversions numériques ne prennent qu'un peu plus de temps pour revenir à datetime
, mais le varchar
la conversion double presque ? Cela révèle la partie du CPU qui est consacrée au calcul de la date dans les requêtes. Certaines parties de l'utilisation du processeur n'impliquent pas de calcul de date, et cela semble être quelque chose de proche de 19 875 ms dans les requêtes ci-dessus. Ensuite, la conversion prend un montant supplémentaire, donc s'il y a deux conversions, ce montant est utilisé environ deux fois.
Un examen plus approfondi révèle que par rapport à Convert(, 112)
, le Convert(, 101)
la requête a des dépenses CPU supplémentaires (puisqu'elle utilise un varchar
plus long ?), car la deuxième conversion vers date
ne coûte pas autant que la conversion initiale en varchar
, mais avec Convert(, 112)
il est plus proche du même coût de base du processeur de 20 000 ms.
Voici ces calculs sur le temps CPU que j'ai utilisés pour l'analyse ci-dessus :
method round single base
----------- ------ ------ -----
date 21324 19891 18458
int 23031 21453 19875
datediff 23782 23218 22654
float 36891 29312 21733
varchar-112 102984 64016 25048
varchar-101 123375 65609 7843
-
rond est le temps CPU pour un aller-retour vers
datetime
. -
célibataire est le temps CPU pour une seule conversion vers le type de données alternatif (celui qui a pour effet secondaire de supprimer la partie temporelle).
-
base est le calcul de la soustraction de
single
la différence entre les deux invocations :single - (round - single)
. C'est un chiffre approximatif qui suppose la conversion vers et depuis ce type de données etdatetime
est à peu près le même dans les deux sens. Il semble que cette hypothèse ne soit pas parfaite, mais qu'elle s'en rapproche car les valeurs sont toutes proches de 20 000 ms à une seule exception près.
Une autre chose intéressante est que le coût de base est presque égal au seul Convert(date)
méthode (qui doit être presque sans coût, car le serveur peut extraire en interne la partie entière du jour dès les quatre premiers octets du datetime
type de données).
Conclusion
Donc, à quoi cela ressemble, c'est que le varchar
unidirectionnel la méthode de conversion prend environ 1,8 μs et la méthode DateDiff
unidirectionnelle prend environ 0,18 μs. Je me base sur le temps "CPU de base" le plus conservateur dans mes tests de 18 458 ms au total pour 25 920 000 lignes, donc 23 218 ms / 25920000 =0,18 μs. L'amélioration apparente de 10x semble beaucoup, mais elle est franchement assez petite jusqu'à ce que vous ayez affaire à des centaines de milliers de lignes (617 000 lignes =1 seconde d'économie).
Même compte tenu de cette petite amélioration absolue, à mon avis, le DateAdd
méthode gagne car c'est la meilleure combinaison de performance et de clarté. La réponse qui nécessite un "nombre magique" de 0.50000004
va mordre quelqu'un un jour (cinq zéros ou six ???), en plus c'est plus difficile à comprendre.
Remarques supplémentaires
Quand j'aurai un peu de temps, je changerai 0.50000004
à '12:00:00.003'
et voir comment ça se passe. Il est converti dans le même datetime
valeur et je la trouve beaucoup plus facile à retenir.
Pour les personnes intéressées, les tests ci-dessus ont été exécutés sur un serveur où @@Version renvoie ce qui suit :
Microsoft SQL Server 2008 (RTM) - 10.0.1600.22 (Intel X86) 9 juillet 2008 14:43:34 Copyright (c) 1988-2008 Microsoft Corporation Standard Edition sur Windows NT 5.2 (Build 3790 :Service Pack 2)