J'ai besoin de déterminer les dates qui sont le "3e vendredi de chaque mois" pour une plage de dates de "1.1.1996 - 30.8.2014" dans SQL Server.
Je pense que je devrais utiliser une combinaison de DENSE_RANK()
et PARTITION BY()
pour définir "rang = 3". Cependant, je suis nouveau sur SQL et incapable de trouver le bon code.
Donné:
Le 3e vendredi du mois tombera toujours du 15 au 21 du mois
select thedate
from yourtable
where datename(weekday, thedate) = 'Friday'
and datepart(day, thedate)>=15 and datepart(day, thedate)<=21;
Vous pouvez également utiliser weekday
avec datepart()
, mais il est plus lisible avec un nom IMO. Les comparaisons de chaînes seront évidemment plus lentes cependant.
Afin d'obtenir une réponse indépendante de la langue/culture, vous devez tenir compte des différents noms de semaine et du début de la semaine.
En Italie, le vendredi est "Venerdì" et le premier jour de la semaine est le lundi, pas le dimanche comme aux États-Unis.
1900-01-01
était un lundi, nous pouvons donc utiliser ces informations pour calculer le jour de la semaine de manière indépendante des paramètres régionaux:
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;
Une autre façon, qui utilise la réponse de Phil comme base et prend en charge différents paramètres:
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
Le 5
le code (si vous voulez un jour de semaine autre que le vendredi) doit être (le même que SET DATEFIRST
codes):
1 for Monday
2 for Tuesday
3 for Wednesday
4 for Thursday
5 for Friday
6 for Saturday
7 for Sunday
Vous pouvez également simplement utiliser une date "bonne connue" pour être en sécurité face aux paramètres de langue. Par exemple, si vous recherchez des vendredis, consultez un calendrier et vérifiez que le 2 janvier 2015 était un vendredi. La première comparaison pourrait alors s'écrire:
DATEPART(weekday,thedate) = DATEPART(weekday,'20150102') --Any Friday
Voir aussi Comment obtenir le Nième jour de la semaine d'un mois par Peter Larsson.
J'ai en fait écrit un article sur ce type de calcul à ici
Fondamentalement, vous pouvez utiliser le code suivant pour rechercher le 3e vendredi de chaque mois dans n'importe quelle plage de dates
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
Oui, je sais que c'est un ancien poste. Je pensais que je fournirais une perspective différente sur les choses malgré son âge. Heh ... et mes excuses. Je viens de réaliser que j'ai presque reproduit ce que @jyao a posté ci-dessus.
Sur la base de la modification actuelle de la question d'origine du PO, je ne pouvais pas comprendre pourquoi les gens publiaient les réponses.
En parcourant les modifications, j'ai trouvé la question d'origine et je l'ai postée ci-dessous ...
J'ai une série chronologique allant du 1.1.1996 au 30.8.2014 dans une base de données SQL, par ex. avec la table "db.dbo.datestable".
J'ai besoin de déterminer les dates qui sont le "3e vendredi de chaque mois" pour cette plage de dates dans SQL.
Je pense que je devrais utiliser une combinaison de "DENSE_RANK ()" et "PARTITION BY ()" pour définir "rank = 3". Cependant, je ne connais pas SQL et je ne trouve pas le bon code.
Pouvez-vous résoudre ce problème?
La partie de la question originale que j'ai soulignée en gras, semble être la clé. Je pourrais certainement me tromper, mais il me semble que le PO déclarait qu'il avait une table "Calendrier" appelée "dbo.datestable" et, pour moi, cela fait une énorme différence et je comprends maintenant pourquoi beaucoup de réponses sont ce qu'ils incluent celui qui a généré les dates car il a été posté le 10 novembre ... un jour après le dernier montage sur la question, ce qui a supprimé les derniers vestiges de la référence au "dbo.datestable".
Comme je l'ai dit, je peux me tromper, mais voici mon interprétation de la question d'origine.
J'ai une table "Calendrier" appelée "dbo.datestable". Étant donné toute plage de dates couverte par ce tableau, comment puis-je renvoyer uniquement les dates qui sont le 3e vendredi de chaque mois dans cette plage de dates donnée?
Étant donné que les méthodes conventionnelles pour ce faire ont déjà été couvertes, j'ajouterai une alternative qui pourrait être utile pour certains.
Simulons quelques colonnes que je pense que l'OP aura déjà dans sa table. Bien sûr, je devine les noms des colonnes. Veuillez sous toutes les colonnes équivalentes pour votre table "Calendrier". De plus, je fais tout cela dans TempDB, donc je ne prends pas le risque d'interférer avec la vraie table "Calendrier" de quelqu'un.
--=================================================================================================
-- 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'est aussi une donnée que je ne sais pas si l'OP peut apporter des modifications à sa table "Calendrier", donc cela pourrait ne pas l'aider mais cela pourrait aider les autres. Dans cet esprit, ajoutons une colonne DWoM (jour de la semaine pour le mois). Si vous n'aimez pas le nom, n'hésitez pas à le changer en ce dont vous avez besoin sur votre propre boîte.
--===== Add the new column.
ALTER TABLE #datestable
ADD DWOM TINYINT NOT NULL DEFAULT (0)
;
Ensuite, nous devons remplir la nouvelle colonne. Le PO en avait une idée dans son poste d'origine non altéré.
--===== 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#
;
Maintenant, comme il s'agit d'une colonne de longueur fixe, qui vient de créer un groupe de fractionnements de page, nous devons donc reconstruire l'index clusterisé pour "reconditionner" la table afin d'avoir autant de lignes par page que possible pour des raisons de performances.
--===== "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)
;
Une fois cela fait, les requêtes qui font des choses comme retourner le 3ème vendredi de chaque mois dans une plage de dates donnée deviennent triviales et assez évidentes à lire.
--===== 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
;
Voici une solution simple de copier-coller. Vous pouvez en faire une fonction si vous le souhaitez.
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