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


20
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é_date aujourd'hui: 2015-07-28. Mon serveur de base de données n'est pas dans mon pays. Quel est le problème ?

Réponses:


16

Puisque vous utilisez le datetimetype 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 
╚═══════════╩═════╩═════════════════════════════╩═════════════════════════════╩══════════╩═══════════╝

entrez la description de l'image ici

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

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

entrez la description de l'image ici Cliquez pour agrandir

DATETIME2existe depuis SQL Server 2008, alors commencez à l'utiliser au lieu de DATETIME. Pour votre situation, vous pouvez utiliser datetime2avec une précision de 3 décimales par exemple datetime2(3).

Avantages de l'utilisation datetime2:

  • Prend en charge jusqu'à 7 décimales pour le composant temps par rapport à la prise en datetimecharge de seulement 3 décimales .. et donc vous voyez le problème d'arrondi car, par défaut, datetimearrondit le plus proche .003 secondsavec des incréments de .000, .003ou .007secondes.
  • datetime2est beaucoup plus précis que datetimeet datetime2vous donne le contrôle de DATEet TIMEpar opposition à datetime.

Référence :


1
gives you control of DATE and TIME as opposed to datetime.Qu'est-ce que ça veut dire?
nurettin

Ré. en utilisant DateTime2vs DateTime: a. Pour - la - vaste - majorité - des - cas d'utilisation du monde réel , les avantages de DateTime2Much <cost. Voir: stackoverflow.com/questions/1334143/… b. Ce n'est pas le problème racine ici. Voir le commentaire suivant.
Tom

Le problème racine ici (comme je parie que la plupart des développeurs seniors seront d'accord) n'est pas une précision insuffisante dans la date / heure de fin inclusive d'une comparaison de plage date-heure, mais plutôt l'utilisation d'une période inclusive (vs exclusive). C'est comme vérifier l'égalité avec Pi, il y a toujours la possibilité que l'un des # ait une précision> ou <(c'est-à-dire si datetime3avec 70 (vs 7) chiffres de précision ajoutés?). La meilleure pratique consiste à utiliser une valeur où la précision n'a pas d'importance, c'est-à-dire <le début de la seconde, minute, heure ou jour suivante vs <= la fin de la seconde, minute, heure ou jour précédente.
Tom

18

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

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

Notez que la plage de temps ne peut jamais l' ê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.

Tableau des règles d'arrondi

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 remplacer l'utilisation du DATETIMEtype de données par un DATEtype de données pour votreposted_date colonne.

En ce qui concerne l'option 3, une très bonne explication sur certains problèmes peut être trouvée ici: Cast to date is sargable but is this good idea?

Je n'aime pas les options 2 et 5, car les .997secondes fractionnaires ne seront qu'un autre nombre magique que les gens voudront "corriger". Pour quelques raisons supplémentaires qui BETWEENne sont pas largement acceptées, vous voudrez peut-être consulter ce post .

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 qu'elle affecte la sargabilité vous ne pouvez pas effectuer une recherche d'index et que cela entraînera souvent 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 d' Aaron Bertrand .

En vous séparant, vous seriez en mesure de conserver votre requête d'origine et elle se comporterait comme vous le souhaitez si vous changez votre posted_datecolonne de a DATETIMEen aDATETIME2(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 petit anecdote, la 1/300précision d'une seconde avec DATETIMEsemble être un maintien d'UNIX par cette réponse StackOverflow . Sybase qui a un héritage partagé a une 1/300précision similaire d'une seconde dans leurs types de données DATETIMEetTIME , mais leurs chiffres les moins significatifs sont légèrement différents à "0", "3" et "6". À mon avis, la 1/300précision d'une seconde et / ou 3,33 ms est une décision architecturale malheureuse car le bloc de 4 octets pour le moment dans le DATETIMEtype de données SQL Server aurait pu facilement prendre en charge une précision de 1 ms.


Oui, mais le principal «problème principal» n'utilise pas l'option 1 (par exemple, en utilisant une valeur de fin de plage inclusive (ou exclusive) où la précision des types de données passés ou potentiels futurs pourrait affecter les résultats). C'est comme vérifier l'égalité avec Pi, il est toujours possible qu'une # ait> ou <précision (à moins que les deux ne soient pré-arrondis à la plus basse précision commune). Et si datetime3avec 70 (contre 7) chiffres de précision ajoutés? La meilleure pratique consiste à utiliser une valeur où la précision n'a pas d'importance, c'est-à-dire <le début de la seconde, minute, heure ou jour suivante vs <= la fin de la seconde, minute, heure ou jour précédente.
Tom

9

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 Posté_date déclaré en tant que Datetime2 (ou Time), la posted_date <= '2015-07-27 23:59:59.99999'clause where échoue car altough 23:59:59.99999est 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.999qui se situe dans cette plage: 2015-07-27 23:59:59.997et 2015-07-28 0:00:00.000.

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

Arrondir vers le haut ou vers le bas ?

Parce qu'il est plus proche de 2015-07-28 0:00:00.000(+1 par rapport à -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.997et votre requête aurait fonctionné comme prévu. Cependant, cela n'aurait pas été une solution mais juste une valeur chanceuse.

Datetime2 ou types d'heure

Plages de temps datetime2 et le temps sont 00:00:00.0000000grâce 23:59:59.9999999avec 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:

  • Date 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.000vous donnera des résultats précis et est la meilleure option
  • <= 2015-07-27 23:59:59.xxx peut renvoyer des valeurs inattendues lorsqu'elle n'est pas arrondie à ce que vous pensez qu'elle 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 casting est ajouté cast(2015-07-27 23:59:59.999' as datetime2), cela fonctionne 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)


6

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


En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.