【问题标题】:how to filter rows between dates without using loop in tsql如何在不使用sql循环的情况下过滤日期之间的行
【发布时间】:2015-04-18 10:05:53
【问题描述】:

我想过滤两个日期时间之间的单个表中的行,这些过滤的行应该在一个日期下,例如

我想获取之间的所有行 (2015 年 3 月 16 日上午 6 点)和(2015 年 3 月 17 日上午 6 点)日期时间为(2015 年 3 月 17 日)日期和 (2015 年 3 月 17 日上午 6 点)和(2015 年 3 月 18 日上午 6 点)日期时间为(2015 年 3 月 18 日)日期等等。

这是我的演示表

Id      Name    LogTime
1       mj      2015-03-16 01:28:03.257
2       mj      2015-03-16 05:28:03.257
3       mj      2015-03-16 06:28:03.257
4       mj      2015-03-16 18:28:03.257
5       mj      2015-03-17 01:28:06.677
6       mj      2015-03-17 06:28:06.677
7       mj      2015-03-17 16:28:07.460
8       mj      2015-03-17 07:28:03.257
9       mj      2015-03-18 01:28:08.193
10      mj      2015-03-18 05:28:03.257
11      mj      2015-03-18 06:28:03.257
12      mj      2015-03-18 18:28:03.257
13      mj      2015-03-19 01:28:06.677
14      mj      2015-03-19 06:28:06.677
15      mj      2015-03-19 16:28:07.460
16      mj      2015-03-19 07:28:03.257
17      mj      2015-03-20 01:28:08.193
18      mj      2015-03-20 05:28:03.257
19      mj      2015-03-20 06:28:03.257
20      mj      2015-03-20 18:28:03.257

下面是我正在使用的查询。

DECLARE @i INT = 1

DECLARE @from DATETIME
    ,   @to DATETIME

WHILE (@i <= 5)
BEGIN

    SET @from = CONVERT(DATETIME, CONVERT(VARCHAR(10), DATEADD(D, -@i, '2015-03-20'), 102) + ' 6:00:00')
    SET @to = CONVERT(DATETIME, CONVERT(VARCHAR(10), DATEADD(D, -@i + 1, '2015-03-20'), 102) + ' 6:00:00')

    SELECT  *,  @to AS 'FetchedOn'
    FROM Biometric
    WHERE LogTime BETWEEN @from AND @to
    ORDER BY LogTime

    SET @i = @i + 1

END

生成以下结果。

Id  Name    LogTime             FetchedOn
14  mj  2015-03-19 06:28:06.677 2015-03-20 06:00:00.000
16  mj  2015-03-19 07:28:03.257 2015-03-20 06:00:00.000
15  mj  2015-03-19 16:28:07.460 2015-03-20 06:00:00.000
17  mj  2015-03-20 01:28:08.193 2015-03-20 06:00:00.000
18  mj  2015-03-20 05:28:03.257 2015-03-20 06:00:00.000

Id  Name    LogTime             FetchedOn
11  mj  2015-03-18 06:28:03.257 2015-03-19 06:00:00.000
12  mj  2015-03-18 18:28:03.257 2015-03-19 06:00:00.000
13  mj  2015-03-19 01:28:06.677 2015-03-19 06:00:00.000

Id  Name    LogTime             FetchedOn
6   mj  2015-03-17 06:28:06.677 2015-03-18 06:00:00.000
8   mj  2015-03-17 07:28:03.257 2015-03-18 06:00:00.000
7   mj  2015-03-17 16:28:07.460 2015-03-18 06:00:00.000
9   mj  2015-03-18 01:28:08.193 2015-03-18 06:00:00.000
10  mj  2015-03-18 05:28:03.257 2015-03-18 06:00:00.000

Id  Name    LogTime             FetchedOn
3   mj  2015-03-16 06:28:03.257 2015-03-17 06:00:00.000
4   mj  2015-03-16 18:28:03.257 2015-03-17 06:00:00.000
5   mj  2015-03-17 01:28:06.677 2015-03-17 06:00:00.000

Id  Name    LogTime             FetchedOn
1   mj  2015-03-16 01:28:03.257 2015-03-16 06:00:00.000
2   mj  2015-03-16 05:28:03.257 2015-03-16 06:00:00.000

现在我想在不使用循环的情况下获得相同的结果。 我正在使用 sql 2014,有没有其他解决方案?

【问题讨论】:

  • 您可以使用 CLR 表值函数。它可能比 t-sql while 循环更快。
  • “相同的结果”是指五个单独的结果集,还是接受带有i 列的单个结果集以按日期对结果进行分组?跨度>
  • 没有表对我来说并不重要,只要它返回正确的结果集。

标签: sql sql-server tsql sql-server-2014


【解决方案1】:

这是一些代码。想法是从测试数据中获取所有可能的不同范围。这是 CTE 返回的内容:

st                      ed
2015-03-15 06:00:00.000 2015-03-16 06:00:00.000
2015-03-16 06:00:00.000 2015-03-17 06:00:00.000
2015-03-17 06:00:00.000 2015-03-18 06:00:00.000
2015-03-18 06:00:00.000 2015-03-19 06:00:00.000
2015-03-19 06:00:00.000 2015-03-20 06:00:00.000

在此之后,它会在数据介于这些范围之间的条件下简单地加入:

DECLARE @t TABLE(ID INT, D DATETIME)

INSERT INTO @t VALUES
(1       ,'2015-03-16 01:28:03.257'),
(2       ,'2015-03-16 05:28:03.257'),
(3       ,'2015-03-16 06:28:03.257'),
(4       ,'2015-03-16 18:28:03.257'),
(5       ,'2015-03-17 01:28:06.677'),
(6       ,'2015-03-17 06:28:06.677'),
(7       ,'2015-03-17 16:28:07.460'),
(8       ,'2015-03-17 07:28:03.257'),
(9       ,'2015-03-18 01:28:08.193'),
(10      ,'2015-03-18 05:28:03.257'),
(11      ,'2015-03-18 06:28:03.257'),
(12      ,'2015-03-18 18:28:03.257'),
(13      ,'2015-03-19 01:28:06.677'),
(14      ,'2015-03-19 06:28:06.677'),
(15      ,'2015-03-19 16:28:07.460'),
(16      ,'2015-03-19 07:28:03.257'),
(17      ,'2015-03-20 01:28:08.193'),
(18      ,'2015-03-20 05:28:03.257'),
(19      ,'2015-03-20 06:28:03.257'),
(20      ,'2015-03-20 18:28:03.257')

;
WITH    cte
          AS ( SELECT DISTINCT
                        DATEADD(HOUR, -18, CAST(CAST(D AS DATE) AS DATETIME)) AS st ,
                        DATEADD(HOUR, 6, CAST(CAST(D AS DATE) AS DATETIME)) AS ed
               FROM     @t
             )
    SELECT  t.ID, t.D, c.ed
    FROM    cte c
            JOIN @t t ON t.D BETWEEN c.st AND c.ed

输出:

ID  D                       ed
1   2015-03-16 01:28:03.257 2015-03-16 06:00:00.000
2   2015-03-16 05:28:03.257 2015-03-16 06:00:00.000
3   2015-03-16 06:28:03.257 2015-03-17 06:00:00.000
4   2015-03-16 18:28:03.257 2015-03-17 06:00:00.000
5   2015-03-17 01:28:06.677 2015-03-17 06:00:00.000
6   2015-03-17 06:28:06.677 2015-03-18 06:00:00.000
7   2015-03-17 16:28:07.460 2015-03-18 06:00:00.000
8   2015-03-17 07:28:03.257 2015-03-18 06:00:00.000
9   2015-03-18 01:28:08.193 2015-03-18 06:00:00.000
10  2015-03-18 05:28:03.257 2015-03-18 06:00:00.000
11  2015-03-18 06:28:03.257 2015-03-19 06:00:00.000
12  2015-03-18 18:28:03.257 2015-03-19 06:00:00.000
13  2015-03-19 01:28:06.677 2015-03-19 06:00:00.000
14  2015-03-19 06:28:06.677 2015-03-20 06:00:00.000
15  2015-03-19 16:28:07.460 2015-03-20 06:00:00.000
16  2015-03-19 07:28:03.257 2015-03-20 06:00:00.000
17  2015-03-20 01:28:08.193 2015-03-20 06:00:00.000
18  2015-03-20 05:28:03.257 2015-03-20 06:00:00.000

【讨论】:

  • 我做到了。我想您的回答并没有错,但令我困扰的是,对于“某些代码”的作用,没有任何解释。如果您的回答不仅仅是代码/结果转储,而且至少附有对其背后想法的简要解释,那么您的回答将非常更有帮助。如果问题得到相应编辑,我很乐意投票。
  • 我会更新,但我真的认为没有什么可评论的。在这种情况下,代码是不言自明的
【解决方案2】:

您可以根据 LogTime 字段的时间部分计算 FetchedOn:

SELECT B.*,  dateadd(day, (iif(cast(LogTime as time) < '06:00:00', 0, 1)), cast(LogTime as date)) + cast('06:00:00' as datetime) as FetchedOn
from Biometric B
ORDER BY FetchedOn DESC, LogTime

更新:

计算 FetchedOn 的公式更简单,并且还添加了一个转换为 datetime 以实现 SQL2012+ 兼容性。

SELECT B.*, cast(cast(dateadd(hour, +18, LogTime) as date) as datetime) + cast('06:00:00' as datetime) as FetchedOn
from Biometric B
ORDER BY FetchedOn DESC, LogTime

【讨论】:

  • 到目前为止所有其他答案都是简单的代码转储,没有任何解释。您的答案要短得多,但它解释了(如果只是简要地)有效解决方案背后的原因。为此 +1。
  • @stakx,你是认真的吗?我将对所有事情视而不见,但这段代码根本不起作用。
  • 首先你在 dateadd 中有语法错误,因为你在那里使用日期类型,这是错误的。您返回所有行。操作仅返回 18 行。但这个想法是正确的,你需要纠正那些错误
  • 我的意思是在 dateadd 函数中你有日期 + 日期时间,这是非法的:Msg 402, Level 16, State 1, Line 37 The data types date and datetime are incompatible in the add operator.
  • iif 永远不会在 sql 2008 及其 sql 2012 及更高版本中工作
【解决方案3】:

下面是适用于 SQL Server 2005+ 的公式:DATEADD(HOUR, 6, CAST(CAST(DATEADD(HOUR, 18, A.LogTime) AS DATE) AS DATETIME))。我们简单地将 LogTime 加 18 小时,滥用 CAST 将时间片段截断,然后将这个值加 6 小时。

完整示例(基于原文):

DECLARE @i INT = 5
DECLARE @endDate DATETIME = '2015-03-20 06:00:00.000';
DECLARE @startDate DATETIME = DATEADD(d, -@i, @endDate);

SELECT *
FROM (
    SELECT A.Id
         , A.LogTime
         , FetchedOn = DATEADD(HOUR, 6, CAST(CAST(DATEADD(HOUR, 18, A.LogTime) AS DATE) AS DATETIME))
    FROM(VALUES
        (1, '2015-03-16 01:28:03.257')
      , (2, '2015-03-16 05:28:03.257')
      , (3, '2015-03-16 06:28:03.257')
      , (4, '2015-03-16 18:28:03.257')
      , (5, '2015-03-17 01:28:06.677')
      , (6, '2015-03-17 06:28:06.677')
      , (7, '2015-03-17 16:28:07.460')
      , (8, '2015-03-17 07:28:03.257')
      , (9, '2015-03-18 01:28:08.193')
      , (10, '2015-03-18 05:28:03.257')
      , (11, '2015-03-18 06:28:03.257')
      , (12, '2015-03-18 18:28:03.257')
      , (13, '2015-03-19 01:28:06.677')
      , (14, '2015-03-19 06:28:06.677')
      , (15, '2015-03-19 16:28:07.460')
      , (16, '2015-03-19 07:28:03.257')
      , (17, '2015-03-20 01:28:08.193')
      , (18, '2015-03-20 05:28:03.257')
      , (19, '2015-03-20 06:28:03.257')
      , (20, '2015-03-20 18:28:03.257')
    ) A (Id, LogTime)
    WHERE A.LogTime BETWEEN @startDate AND @endDate
) A
ORDER BY A.FetchedOn DESC, A.LogTime ASC;

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-04-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-12-17
    • 2019-05-30
    • 2011-11-29
    相关资源
    最近更新 更多