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 exemple 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 suis nouveau sur SQL et incapable de trouver 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". Compte tenu de 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, parce que c'est une colonne de longueur fixe, qui vient de créer un tas de fractionnements de page, nous devons donc reconstruire l'index clusterisé pour "reconditionner" la table pour 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 3e 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
;