SQL Date сравнивать, используя только дату, а не время - PullRequest
4 голосов
/ 27 июля 2010

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

SELECT *
FROM Employee e
inner join OT_Hours o on o.Emp_ID=e.Emp_ID
inner join Position p on p.Position_ID=e.Position_ID
inner join Signup_Sheet s on s.Employee_ID=e.Emp_ID
WHERE e.Eligible_OT=1 and s.Day_Shift = 1 
and p.Position_Name = 'Controller' 
and Convert(Varchar(20),s.Date,101) = '07/26/2010'
and Convert(Varchar(20),o.Date,101) <='07/26/2010'
and Convert(Varchar(20),o.Date,101) > '07/26/2009'
and o.Quantity NOT IN(0.3) order by o.Date DESC

Я не получил бы результата, когда запустил этот запрос, но когда я удалил вторую последнюю строку, он вернул бы 12 результатов (<=) и когда яудалил 3-ю последнюю строку, но сохранил вторую последнюю, он вернет 6 результатов (>).После просмотра данных я увидел, что 4 из этих результатов должны были быть возвращены.Теперь о странной части.Ниже приведен код, который я сейчас использую.

SELECT DISTINCT o.Date, e.Emp_ID as Emp_ID, e.First_Name+ ' ' +e.Last_Name as Name, o.Quantity as Sum
FROM Employee e
left join OT_Hours o on o.Emp_ID=e.Emp_ID
left join Position p on p.Position_ID=e.Position_ID
left join Signup_Sheet s on s.Employee_ID=e.Emp_ID
WHERE e.Eligible_OT=1 and s.Day_Shift = 1 
and p.Position_Name = 'Controller' 
and Convert(Varchar(20),s.Date,101) = '07/26/2010'
and o.Date between '07/26/2009' and '07/26/2010'
and o.Quantity NOT IN(0.3) order by o.Date DESC

Этот запрос будет возвращать результаты, но я также проверял его так же, как и другой, когда o.Date был выше и ниже указанной даты.Когда дата была <= 16 результатов были возвращены, когда> 8 результатов были возвращены.Последний запрос дал 6 результатов.Теперь это не производственная база данных, к которой я обращаюсь, и я единственный, кто ее использует, поэтому данные не изменились.Любое объяснение, почему это происходит?Я предполагаю, что это как-то связано с преобразованием его в varchar, и оно не может сравниться должным образом, но это не объясняет, почему я получу 12 <=, 6>, а затем никаких результатов в конце.Также, если кто-нибудь знает лучший способ реализовать это, пожалуйста, дайте мне знать.

Ответы [ 6 ]

7 голосов
/ 27 июля 2010

Два запроса не совпадают - это:

and o.Date between '07/26/2009' and '07/26/2010'

... является эквивалентом:

and o.Date >= '07/26/2009' 
and o.Date <= '07/26/2010'

МЕЖДУ является стандартом ANSI и включает все базы данных, с которыми я когда-либо сталкивался.

Имейте в виду, что если вы не укажете часть времени для DATETIME, значение по умолчанию будет начинаться в полночь дня - 00: 00: 00.

5 голосов
/ 27 июля 2010

Я изучил эту технику из журнала SQL Server, февраль 2007 г. (расчеты даты и времени Ицик Бен-Гана).Таким образом, ваш «между» будет работать независимо от того, будет ли дата строки после полуночи, так как все было нормализовано до полуночи с этим сравнением:

select *
from someTable
where dateadd(day, datediff(day, 0, somedate), 0) between '07/26/2009' and '07/26/2010' 

Datediff и dateadd работают вместе для удалениявремя и оставить дату.Затем вы можете сравнить его со строковыми литералами или другими датами, для которых была сделана такая же модификация.Я бы рекомендовал поместить это в функцию.

РЕДАКТИРОВАТЬ: На основе комментариев OMG Ponies.Это не будет использовать индекс по столбцу даты.Альтернативой может быть использование техники снятия времени в дополнение к технике, упомянутой другими.Таким образом, вместо того, чтобы делать это в столбце таблицы, делайте это в последнем аргументе, равном «между».У вас может быть такая функция:

CREATE FUNCTION [dbo].[fn_enddate](@enddate datetime)
RETURNS datetime AS  
BEGIN
    DECLARE @endOfDay datetime
    set @endOfDay = dateadd(millisecond, -2, dateadd(day, datediff(day, 0, @enddate) + 1, 0))
    return @endOfDay
END

Она принимает дату аргумента, устанавливает ее равной полуночи следующего дня, затем вычитает две миллисекунды, давая конец дня данного времени и даты.Тогда вы можете сделать:

select *
from someTable
where somedate between '07/26/2009' and dbo.fn_enddate('07/26/2010')
4 голосов
/ 27 июля 2010

В третьей из последней строки в вашем первом запросе вы сравниваете две строки.

Таким образом, 01/02/2009 больше, чем 01/01/2010

Я обычно назначаю дату BETWEEN '01/02/2009 00:00:00.000' AND '01/01/2010 23:59:59.997', но будет интересно увидеть лучшее решение.

1 голос
/ 27 июля 2010

Мое предложение было бы изменить ваши критерии для учета времени, а не самих дат. Если вы преобразуете или иным образом манипулируете столбцами для сравнения, вы можете прекратить использование индексов в запросе.

Кроме того, всегда сравнивайте даты с датами. Когда вы конвертируете их в строки и пытаетесь сравнить, вы сталкиваетесь с проблемами, как указывает Крис Дайвер.

В вашем случае я бы попробовал:

SELECT
    o.Date,
    e.Emp_ID as Emp_ID,
    e.First_Name+ ' ' +e.Last_Name as Name,
    o.Quantity as Sum
FROM
    Employee e
LEFT JOIN OT_Hours o ON o.Emp_ID = e.Emp_ID
LEFT JOIN Position p ON p.Position_ID = e.Position_ID
LEFT JOIN Signup_Sheet s ON s.Employee_ID = e.Emp_ID
WHERE
    e.Eligible_OT = 1 AND
    s.Day_Shift = 1 AND
    p.Position_Name = 'Controller' AND
    (s.Date >= @signup_date AND s.Date < DATEADD(dy, 1, @signup_date)) AND
    (o.Date >= @order_start_date AND o.Date < DATEADD(dy, 1, @order_end_date)) AND
    o.Quantity NOT IN (0.3)
ORDER BY
    o.Date DESC

Вам необходимо убедиться, что параметры или переменные всегда без временной части.

1 голос
/ 27 июля 2010
...
AND s.Date BETWEEN '2010-07-26 00:00:00.000' AND '2010-07-26 23:59:59.997'
AND o.Date BETWEEN '2009-07-26 00:00:00.000' AND '2010-07-26 23:59:59.997'
...
1 голос
/ 27 июля 2010

Если ваша база данных - SQL Server, то, что я сделал, что работает очень хорошо, чтобы убрать время, выглядит примерно так:

SELECT DISTINCT o.Date, e.Emp_ID as Emp_ID, e.First_Name+ ' ' +e.Last_Name as Name, o.Quantity as Sum 
FROM Employee e 
left join OT_Hours o on o.Emp_ID=e.Emp_ID 
left join Position p on p.Position_ID=e.Position_ID 
left join Signup_Sheet s on s.Employee_ID=e.Emp_ID 
WHERE e.Eligible_OT=1 and s.Day_Shift = 1  
and p.Position_Name = 'Controller'  
and CAST(FLOOR(CAST(s.Date AS FLOAT)) AS DATETIME) = '07/26/2010' 
and CAST(FLOOR(CAST(o.Date AS FLOAT)) AS DATETIME) between '07/26/2009' and '07/26/2010' 
and o.Quantity NOT IN(0.3) order by o.Date DESC 

В зависимости от того, как установлены ваши параметры '07 /26/2010 ', '07 / 26/2009' вы можете сохранить их в переменных datetime и выполнить ту же самую операцию cast(floor(cast(@datevar as float)) as datetime).

Это похоже на репост.Проверьте принятый ответ здесь ... Как удалить временную часть значения datetime (SQL Server)?

...