web-dev-qa-db-fra.com

Pourquoi la date de recherche de ma requête ne correspond-elle pas?

select * 
from A 
where posted_date >= '2015-07-27 00:00:00.000' 
  and posted_date  <= '2015-07-27 23:59:59.999'

Mais le résultat contient un record qui a posté la date d'aujourd'hui: 2015-07-28. Mon serveur de base de données n'est pas dans mon pays. Quel est le problème ?

21
lecuong92

Puisque vous utilisez datetime type de données, vous devez comprendre comment le serveur SQL arrondit les données datetime.

╔═══════════╦═════╦═════════════════════════════╦═════════════════════════════╦══════════╦═══════════╗
║   Name    ║ sn  ║        Minimum value        ║        Maximum value        ║ Accuracy ║  Storage  ║
╠═══════════╬═════╬═════════════════════════════╬═════════════════════════════╬══════════╬═══════════╣
║ datetime  ║ dt  ║ 1753-01-01 00:00:00.000     ║ 9999-12-31 23:59:59.997     ║ 3.33 ms  ║ 8 bytes   ║
║ datetime2 ║ dt2 ║ 0001-01-01 00:00:00.0000000 ║ 9999-12-31 23:59:59.9999999 ║ 100ns    ║ 6-8 bytes ║
╚═══════════╩═════╩═════════════════════════════╩═════════════════════════════╩══════════╩═══════════╝

enter image description here

En utilisant la requête ci-dessous, vous pouvez facilement voir le problème d'arrondi que fait le serveur SQL lorsque vous utilisez le type de données DATETIME.

select  '2015-07-27 00:00:00.000'                       as Original_startDateTime,
        convert(datetime ,'2015-07-27 00:00:00.000')    as startDateTime,
        '2015-07-27 23:59:59.999'                       as Original_endDateTime,
        convert(datetime ,'2015-07-27 23:59:59.999')    as endDateTime,
        '2015-07-27 00:00:00.000'                       as Original_startDateTime2,
        convert(datetime2 ,'2015-07-27 00:00:00.000')   as startDateTime2,  -- default precision is 7
        '2015-07-27 23:59:59.999'                       as Original_endDateTime2,
        convert(datetime2 ,'2015-07-27 23:59:59.999')   as endDateTime2     -- default precision is 7

enter image description herecliquez pour agrandir

DATETIME2 existe depuis SQL Server 2008, alors commencez à l'utiliser au lieu de DATETIME. Pour votre situation, vous pouvez utiliser datetime2 Avec précision de 3 décimales par ex. datetime2(3).

Avantages de l'utilisation de datetime2:

  • Prend en charge jusqu'à 7 décimales pour le composant temps vs datetime ne prend en charge que 3 décimales .. et donc vous voyez le problème d'arrondi puisque par défaut datetime arrondit le .003 seconds Le plus proche avec des incréments de .000, .003 ou .007 secondes.
  • datetime2 Est beaucoup plus précis que datetime et datetime2 Vous donne le contrôle de DATE et TIME par opposition à datetime .

Référence :

16
Kin Shah

Comme plusieurs autres l'ont mentionné dans les commentaires et autres réponses à votre question, le problème principal est que 2015-07-27 23:59:59.999 Est arrondi à 2015-07-28 00:00:00.000 Par SQL Server. selon la documentation pour DATETIME:

Plage de temps - 00:00:00 à 23: 59: 59.997

Notez que la plage de temps ne peut jamais être .999. Plus bas dans la documentation, il spécifie les règles d'arrondi que SQL Server utilise pour le chiffre le moins significatif.

Table showing rounding rules

Notez que le chiffre le moins significatif ne peut avoir qu'une des trois valeurs potentielles: "0", "3" ou "7".

Il existe plusieurs solutions/solutions de contournement pour cela que vous pouvez utiliser.

-- Option 1
SELECT 
    * 
FROM A 
WHERE posted_date >= '2015-07-27 00:00:00.000' 
  AND posted_date <  '2015-07-28 00:00:00.000' --Round up and remove equality

-- Option 2
SELECT 
    * 
FROM A 
WHERE posted_date >= '2015-07-27 00:00:00.000' 
  AND posted_date <=  '2015-07-27 23:59:59.997' --Round down and keep equality

-- Option 3
SELECT 
    * 
FROM A 
WHERE CAST(posted_date AS DATE) = '2015-07-27' -- Use different data type

-- Option 4
SELECT 
    * 
FROM A 
WHERE CONVERT(CHAR(8), DateColumn, 112) = '20150727' -- Cast to string stripping off time

-- Option 5
SELECT 
    * 
FROM A 
WHERE posted_date BETWEEN '2015-07-27 00:00:00.000' 
  AND '2015-07-27 23:59:59.997' --Use between

Sur les cinq options que j'ai présentées ci-dessus, je considérerais les options 1 et 3 comme les seules options viables. Ils expriment clairement votre intention et ne briseront pas si vous mettez à jour les types de données. Si vous utilisez SQL Server 2008 ou une version plus récente, je pense que l'option 3 devrait être votre approche préférée. Cela est particulièrement vrai si vous pouvez éviter d'utiliser le DATETIME type de données à un DATE type de données pour votre colonne posted_date.

En ce qui concerne l'option 3, une très bonne explication de certains problèmes peut être trouvée ici: Le casting à ce jour est sargable mais est-ce une bonne idée?

Je n'aime pas les options 2 et 5 parce que les secondes fractionnelles .997 Vont être juste un autre nombre magique que les gens vont vouloir "réparer". Pour quelques autres raisons pour lesquelles BETWEEN n'est pas largement adopté, vous voudrez peut-être vérifier ce message .

Je n'aime pas l'option 4 car la conversion des types de données en chaîne à des fins de comparaison me semble sale. Une raison plus qualitative de l'éviter dans SQL Server est son impact sargability aka vous ne pouvez pas effectuer une recherche d'index et cela se traduira souvent par de moins bonnes performances.

Pour plus d'informations sur la bonne et la mauvaise façon de gérer les requêtes de plage de dates, consultez cet article par Aaron Bertrand .

En vous séparant, vous pourrez conserver votre requête d'origine et elle se comporterait comme vous le souhaitez si vous modifiez votre colonne posted_date À partir d'un DATETIME à un DATETIME2(3) . Cela économiserait de l'espace de stockage sur le serveur, vous donnerait une plus grande précision à la même précision, serait plus conforme aux normes/portable et vous permettrait d'ajuster facilement la précision/précision si vos besoins changent à l'avenir. Cependant, ce n'est qu'une option si vous utilisez SQL Server 2008 ou une version plus récente.

Comme un peu de trivia le 1/300 D'une seconde précision avec DATETIME semble être un maintien d'UNIX par cette réponse StackOverflow . Sybase qui a un héritage partagé a une précision similaire 1/300 D'une seconde précision dans leurs types de données DATETIME et TIME mais leurs chiffres les moins significatifs sont légèrement différents à "0", "3" et "6". À mon avis, le 1/300 D'une seconde et/ou 3,33 ms de précision est une décision architecturale malheureuse puisque le bloc de 4 octets pour le moment dans SQL Server's DATETIME le type de données aurait pu facilement prendre en charge une précision de 1 ms.

19
Erik

Conversion implicite

Je supposais que le type de données posted_date est Datetime. Cependant, peu importe que le type de l'autre côté soit Datetime, Datetime2 ou simplement Time car la chaîne (Varchar) sera implicitement convertie en Datetime.

Avec la date de publication déclarée en tant que Datetime2 (ou Time), la clause posted_date <= '2015-07-27 23:59:59.99999' Where échoue, car 23:59:59.99999 Est une valeur Datetime2 valide, ce n'est pas une valeur Datetime valide:

 Conversion failed when converting date and/or time from character string.

Plage de temps pour Datetime

La plage horaire de Datetime est de 00:00:00 à 23: 59: 59.997. Par conséquent, 23: 59: 59.999 est hors de portée et doit être arrondi à la valeur la plus proche.

Précision

En outre, les valeurs Datetime sont arrondies par incréments de .000, .003 ou .007 secondes. (c'est-à-dire 000, 003, 007, 010, 013, 017, 020, ..., 997)

Ce n'est pas le cas avec la valeur 2015-07-27 23:59:59.999 Qui se situe dans cette plage: 2015-07-27 23:59:59.997 Et 2015-07-28 0:00:00.000.

Cette plage correspond aux options précédentes et suivantes les plus proches, se terminant toutes les deux par .000, .003 ou .007.

Arrondir vers le haut ou vers le bas ?

Parce qu'elle est plus proche de 2015-07-28 0:00:00.000 (+1 contre -2) que 2015-07-27 23:59:59.997, La chaîne est arrondie et devient cette valeur Datetime: 2015-07-28 0:00:00.000.

Avec une limite supérieure comme 2015-07-27 23:59:59.998 (Ou .995, .996, .997, .998), elle aurait été arrondie à 2015-07-27 23:59:59.997 Et votre requête aurait fonctionné comme prévu. Cependant, cela n'aurait pas été une solution mais juste une valeur chanceuse.

Types Datetime2 ou Time

Les plages de temps Datetime2 et Time sont comprises entre 00:00:00.0000000 Et 23:59:59.9999999 Avec une précision de 100 ns (le dernier chiffre lorsqu'il est utilisé avec une précision à 7 chiffres).

Cependant, une plage Datetime (3) n'est pas similaire à la plage Datetime:

  • Datetime 0:0:00.000 À 23:59:59.997
  • Datetime2 0:0:00.000000000 À 23:59:59.999

Solution

En fin de compte, il est plus sûr de rechercher des dates inférieures au lendemain que des dates inférieures ou égales à ce que vous pensez que c'est le dernier fragment de l'heure de la journée. C'est principalement parce que vous savez que le lendemain commence toujours à 0: 00: 00.000 mais que différents types de données peuvent ne pas avoir la même heure à la fin de la journée:

Datetime `0:0:00.000` to `23:59:59.997`
Datetime2 `0:0:00.000000000` to `23:59:59.999-999-900`
Time2 `0:0:00.000000000` to `23:59:59.999-999-900`
  • < 2015-07-28 0:00:00.000 vous donnera un résultat précis et est la meilleure option
  • <= 2015-07-27 23:59:59.xxx Peut renvoyer des valeurs inattendues lorsqu'il n'est pas arrondi à ce que vous pensez qu'il devrait être.
  • La conversion en date et l'utilisation de la fonction doivent être évitées car elles limitent l'utilisation des index

Nous pourrions penser que le changement de [posted_date] en Datetime2 et sa précision plus élevée pourraient résoudre ce problème, mais cela n'aidera pas car la chaîne est toujours convertie en Datetime. Cependant, si un cast est ajouté cast(2015-07-27 23:59:59.999' as datetime2), cela fonctionne très bien

Cast et conversion

Cast peut convertir une valeur avec jusqu'à 3 chiffres en Datetime ou avec jusqu'à 9 chiffres en Datetime2 ou Time et l'arrondir à la précision correcte.

Il est à noter que Cast of Datetime2 et Time2 peuvent donner des résultats différents:

  • select cast('20150101 23:59:59.999999999' as datetime2(7)) est arrondi 2015-05-03 00: 00: 00.0000000 (pour une valeur supérieure à 999999949)
  • select cast('23:59:59.999999999' as time(7)) => 23: 59: 59.9999999

Il résout en quelque sorte le problème de datetime avec les incréments de 0, 3 et 7, bien qu'il soit toujours préférable de rechercher des dates avant la 1re nano seconde du jour suivant (toujours 0: 00: 00.000).

MSDN source: datetime (Transact-SQL)

9
Julien Vavasseur

Il arrondit

 select cast('2015-07-27 23:59:59.999' as datetime) 
 returns 2015-07-28 00:00:00.000

.998, .997, .996, .995 tous moulés/ronds à .997

Devrait utiliser

select * 
from A 
where posted_date >= '2015-07-27 00:00:00.000' 
  and posted_date <  '2015-07-28 00:00:00.000'

ou

where cast(posted_date as date) = '2015-07-27'

Voir la précision dans ce lien
Toujours signalé comme .000, .003, .007

6
paparazzo