Answers:
为了获得与语言/文化无关的答案,您需要考虑不同的工作日名称和一周的开始时间。
在意大利,星期五是“Venerdì”,一周的第一天是星期一,而不是美国的星期日。
1900-01-01
是星期一,因此我们可以使用此信息以与语言环境无关的方式计算工作日:
WITH dates AS (
SELECT DATEADD(day, number, GETDATE()) AS theDate
FROM master.dbo.spt_values
WHERE type = 'P'
)
SELECT theDate, DATENAME(dw, theDate), DATEPART(dw, theDate)
FROM dates
WHERE DATEDIFF(day, '19000101', theDate) % 7 = 4
AND DATEPART(day, thedate)>=15 and DATEPART(day, thedate)<=21;
另一种方法是,以Phil的答案为基础,并照顾不同的环境:
select thedate
from yourtable
where (datepart(weekday, thedate) + @@DATEFIRST - 2) % 7 + 1 = 5 -- 5 -> Friday
and (datepart(day, thedate) - 1) / 7 + 1 = 3 ; -- 3 -> 3rd week
该5
代码(如果你想星期五以外工作日)应该是(为同一SET DATEFIRST
代码):
1 for Monday
2 for Tuesday
3 for Wednesday
4 for Thursday
5 for Friday
6 for Saturday
7 for Sunday
面对语言设置,您也可以只使用“已知的好”日期来保证安全。例如,如果要查找星期五,请检查日历,看看2015年1月2日是星期五。那么第一个比较可以写成:
DATEPART(weekday,thedate) = DATEPART(weekday,'20150102') --Any Friday
另请参阅Peter Larsson 如何获得每月的第N个工作日。
我实际上在这里写了一篇有关这种计算的文章
基本上,您可以使用以下代码在任何日期范围内找到每月的第三个星期五
USE TEMPDB
set nocount on;
IF OBJECT_ID('dbo.#t') is not null
DROP TABLE dbo.#t;
CREATE TABLE #t ([Date] datetime,
[Year] smallint, [Quarter] tinyint, [Month] tinyint
, [Day] smallint -- from 1 to 366 = 1st to 366th day in a year
, [Week] tinyint -- from 1 to 54 = the 1st to 54th week in a year;
, [Monthly_week] tinyint -- 1/2/3/4/5=1st/2nd/3rd/4th/5th week in a month
, [Week_day] tinyint -- 1=Mon, 2=Tue, 3=Wed, 4=Thu, 5=Fri, 6=Sat, 7=Sun
);
GO
USE TEMPDB
-- populate the table #t, and the day of week is defined as
-- 1=Mon, 2=Tue, 3=Wed, 4=Thu,5=Fri, 6=Sat, 7=Sun
;WITH C0 AS (SELECT c FROM (VALUES(1),(1)) AS D(c)),
C1 AS (SELECT 1 AS c FROM C0 AS A CROSS JOIN C0 AS B),
C2 AS (SELECT 1 AS c FROM C1 AS A CROSS JOIN C1 AS B),
C3 AS (SELECT 1 AS c FROM C2 AS A CROSS JOIN C2 AS B),
C4 AS (SELECT 1 AS c FROM C3 AS A CROSS JOIN C3 AS B),
C5 AS (SELECT 1 AS c FROM C4 AS A CROSS JOIN C3 AS B),
C6 AS (select rn=row_number() over (order by c) from C5),
C7 as (select [date]=dateadd(day, rn-1, '19000101') FROM C6 WHERE rn <= datediff(day, '19000101', '99991231')+1)
INSERT INTO #t ([year], [quarter], [month], [week], [day], [monthly_week], [week_day], [date])
SELECT datepart(yy, [DATE]), datepart(qq, [date]), datepart(mm, [date]), datepart(wk, [date])
, datediff(day, dateadd(year, datediff(year, 0, [date]), 0), [date])+1
, datepart(week, [date]) -datepart(week, dateadd(month, datediff(month, 0, [date]) , 0))+1
, CASE WHEN datepart(dw, [date])+@@datefirst-1 > 7 THEN (datepart(dw, [date])+@@datefirst-1)%7
ELSE datepart(dw, [date])+@@datefirst-1 END
, [date]
FROM C7
--where [date] between '19900101' and '20990101'; -- if you want to populate a range of dates
GO
select convert(char(10), [Date], 120)
from #t
where Monthly_week=3
and week_day=5
and [date] between '2015-01-01' and '2015-12-31' -- change to your own date range
是的,我知道这是旧帖子。认为尽管年代久远,我还是会有所不同。嘿...和我的道歉。我只是意识到我几乎复制了上面@jyao的内容。
根据OP原始问题的当前编辑,我无法弄清楚为什么人们在这里发布他们的答案。
通过编辑,我发现了原始问题并将其发布在下面...
我在一个SQL数据库(例如,具有表“ db.dbo.datestable”)中的时间序列范围为1.1.1996-30.8.2014 。
我需要确定此日期范围在SQL中的日期是“每月的第三个星期五”。
我希望我应该结合使用“ DENSE_RANK()”和“ PARTITION BY()”来设置“ rank = 3”。但是,我是SQL的新手,无法找到正确的代码。
你能解决这个问题吗?
我用粗体强调的原始问题部分似乎是关键。我肯定是不正确的,但是在我看来,OP表示他有一个名为“ dbo.datestable”的“ Calendar”表,对我来说,这造成了很大的不同,我现在理解为什么很多答案是因为它们包含生成日期的日期,因为日期是11月10日发布的……在对该问题进行最终编辑后的第二天,该日期删除了对“ dbo.datestable”的引用的最终痕迹。
就像我说的那样,我可能是错的,但这是我对原始问题的解释。
我有一个名为“ dbo.datestable”的“日历”表。给定该表涵盖的日期范围,我如何只返回给定日期范围内每个月的第三个星期五的日期?
由于已经涵盖了执行此操作的常规方法,因此我将添加一个可能对某些人有用的替代方法。
让我们模拟一下我认为OP将在他的表中包含的几列。当然,我在猜测列名。请为您的“日历”表中的列替换所有等同项。另外,我正在TempDB中完成所有这些工作,因此我没有机会干扰某人的真实“日历”表。
--=================================================================================================
-- Simulate just a couple of the necessary columns of the OPs "Calendar" table.
-- This is not a part of the solution. We're just trying to simulate what the OP has.
--=================================================================================================
--===== Variables to control the dates that will appear in the "Calendar" table.
DECLARE @StartDT DATETIME
,@EndDT DATETIME
;
SELECT @StartDT = '1900' --Will be inclusive start of this year in calculations.
,@EndDT = '2100' --Will be exclusive start of this year in calculations.
;
--===== Create the "Calendar" table with just enough columns to simulate the OP's.
CREATE TABLE #datestable
(
TheDate DATETIME NOT NULL
,DW TINYINT NOT NULL --SQL standard abbreviate of "Day of Week"
)
;
--===== Populate the "Calendar" table (uses "Minimal Logging" in 2008+ this case).
WITH cteGenDates AS
(
SELECT TOP (DATEDIFF(dd,@StartDT,@EndDT)) --You can use "DAY" instead of "dd" if you prefer. I don't like it, though.
TheDate = DATEADD(dd, ROW_NUMBER() OVER (ORDER BY (SELECT NULL))-1, @StartDT)
FROM sys.all_columns ac1
CROSS JOIN sys.all_columns ac2
)
INSERT INTO #datestable WITH (TABLOCK)
SELECT TheDate
,DW = DATEDIFF(dd,0,TheDate)%7+1 --Monday is 1, Friday is 5, Sunday is 7 etc.
FROM cteGenDates
OPTION (RECOMPILE) -- Help keep "Minimal Logging" in the presence of variables.
;
--===== Add the expected named PK for this example.
ALTER TABLE #datestable
ADD CONSTRAINT PK_datestable PRIMARY KEY CLUSTERED (TheDate)
;
鉴于我不知道OP是否可以更改他的“日历”表,因此这可能对他没有帮助,但可能对其他人有帮助。考虑到这一点,让我们添加一个DWoM(每月的星期几)列。如果您不喜欢该名称,请随时在自己的盒子上将其更改为所需的名称。
--===== Add the new column.
ALTER TABLE #datestable
ADD DWOM TINYINT NOT NULL DEFAULT (0)
;
接下来,我们需要填充新列。OP在他最初的纯真职位上对此有所了解。
--===== Populate the new column using the CTE trick for updates so that
-- we can use a Windowing Function in an UPDATE.
WITH cteGenDWOM AS
(
SELECT DW# = ROW_NUMBER() OVER (PARTITION BY DATEDIFF(mm,0,TheDate), DW
ORDER BY TheDate)
,DWOM
FROM #datestable
)
UPDATE cteGenDWOM
SET DWOM = DW#
;
现在,因为这是一个固定长度的列,所以刚刚创建了一堆页面拆分,因此为了性能起见,我们需要重建聚簇索引以“重新打包”表,以便每页具有尽可能多的行。
--===== "Repack" the Clustered Index to get rid of the page splits we
-- caused by adding the new column.
ALTER INDEX PK_datestable
ON #datestable
REBUILD WITH (FILLFACTOR = 100, SORT_IN_TEMPDB = ON)
;
完成此操作后,执行诸如在给定日期范围内返回每月的第三个星期五之类的查询变得很简单,并且很容易阅读。
--===== Return the 3rd Friday of every month included in the given date range.
SELECT *
FROM #datestable
WHERE TheDate >= '1996-01-01' --I never use "BETWEEN" for dates out of habit for end date offsets.
AND TheDate <= '2014-08-30'
AND DW = 5 --Friday
AND DWOM = 3 --The 3rd one for every month
ORDER BY TheDate
;
这是一个简单的剪切和粘贴解决方案。您可以根据需要将其转换为功能。
Declare @CurrDate Date
Set @CurrDate = '11-20-2016'
declare @first datetime -- First of the month of interest (no time part)
declare @nth tinyint -- Which of them - 1st, 2nd, etc.
declare @dow tinyint -- Day of week we want
set @first = DATEFROMPARTS(YEAR(@CurrDate), MONTH(@CurrDate), 1)
set @nth = 3
set @dow = 6
declare @result datetime
set @result = @first + 7*(@nth-1)
select @result + (7 + @dow - datepart(weekday,@result))%7