Odpowiedzi:
Dany:
Trzeci piątek miesiąca zawsze przypada na 15–21 miesiąca
select thedate
from yourtable
where datename(weekday, thedate) = 'Friday'
and datepart(day, thedate)>=15 and datepart(day, thedate)<=21;
Możesz także używać weekday
z datepart()
, ale jest bardziej czytelna z nazwą IMO. Porównywanie ciągów będzie oczywiście wolniejsze.
Aby uzyskać odpowiedź niezależną od języka / kultury, musisz uwzględnić różne nazwy dni tygodnia i początek tygodnia.
We Włoszech piątek to „Venerdì”, a pierwszym dniem tygodnia jest poniedziałek, a nie niedziela jak w USA.
1900-01-01
był poniedziałek, więc możemy wykorzystać te informacje do obliczenia dnia tygodnia w sposób niezależny od regionu:
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;
Innym sposobem, który wykorzystuje odpowiedź Phila jako bazę i zajmuje się innymi ustawieniami:
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
Kod (jeśli chcesz tygodnia inny niż w piątek) powinny być (tak samo jak SET DATEFIRST
kody):
1 for Monday
2 for Tuesday
3 for Wednesday
4 for Thursday
5 for Friday
6 for Saturday
7 for Sunday
Możesz także użyć „znanej dobrej” daty, aby być bezpiecznym w obliczu ustawień językowych. Na przykład, jeśli szukasz piątków, sprawdź kalendarz i przekonaj się, że 2 stycznia 2015 r. Był piątek. Pierwsze porównanie można następnie zapisać jako:
DATEPART(weekday,thedate) = DATEPART(weekday,'20150102') --Any Friday
Zobacz także Jak uzyskać N-ty dzień tygodnia miesiąca autorstwa Petera Larssona.
I rzeczywiście napisał artykuł na temat tego typu obliczeń w tutaj
Zasadniczo możesz użyć następującego kodu, aby znaleźć trzeci piątek każdego miesiąca w dowolnym zakresie dat
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
Tak, wiem, że to stary post. Pomyślałem, że mimo wieku podążę inaczej. Heh ... i przepraszam. Właśnie zdałem sobie sprawę, że prawie zduplikowałem to, co @jyao zamieściło powyżej.
Na podstawie bieżącej edycji pierwotnego pytania PO nie mogłem zrozumieć, dlaczego ludzie zamieszczali odpowiedzi, które zrobili.
Przeglądając zmiany, znalazłem oryginalne pytanie i zamieściłem je poniżej ...
Mam szereg czasowy od 1.1.1996 - 30.8.2014 w bazie danych SQL, np. Z tabelą „db.dbo.datestable”.
Muszę ustalić daty, które są „trzecim piątkiem każdego miesiąca” dla tego zakresu dat w SQL.
Spodziewam się, że powinienem użyć kombinacji „DENSE_RANK ()” i „PARTITION BY ()”, aby ustawić „rank = 3”. Jednak jestem nowy w SQL i nie mogę znaleźć poprawnego kodu.
Czy potrafisz rozwiązać ten problem?
Część pierwotnego pytania, którą podkreśliłem pogrubieniem, wydaje się być kluczem. Z pewnością mogę się mylić, ale wydaje mi się, że OP stwierdził, że ma tabelę „Kalendarz” o nazwie „dbo.datestable”, co dla mnie stanowi ogromną różnicę i teraz rozumiem, dlaczego wiele odpowiedzi są tym, w tym tym, który wygenerował daty, ponieważ został opublikowany 10 listopada ... jeden dzień po ostatecznej edycji pytania, co usunęło ostatnie ślady odniesienia do „dbo.datestable”.
Tak jak powiedziałem, mogę się mylić, ale oto moja interpretacja pierwotnego pytania.
Mam tabelę „Kalendarz” o nazwie „dbo.datestable”. Biorąc pod uwagę dowolny zakres dat objętych tą tabelą, w jaki sposób mogę zwrócić tylko te daty, które są w trzeci piątek każdego miesiąca w tym zakresie dat?
Ponieważ konwencjonalne metody wykonania tego zostały już omówione, dodam alternatywę, która może być pomocna dla niektórych.
Symulujmy kilka kolumn, które - jak sądzę - OP będą już miały w jego tabeli. Oczywiście zgaduję nazwy kolumn. Podaj dowolne równoważne kolumny dla tabeli „Kalendarz”. Robię to wszystko w TempDB, więc nie ryzykuję ingerowania w czyjąś tabelę „Kalendarz”.
--=================================================================================================
-- 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)
;
Jest to również rzecz oczywista, że nie wiem, czy OP może wprowadzić zmiany w swojej tabeli „Kalendarz”, więc to może mu nie pomóc, ale może pomóc innym. Mając to na uwadze, dodajmy kolumnę DWoM (Dzień tygodnia dla miesiąca). Jeśli nie podoba ci się nazwa, możesz ją zmienić na dowolną, potrzebną na własnym pudełku.
--===== Add the new column.
ALTER TABLE #datestable
ADD DWOM TINYINT NOT NULL DEFAULT (0)
;
Następnie musimy wypełnić nową kolumnę. OP miał to w swoim oryginalnym nieskażonym poście.
--===== 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#
;
Teraz, ponieważ jest to kolumna o stałej długości, która właśnie utworzyła kilka podziałów stron, dlatego musimy odbudować Indeks klastrowany, aby „przepakować” tabelę, aby mieć jak najwięcej wierszy na stronę ze względu na wydajność.
--===== "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)
;
Po wykonaniu tej czynności zapytania, takie jak zwracanie trzeciego piątku każdego miesiąca w danym zakresie dat, stają się trywialne i dość oczywiste do przeczytania.
--===== 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
;
Oto proste rozwiązanie do wycinania i wklejania. Możesz to zmienić w funkcję, jeśli chcesz.
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