Визначте 3-ту п’ятницю кожного місяця


16

Мені потрібно визначити дати, які є "3-ї п'ятницею кожного місяця" для діапазону дат "1.1.1996 - 30.8.2014" у SQL Server.

Я очікую, що я повинен використовувати комбінацію DENSE_RANK()та PARTITION BY()встановити "rank = 3". Однак я новачок у SQL і не можу знайти правильний код.

Відповіді:


26

Подано:

  • П'ятниця називається "П'ятниця"
  • 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. Порівняно, порівняння рядків очевидно буде повільнішим.


14

Для того, щоб отримати незалежну відповідь щодо мови / культури, вам потрібно врахувати різні назви буднів та початок тижня.

В Італії п'ятниця - "Венерді", а найвищим днем ​​тижня є понеділок, а не неділя, як у США.

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;

12

Ще один спосіб, який використовує відповідь Філа в якості основи і піклується про різні умови:

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-й тиждень місяця від Пітера Ларссона.


4

Я фактично написав статтю про цей тип розрахунку тут

В основному, ви можете використовувати наступний код, щоб знайти 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

2

Так, я знаю, що це стара публікація. Думав, що я б подав інший нахил на речі, незважаючи на свій вік. Хе ... і мої вибачення. Я щойно зрозумів, що я майже повторював те, що @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
;

0

Ось простий виріз та наклеювання розчину. Ви можете перетворити це на функцію, якщо хочете.

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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.