Відповіді:
Подано:
3-я п’ятниця місяця завжди припадатиме на 15–21 числа місяця
select thedate
from yourtable
where datename(weekday, thedate) = 'Friday'
and datepart(day, thedate)>=15 and datepart(day, thedate)<=21;
Ви також можете використовувати weekday
з datepart()
, але це читабельніше з ім'ям IMO. Порівняно, порівняння рядків очевидно буде повільнішим.
Для того, щоб отримати незалежну відповідь щодо мови / культури, вам потрібно врахувати різні назви буднів та початок тижня.
В Італії п'ятниця - "Венерді", а найвищим днем тижня є понеділок, а не неділя, як у США.
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;
Ще один спосіб, який використовує відповідь Філа в якості основи і піклується про різні умови:
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
Ви також можете просто використовувати "відому добру" дату, щоб бути безпечною перед мовними налаштуваннями. Наприклад, якщо ви шукаєте п’ятницю, перевірте календар і побачите, що 2 січня 2015 року було п’ятницею. Перше порівняння потім може бути записане як:
DATEPART(weekday,thedate) = DATEPART(weekday,'20150102') --Any Friday
Дивіться також Як отримати N-й тиждень місяця від Пітера Ларссона.
Я фактично написав статтю про цей тип розрахунку тут
В основному, ви можете використовувати наступний код, щоб знайти 3-ту п’ятницю кожного місяця в будь-якому діапазоні дат
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 розмістив вище.
На підставі поточної редакції оригінального питання ОП, я не міг зрозуміти, чому люди, де публікують відповіді, вони робили.
Переглянувши правки, я знайшов оригінальне запитання і опублікував його нижче ...
У мене є часовий ряд, починаючи з 1.1.1996 - 30.8.2014 в базі даних SQL, наприклад, з таблицею "db.dbo.datestable".
Мені потрібно визначити дати, які є "3-ї п’ятниці кожного місяця" для цього діапазону дат у SQL.
Я думаю, що я повинен використовувати комбінацію "DENSE_RANK ()" та "PARTITION BY ()", щоб встановити "rank = 3". Однак я новачок у SQL і не можу знайти правильний код.
Чи можете ви вирішити цю проблему?
Частина оригінального питання, яку я підкреслював жирним шрифтом, здається, є ключовою. Я, безумовно, можу бути невірним, але мені здається, що ОП заявив, що у нього є таблиця "Календар", яка називається "dbo.datestable", і, на мене, це робить велику різницю, і я тепер розумію, чому багато відповідей - це те, що вони включають таку, яка створила дати, оскільки вона була опублікована 10 листопада ... через день після остаточного редагування питання, що видалило остаточні залишки посилання на "dbo.datestable".
Як я вже говорив, я можу помилитися, але ось моє тлумачення оригінального питання.
У мене є таблиця "Календар" під назвою "dbo.datestable". З огляду на будь-який діапазон дат, охоплених цією таблицею, як я можу повернути лише дати, які є 3-ю п’ятницею кожного місяця в межах даного діапазону дат?
Оскільки загальноприйняті методи для цього вже охоплені, я додам альтернативу, яка може бути корисною для деяких.
Давайте змоделюємо пару стовпців, які, на мою думку, ОП вже матимуть у його таблиці. Звичайно, я нагадаю назви стовпців. Будь ласка, вкажіть будь-які еквівалентні стовпці для таблиці "Календар". Крім того, я все це роблю в 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)
;
Це також дані, які я не знаю, чи може ОП внести зміни до своєї таблиці «Календар», щоб це не допомогло йому, але може допомогти іншим. Зважаючи на це, додамо колонку DWoM (День тижня за місяць). Якщо ім'я вам не подобається, будь ласка, змініть його на все, що вам потрібно у власній коробці.
--===== Add the new column.
ALTER TABLE #datestable
ADD DWOM TINYINT NOT NULL DEFAULT (0)
;
Далі нам потрібно заповнити новий стовпець. ОП відчував це в своєму первісному непорушному пості.
--===== 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)
;
Після цього запити, які виконують такі дії, як повернення 3-ї п’ятниці кожного місяця в заданий діапазон дат, стають тривіальними і досить очевидними для читання.
--===== 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