SELECT在日期之间查询,仅在开始和结束字段之间选择项目

时间:2014-01-31 03:09:41

标签: sql sql-server date

我有两个表格,我将用于跟踪目的,日期表项目表日期表用于跟踪被跟踪ID的开始和结束日期。 项目表是在ID的特定日期提取的项目数量。 id是这两个表之间的外键。

我想要做的是具有项目ID的GROUP BY的项目的总和,但仅基于拉出项目的日期是否落在被跟踪的start_date和end_date之间来对项目求和标识。

日期表

id start_date  end_date
1   2014-01-01  NULL
2   2014-01-01  2014-01-02
3   2014-01-25  NULL

项目表

id  items   date
1   3   2014-01-01
1   5   2014-01-02
1   5   2014-01-26
2   2   2014-01-01
2   3   2014-01-05
2   2   2014-01-26
3   2   2014-01-01
3   3   2014-01-05
3   2   2014-01-26

我到目前为止已经使用SQL了,但是我从这里添加了什么内容,我很遗憾。

SELECT 
    a.id, 
    SUM(items) 
FROM 
    ww_test.dbo.items a
INNER JOIN ww_test.dbo.dates b ON
    a.id = b.id
WHERE
    a.date >= '2014-01-01' AND a.date <= '2014-01-30'
GROUP BY 
    a.id
ORDER BY 
    a.id

输出应为:

id  items
1   13
2   2
3   2

而不是:

id  items
1   13
2   7
3   7

3 个答案:

答案 0 :(得分:1)

首先,我强烈建议您停止在日期范围内使用NULL来表示“无结束日期”,而是使用标记值,例如9999-12-31 。其原因主要是性能,其次是查询简单性 - 现在在编写查询时以及后来需要维护查询的人或其他人都会受益。在前端或中间代码中,将日期范围与Null9999-12-31进行比较几乎没有区别,事实上,您可以获得与简化代码相同的一些好处。在你的SQL中。我的建议基于10年以上的全职专业SQL查询编写经验。

要按原样修复您的查询,我认为这样可行:

SELECT 
   a.id, 
   ItemsSum = SUM(items)
FROM 
   ww_test.dbo.items a
   INNER JOIN ww_test.dbo.dates b
      ON a.id = b.id
      AND a.date >= Coalesce(b.start_date, 0)
      AND a.date <= Coalesce(b.end_date, '99991231')
WHERE
   a.date >= '20140101'
   AND a.date <= '20140130'
GROUP BY 
   a.id
ORDER BY 
   a.id
;

请注意,如果您遵循我的建议,那么您的查询JOIN条件可能如下所示:

INNER JOIN ww_test.dbo.dates b
   ON a.id = b.id
   AND a.date >= b.start_date
   AND a.date <= b.end_date

您会发现,如果您的数据集变得很大,那么必须在其中放置CoalesceIsNull会严重影响性能。使用OR子句也无济于事:

INNER JOIN ww_test.dbo.dates b
   ON a.id = b.id
   AND (a.date >= b.start_date OR b.start_date IS NULL)
   AND (a.date <= b.end_date OR b.end_date IS NULL)

这会产生同样的问题(例如,当有合适的索引时,可能会将搜索转换为扫描,这会非常令人难过)。

最后,我还建议您将结束日期更改为独占而不是包含。这意味着对于结束日期,您不是输入最后一天开始日期的信息,而是将第一天的日期设为不再为真。这个建议有几个原因:

  • 如果您的日期解决方案变为小时,分钟或秒,那么您编写的处理此数据的每一段代码都必须更改(如果您使用独占结束日期,则不会更改。)
  • 如果您必须相互比较日期范围(将日期范围折叠在一起或找到连续范围或甚至找到不连续的范围),您现在必须在a.end_date + 1 = b.start_date上进行所有比较而不是a.end_date = b.start_date的简单等值连接。这很痛苦,容易犯错误。
  • 始终将日期视为建议时间对于您在任何语言中的编码能力都非常有益。人们忘记日期时会发生许多错误,即使是那些不能表示时间部分的格式(例如SQL 2008及以上的date数据类型)仍然具有隐含的时间部分,可以直接转换为具有时间部分的日期数据类型,该时间部分始终为012 a.m.

唯一的缺点是,在某些情况下,您必须仔细考虑您向用户显示的日期(转换为包含日期),然后将他们输入的日期转换为存储到数据库的独占日期。但这仅限于UI处理代码,而不是整个数据库,所以它不是一个很大的缺点。

您查询的唯一更改是:

INNER JOIN ww_test.dbo.dates b
   ON a.id = b.id
   AND a.date >= b.start_date
   AND a.date < b.end_date -- no equal sign now

最后一件事:请注意日期格式'yyyy-mm-dd'不是文化安全的。

SET LANGUAGE FRENCH;
SELECT Convert(datetime, '2014-01-30'); -- fails with an error

SQL Server中日期时间唯一不变的文化安全格式是:

yyyymmdd
yyyy-mm-ddThh:mm:ss

答案 1 :(得分:0)

我认为您要做的是比较start_date表的end_dateData之间的日期。

将您的查询更改为以下内容并尝试

SELECT 
    a.id, 
    SUM(items) 
FROM 
    ww_test.dbo.items a
    INNER JOIN ww_test.dbo.dates b ON a.id = b.id
WHERE
    a.date >= ISNULL(b.start_date, GETDATE()) 
    AND a.date <= ISNULL(b.end_date, GETDATE())
GROUP BY a.id
ORDER BY a.id

答案 2 :(得分:0)

查询的问题是条件部分。 此外,由于您需要根据Dates表中定义的条件检索数据,因此您不必显式硬编码条件。 假设您的结束日期可以为空或具有值,您可以使用以下内容 查询:

SELECT 
    a.id, 
    SUM(items) 
FROM 
    ww_test.dbo.items a
INNER JOIN ww_test.dbo.dates b ON
    a.id = b.id
where (b.end_date is not null and a.date between b.start_date and b.end_date)
or (b.end_date is null and a.date >= b.start_date)
GROUP BY 
    a.id
ORDER BY 
    a.id