【问题标题】:How can I compare time in SQL Server?如何比较 SQL Server 中的时间?
【发布时间】:2009-04-30 16:43:11
【问题描述】:

我正在尝试在 SQL 查询中比较 datetime 字段 中的时间,但我不知道它是否正确。我不想比较日期部分,只比较时间部分。

我正在这样做:

SELECT timeEvent 
FROM tbEvents 
WHERE convert(datetime, startHour, 8) >= convert(datetime, @startHour, 8)

正确吗?

我问这个是因为我需要知道 08:00:00 是小于还是大于 07:30:00 并且我不想比较日期,只是比较 时间 部分。

谢谢!

【问题讨论】:

  • 为什么我必须转换为 VARCHAR 以便比较一个时间是否大于另一个?我不会这样比较字符串和字符串吗??
  • DateDiff 函数接受日期时间字段作为输入,这样您就可以克服困难。

标签: sql sql-server datetime


【解决方案1】:

您的比较会起作用,但速度会很慢,因为每一行的日期都转换为字符串。要有效地比较两个时间部分,请尝试:

declare @first datetime
set @first = '2009-04-30 19:47:16.123'
declare @second datetime
set @second = '2009-04-10 19:47:16.123'

select (cast(@first as float) - floor(cast(@first as float))) -
       (cast(@second as float) - floor(cast(@second as float)))
       as Difference

详细说明:SQL Server 中的日期存储为浮点数。小数点前的数字代表日期。小数点后的数字代表时间。

所以这里是一个示例日期:

declare @mydate datetime
set @mydate = '2009-04-30 19:47:16.123'

让我们将其转换为浮点数:

declare @myfloat float
set @myfloat = cast(@mydate as float)
select @myfloat
-- Shows 39931,8244921682

现在取逗号后面的部分,即时间:

set @myfloat = @myfloat - floor(@myfloat) 
select @myfloat
-- Shows 0,824492168212601

将其转换回日期时间:

declare @mytime datetime
set @mytime = convert(datetime,@myfloat)
select @mytime
-- Shows 1900-01-01 19:47:16.123

1900-01-01 只是“零”日期;您可以使用 convert 显示时间部分,例如指定格式 108,这就是时间:

select convert(varchar(32),@mytime,108)
-- Shows 19:47:16

datetime 和 float 之间的转换非常快,因为它们的存储方式基本相同。

【讨论】:

  • 这太好了,谢谢!我一直在做很多 varchar 转换,这似乎是一种更好的方法(虽然看起来不是很明显)。
【解决方案2】:
convert(varchar(5), thedate, 108) between @leftTime and @rightTime

解释:

如果您有varchar(5),您将获得HH:mm

如果您有varchar(8),您将获得HH:mm ss

108 只获取从 SQL 日期开始的时间

@leftTime@rightTime 是要比较的两个变量

【讨论】:

    【解决方案3】:

    如果您使用的是 SQL Server 2008,您可以这样做:

    WHERE CONVERT(time(0), startHour) >= CONVERT(time(0), @startTime)
    

    这是一个完整的测试:

    DECLARE @tbEvents TABLE (
        timeEvent   int      IDENTITY,
        startHour   datetime
    )
    
    INSERT INTO @tbEvents (startHour) SELECT DATEADD(hh, 0, GETDATE())
    INSERT INTO @tbEvents (startHour) SELECT DATEADD(hh, 1, GETDATE())
    INSERT INTO @tbEvents (startHour) SELECT DATEADD(hh, 2, GETDATE())
    INSERT INTO @tbEvents (startHour) SELECT DATEADD(hh, 3, GETDATE())
    INSERT INTO @tbEvents (startHour) SELECT DATEADD(hh, 4, GETDATE())
    INSERT INTO @tbEvents (startHour) SELECT DATEADD(hh, 5, GETDATE())
    
    --SELECT * FROM @tbEvents
    
    DECLARE @startTime  datetime
    
    SET @startTime = DATEADD(mi, 65, GETDATE())
    
    SELECT
        timeEvent,
        CONVERT(time(0), startHour)  AS 'startHour',
        CONVERT(time(0), @startTime) AS '@startTime'
    FROM @tbEvents
    WHERE CONVERT(time(0), startHour) >= CONVERT(time(0), @startTime)
    

    【讨论】:

      【解决方案4】:

      到目前为止,我注意到解决方案的一个(可能很小)问题是,它们似乎都需要函数调用来处理比较。这意味着查询引擎将需要进行全表扫描以查找您所追求的行 - 并且无法使用索引。如果表不会变得特别大,这可能不会产生任何不利影响(您可以很高兴地忽略这个答案)。

      另一方面,如果表变得非常大,查询的性能可能会受到影响。

      我知道您说您不希望比较日期部分 - 但是日期时间列中是否存储了实际日期,或者您是否仅使用它来存储时间?如果是后者,您可以使用简单的比较运算符,这将降低 CPU 使用率,并允许查询引擎使用统计信息和索引(如果存在)来优化查询。

      但是,如果使用 datetime 列来存储事件的日期和时间,这显然是行不通的。在这种情况下,如果您可以修改应用程序和表结构,将日期和时间分成两个单独的日期时间列,或者创建一个索引视图来选择源表的所有(相关)列,以及包含您希望搜索的时间元素(使用之前的任何答案来计算) - 并更改应用程序以查询视图。

      【讨论】:

        【解决方案5】:
        if (cast('2012-06-20 23:49:14.363' as time) between 
            cast('2012-06-20 23:49:14.363' as time) and 
            cast('2012-06-20 23:49:14.363' as time))
        

        【讨论】:

          【解决方案6】:

          只需将转换日期时间更改为应该可以解决问题的时间:

          SELECT timeEvent 
          FROM tbEvents 
          WHERE convert(time, startHour) >= convert(time, @startHour)
          

          【讨论】:

            【解决方案7】:

            使用浮动不起作用。

            DECLARE @t1 datetime, @t2 datetime
            SELECT @t1 = '19000101 23:55:00', @t2 = '20001102 23:55:00'
            SELECT CAST(@t1 as float) - floor(CAST(@t1 as float)), CAST(@t2 as float) - floor(CAST(@t2 as float))
            

            您会看到这些值不一样 (SQL Server 2005)。我想用这个方法来检查午夜前后的时间(完整的方法有更多细节),我正在比较当前时间在 23:55:00 和 00:05:00 之间。

            【讨论】:

              【解决方案8】:

              添加到其他答案:

              您可以创建一个用于从日期时间修剪日期的函数

              CREATE FUNCTION dbo.f_trimdate (@dat datetime) RETURNS DATETIME AS BEGIN
                  RETURN CONVERT(DATETIME, CONVERT(FLOAT, @dat) - CONVERT(INT, @dat))
              END
              

              所以这个:

              DECLARE @dat DATETIME
              SELECT @dat = '20080201 02:25:46.000'
              SELECT dbo.f_trimdate(@dat)
              

              将返回 1900-01-01 02:25:46.000

              【讨论】:

              • 转换为 INT 轮,这会使结果并不总是准确,FLOOR 是要走的路
              • 这就是示例的原因:基于日期四舍五入,所以我们可以只在时间字段上工作。你测试了吗?
              【解决方案9】:

              使用Datepart函数:DATEPART(datepart, date)

              例如#

              选择日期部分(@YourVar, hh)*60) + DatePart(@YourVar, mi)*60)

              这将为您提供一天中的总时间(以分钟为单位),让您更轻松地进行比较。

              如果您的日期相同,您可以使用 DateDiff,否则您需要像上面一样删除日期

              【讨论】:

                【解决方案10】:

                您可以创建两个日期时间变量,并仅设置您需要比较的日期时间。

                declare  @date1 datetime;
                declare  @date2 datetime;
                
                select   @date1 = CONVERT(varchar(20),CONVERT(datetime, '2011-02-11 08:00:00'), 114)
                select   @date2 = CONVERT(varchar(20),GETDATE(), 114)
                

                日期将是“1900-01-01”你可以比较一下

                if @date1 <= @date2
                   print '@date1 less then @date2'
                else
                   print '@date1 more then @date2'
                

                【讨论】:

                  【解决方案11】:
                  SELECT timeEvent 
                    FROM tbEvents 
                   WHERE CONVERT(VARCHAR,startHour,108) >= '01:01:01'
                  

                  这告诉 SQL Server 使用样式 108 将当前日期/时间转换为 varchar,即“hh:mm:ss”。如有必要,您还可以替换另一个转换的 '01:01:01'。

                  【讨论】:

                    【解决方案12】:

                    我相信你想使用DATEPART('hour', datetime)

                    参考在这里:

                    http://msdn.microsoft.com/en-us/library/ms174420.aspx

                    【讨论】:

                      【解决方案13】:

                      我不喜欢依赖存储内部结构(日期时间是一个浮点数,整数 = 天,小数 = 时间),但我和 Jhonny D. Cano 的答案一样。这是我认识的所有数据库开发人员的方式。绝对不要转换为字符串。如果您必须避免处理为 float/int,那么最好的选择是使用 DatePart() 提取小时/分钟/秒/毫秒

                      【讨论】:

                        【解决方案14】:

                        我假设您的 startHour 列和 @startHour 变量都是 DATETIME;在这种情况下,您应该转换为字符串:

                        SELECT timeEvent
                        FROM tbEvents
                        WHERE convert(VARCHAR(8), startHour, 8) >= convert(VARCHAR(8), @startHour, 8)
                        

                        【讨论】:

                        • 克里斯,为什么我必须转换为 VARCHAr 来比较时间?这样,我不会比较字符串和字符串吗?
                        • 两个原因 - 1) CONVERT() 方法旨在从一种类型转换为另一种类型;如果将 DATETIME 转换为 DATETIME,则不会发生任何事情。 2) CONVERT() 的第三个参数(第 8 个)是转为字符串类型时格式化数据的代码;转换为 DATETIME 时没有任何意义。
                        【解决方案15】:

                        以下查询为您提供日期时间

                        select DateAdd(day,-DateDiff(day,0,YourDateTime),YourDateTime) As NewTime from Table
                        

                        【讨论】:

                          【解决方案16】:

                          @ronmurp 提出了一个有效的问题——cast/floor 方法同时返回不同的值。根据@littlechris 的回答,对于解决具有分钟、秒、毫秒分量的时间的更通用的解决方案,您可以使用此函数来计算从一天开始的毫秒数。

                          Create Function [dbo].[MsFromStartOfDay] ( @DateTime datetime )
                          Returns int
                          As
                            Begin
                                Return (
                                         ( Datepart( ms , @DateTime ) ) +
                                         ( Datepart( ss , @DateTime ) * 1000 ) +
                                         ( Datepart( mi , @DateTime ) * 1000 * 60 ) +
                                         ( Datepart( hh , @DateTime ) * 1000 * 60 * 60 )
                                        )
                            End
                          

                          我已经验证它在同一时间为两个不同的日期返回相同的 int

                          declare @first datetime
                          set @first = '1900-01-01 23:59:39.090'
                          declare @second datetime
                          set @second = '2000-11-02 23:56:39.090'
                          Select dbo.MsFromStartOfDay( @first )
                          Select dbo.MsFromStartOfDay( @second )
                          

                          此解决方案并不总是返回您期望的 int。例如,在 SQL 2005 中尝试以下操作,它返回一个以 '557' 而不是 '556' 结尾的 int。

                          set @first = '1900-01-01 23:59:39.556'
                          set @second = '2000-11-02 23:56:39.556'
                          

                          我认为这与存储为浮点数的 DateTime 的性质有关。不过,您仍然可以比较这两个数字。当我在使用 DateTime.Now() 在 .NET 中捕获并存储在 SQL 中的 DateTime 的“真实”数据集上使用这种方法时,我发现计算是准确的。

                          【讨论】:

                          • 注意 - 这种方法的性能似乎不太好
                          【解决方案17】:

                          TL;DR

                          如果您想在搜索中使用索引,请将时间值与日期值分开(出于性能考虑,您可能应该这样做)。您可以:(1) 使用基于函数的索引或 (2) 仅为时间创建一个新列,索引该列并在 SELECT 子句中使用它。


                          请记住,如果您在 SQL 的 WHERE 子句中使用函数,您将失去任何索引性能提升,引擎必须执行 扫描搜索。只需使用EXPLAIN SELECT... 运行您的查询以确认这一点。发生这种情况是因为引擎必须为每个比较处理字段中的每个值,并且转换后的值没有被索引。

                          大多数答案说使用float()convert()cast()addtime() 等。同样,如果你这样做,你的数据库将不会使用索引。对于可能没问题的小桌子。

                          虽然 (where field = func(value)) 可以使用 WHERE 参数中的函数,因为您不会更改表中每个字段的值。

                          如果你想继续使用索引,你可以为时间值创建一个基于函数的索引。执行此操作(并支持它)的正确方法可能取决于您的数据库引擎。另一种选择是添加一列以仅存储时间值并为该列建立索引,但请先尝试前一种方法。


                          编辑 06-02

                          在更新数据库以使用新的时间列或使用索引之前进行一些性能测试。在我的测试中,我发现性能提升很小(当我可以看到一些改进时)并且不值得添加新索引的麻烦和开销。

                          【讨论】:

                            猜你喜欢
                            • 2018-07-29
                            • 2011-03-19
                            • 2011-05-27
                            • 2016-11-17
                            • 1970-01-01
                            • 1970-01-01
                            • 1970-01-01
                            • 1970-01-01
                            • 1970-01-01
                            相关资源
                            最近更新 更多