这将符合您的规范。它不关心您在哪一天运行它,并且如果 EndDate 不在确切的边界上,它会将其调整为当前日期。我无法与您给定的代码完全比较,因为它在某些日期(例如20120201
)有错误。
DECLARE
@StartDate datetime,
@EndDate datetime;
SELECT
@StartDate = Convert(date, M.Anchor + X.S),
@EndDate = Convert(date,
M.Anchor + Day(GetDate() - 1)
- CASE WHEN Day(GetDate()) IN (1, 16) THEN 1 ELSE 0 END
)
FROM
(VALUES (1, 15, 0), (16, 31, 15)) X (F, T, S)
CROSS APPLY (
SELECT DateAdd(month, DateDiff(month, 0, GetDate() - 1), 0)
) M (Anchor)
WHERE Day(GetDate() - 1) BETWEEN X.F AND X.T;
我针对一年的日期测试了这段代码,我相信一切正常。
通过在这个 SqlFiddle中检查一年的计算结果,自己验证它的正确性。
基本上:从昨天的日期计算每月的第一天。使用该日期,确定从月初的偏移量以计算开始日期(如果 1 日 - 15 日,使用 0;如果 16 日 - 31 日,使用 15)。最后,从月初 + 计算的日期计算结束日期,对 1 日和 16 日进行特殊调整以使用前一天的结束期间。
实际上,始终指定到周期结束(1 - 16 或 1 - 28/29/30/31)而不是一直持续到这一天并没有什么害处,但这不是你所要求的。
回答您关于查询中发生的事情的问题。
派生表是行集返回查询,通常以 a 开头,SELECT
括在括号中并给出别名。例如:
SELECT X.FullName
FROM
(
SELECT FullName = FirstName + ' ' + LastName
FROM dbo.User
WHERE ID = 123
) AS X
在这里,我们派生X
了包含其中查询结果的表。我喜欢跳过这一AS
部分,因为对我来说它只会增加混乱。请注意,这只是一个简单的示例——派生表通常比这更复杂,并且在各种情况下都很有用。我在dbo.User
桌子上没有使用别名,但最好这样做。但重要的是要注意派生表中的所有列引用都是从派生表中解析的。不使用外面的桌子。
VALUES
是一种从查询中提供的值创建行集的方法。您可能熟悉的正常语法是INSERT dbo.Table VALUES (1, 'a');
. 在 SQL Server 2008 中,这被扩展为同时允许多行,如INSERT dbo.Table VALUES (1, 'a'), (2, 'b');
. 我还允许这种特殊的多行表示法代替SELECT
派生表中的查询。例如:
SELECT X.*
FROM (VALUES (1, 'a'), (2, 'b')) X
-- Alias X is for a derived table of 2 rows & 2 unnamed columns
派生表的列名通常是从查询中发现的。看到在#1 中,X.FullName
实际上是指包含来自 table 的多个列的表达式dbo.User
。表达式在查询中被显式地赋予了一个新别名。如果您没有提供别名,则会收到错误消息,因为表达式没有内在名称。事实上,前一点的查询不会按原样运行,因为派生表X
没有列名!但是,有一种语法可以在派生表之外提供显式列别名:
SELECT X.ID, X.Name
FROM (VALUES (1, 'a'), (2, 'b')) X (ID, Name)
我更喜欢第二种语法,因为它确实有助于明确其意图,它有助于将表达式与其名称分开,而且通常对我来说,在派生表中列别名更好。很多时候,我从其他地方复制和粘贴派生表中的内容,而且不必每次都拼凑列名是很好的。别名F
,T
和S
是给我From
的To
, 和Start
. 我本可以让它们成为更长的名字,但我选择不这样做。
使用SELECT Alias = <Expression>
只是SELECT <Expression> AS Alias
. 我更喜欢这种=
方法,因为这样别名可靠地位于一个易于扫描的列的左侧,而不是可变长度表达式的末尾。由于能够通过添加一个字符来更改变量名称,或者UPDATE
轻松地将查询转换为语句,因此这种语法也有好处。
您可以更改任何查询以将值分配给变量,而不是仅通过@Variable =
在每个列表达式前面添加来返回行集。需要注意的一个问题是,如果查询返回多行,虽然您仍然可以使用该@Variable =
语法,但服务器仍将完成所有实现所有行的工作,并且您的变量将仅具有值) 从一些,一个,行。它可能是第一行,也可能是最后一行,但即使您认为返回的行一致,您也应该始终假设它是随机行。如果您需要特定行,则提供 WHERE 子句或带有 的TOP
语句ORDER BY
以强制使用特定行的值。
CROSS APPLY
只是一个派生表,它具有允许具有“外部引用”的特殊属性,也就是说,它可以在括号内使用之前引入的表中的列值。它在没有返回行时限制行(就像INNER JOIN
ed 派生表一样),或者OUTER APPLY
不限制行(就像OUTER JOIN
ed 派生表一样)。它不需要ON
子句,因为所有过滤都是通过WHERE
子句完成的。优化器非常善于理解查询的意图,并且最终不会APPLY
为每个外部行运行一次——它几乎总是能够智能地获取数据,就好像它只是一个常规连接一样。我在这里使用它作为一种廉价的方法来获取我可以多次使用的查询中的计算值。我也可以做到DECLARE @MonthDate = <same expression>
并改用它,但我的某些部分喜欢在我不需要时不声明变量(因为 @MonthDate 变量仅用于单个查询)。
PS我想指出我在您的示例代码中看到的一件事(如果您允许的话)。考虑这一部分:
IF Month(@RunDate) = 1
SET @Month = 12
ELSE
SET @Month = Month(@RunDate) - 1
IF Month(@RunDate) = 1
SET @Year = Year(@RunDate) - 1
ELSE
SET @Year = Year(@RunDate)
所有这些排列都非常适合尝试传达 IF / ELSE 块的意图,但在我的专业意见中,您应该使用BEGIN
and END
。编写此代码的人选择以这种方式布置条件的一个原因是缩进样式(对我而言)有点过分。每个BEGIN
最终都有 2 个缩进深度,一个用于IF
(将其视为一个缩进块),一个用于BEGIN .. END
. 但是这种对没有块的单语句 IF 的附加会导致几个问题。
上面的表达式没有正确传达意图,并迫使审阅者评估为什么某事被做了两次,实际上这两个条件是相同的。对于初学者,该块应重写为:
IF Month(@RunDate) = 1
BEGIN
SET @Month = 12;
SET @Year = Year(@RunDate) - 1;
END
ELSE
BEGIN
SET @Month = Month(@RunDate) - 1;
SET @Year = Year(@RunDate);
END;
现在可以明智地理解和审查这一点。
一旦您开始添加多个条件或嵌套级别,您就会陷入大麻烦并发现几乎无法调试。如果某个有进取心的开发人员认识到这两个条件是相同的,并且将它们的内容结合起来最有意义,那么会编写以下代码:
IF Month(@RunDate) = 1
SET @Month = 12;
SET @Year = Year(@RunDate) - 1;
ELSE
SET @Month = Month(@RunDate) - 1;
SET @Year = Year(@RunDate);
这看起来不错,但隐藏了第二个SET
语句不属于IF
它之前的事实。现在,它不会正确编译,因为它ELSE
是孤立的。但如果改变是这样的:
IF Month(@RunDate) = 1
SET @Month = 12;
IF @SpecialFlag = 1
SET @Year = Year(@RunDate) - 1;
ELSE
SET @Month = Month(@RunDate) - 1;
IF @SpecialFlag = 1
SET @Year = Year(@RunDate);
现在我们有一个可怕的混乱!这将正确解析,但与开发人员预期的结果相去甚远:ELSE
块是@SpecialFlag
条件的一部分!由于缩进,它在代码中看起来肯定不像它。
因此,尽管我知道代码格式约定可能会因坚定的信念而受到青睐,并且组织和人员可能会极大地抵制更改,但我想建议您将获得一些好处,如果 1)您在所有块中使用BEGIN
和,并且2)为了减轻由于双缩进所有内容的麻烦而引起的斗争,您重新制定您的块缩进练习,如下所示:END
IF
这将是:
IF Month(@RunDate) = 1 BEGIN
SET @Month = 12;
SET @Year = Year(@RunDate) - 1;
END
ELSE BEGIN
SET @Month = Month(@RunDate) - 1;
SET @Year = Year(@RunDate);
END;
我认识到这对于习惯于不同缩进风格的人来说看起来很奇怪——所有的改变一开始都很难。但我相信,只要稍加练习,它就会在你身上生长,变得不那么痛苦。只是训练眼睛去配合END
其他东西而已BEGIN
。最终,您意识到您甚至都不在乎寻找,因为它不会为以orBEGIN
开头的行添加任何额外的含义。最后,您不必做疯狂的、劳动密集型的缩进模式(包括在后面放置多个空格并让所有内容对齐),因为缩进很有效,而且您只需为每个块做一个级别而不是许多。而且你永远不会有代码脱离IF
ELSE
IF
ELSE
IF
如上所示意外阻塞。
100% 明确:使用这种样式,如果BEGIN
/END
块中只有一个语句,则无需重新排列任何内容以添加第二个语句,并且代码不会中断。
最后,请注意我在自己的示例中添加了分号,原因很简单,在 SQL Server 中它们有一天会被需要,我希望我的所有生产代码都可以继续工作,而不需要一个巨大而痛苦的分号项目!这也带来了明确指示您的块何时停止的好处(坦率地说,我不知道在END
前面的立即数之后是否需要分号 - 如果是ELSE
这样,我至少会有一个小分号项目而不是巨大的)。