Sqlserver
 sql >> Base de données >  >> RDS >> Sqlserver

Comment supprimer la partie heure d'une valeur datetime (SQL Server) ?

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 et datetime 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)