Determinar a terceira sexta-feira de cada mês

16

Preciso determinar as datas que são a "terceira sexta-feira de cada mês" para um período de "1.1.1996 - 30.8.2014" no SQL Server.

Espero usar uma combinação de DENSE_RANK()e PARTITION BY()definir "rank = 3". No entanto, sou novo no SQL e não consigo encontrar o código correto.

emcor
fonte

Respostas:

26

Dado:

  • Sexta-feira é chamado "sexta-feira"
  • A 3ª sexta-feira do mês sempre cairá de 15 a 21 de cada mês

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

Você também pode usar weekdaycom datepart(), mas é mais legível com um nome IMO. As comparações de strings serão obviamente mais lentas.

Philᵀᴹ
fonte
14

Para obter uma resposta independente de idioma / cultura, é necessário considerar os diferentes nomes dos dias da semana e o início da semana.

Na Itália, sexta-feira é "Venerdì" e o primeiro dia da semana é segunda-feira, não domingo como nos EUA.

1900-01-01 era segunda-feira, para que possamos usar essas informações para calcular o dia da semana de forma independente da localidade:

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
fonte
12

Outra maneira, que usa a resposta de Phil como base e cuida de configurações diferentes:

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

O 5código (se você quiser um dia da semana que não seja sexta-feira) deve ser (o mesmo que SET DATEFIRSTcódigos):

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

Você também pode usar apenas uma data "conhecida" para garantir a segurança das configurações de idioma. Por exemplo, se estiver procurando sextas-feiras, verifique um calendário e veja se 2 de janeiro de 2015 era sexta-feira. A primeira comparação poderia então ser escrita como:

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

Veja também Como obter o nono dia da semana de um mês por Peter Larsson.

ypercubeᵀᴹ
fonte
4

Na verdade, eu escrevi um artigo sobre esse tipo de cálculo aqui

Basicamente, você pode usar o código a seguir para encontrar a terceira sexta-feira de cada mês em qualquer período

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

Sim, eu sei que este é um post antigo. Pensei que eu forneceria uma inclinação diferente sobre as coisas, apesar da idade. Heh ... e minhas desculpas. Acabei de perceber que quase duplicei o que @jyao postou acima.

Com base na edição atual da pergunta original do OP, eu não conseguia descobrir por que as pessoas postavam as respostas.

Analisando as edições, encontrei a pergunta original e a publiquei abaixo ...

Eu tenho uma série temporal que varia de 1.1.1996 a 30.8.2014 em um banco de dados SQL, por exemplo, com a tabela "db.dbo.datestable".

Preciso determinar as datas que são a "terceira sexta-feira de cada mês" para esse período no SQL.

Espero que eu use uma combinação de "DENSE_RANK ()" e "PARTITION BY ()" para definir "rank = 3". No entanto, sou novo no SQL e não consigo encontrar o código correto.

Você pode resolver este problema?

A parte da pergunta original que enfatizei em negrito parece ser a chave. Eu certamente poderia estar incorreto, mas parece-me que o OP estava afirmando que ele tem uma tabela "Calendário" chamada "dbo.datestable" e, para mim, isso faz muita diferença e agora eu entendo por que muitas das respostas são o que incluem, o que gerou as datas porque foi postada em 10 de novembro ... um dia após a edição final da pergunta, que removeu os vestígios finais da referência ao "dbo.datestable".

Como eu disse, posso estar errado, mas aqui está a minha interpretação da pergunta original.

Eu tenho uma tabela "Calendário" chamada "dbo.datestable". Dado qualquer intervalo de datas coberto por essa tabela, como posso retornar apenas as datas da 3ª sexta-feira de cada mês dentro desse período?

Como os métodos convencionais para fazer isso já foram abordados, adicionarei uma alternativa que pode ser útil para alguns.

Vamos simular algumas das colunas que acho que o OP já terá em sua tabela. Claro, estou adivinhando os nomes das colunas. Por favor, insira as colunas equivalentes na tabela "Calendário". Além disso, estou fazendo tudo isso no TempDB, para não ter a chance de interferir na tabela "Calendário" real de alguém.

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

Também é certo que eu não sei se o OP pode fazer alterações na tabela "Calendário", portanto isso pode não ajudá-lo, mas pode ajudar outras pessoas. Com isso em mente, vamos adicionar uma coluna DWoM (dia da semana para o mês). Se você não gostar do nome, altere-o para o que precisar na sua própria caixa.

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

Em seguida, precisamos preencher a nova coluna. O OP percebeu isso em seu post original e não adulterado.

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

Agora, como essa é uma coluna de comprimento fixo, que acabou de criar várias divisões de páginas, precisamos reconstruir o Índice de Cluster para "reembalar" a tabela para ter o maior número possível de linhas por página, por uma questão de desempenho.

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

Uma vez feito isso, as consultas que fazem coisas como retornar a 3ª sexta-feira de cada mês em um determinado período tornam-se triviais e bastante óbvias para leitura.

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

Aqui está uma solução simples de recortar e colar. Você pode transformar isso em uma função, se quiser.

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
fonte