Предупреждение о округлении параметра SQLT DateTime - PullRequest
1 голос
/ 27 августа 2010

Больше предупреждения, чем вопроса:

Мы решили очень странную ошибку этим утром.У нас есть множество отчетов, которые позволяют пользователям вводить диапазоны дат, которые они хотят запустить.Предполагается, что если вы запрашиваете отчет с 1 августа 2010 года по 8 октября 2010 года, вы намеревались включить , включая 8 октября 2010 года, поэтому дата окончания отчета не будет 8 /10, это что-то после этого.

Это не может быть 8/11/2010, потому что некоторые из этих отчетов объединяют все, что происходило в течение дня, группируя их по тому дню, который наступает в полночь, поэтому ежедневный свод был бывключать дополнительный день - не то, что мы хотели.

Чтобы избежать возможности пропустить какие-либо предметы очень близко к концу дня, мы вычислили дату окончания как «на один тик» меньше, чем завтра:

public static DateTime EndOfDay(DateTime day)
{
    return day.Date.AddDays(1).AddTicks(-1);
}

Внутренне это заканчивается примерно 10/10/2010 12: 59: 59.9999PM

Что ж, когда вы передаете этот DateTime параметру DATETIME в SQL Server, он округляет значение до8/11/2010 00:00:00!И так как в нашем запросе вместо

DateField >= @FromDate AND DateField < @ToDate

используется

DateField BETWEEN @FromDate AND @ToDate

Мы видели отчеты от 8/1 / 2010-8 / 10/2010, включающие элементы от 8/11/2010.

Единственный способ, которым мы обнаружили настоящую проблему, - это циклическое переключение дат через строку.DateTime.ToString () тоже округляется, поэтому мы должны получить 01.08.2010 12:59:59 PM, которой SQL Server был доволен.

Так что теперь наш метод «конец дня» выглядит следующим образом:

public static DateTime EndOfDay(DateTime day)
{
    // Cant' subtract anything smaller (like a tick) because SQL Server rounds UP! Nice, eh?
    return day.Date.AddDays(1).AddSeconds(-1);
}

Извините, не вопрос - просто подумал, что кто-то может найти это полезным.

Ответы [ 4 ]

5 голосов
/ 27 августа 2010

Это из-за точности типа данных DATETIME, который имеет точность ( quote ):

Округлено с шагом .000, .003 или .007 секунд

Так что да, вы должны быть осторожны в определенных ситуациях (например, 23: 59: 59.999 будет округлено до 00:00 следующего дня, 23: 59: 59.998 будет округлено до23: 59: 59.997)

SELECT CAST('2010-08-27T23:59:59.997' AS DATETIME)
SELECT CAST('2010-08-27T23:59:59.998' AS DATETIME)
SELECT CAST('2010-08-27T23:59:59.999' AS DATETIME)

Начиная с SQL Server 2008, существует новый тип данных DATETIME2 , который повышает точность до 100 наносекунд.

Когда яделаю запросы к полю DATETIME, которое содержит элемент времени, поэтому я не использую BETWEEN.

например, я предпочитаю

WHERE DateField >= '2010-08-27' AND DateField < '2010-08-28'

вместо:

WHERE DateField BETWEEN '2010-08-27' AND '2010-08-27T23:59:59.997'
2 голосов
/ 28 августа 2010

Решение, которое вы опубликовали, к сожалению, добавляет еще одну тонкую проблему, которая в конечном итоге вернется и укусит вас: теперь вы пропускаете все даты, которые> = 23: 59: 59.003 и <= 23: 59: 59.997.Я сильно подозреваю, что вы МОЖЕТЕ вычесть что-то меньше 1 секунды, и это 3 такта, если вы не делаете что-то, что лишает дополнительного времени.Имейте в виду, что smalldatetime даже не будет хранить секунды. </p>

При работе с граничными краями никогда не стоит использовать «известное разрешение» системы, представляющее значение, для моделирования исключительной конечной точки с инклюзивным значением.Вы выяснили это нелегко, когда конвертировали между двумя репрезентативными системами с разным разрешением.Наилучший способ подняться прямо к границе, но исключить ее, - это использовать обычные сравнительные операторы.

Итак, как говорили другие авторы, правильный ответ - использовать Dt >= @Dt1 AND Dt < @Dt2.Я понимаю, что у вас есть миллиард хранимых процедур, где это нужно исправить, поэтому я могу предложить следующую схему, чтобы исправить это:

  1. Измените вашу функцию, чтобы она возвращалась на следующий день без вычитаемых секундили отметки.

  2. Программно создайте SP-оболочки "обертки" для всех ваших хранимых процедур, которые используют исходные имена SP.Переименуйте оригиналы во что-то вроде SPName_NDC (без даты).Вычтите 3 мс из каждого «до даты» перед передачей его в версию NDC.

    Примечание. Типы и имена параметров SP можно получить из системных таблиц.Это может помочь вам:

    SELECT
       ObjectSchema = Schema_Name(SO.schema_id),
       ObjectName = SO.name,
       ObjectType = SO.Type_Desc,
       Position = P.parameter_id,
       ParameterName = P.name,
       ParameterDataType = Type_name(P.user_type_id),
       P.max_length,
       P.[Precision],
       P.Scale,
       P.Is_Output,
       P.Has_Default_Value,
       P.Default_Value
    FROM
       sys.objects AS SO
       INNER JOIN sys.parameters AS P ON SO.OBJECT_ID = P.OBJECT_ID
    WHERE
       SO.Type = 'P'
    
  3. Теперь вы можете постепенно исправлять каждый несовместимый SP с течением времени.Не должно быть заметного снижения производительности при использовании оболочек.

Пример SP оболочки может выглядеть следующим образом:

CREATE PROCEDURE dbo.ProfitReport
   @FromDate datetime,
   @ToDate datetime = NULL OUT
AS
SET @ToDate = DateAdd(ms, -3, @ToDate)
DECLARE @RC int
EXEC @RC = dbo.ProfitReport_NDC @FromDate, @ToDate OUT
RETURN @RC

Вам понадобитсяполучить список всех параметров даты и решить, какие из них представляют границы даты окончания.Будьте осторожны, если у ваших SP есть какие-либо XML или табличные параметры.

Вы можете снова вернуться в мир здравомыслия!

Примечание: если вы обновитесь до SQL 2008, прежде чем вы сможетев любом случае используйте тип данных datetime2, который вам понадобится исправить.

1 голос
/ 27 августа 2010

Наибольшее подсекундное значение, которое может быть сохранено в datetime, равно 0,997.

Таким образом, для использования between необходимо (например)

between '2010-08-27 00:00:00.000' and '2010-08-27 23:59:59.997'

В идеале вы должны использовать < вместо between, чтобы ваш код был совместим с1010 * тип данных, где это предположение не выполняется.

1 голос
/ 27 августа 2010

Тип данных datetime SQL Server с точностью до 333 секунды, то есть .003, .006, .009 и т. Д.Вот почему ваш .999 округляется до 0. Добро пожаловать в ряды разработчиков (иначе говоря, «всех нас»), однажды испорченный этой реализацией.

...