Xác định thứ Sáu thứ 3 mỗi tháng


16

Tôi cần xác định ngày là "Thứ Sáu thứ 3 mỗi tháng" cho phạm vi ngày là "1.1.1996 - 30.8.2014" trong SQL Server.

Tôi hy vọng tôi nên sử dụng kết hợp DENSE_RANK()PARTITION BY()để đặt "xếp hạng = 3". Tuy nhiên, tôi chưa quen với SQL và không thể tìm đúng mã.

Câu trả lời:


26

Được:

  • Thứ sáu được gọi là "Thứ sáu"
  • Thứ Sáu thứ 3 của tháng sẽ luôn rơi từ ngày 15 đến ngày 21 của tháng

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

Bạn cũng có thể sử dụng weekdayvới datepart(), nhưng nó dễ đọc hơn với tên IMO. So sánh chuỗi rõ ràng sẽ chậm hơn mặc dù.


14

Để có được câu trả lời độc lập về ngôn ngữ / văn hóa, bạn cần tính đến các tên trong tuần khác nhau và bắt đầu tuần.

Ở Ý, Thứ Sáu là "Venerdì" và ngày fisrt trong tuần là Thứ Hai, không phải Chủ Nhật như ở Mỹ.

1900-01-01 là một ngày thứ hai, vì vậy chúng tôi có thể sử dụng thông tin này để tính toán các ngày trong tuần theo cách độc lập với địa phương:

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

Một cách khác, sử dụng câu trả lời của Phil làm cơ sở và quan tâm đến các thiết lập khác nhau:

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

Các 5mã (nếu bạn muốn có một ngày trong tuần trừ thứ Sáu) nên (giống như SET DATEFIRSTmã):

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

Bạn cũng có thể chỉ cần sử dụng một ngày "được biết là tốt" để an toàn khi đối mặt với các cài đặt ngôn ngữ. Ví dụ: nếu tìm kiếm Thứ Sáu, hãy kiểm tra lịch và xem ngày 2 tháng 1 năm 2015 là Thứ Sáu. So sánh đầu tiên sau đó có thể được viết là:

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

Xem thêm Làm thế nào để có được ngày thứ N trong tháng của Peter Larsson.


4

Tôi thực sự đã viết một bài viết về loại tính toán này tại đây

Về cơ bản, bạn có thể sử dụng mã sau đây để tìm Thứ Sáu thứ 3 mỗi tháng trong bất kỳ phạm vi ngày nào

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

Vâng, tôi biết đây là bài viết cũ. Nghĩ rằng tôi sẽ cung cấp một sự khác biệt về mọi thứ mặc dù tuổi của nó. Heh ... và lời xin lỗi của tôi. Tôi mới nhận ra rằng tôi đã gần như sao chép những gì @jyao đã đăng ở trên.

Dựa trên bản chỉnh sửa hiện tại của câu hỏi ban đầu của OP, tôi không thể hiểu tại sao mọi người đăng câu trả lời họ đã làm.

Nhìn qua các chỉnh sửa, tôi tìm thấy câu hỏi ban đầu và đã đăng nó bên dưới ...

Tôi có một chuỗi thời gian từ 1.1.1996 - 30.8.2014 trong cơ sở dữ liệu SQL, ví dụ như với bảng "db.dbo.datestable".

Tôi cần xác định ngày là "Thứ Sáu thứ 3 mỗi tháng" cho phạm vi ngày này trong SQL.

Tôi hy vọng tôi nên sử dụng kết hợp "DENSE_RANK ()" và "PHẦN THAM GIA B (NG ()" để đặt "xếp hạng = 3". Tuy nhiên, tôi chưa quen với SQL và không thể tìm đúng mã.

Bạn có thể giải quyết vấn đề này?

Một phần của câu hỏi ban đầu mà tôi đã nhấn mạnh bằng cách đánh nó dường như là chìa khóa. Tôi chắc chắn có thể không chính xác nhưng dường như với tôi rằng OP đã tuyên bố rằng anh ta có một bảng "Lịch" có tên là "dbo.datestable" và, với tôi, điều đó tạo ra sự khác biệt rất lớn và giờ tôi đã hiểu tại sao nhiều câu trả lời là những gì họ đang bao gồm một trong những ngày tạo ra bởi vì nó đã được đăng vào ngày 10 tháng 11 ... một ngày sau khi chỉnh sửa cuối cùng cho câu hỏi, đã loại bỏ các dấu tích cuối cùng của tham chiếu đến "dbo.datestable".

Như tôi đã nói, tôi có thể sai nhưng đây là cách giải thích của tôi về câu hỏi ban đầu.

Tôi có một bảng "Lịch" được gọi là "dbo.datestable". Với bất kỳ phạm vi ngày nào được bao phủ bởi bảng đó, làm cách nào tôi có thể trả lại những ngày là Thứ Sáu thứ 3 của mỗi tháng trong phạm vi ngày đã cho?

Vì các phương pháp thông thường để làm điều này đã được đề cập, tôi sẽ thêm một phương án có thể hữu ích cho một số người.

Hãy mô phỏng một vài cột mà tôi nghĩ OP sẽ có trong bảng của anh ấy. Tất nhiên, tôi đoán tên cột. Vui lòng phụ bất cứ cột nào tương đương cho bảng "Lịch" của bạn. Ngoài ra, tôi đang làm tất cả điều này trong TempDB vì vậy tôi không có cơ hội can thiệp vào bảng "Lịch" thực sự của ai đó.

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

Đó cũng là một điều mà tôi không biết nếu OP có thể thay đổi bảng "Lịch" của mình để điều này có thể không giúp anh ta nhưng nó có thể giúp những người khác. Với ý nghĩ đó, hãy thêm cột DWoM (Ngày trong tuần cho tháng). Nếu bạn không thích tên này, xin vui lòng thay đổi nó thành bất cứ thứ gì bạn cần trên hộp của riêng bạn.

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

Tiếp theo, chúng ta cần điền vào cột mới. OP đã có ý thức về điều này trong bài viết không bị thay đổi ban đầu của mình.

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

Bây giờ, vì đó là một cột có độ dài cố định, chỉ tạo ra một loạt các phân chia trang nên chúng tôi cần xây dựng lại Chỉ mục cụm để "đóng gói lại" bảng để có càng nhiều hàng trên mỗi trang càng tốt vì hiệu suất.

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

Khi đã xong, các truy vấn thực hiện những việc như trả lại ngày thứ Sáu thứ 3 hàng tháng trong một phạm vi ngày nhất định trở nên tầm thường và khá rõ ràng để đọc.

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

Đây là một giải pháp cắt và dán đơn giản. Bạn có thể biến điều này thành một chức năng nếu bạn muốn.

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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.