2009-11-12 5 views
4

Mon problème est simple ... ou peut-être pas. J'ai une table qui contient deux dates:Vérifiez si deux dates contiennent un mois donné

StartDate 
EndDate 

Et j'ai une constante qui est un mois. Par exemple:

DECLARE @MonthCode AS INT 
SELECT @MonthCode = 11 /* NOVEMBER */ 

je besoin d'un QUERY unique pour trouver tous les documents dont la date de début et EndDate comprend le mois donné. Par exemple:

/* Case 1 */ Aug/10/2009 - Jan/01/2010 
/* Case 2 */ Aug/10/2009 - Nov/15/2009 
/* Case 3 */ Nov/15/2009 - Jan/01/2010 
/* Case 4 */ Nov/15/2009 - Nov/15/2009 
/* Case 5 */ Oct/01/2010 - Dec/31/2010 

Le premier et le dernier cas nécessitent une attention particulière: Les deux dates sont en dehors Novembre, mais la croix dessus.

La requête suivante ne prend pas en charge le cas 1 et 5:

WHERE MONTH(StartDate) = @MonthCode OR MONTH(EndDate) = @MonthCode 

La requête suivante a également échoué parce que août < novembre et novembre < Jan = false:

WHERE MONTH(StartDate) = @MonthCode OR MONTH(EndDate) = @MonthCode OR (
MONTH(StartDate) < @MonthCode AND @MonthCode < MONTH(EndDate) 
) 
+0

Il semble que vous ayez vraiment besoin d'un certain mois au cours d'une année donnée, où le mois croise la période entre deux dates. La façon dont vous l'avez dit donne l'impression que seules les dates de début et de fin doivent avoir le mois. – Chris

+0

Qu'est-ce que Chris a dit - étant donné l'entrée ci-dessus quels enregistrements voulez-vous sélectionner? – Murph

+0

Tous les 5, car ils incluent ou chevauchent le mois de novembre. L'année n'a pas d'importance. La plage de dates doit inclure au moins un jour en novembre. –

Répondre

2
DECLARE @MonthCode AS INT 
SELECT @MonthCode = 11 /* NOVEMBER */ 

declare @yourtable table(
    startdate datetime 
    , enddate datetime 
) 
insert into @yourtable(
    startdate 
    , enddate 
) 
(
select '8/10/2009', '01/01/2010' 
union all 
select '8/10/2009' , '11/15/2009' 
union all 
select '11/15/2009' , '01/01/2010' 
union all 
select '11/15/2009' , '11/15/2009' 
union all 
select '10/01/2010' , '12/31/2010' 
union all 
select '05/01/2009', '10/30/2009' 
) 

select * 
from @yourtable 
where DateDiff(mm, startdate, enddate) > @MonthCode  -- can't go over 11 months without crossing date 
    OR (Month(startdate) <= @MonthCode     -- before Month selected 
      AND (month(enddate) >[email protected]   -- after month selected 
       OR year(enddate) > year(startdate) -- or crosses into next year 
       ) 
     ) 
    OR (Month(startdate) >= @MonthCode     -- starts after in same year after month 
      and month(enddate) >= @MonthCode   -- must end on/after same month assume next year 
      and year(enddate) > year(startdate) 
     ) 
+0

Je teste peu de cas mais jusqu'ici tout va bien! –

0

Filtre pour les lignes qui commencent avant la fin du mois et se terminent après le début du mois. Pour Octobre 2009:

select * 
from YourTable 
where StartDate < '2009-11-01' and EndDate >= '2009-10-01' 

Ou, avec juste le mois comme entrée:

declare @month datetime 
set @month = '2009-10-01' 

select * 
from YourTable 
where StartDate < dateadd(month,1,@month) 
and EndDate >= @month 
5

Je comprends que vous êtes à la recherche d'un moyen de sélectionner toutes les gammes qui s'entrecroisent Novembre, en une année .

Voici la logique:

  • si la plage tombe sur une seule année (par exemple 2009), le mois de départ doit être antérieure ou égale à Novembre et le mois de fin supérieures ou égales à Novembre

  • si la plage tombe sur deux années suivantes (par exemple 2009-2010), le mois de départ doit être antérieure ou égale à Novembre ou le mois de fin supérieures ou égales à Novembre

  • si la plage tombe sur deux ans avec plus d'un an à di référence (par ex. 2008-2010), Novembre est toujours inclus dans la gamme (ici Novembre 2009)

traduit en pseudo-code, la condition est la suivante:

// first case 
(
    (YEAR(StartDate)=YEAR(EndDate)) AND 
    (MONTH(StartDate)<=MonthCode AND MONTH(EndDate)>=MonthCode) 
) 
OR 
// second case 
(
    (YEAR(EndDate)-YEAR(StartDate)=1) AND 
    (MONTH(StartDate)<=MonthCode OR MONTH(EndDate)>=MonthCode) 
) 
OR 
// third case 
(
    YEAR(EndDate)-YEAR(StartDate)>1 
) 
+0

Comparer datetimes au lieu de MONTH() et YEAR() et vous pouvez faire sans les trois voies OU – Andomar

+0

Je ne pense pas. La comparaison des dates complètes ne permettrait pas de trouver les intervalles qui se croisent en novembre * –

-1

SQL Server 200/2005, Vous pouvez également faire:

select 
    * 
from 
    table 
where 
    datepart(m,startDate) = 11 
    and datepart(m,EndDate) = 11 

MISE à JOUR: Retirées and datepart(yyyy,startDate) = datepart(yyyy,endDate) voulez un mois quel que soit Ye ar ou jour?

+0

+1 Oui, ou même 'MONTH (StartDate) = 11 et YEAR (StartDate) = 2009' – Andomar

+0

Est-ce que cela prend en charge" Nov/15/2009 - Jan/01/2010 "cas? –

+0

la seule date à laquelle cela reviendra est: 2009-11-15 - 2009-11-15 – JeffO

0

Il existe différentes fonctions que vous pouvez utiliser pour y parvenir, comme DATEPART et DATETIFF. Cependant, le vrai problème n'est pas comment exprimer la condition de StartDate ou EndDate tombant sur le mois donné, mais comment faire ceci d'une manière qui rend la requête efficace. En d'autres termes, comment exprimer cela d'une manière SARGable. Dans le cas où vous recherchez une petite table de changement, n'importe quoi sous 10k pages, alors cela ne fait pas tellement de différence, un balayage complet serait probablement parfaitement acceptable. La vraie question est de savoir si la ou les tables ont une taille significative et si une analyse complète est inacceptable.

Si vous ne possédez aucun index sur les colonnes StartDate ou EndDate, cela ne fait aucune différence, le critère n'est pas consultable et la requête analyse quand même la totalité de la table. Cependant, s'il existe des index sur StartDate et EndDate, la façon dont vous exprimez la condition fait toute la différence. La partie critique pour les index DATETIME est que vous devez exprimer la recherche comme une plage de dates exacte. L'expression de la condition en tant que fonction en fonction du champ DATETIME rendra la condition impossible à rechercher, ce qui entraînera une analyse complète de la table. Donc, cette connaissance se rendre à la manière correcte la recherche d'une plage de dates:

select ... from table 
where StartDate between '20091101' and '20091201' 
or EndDate between '20091101' and '20091201'; 

Cela peut aussi être exprimée comme suit:

select ... from table 
where StartDate between '20091101' and '20091201' 
union all 
select ... from table 
where EndDate between '20091101' and '20091201' 
and StartDate not between '20091101' and '20091201'; 

Quelle requête fonctionne mieux dépend d'un certain nombre de facteurs, comme la taille de la table et les statistiques des données réelles dans le tableau.

Cependant, vous voulez le mois de novembre de année, que cette requête ne vous donne pas. La solution à ce problème est contre tous les instincts d'un programmeur: coder dur les années pertinentes. La plupart du temps les tables ont un petit nombre d'années de toute façon, quelque chose dans la gamme de 4-5 années de données passées et de planifier 3-4 ans plus jusqu'à ce que le système sera révisé:

select ... from table 
where StartDate between '20051101' and '20051201' 
or EndDate between '20051101' and '20051201' 
union all 
select ... from table 
where StartDate between '20061101' and '20061201' 
or EndDate between '20061101' and '20061201' 
union all 
... 
select ... from table 
where StartDate between '20151101' and '20151201' 
or EndDate between '20151101' and '20151201'; 

Il y a 12 mois en une année, écrivez 12 procédures distinctes. Est-ce que ça a l'air fou? C'est sûr, mais c'est la chose optimale de la perspective du compilateur de requêtes SQL et de l'optimiseur. Comment peut-on maintenir un tel code? 12 procédure distincte, avec une requête qui se répète 10 fois (20 fois si vous utilisez l'UNION entre StartDate et EndDate pour supprimer l'OR), 120 répétitions de code, doit être être non-sens. En fait, ce n'est pas le cas. Utilisez la génération de code pour créer les procédures, comme XML/XSLT, de sorte que vous pouvez facilement le modifier et le maintenir. Est-ce que le client doit connaître les 12 procédures et appeler la bonne? Bien sûr que non, il appelle une procédure wrapper qui discrimine l'argument @Month pour appeler le bon.

Je reconnais que toute personne qui se penchera sur le système après les faits croira probablement que cette requête a été écrite par une bande de singes ivres. Pourtant, quelque part entre le reniflage de paramètres, l'indice SARGability et les caprices de SQL DATETIME, il en résulte que c'est aujourd'hui l'état de l'art lorsqu'il s'agit de rechercher des intervalles de calendrier.

Oh, et si la requête frappe le Index Tipping Point il rendra l'ensemble de l'argument muet de toute façon ...

Mise à jour

BTW il y a aussi un moyen pas cher si vous êtes prêt à sacrifier un peu d'espace de stockage: deux persista colonnes calculées sur StartMonth AS DATEPART(month, StartDate) et EndDate AS DATEPART(month, EndDate), et l'index sur chaque et requête WHERE StartMonth = @Month OR EndMonth = @Month (ou encore UNION entre deux requêtes une pour Démarrer un pour Fin, pour supprimer le OU).

+0

En ce qui concerne l'optimisation, il y aura d'autres éléments dans la clause where qui limiteront la ligne à un maximum de 10 - Je dois limiter davantage les résultats par ceux qui tombent dans le mois spécifié. –

1

Essayez ceci:

 
select * from Mytable 
where 
month(StartDate) = @MonthCode or month(EndDate) = @MonthCode // Nov/15/2009 - Nov/15/2009 
or 
dateadd(month,@MonthCode-1,convert(datetime,convert(varchar,year(StartDate)))) 
between StartDate and EndDate // Oct/01/2010 - Dec/31/2010 
or 
dateadd(month,@MonthCode-1,convert(datetime,convert(varchar,year(EndDate)))) 
between StartDate and EndDate // Dec/01/2009 - Dec/31/2010 - tricky one 

La principale ideea est de vérifier où sont les dates 01.November.StartYear et 01.November.EndYear situés.

Espérons que ça aide.

Questions connexes