Tentukan hari Jumat ketiga setiap bulan

16

Saya perlu menentukan tanggal yang merupakan "Jumat ke-3 setiap bulan" untuk rentang tanggal "1.1.1996 - 30.8.2014" di SQL Server.

Saya berharap saya harus menggunakan kombinasi DENSE_RANK()dan PARTITION BY()untuk menetapkan "peringkat = 3". Namun, saya baru mengenal SQL dan tidak dapat menemukan kode yang benar.

emcor
sumber

Jawaban:

26

Diberikan:

  • Jumat disebut "Jumat"
  • Jumat ke-3 setiap bulan akan selalu jatuh dari tanggal 15-21 setiap bulan

    select thedate
    from yourtable
    where datename(weekday, thedate) = 'Friday'
    and datepart(day, thedate)>=15 and datepart(day, thedate)<=21;

Anda juga bisa menggunakannya weekdaydengan datepart(), tetapi lebih mudah dibaca dengan nama IMO. Perbandingan string jelas akan lebih lambat.

Philᵀᴹ
sumber
14

Untuk mendapatkan jawaban independen bahasa / budaya, Anda perlu memperhitungkan berbagai nama hari kerja dan awal minggu.

Di Italia, hari Jumat adalah "Venerdì" dan hari pertama minggu itu adalah hari Senin, bukan hari Minggu seperti di AS.

1900-01-01 adalah hari Senin, jadi kami dapat menggunakan informasi ini untuk menghitung hari kerja secara independen-lokal:

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;
spaghettidba
sumber
12

Cara lain, yang menggunakan jawaban Phil sebagai basis, dan menangani pengaturan yang berbeda:

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

The 5kode (jika Anda ingin hari kerja selain Jumat) harus (sama dengan SET DATEFIRSTkode):

1 for Monday
2 for Tuesday
3 for Wednesday
4 for Thursday
5 for Friday
6 for Saturday
7 for Sunday

Anda juga bisa menggunakan tanggal "dikenal baik" agar aman dalam menghadapi pengaturan bahasa. Misalnya, jika mencari hari Jumat, periksa kalender dan lihat bahwa 2 Januari 2015 adalah hari Jumat. Perbandingan pertama kemudian dapat ditulis sebagai:

DATEPART(weekday,thedate) = DATEPART(weekday,'20150102') --Any Friday

Lihat juga Cara mendapatkan hari kerja kesembilan dalam sebulan oleh Peter Larsson.

ypercubeᵀᴹ
sumber
4

Saya sebenarnya menulis artikel tentang jenis perhitungan ini di sini

Pada dasarnya, Anda dapat menggunakan kode berikut untuk menemukan hari Jumat ketiga setiap bulan dalam rentang tanggal apa pun

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
sumber
2

Ya, saya tahu ini posting lama. Kupikir aku akan memberikan pandangan yang berbeda pada hal-hal meskipun usianya. Heh ... dan permintaan maaf saya. Saya baru sadar bahwa saya hampir menduplikasi apa yang diposting @ jyao di atas.

Berdasarkan hasil edit dari pertanyaan awal OP, saya tidak tahu mengapa orang-orang memposting jawaban yang mereka lakukan.

Melihat melalui pengeditan, saya menemukan pertanyaan asli dan mempostingnya di bawah ...

Saya memiliki serangkaian waktu mulai dari 1.1.1996 - 30.8.2014 dalam database SQL misalnya dengan tabel "db.dbo.datestable".

Saya perlu menentukan tanggal yang merupakan "Jumat ke-3 setiap bulan" untuk rentang tanggal ini dalam SQL.

Saya berharap saya harus menggunakan kombinasi "DENSE_RANK ()" dan "PARTITION BY ()" untuk menetapkan "peringkat = 3". Namun, saya baru mengenal SQL dan tidak dapat menemukan kode yang benar.

Bisakah Anda mengatasi masalah ini?

Bagian dari pertanyaan awal yang saya tekankan dengan huruf tebal tampaknya menjadi kuncinya. Saya tentu saja bisa salah, tetapi bagi saya tampaknya OP menyatakan bahwa dia memiliki tabel "Kalender" yang disebut "dbo.datestable" dan, bagi saya, itu membuat perbedaan yang sangat besar dan saya sekarang mengerti mengapa banyak jawaban adalah apa yang mereka termasuk yang menghasilkan tanggal karena itu diposting pada 10 November ... satu hari setelah suntingan terakhir pada pertanyaan, yang menghapus sisa-sisa akhir dari referensi ke "dbo.datestable".

Seperti yang saya katakan, saya bisa saja salah tetapi inilah interpretasi saya terhadap pertanyaan awal.

Saya memiliki tabel "Kalender" yang disebut "dbo.datestable". Dengan rentang tanggal yang dicakup oleh tabel itu, bagaimana saya bisa mengembalikan tanggal yang merupakan hari Jumat ketiga setiap bulan dalam rentang tanggal yang diberikan?

Karena metode konvensional untuk melakukan ini sudah dibahas, saya akan menambahkan alternatif yang bisa membantu sebagian orang.

Mari kita mensimulasikan beberapa kolom yang saya pikir OP akan sudah ada di mejanya. Tentu saja, saya menebak nama kolomnya. Silakan sub apa pun kolom setara untuk tabel "Kalender" Anda. Juga, saya melakukan ini semua di TempDB jadi saya tidak mengambil kesempatan mengganggu meja "Kalender" seseorang yang sebenarnya.

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

Ini juga mengingat bahwa saya tidak tahu apakah OP dapat membuat perubahan pada tabel "Kalender" -nya sehingga ini mungkin tidak membantunya tetapi mungkin membantu orang lain. Dengan mengingat hal itu, mari kita tambahkan kolom DWoM (Day of Week for the Month). Jika Anda tidak suka namanya, silakan mengubahnya ke apa pun yang Anda butuhkan di kotak Anda sendiri.

--===== Add the new column.
  ALTER TABLE #datestable
    ADD DWOM TINYINT NOT NULL DEFAULT (0)
;

Selanjutnya, kita perlu mengisi kolom baru. OP merasakan hal ini dalam posnya yang asli yang tidak dirusak.

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

Sekarang, karena itu adalah kolom panjang tetap, yang baru saja membuat banyak pemisahan halaman sehingga kita perlu membangun kembali Indeks Clustered untuk "mengepak kembali" tabel untuk memiliki sebanyak mungkin baris per halaman demi kinerja.

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

Setelah selesai, pertanyaan yang melakukan hal-hal seperti mengembalikan Jumat ke-3 setiap bulan dalam rentang tanggal tertentu menjadi sepele dan cukup jelas untuk dibaca.

--===== 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
;
Jeff Moden
sumber
0

Ini adalah solusi potong dan tempel sederhana. Anda bisa mengubahnya menjadi fungsi jika Anda mau.

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
jeffm
sumber