Determina il 3 ° venerdì di ogni mese


16

Devo determinare le date che sono il "3 ° venerdì di ogni mese" per un intervallo di date "1.1.1996 - 30.8.2014" in SQL Server.

Mi aspetto che dovrei usare una combinazione di DENSE_RANK()e PARTITION BY()per impostare "rank = 3". Tuttavia, sono nuovo di SQL e non riesco a trovare il codice corretto.

Risposte:


26

Dato:

  • Il venerdì si chiama "venerdì"
  • Il 3 ° venerdì del mese cadrà sempre dal 15 al 21 del mese

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

Puoi anche usarlo weekdaycon datepart(), ma è più leggibile con un nome IMO. I confronti delle stringhe saranno ovviamente più lenti.


14

Per ottenere una risposta indipendente da lingua / cultura, è necessario tenere conto dei diversi nomi dei giorni feriali e dell'inizio della settimana.

In Italia, venerdì è "Venerdì" e il primo giorno della settimana è lunedì, non domenica come negli Stati Uniti.

1900-01-01 era un lunedì, quindi possiamo usare queste informazioni per calcolare il giorno della settimana in modo indipendente dalla locale:

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

Un altro modo, che utilizza la risposta di Phil come base e si occupa di impostazioni diverse:

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

Il 5codice (se si desidera un giorno feriale diverso da venerdì) dovrebbe essere (uguale ai SET DATEFIRSTcodici):

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

Puoi anche semplicemente usare una data "ben nota" per essere al sicuro di fronte alle impostazioni della lingua. Ad esempio, se cerchi il venerdì, controlla un calendario e vedi che il 2 gennaio 2015 era un venerdì. Il primo confronto potrebbe quindi essere scritto come:

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

Vedi anche Come ottenere l'ennesimo giorno della settimana di un mese da Peter Larsson.


4

In realtà ho scritto un articolo su questo tipo di calcolo qui

Fondamentalmente, puoi usare il seguente codice per trovare il 3 ° venerdì di ogni mese in qualsiasi intervallo di date

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

Sì, so che questo è un vecchio post. Pensavo che avrei fornito un'inclinazione diversa sulle cose nonostante la sua età. Heh ... e le mie scuse. Ho appena realizzato che ho quasi duplicato ciò che @jyao ha pubblicato sopra.

Sulla base dell'attuale modifica della domanda originale del PO, non sono riuscito a capire perché le persone dovevano pubblicare le risposte.

Osservando le modifiche, ho trovato la domanda originale e l'ho pubblicata di seguito ...

Ho una serie temporale che va dall'1.1.1996 al 30.8.2014 in un database SQL, ad es. Con la tabella "db.dbo.datestable".

Devo determinare le date che sono il "3 ° venerdì di ogni mese" per questo intervallo di date in SQL.

Mi aspetto che dovrei usare una combinazione di "DENSE_RANK ()" e "PARTITION BY ()" per impostare "rank = 3". Tuttavia, sono nuovo di SQL e non riesco a trovare il codice corretto.

Puoi risolvere questo problema?

La parte della domanda originale che ho sottolineato in grassetto sembra essere la chiave. Potrei certamente essere errato, ma mi sembra che l'OP affermasse che ha una tabella "Calendario" chiamata "dbo.datestable" e, per me, questo fa una grande differenza e ora capisco perché molte delle risposte sono ciò che stanno includendo quello che ha generato le date perché è stato pubblicato il 10 novembre ... un giorno dopo la modifica finale sulla domanda, che ha rimosso le vestigia finali del riferimento al "dbo.datestable".

Come ho detto, potrei sbagliarmi, ma ecco la mia interpretazione della domanda originale.

Ho una tabella "Calendario" chiamata "dbo.datestable". Dato qualsiasi intervallo di date coperto da quella tabella, come posso restituire solo le date che sono il 3 ° venerdì di ogni mese all'interno di tale intervallo di date?

Poiché i metodi convenzionali per farlo sono già stati trattati, aggiungerò un'alternativa che potrebbe essere utile per alcuni.

Simuliamo un paio di colonne che penso che l'OP avrà già nella sua tabella. Certo, sto indovinando i nomi delle colonne. Inserisci le colonne equivalenti per la tabella "Calendario". Inoltre, sto facendo tutto questo in TempDB, quindi non ho alcuna possibilità di interferire con la vera tabella "Calendario" di qualcuno.

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

È anche un dato di fatto che non so se l'OP può apportare modifiche alla sua tabella "Calendario", quindi questo potrebbe non aiutarlo ma potrebbe aiutare gli altri. Tenendo presente ciò, aggiungiamo una colonna DWoM (giorno della settimana per il mese). Se non ti piace il nome, sentiti libero di cambiarlo con qualsiasi cosa tu abbia bisogno sulla tua scatola.

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

Successivamente, dobbiamo popolare la nuova colonna. L'OP ha avuto un'idea di ciò nel suo post originale non alterato.

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

Ora, poiché si tratta di una colonna di lunghezza fissa, che ha appena creato un gruppo di suddivisioni di pagina, quindi è necessario ricostruire l'indice cluster per "reimballare" la tabella in modo da avere il maggior numero di righe per pagina per motivi di prestazioni.

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

Una volta fatto, le query che fanno cose come restituire il 3 ° venerdì di ogni mese in un determinato intervallo di date diventano banali e abbastanza ovvie da leggere.

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

Ecco una semplice soluzione taglia e incolla. Se lo desideri, puoi trasformarlo in una funzione.

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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.