กำหนดวันศุกร์ที่ 3 ของทุกเดือน


16

ฉันต้องกำหนดวันที่ซึ่งเป็น "วันศุกร์ที่ 3 ของแต่ละเดือน" สำหรับช่วงวันที่ "1.1.1996 - 30.8.2014" ใน SQL Server

ฉันคาดหวังว่าฉันควรใช้การรวมกันของDENSE_RANK()และPARTITION BY()เพื่อตั้ง "อันดับ = 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

เพื่อให้ได้คำตอบที่เป็นอิสระจากภาษา / วัฒนธรรมคุณต้องคำนึงถึงชื่อวันทำงานที่แตกต่างกันและเริ่มต้นของสัปดาห์

ในอิตาลีวันศุกร์คือ "Venerdì" และวัน fisrt ของสัปดาห์คือวันจันทร์ไม่ใช่วันอาทิตย์เหมือนกับในสหรัฐอเมริกา

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

คุณสามารถใช้วันที่ "รู้จักดี" เพื่อความปลอดภัยในการตั้งค่าภาษา ตัวอย่างเช่นหากกำลังมองหาวันศุกร์ให้ตรวจสอบปฏิทินและดูว่าวันที่ 2 มกราคม 2015 เป็นวันศุกร์ การเปรียบเทียบครั้งแรกนั้นสามารถเขียนเป็น:

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

ดูวิธีการรับวันทำงาน Nth ของเดือนโดย Peter Larsson


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 โพสต์ไว้ด้านบน

จากการแก้ไขคำถามเดิมของ OP ปัจจุบันฉันไม่สามารถเข้าใจได้ว่าทำไมผู้คนที่โพสต์คำตอบที่พวกเขาทำ

เมื่อดูการแก้ไขแล้วฉันพบคำถามเดิมและโพสต์ไว้ด้านล่าง ...

ฉันมีอนุกรมเวลาตั้งแต่ 1.1.1996 - 30.8.2014 ในฐานข้อมูล SQL เช่น กับตาราง "db.dbo.datestable"

ฉันต้องกำหนดวันที่ซึ่งเป็น "วันศุกร์ที่ 3 ของแต่ละเดือน" สำหรับช่วงวันที่นี้ใน SQL

ฉันคาดว่าฉันควรใช้ "DENSE_RANK ()" และ "PARTITION BY ()" เพื่อตั้งค่า "rank = 3" อย่างไรก็ตามฉันใหม่กับ SQL และไม่สามารถค้นหารหัสที่ถูกต้องได้

คุณช่วยแก้ปัญหานี้ได้ไหม?

ส่วนหนึ่งของคำถามดั้งเดิมที่ฉันได้เน้นย้ำว่าเป็นกุญแจสำคัญ ฉันอาจไม่ถูกต้องอย่างแน่นอน แต่สำหรับฉันแล้ว OP พบว่าเขามีตาราง "ปฏิทิน" ที่เรียกว่า "dbo.datestable" และสำหรับฉันแล้วนั่นทำให้แตกต่างกันมากและตอนนี้ฉันเข้าใจว่าทำไมคำตอบหลายข้อ คือสิ่งที่พวกเขารวมถึงที่สร้างวันเพราะมันถูกโพสต์เมื่อวันที่ 10 พฤศจิกายน ... หนึ่งวันหลังจากการแก้ไขครั้งสุดท้ายในคำถามซึ่งลบร่องรอยสุดท้ายของการอ้างอิงถึง "dbo.datestable"

อย่างที่ฉันพูดฉันอาจผิด แต่นี่คือการตีความคำถามดั้งเดิมของฉัน

ฉันมีตาราง "ปฏิทิน" ชื่อ "dbo.datestable" เมื่อกำหนดช่วงวันที่ใด ๆ ที่ครอบคลุมโดยตารางนั้นฉันจะคืนวันที่ที่เป็นวันศุกร์ที่ 3 ของแต่ละเดือนภายในช่วงวันที่ที่กำหนดได้อย่างไร

เนื่องจากวิธีการทั่วไปในการทำเช่นนี้ได้รับการคุ้มครองแล้วฉันจะเพิ่มทางเลือกที่อาจเป็นประโยชน์สำหรับบางคน

ลองจำลองสองคอลัมน์ที่ฉันคิดว่า 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 (วันของสัปดาห์สำหรับเดือน) หากคุณไม่ชอบชื่อโปรดเปลี่ยนเป็นอะไรก็ได้ที่คุณต้องการในกล่องของคุณเอง

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

เมื่อเสร็จสิ้นแล้วแบบสอบถามที่ทำสิ่งต่าง ๆ เช่นคืนวันศุกร์ที่ 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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.