매월 셋째 금요일 결정


16

SQL Server에서 "1.1.1996-30.8.2014"의 날짜 범위에 대해 "매월 세 번째 금요일"인 날짜를 결정해야합니다.

나는의 조합을 사용해야합니다 기대 DENSE_RANK()하고 PARTITION BY()세트 "순위 = 3"에 있습니다. 그러나 SQL을 처음 사용하여 올바른 코드를 찾을 수 없습니다.

답변:


26

주어진:

  • 금요일은 "금요일"이라고합니다
  • 매월 셋째 금요일은 항상 매월 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

언어 / 문화 독립적 인 답변을 얻으려면 다른 요일 이름과 요일을 고려해야합니다.

이탈리아에서는 금요일이 "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;

12

또 다른 방법으로 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 번째 요일을 얻는 방법 도 참조하십시오 .


4

실제로 여기 에 이런 유형의 계산에 관한 기사를 썼습니다

기본적으로 다음 코드를 사용하여 모든 날짜 범위에서 매월 셋째 금요일을 찾을 수 있습니다.

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가 위에 게시 한 것을 거의 복제한다는 것을 깨달았습니다.

OP의 원래 질문에 대한 현재 편집 내용을 바탕으로 사람들이 왜 답변을 게시했는지 알 수 없었습니다.

편집 내용을 살펴보면서 원래 질문을 찾아 아래에 게시했습니다 ...

테이블 "db.dbo.datestable"과 같은 SQL 데이터베이스에서 1.1.1996-30.8.2014 범위의 시계열이 있습니다.

SQL에서이 날짜 범위의 "매월 세 번째 금요일"인 날짜를 결정해야합니다.

"rank = 3"을 설정하려면 "DENSE_RANK ()"와 "PARTITION BY ()"의 조합을 사용해야합니다. 그러나 SQL을 처음 사용하여 올바른 코드를 찾을 수 없습니다.

이 문제를 해결할 수 있습니까?

굵게 표시하여 강조한 원래 질문의 일부가 핵심 인 것 같습니다. 나는 확실히 틀릴 수는 있지만 OP가 "dbo.datestable"이라는 "Calendar"테이블을 가지고 있다고 말하고 나에게 큰 차이가 나는 이유는 무엇인지 이해합니다. 날짜가 11 월 10 일에 게시 되었기 때문에 날짜를 생성 한 날짜를 포함하고 있습니다. 질문에 대한 최종 편집 후 하루는 "dbo.datestable"에 대한 참조의 최종 흔적을 제거했습니다.

내가 말했듯이, 나는 틀릴 수 있지만 여기에 원래 질문에 대한 나의 해석이 있습니다.

"dbo.datestable"이라는 "Calendar"테이블이 있습니다. 해당 표에서 다루는 날짜 범위가 주어진 경우 해당 날짜 범위 내에서 매월 셋째 금요일 날짜 만 반환하려면 어떻게해야합니까?

이 작업을 수행하는 일반적인 방법은 이미 다루었으므로 일부 도움이 될 수있는 대안을 추가하겠습니다.

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 (Day of Week of the Month) 열을 추가하겠습니다. 이름이 마음에 들지 않으면 자유롭게 상자에 원하는 이름으로 변경하십시오.

--===== 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
;

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
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.