Préambule▲
La difficulté des calculs portant sur des dates ou des horaires (et parfois les deux) est liée à la codification même de la mesure du temps ainsi qu'au fait que le temps est une entropie (disons-le, même une « isentroprie »), c'est-à-dire qu'il s'écoule de manière uniforme (iso) et dans un seul sens irréversible (vers le futur).
La mesure du temps n'obéit pas à des règles conventionnelles, comme le comptage décimal ou l'origine zéro. En effet :
- les années comptent tantôt 365 tantôt 366 jours en fait 365 jours 5 heures 48 min. 45,97 sec… ;
- les mois de 29 à 31 jours ;
- il n'y a pas d'année 0, mais il y a des années négatives (av. J.-C. !) ;
- les siècles et millénaires commencent par une année unitaire (1, 1901, 2001…) ;
- les jours comptent 24 heures et il y a une heure zéro !
- les heures comptent 60 minutes et les minutes 60 secondes ;
- il n'y a pas recouvrement exact des semaines par rapport aux mois… ;
- les heures changent par rapport aux différents fuseaux horaires de la planète !
- certaines opérations sur les dates sont impossibles (par exemple, rajouter exactement trois mois à une date)… ;
- le 10 octobre 1582 n'a jamais existé !!!
Tous ces éléments font que les calculs, notamment de comptage du temps obéissent à des algorithmes complexes.
De ce fait, la norme SQL 2, propose une solution générale assez intelligente. Mais elle est rarement implantée. Nous étudierons donc la norme, ce que propose les éditeurs est finalement une solution basée sur des relations entre tables avec une table principale stockant toutes les dates.
NOTA : il est dommage que le calendrier mis en place lors de la Révolution française n'ait pas subsisté. L'année y était divisée en 12 mois de 30 jours. Chaque semaine faisait 10 jours. Les 5 ou 6 jours restants étaient placés à la fin de l'année et constituaient des vacances… En quelque sorte, les révolutionnaires étaient en avance sur les congés payés de 1936 et sur les 35 heures de la mère Aubry !
ATTENTION : la numérotation des semaines est standardisée depuis 1976 par l'ISO, avec les règles suivantes :
- le lundi est considéré comme le premier jour de la semaine ;
- les semaines d'une même année sont numérotées de 01 à 52 (parfois 53) ;
- la semaine qui porte le numéro 01 est celle qui contient le premier jeudi de janvier ;
- il peut exister une semaine n° 53 (années communes finissant un jeudi, bissextiles finissant un jeudi ou un vendredi).
La numérotation des semaines aux USA, comme la numérotation des jours diffère totalement de cette norme !
1. La solution normative▲
Elle se compose de trois éléments : des types spécifiques, une algèbre et des opérateurs particuliers.
1-1. Les types temporels SQL 2▲
La norme propose les types :
DATE
TIME
[WITH TIME ZONE]
TIMESTAMP
[WITH TIME ZONE]
INTERVAL
La précision de TIME ZONE permet de définir le décalage du fuseau horaire par rapport à l'heure universelle (UTC).
La norme SQL 2 impose en outre la représentation des dates et heures suivant le masque :
HEURE |
DATE |
DATE et HEURE |
---|---|---|
hh:mm:ss.nnn |
AAAA-MM-JJ |
AAAA-MM-JJ hh:mm:ss.nnn |
Avec des dates allant du premier janvier de l'an 1 au 31 décembre de l'an 9999.
NOTA : le typage rapide est permis et même conseillé dans les expressions temporelles. En effet, comment savoir si 21:16 fait référence à 21h16 ou 21 minutes et 16 secondes ? Pour lever le doute, on peut, plutôt que d'utiliser la fonction CAST, préfixer la donnée :
Exemple :
TIME
'00:21:16'
, DATE
'2002-04-05'
Le type INTERVAL permet de stocker des durées. Sa syntaxe est de la forme :
nom_colonne INTERVAL mesure_temps1 [TO mesure_temps2]
dans laquelle mesure_temp peut être :
YEAR | MONTH | DAY | HOUR | MINUTE | SECOND
avec les contraintes suivantes :
- mesure_temps1 doit englober mesure_temp2 ;
- mesure_temps2 et toutes les mesures intermédiaires entre mesure_temps1 et mesure_temps2 ne peuvent « déborder ».
Exemples :
VALIDE |
NON VALIDE |
Pourquoi ? |
---|---|---|
Sélectionnez
|
Sélectionnez
|
DAY n'est pas compris dans DAY |
Sélectionnez
|
Sélectionnez
|
HOUR n'est pas compris dans MINUTE |
Sélectionnez
|
Sélectionnez
|
MONTH n'est pas compris dans DAY |
Sélectionnez
|
Sélectionnez
|
300 minutes déborde vers heure |
Sélectionnez
|
Sélectionnez
|
Format ISO non respecté |
1-2. Mathématique normative des calculs temporels▲
1-2-1. Fonctions▲
Les fonctions CURRENT_DATE, CURRENT_TIME,CURRENT_TIMESTAMP permettent de récupérer respectivement la date, l'heure et le combiné date/heure courantes depuis le système. Attention, ce sont des fonctions non déterministes, c'est-à-dire que réexécutées plusieurs fois de suite, elles peuvent ne pas donner un résultat identique…
La fonction EXTRACT permet d'extraire une partie temporelle sous forme numérique d'une donnée de type temporel.
La syntaxe de la fonction EXTRACT est la suivante :
EXTRACT
(
{YEAR
|
MONTH
|
DAY
|
HOUR
|
MINUTE
|
SECOND
} FROM
donnée)
Exemples :
Sélectionnez
|
4 |
Sélectionnez
|
16 |
1-2-2. Le prédicat OVERLAPS▲
Un outil puissant nous est fourni par SQL avec le prédicat OVERLAPS. Il permet de préciser si une période en recouvre (au moins partiellement) une autre. C'est très pratique si vous voulez gérer un diagramme de GANTT par exemple, ou il convient que certaines tâches ne démarrent pas avant la fin d'autres tâches.
La syntaxe du prédicat OVERLAPS est la suivante :
Sélectionnez
|
Sélectionnez
|
Sélectionnez
|
Sélectionnez
|
Avec la contrainte suivante :
- borne_début et borne_fin doivent être de même type dans les périodes sauf si borne_fin est de type INTERVAL.
Exemples :
VALIDE |
NON VALIDE |
Pourquoi ? |
---|---|---|
Sélectionnez
|
Sélectionnez
|
différence de type dans la période |
Sélectionnez
|
Sélectionnez
|
différence de type dans les mêmes bornes |
Sélectionnez
|
Sélectionnez
|
Utilisation de type INTERVAL illicite en borne_debut |
Maintenant penchons-nous sur le résultat. OVERLAPS étant un prédicat il ne peut fournir que… trois valeurs : TRUE, FALSE et UNKNOWN (du fait de la présence possible de marqueur NULL dans les données).
Le prédicat OVERLAPS est vrai si :
((période1.borne_debut > période2.borne_debut
et (période1.borne_debut < période2.borne_debut ou période1.borne_fin < période2.borne_fin)) ou
(période2.borne_debut > période1.borne_debut
et (période2.borne_debut < période1.borne_fin ou période2.borne_fin < période1.borne_fin)) ou
(période1.borne_debut = période2.borne_debut et (période1.borne_fin NON NUL et période2.borne_fin NON NUL)
Ce qui peut se traduire en bon français par : la période P2 recouvre la période P1, si tout ou partie de la période P2 est inclus dans la période P1.
ATTENTION : un effet de bord dû à l'asymétrie du prédicat est à remarquer…
Un bon exemple valant mieux qu'un long discours, voici quelques données permettant de mieux comprendre l'intérêt de ce prédicat. Le jeu d'essais suivant va nous permettre de mieux comprendre…
Les périodes dont définies comme suit :
- a1 va de 8h à 9h et a2 de 8h30 à 9h30
- b1 va de 8h à 9h et b2 de 9h à 9h30
- c1 va de 9h à 9h30 et c2 de 9h à 9h
- d1 va de 9h à 9h30 et d2 de 9h30 à 9h30
- e1 va de 8h à 8h30 et e2 de 9h à 9h30
- f1 va de 8h30 à 9h et f2 de 8h à 8h30
- g1 va de 8h30 à 10h et g2 de 8h à 9h
Détails des intervalles de temps c2 et d
|
|
On peut les modéliser ainsi :
Sélectionnez
|
Sélectionnez
|
La requête suivante donne :
Sélectionnez
|
Sélectionnez
|
Seules, les périodes des exemples a, c et g répondent à l'opérateur OVERLAPS.
Les périodes recouvrantes sont sur fond blanc, celles non recouvrantes sur fond gris.
L'explication est extraite de mon ouvrage, « SQL », la référence, collection développement, Campus Press Editeur Paris 2001.
"
Par analogie, avec les données que nous venons de voir, il s'agit de considérer des spectateurs qui seraient entrés dans une salle de cinéma pendant la projection d'un film. Le film se serait déroulé en période P1 et chaque spectateur aurait séjourné dans la salle en période P2. Dès lors tout devient clair : le prédicat OVERLAPS permet de savoir qui a vu le film, au moins en partie !
On constate au passage que quelqu'un qui ne reste qu'un temps infiniment court, c'est-à-dire que la période est caractérisée par le fait que le début est égal à la fin, a vu le film, si cet instant est inclus dans la limite [début, fin[ de la période de référence. En effet s'il arrive au début il est considéré comme ayant vu le film tandis que s'il arrive à la fin, il est considéré comme n'ayant pas vu le film. Ceci est logique, mais va à l'encontre de l'intuition… C'est logique parce que le temps s'écoule de manière uniforme dans un seul sens ! On ne peut revenir en arrière et la mesure du temps ne peut qu'augmenter puisqu'allant toujours dans le sens du vieillissement. On peut dire que l'écoulement du temps est de nature isentropique, c'est-à-dire à entropie constante. En fait si on se livre à un calcul mathématique aux limites on peut toujours partir d'un intervalle donné que l'on restreint petit à petit pour arriver à une durée nulle. Si cet instant de durée infime démarre au début d'un autre instant, il reste toujours inclus dedans, tandis que s'il démarre à la fin, il ne sera jamais inclus dedans ! Là où l'affaire se complique, c'est quand une valeur au moins est nulle…
"
Bien entendu en l'absence d'un tel prédicat, vous pouvez le fabriquer de toutes pièces par une construction SQL. En effet reformulé sous SQL, notre prédicat OVERLAPS est :
SELECT
*
FROM
PERIODE
WHERE
(
P1_DEBUT >
P2_DEBUT AND
(
P1_DEBUT <
P2_FIN OR
P1_FIN <
P2_FIN))
OR
(
P2_DEBUT >
P1_DEBUT AND
(
P2_DEBUT <
P1_FIN OR
P2_FIN <
P1_FIN))
OR
(
P1_DEBUT =
P2_DEBUT AND
(
P1_FIN IS
NOT
NULL
AND
P2_FIN IS
NOT
NULL
))
Et donne, bien évidemment le même résultat que précédemment.
1-2-3. Algèbre temporelle▲
Il est possible d'utiliser les opérations algébriques + - x et / avec quelques restrictions :
premier opérande |
opérateur |
second opérande |
résultat |
---|---|---|---|
TIMESTAMP | DATE | TIME |
- |
TIMESTAMP | DATE | TIME |
INTERVAL |
TIMESTAMP | DATE | TIME |
+ |
INTERVAL |
TIMESTAMP | DATE | TIME |
TIMESTAMP | DATE | TIME |
- |
INTERVAL |
TIMESTAMP | DATE | TIME |
INTERVAL |
+ |
TIMESTAMP | DATE | TIME |
TIMESTAMP | DATE | TIME |
INTERVAL |
+ |
INTERVAL |
INTERVAL |
INTERVAL |
- |
INTERVAL |
INTERVAL |
INTERVAL |
* |
nombre |
INTERVAL |
INTERVAL |
/ |
nombre |
INTERVAL |
nombre |
* |
INTERVAL |
INTERVAL |
Exemples :
VALIDE |
NON VALIDE |
Pourquoi ? |
---|---|---|
Sélectionnez
|
Sélectionnez
|
différence de type |
Sélectionnez
|
Sélectionnez
|
addition de date impossible |
Sélectionnez
|
Sélectionnez
|
pas de sens |
1-2-4. Logique temporelle▲
Bien entendu la comparaison entre des types temporels est possible, mais dans certaines limites.
Voici les différentes combinaisons possibles :
DATE |
[NOT] < <= = >= > <> |
DATE |
TIME |
[NOT] < <= = >= > <> |
TIME |
TIMESTAMP |
[NOT] < <= = >= > <> |
TIMESTAMP |
INTERVAL YEAR |
[NOT] < <= = >= > <> |
INTERVAL YEAR |
INTERVAL MONTH |
[NOT] < <= = >= > <> |
INTERVAL MONTH |
INTERVAL YEAR TO MONTH |
[NOT] < <= = >= > <> |
INTERVAL YEAR TO MONTH |
INTERVAL DAY |
[NOT] < <= = >= > <> |
INTERVAL DAY |
INTERVAL DAY TO HOUR |
[NOT] < <= = >= > <> |
INTERVAL DAY TO HOUR |
INTERVAL DAY TO MINUTE |
[NOT] < <= = >= > <> |
INTERVAL DAY TO MINUTE |
INTERVAL DAY TO SECOND |
[NOT] < <= = >= > <> |
INTERVAL DAY TO SECOND |
On pourra noter que tout intervalle comprenant le mois autrement qu'en borne de fin ne peut faire l'objet d'une comparaison. Ceci est dû au fait que le nombre de jours d'un mois diffère d'un mois à l'autre ce qui rend impossible la comparaison d'intervalle basée sur des durées de mois…
2. Le discours des éditeurs▲
La complexité de la logique temporelle et du calcul de date ont fait que peu d'éditeurs de SGBDR ont implémenté la norme de manière drastique. Certains même se contentent de ne fournir que le type TIMESTAMP.
Quels sont alors les trucs qu'ils utilisent pour donner satisfaction à leurs clients.
SQL Server de Microsoft, n'inclut que le type DATETIME (équivalent du normatif TIMESTAMP) et propose en sus, les cinq fonctions suivantes : CURRENT_TIMESTAMP, DATEPART, DATEADD, DATEDIFF et DATENAME (nous passerons sous silence les DAY, MONTH, YEAR qui sont virtuellement inclus dans DATEPART).
CURRENT_TIMESTAMP |
Date et heure courante |
DATEPART |
Équivalent du EXTRACT de la norme |
DATEADD |
Ajout de durée dans date |
DATEDIFF |
Retrait de durée dans date |
DATENAME |
Nom d'une partie de date |
Intéressons-nous aux fonctions DATEADD et DATEDIFF. Leur syntaxe est :
DATEADD |
DATEDIFF
(
partie_de_date, nombre, date
)
Exemple :
Sélectionnez
|
2002-02-28 00:00:00.000 |
Sélectionnez
|
2002-01-28 00:00:00.000 |
L'ajout d'un mois au 31 janvier 2002 ne provoque pas un saut à mars, car l'algorithme reprend bien la fin du mois suivant, soit le 28 février. Bravo SQL Server.
En revanche la seconde requête est une aberration… en effet l'ajout et le retrait imbriqué d'un mois, donne une date décalée de 3 jours. C'est une catastrophe… Au secours SQL Server !!!
Néanmoins la solution SQL Server permet des calculs de base sur les données temporelles pour peu que l'on prenne quelques précautions.
3. La solution intelligente▲
Elle consiste tout simplement à modéliser un planning avec non seulement une continuité des dates exploitées dans la base, mais un ensemble de tables « satellites » ayant chacune un découpage du temps. Autrement dit, autour de la table des dates, une table des mois, des jours de semaine (de 1 lundi à 7 dimanche), des jours du mois (de 1 à 28, 29, 30 ou 31) une table des années, des semestres, des trimestres, des semaines…
3-1. Le modèle de données▲
Voici un tel modèle MERISE :
On trouvera dans T_PLN_JOUR_PJR une entité composée d'une colonne clef représentant les dates, une colonne rang numérotant les dates dans leur ordre chronologique à l'aide d'une valeur discrète (entier), mais aussi les semaines, les mois les trimestres les semestres et les années avec des valeurs continues (décimaux) et une colonne alea dont je vous expliquerai un jour l'utilité !
J'y ai rajouté une petite entité « INTERVALLE_TRAVAIL » permettant de définir des plages horaires d'ouverture de l'entreprise. Par exemple de 8h30 à 12h30 et de 14h à 18h du lundi au jeudi et de 9h à 12h et de 14h à 16h le vendredi (cette entité étant liée avec les jours de semaine).
Ce modèle aboutit à la représentation physique suivante :
Où l'on voit que la table T_PLN_JOUR_PJR est garnie de 8 clefs étrangères…
Le secret de ce modèle et de son utilité, ce sont les colonnes PJR_RANG_… de la table des dates. Nous allons voir ce qu'on y met dedans, mais surtout, comment on s'en sert…
3-2. Les données du référentiel▲
Bien entendu, pour pouvoir fonctionner, un tel modèle doit être garni, c'est-à-dire que toutes les tables doivent être populées. En annexe vous trouverez les ordres SQL pour créer cette base et la peupler.
Ce script et les deux procédures stockées (les procédures ont été écrites pour SQL Server, mais sont facilement transposables dans le langage procédural de votre SGBDR ou dans un langage comme DELPHI) permettent de peupler les tables référentielles, TR_*, mais aussi une partie de la table principale (T_PLN_JOUR_PJR), notamment les colonnes PJR_DATE (date du jour), PAN_ID (année), PMS_ID (mois), PJM_ID (jour du mois de 1 à 31), PSM_ID (semaine de l'année de 1 à 52 ou 53), PJS_ID (jour de la semaine de 1 à 7), PJA_ID (jour de l'année de 1 à 365 ou 366) PTR_ID (trimestre de l'année) et PST_ID (semestre de l'année).
3-3. Les mesures temporelles▲
Intéressons-nous maintenant aux colonnes PJR_RANG_*. (1)
Notre table est composée de lignes possédant chacune une valeur discrète : celle d'un jour, d'une date dans la continuité du temps.
La colonne PJR_RANG_JOUR est donc numérotée en continu de façon à ce que chaque lendemain soit incrémenté d'une unité. Autrement dit, PJR_RANG_JOUR + 1 équivaut à ajouter un jour… Le type de données sous-jacent à PJR_RANG_JOUR est donc un entier.
Mais il faut trouver une origine de numérotation. On peut la fixer arbitrairement, par exemple au 14 octobre 1967 (c'est fou ce qu'on trouve à une date aussi arbitraire que celle-là lorsqu'on cherche sur un moteur du web comme Yahoo ), mais l'habitude est de se caler sur le premier janvier 1900 qui constitue le point d'origine de l'axe des dates et par conséquent le jour 0.
Dès lors nous allons avoir la numérotation suivante :
DATE RANG
---------- -----
1900-01-01 1
1900-01-02 2
1900-01-03 3
...
1900-12-31 365
1901-01-01 366
1901-01-02 367
...
1967-10-14 24757
...
1999-12-31 36523 => autrement dit 365 * 100 + 23 soit 23 années bissextiles !
2000-01-01 36524
...
2030-12-31 47846
Là où notre affaire se complique, c'est pour numéroter chaque jour en fraction de mois, semaine, année…
En fait ce n'est pas très compliqué.
Le premier jour d'une année, par exemple le jour de l'an de 1930 :
- le rang de l'année est 30,0000
- le rang du mois est 30*12 + 1 = 361,0000
- le rang de la semaine est 1571, 000
- le rang du trimestre est 30 * 4 + 1 = 121
- le rang du semestre est 30 * 2 + 1 = 61
Pour le rang de la semaine, c'est un peu plus difficile, car il faut compter le nombre de semaines de chaque année, cela pouvant varier entre 52 et 53… Au passage notez les décimales !
Le lendemain de ce jour, au 2 janvier 1930, les rangs sont les suivants :
- année : 30 + 1/365 (365, nombre de jours de l'année) = 30,0027
- mois : 361 + 1/31 (31, nombre de jours du mois) = 361,0323
- semaine : 1571 + 1/7 (7, nombre de jours de la semaine) = 1571,1429
- trimestre : 121 + 1/90 (90, nombre de jours du trimestre) = 121,0111
- semestre : 61 + 1/181 (181, nombre de jours du semestre) = 61,0055,
etc.
Voici une des requêtes pour calculer ces nouvelles données. Elle concerne la mise à jour du rang de l'année dans la table T_PLN_JOUR_PJR :
Sélectionnez
|
Sélectionnez
|
Sélectionnez
|
Sélectionnez
|
Sélectionnez
|
Sélectionnez
|
Sélectionnez
|
Sélectionnez
|
Sélectionnez
|
Sélectionnez
|
Bien évidemment ces requêtes peuvent être transformées en requêtes de mise à jour dont on trouvera une version en annexe…
Une fois ces données saisies dans notre table, les calculs sur les mesures temporelles deviennent triviaux. Pour nous aider à voir comment cela fonctionne, nous allons ajouter une table de test qui servira pour nos calculs.
Exemple :
Sélectionnez
|
Sélectionnez
|
3-3-1. Le nombre de… jours, mois, années, trimestres, semestres▲
Comment donc obtenir le nombre de… jours, mois, années, trimestres, semestres entre deux dates ?
Le nombre de jours s'obtient par soustraction du rang jour. Mais pour faire cette soustraction il faut deux valeurs de rang jour, donc, deux fois la table T_PLN_JOUR_PJR dans la requête :
Sélectionnez
|
Sélectionnez
|
Bien entendu, les calculs de nombre de mois, années, etc. sont tout aussi triviaux :
Sélectionnez
|
Sélectionnez
|
3-3-2. Comment ajouter exactement un, deux ou trois mois, et pouvoir retomber sur nos dates en les y retranchant ?▲
L'ajout et la soustraction sont presque aussi simples :
Sélectionnez
|
Sélectionnez
|
Sélectionnez
|
Sélectionnez
|
Sélectionnez
|
Sélectionnez
|
NOTEZ la différence entre l'ajout de jours, toujours exact parce que sur des entiers, et l'ajout de mois, année, trimestre, etc. qui, opérant sur des nombres réels doit faire l'objet d'une inéquation pour laquelle on recherche le minimum.
Attention donc au signe de cette inéquation et à l'utilisation de l'agrégat :
AJOUT an, mois, semaine, trimestre, semestre |
condition >= dans la jointure |
condition >= dans la jointure |
RETRAIT an, mois, semaine, trimestre, semestre |
agrégat MAX dans le SELECT |
condition < dans la jointure |
Dernier essai, reprenons l'exemple vu avec SQL Server et ses limites de calculs temporels…
INSERT
INTO
TEST_PLN VALUES
(
'2002-01-31'
, NULL
)
Sélectionnez
|
Sélectionnez
|
Sélectionnez
|
Sélectionnez
|
3-3-3. Toutes les… dates, mois, années … entre deux dates▲
Autre demande qui revient souvent, connaître toutes les dates, les mois, les années entre deux dates :
Sélectionnez
|
Sélectionnez
|
Cela peut paraître inutile… c'est souvent indispensable.
Voici un exemple qui illustre l'absolue nécessité d'une table de dates. Notre service SAV fait des interventions en principe tous les jours. La table et les données associées sont les suivantes et concernent la semaine allant du lundi 4 mars au vendredi 8 mars :
Sélectionnez
|
Sélectionnez
|
Notre directeur du SAV veut savoir quel est le volume des interventions pour chaque jour de la semaine, et effectue ma requête suivante :
Sélectionnez
|
Sélectionnez
|
Surprise ! La semaine ne compterait que 4 jours du lundi au vendredi ??? Où est passé le jeudi 7 ? Il n'existe pas dans la table, car aucune intervention n'a eu lieu ce jour. Il aurait fallu quand même obtenir cette date avec une valeur 0 pour le nombre d'interventions… Quelque chose comme :
NOMBRE SAV_DATE_INTERV
----------- ----------
2 2002-03-04
1 2002-03-05
2 2002-03-06
0 2002-03-07 <= ligne manquante...
3 2002-03-08
Afin de remédier à cette anomalie, il suffit de faire une jointure avec la table des dates :
Sélectionnez
|
Sélectionnez
|
L'erreur est encore plus criante si l'on tente de mesurer la moyenne du nombre d'interventions par jour :
Sélectionnez
|
Sélectionnez
|
Or 8 interventions sur 5 jours, cela représente 1,6 intervention par jour et non 2 ! Une statistique faussée par ce « trou ».
Encore une fois la solution nous est fournie par la jointure sur la table des dates :
Sélectionnez
|
Sélectionnez
|
4. Dates partielles▲
Il arrive que l'on doive stocker des dates dont certaines parties sont inconnues ou imprécises. Plusieurs solutions sont envisageables : l'utilisation de données séparées pour les éléments composant les dates ou bien l'utilisation d'une fourchette de dates.
4-1. Dates composites▲
Cette solution consiste à représenter les dates sous la forme de trois colonnes : AN, MOIS et JOUR. L'important est de spécifier que ces colonnes peuvent être vides… Autrement dit il ne faut pas construire ces colonnes avec l'option NOT NULL.
Exemples :
CREATE
TABLE
T_DATES_PARTIELLES_DTP
(
...
DTP_AN INTEGER
,
DPT_MOIS INTEGER
,
DPT_JOUR INTEGER
)
À l'insertion comme lors des mises à jour, on veillera à ne stocker que les éléments connus des dates.
Voici maintenant comment les requêtes doivent se présenter dans différents cas de figure :
Rechercher les zigzornifles datés du 4/5/1990 :
SELECT
...
FROM
...
WHERE
DPT_JOUR =
4
AND
DPT_MOIS =
5
AND
DPT_AN =
1990
Les zigzornifles datant de mai 1990 :
SELECT
...
FROM
...
WHERE
DPT_MOIS =
5
AND
DPT_AN =
1990
Les zigzornifles de l'an 1990 :
SELECT
...
FROM
...
WHERE
DPT_AN =
1990
Les zigzornifles entre le 4/5/1990 et le 8/7/1990 :
SELECT
...
FROM
...
WHERE
(
COALESCE
(
DPT_JOUR, 0
)
,
COALESCE
(
DPT_MOIS, 0
)
,
COALESCE
(
DPT_AN, 0
))
>=
(
4
, 5
, 1990
)
AND
(
COALESCE
(
DPT_JOUR, 0
)
,
COALESCE
(
DPT_MOIS, 0
)
,
COALESCE
(
DPT_AN, 0
))
<=
(
8
, 7
, 1990
)
Mais si votre SGBDR ne supporte pas le constructeur de lignes valuées, alors il faut écrire un équivalent SQL.
SELECT
...
FROM
...
WHERE
(
COALESCE
(
DPT_JOUR, 0
)
>=
4
OR
(
COALESCE
(
DPT_JOUR, 0
)
=
4
AND
COALESCE
(
DPT_MOIS, 0
)
>=
5
)
OR
(
COALESCE
(
DPT_JOUR, 0
)
=
4
AND
COALESCE
(
DPT_MOIS, 0
)
=
5
AND
COALESCE
(
DPT_AN, 0
))
>=
1990
))
AND
(
COALESCE
(
DPT_JOUR, 0
)
<=
8
OR
(
COALESCE
(
DPT_JOUR, 0
)
=
8
AND
COALESCE
(
DPT_MOIS, 0
)
<=
7
)
OR
(
COALESCE
(
DPT_JOUR, 0
)
=
8
AND
COALESCE
(
DPT_MOIS, 0
)
=
7
AND
COALESCE
(
DPT_AN, 0
))
<=
1990
))
Sur le constructeur de lignes valuées, lire : Constructeur de lignes valuées (ROW VALUE CONSTRUCTOR)
4-2. Fourchette de date▲
La seconde technique consiste à créer deux colonnes de date afin de définir un intervalle. Voici un exemple du modèle de table :
Exemples :
CREATE
TABLE
T_DATES_INCOMPLETE_DTI
(
...
DTI_DATE_MIN DATE
,
DTI_DATE_MAX DATE
)
Si la date est complète, on veillera à la recopier dans les deux colonnes. Pour cela on peut s'aider d'un trigger pour qu'en cas de présence du NULL dans la seconde date on reprenne la valeur de la première date.
À l'insertion, comme à la mise à jour, la partie de date qui est inconnue doit prendre la plage maximale des valeurs. Par exemple si nous voulons insérer un brandouillon daté de juin 1990 sans que nous en connaissions le jour, il convient d'insérer de la manière suivante :
INSERT
INTO
T_DATES_INCOMPLETE_DTI (
..., DTI_DATE_MIN DATE
, DTI_DATE_MAX DATE
)
VALUES
(
..., '1990-06-01'
, '1990-06-30'
)
Voici maintenant comment les requêtes doivent se présenter dans différents cas de figure :
Rechercher les brandouillons datés du 4/5/1990 :
Sélectionnez
|
Sélectionnez
|
Les deux requêtes devant donner les mêmes résultats.
Les brandouillons datant de mai 1990 :
SELECT
...
FROM
...
WHERE
DPI_DATE_MIN >=
'1990-05-01'
AND
DPI_DATE_MAX <=
'1990-05-31'
Les brandouillons de l'an 1990 :
SELECT
...
FROM
...
WHERE
DPI_DATE_MIN >=
'1990-01-01'
AND
DPI_DATE_MAX <=
'1990-12-31'
Les brandouillons entre le 4/5/1990 et le 8/7/1990 :
SELECT
...
FROM
...
WHERE
DPI_DATE_MIN >=
'1990-05-04'
AND
DPI_DATE_MAX <=
'1990-07-08'
4-3. Affinage▲
Ces deux modèles possèdent néanmoins un inconvénient… En effet, si notre utilisateur veut reprendre dans sa requête les bidules situés entre le 4/5/1990 et le 8/7/1990, il convient de se demander si un zirzornifle ou un brandouillon daté de mai 1990 sans précision de jour doit figurer dans le résultat !
Car l'absence de la connaissance du jour ne signifie nullement qu'il est obligatoirement inférieur au 4 mai 1990…
Dans ce cas, que l'on pourrait appeler « critère externe », lever cette ambiguïté peut être fait de différentes manières…
Dans la première représentation (dates composites) une simple modification de la requête suffit :
SELECT
...
FROM
...
WHERE
(
COALESCE
(
DPT_JOUR, 31
)
,
COALESCE
(
DPT_MOIS, 5
)
,
COALESCE
(
DPT_AN, 0
))
>=
(
4
, 5
, 1990
)
AND
(
COALESCE
(
DPT_JOUR, 1
)
,
COALESCE
(
DPT_MOIS, 7
)
,
COALESCE
(
DPT_AN, 0
))
<=
(
8
, 7
, 1990
)
Les valeurs du coalesce sont, dans le premier prédicat de filtrage le dernier jour du mois et le mois de la borne basse du filtre, dans le second prédicat le premier jour du mois et le mois de la borne haute du filtre.
Le problème est plus complexe concernant la seconde représentation. En effet l'indication de l'imprécision de la date nous est donné par le fait que DTI_DATE_MIN est différent de DTI_DATE_MAX.
Il faut alors réaliser une requête plus complexe :
SELECT
...
FROM
...
-- date exacte
WHERE
(
DPI_DATE_MIN >=
'1990-05-04'
AND
DPI_DATE_MAX <=
'1990-07-08'
AND
DPI_DATE_MIN =
DPI_DATE_MAX)
-- date partielle
OR
(
DPI_DATE_MIN >=
'1990-05-01'
AND
DPI_DATE_MAX <=
'1990-07-31'
AND
DPI_DATE_MIN <>
DPI_DATE_MAX)
Dans le second prédicat, on reprend les dates de début de mois de la borne basse et de fin de mois de la borne haute si les dates min et max sont différentes.
5. Conclusion▲
Voici un ensemble de données et ses règles qui marchent de manière irréprochable afin de traiter tous les cas de figure de manipulation portant sur des dates. Bien entendu vous pouvez transformer ces requêtes en procédures stockées, voire en fonction utilisateur si votre SGBDR en est doté.
Mais, peut-être le volume de données à manipuler vous effraye-t-il ?
À titre d'indication, sous SQL server v7 le volume des données pour 30 années de dates (2000 à 2030), soit plus de 11 300 lignes, représente : 6 Mo index compris… Pour 130 années, c'est-à-dire les dates de 1900 à 2030 le volume des données est de 15Mo… (plus de 47 500 lignes). Quand son sait que la clef de cette table est une date et se trouve organisée généralement en cluster, on ne manipule jamais plus de 1 Mo de données si l'on ne fait pas de calculs sur une plage de dates de plus de 5 à 6 ans.
6. ANNEXE - les scripts de création du modèle de planning…▲
/* ============================================================ */
/* Nom de la base : PLN_PLANNING */
/* Nom de SGBD : SQL 2 - standard ISO 1992 */
/* Date de création : 04/08/2002 17:50 */
/* ============================================================ */
-- version avec domaines
CREATE
DOMAIN D_PLN_ANNEE SMALLINT
CONSTRAINT
CKD_ANNEE CHECK
(
VALUE
BETWEEN
1
AND
9999
)
;
CREATE
DOMAIN D_PLN_BOOLEEN bit
(
1
)
;
CREATE
DOMAIN D_PLN_DATE DATE
;
CREATE
DOMAIN D_PLN_ENTIER_AUTOINC INTEGER
CONSTRAINT
CKD_ENTIER CHECK
(
VALUE
>=
1
)
;
CREATE
DOMAIN D_PLN_ENTIER_NAT_LONG INTEGER
CONSTRAINT
CKD_ENTIER_LONG CHECK
(
VALUE
>=
1
)
;
CREATE
DOMAIN D_PLN_HEURE SMALLINT
CONSTRAINT
CKD_HEURE CHECK
(
VALUE
BETWEEN
0
AND
24
)
;
CREATE
DOMAIN D_PLN_HEURE_DECIMALE FLOAT
CONSTRAINT
CKD_HEURE_DEC CHECK
(
VALUE
BETWEEN
0
AND
24
)
;
CREATE
DOMAIN D_PLN_JOUR_AN SMALLINT
CONSTRAINT
CKD_JOUR_AN CHECK
(
VALUE
BETWEEN
1
AND
366
)
;
CREATE
DOMAIN D_PLN_JOUR_MOIS SMALLINT
CONSTRAINT
CKD_JOUR_MOIS CHECK
(
VALUE
BETWEEN
1
AND
31
)
;
CREATE
DOMAIN D_PLN_JOUR_SEMAINE SMALLINT
CONSTRAINT
CKD_JOUR_SEMAINE CHECK
(
VALUE
1
AND
7
)
;
CREATE
DOMAIN D_PLN_LIB_COURT CHAR
(
32
)
;
CREATE
DOMAIN D_PLN_LIB_LONG VARCHAR
(
256
)
;
CREATE
DOMAIN D_PLN_MINUTE SMALLINT
CONSTRAINT
CKD_MINUTE CHECK
(
VALUE
BETWEEN
0
AND
60
)
;
CREATE
DOMAIN D_PLN_MOIS SMALLINT
CONSTRAINT
CKD_MOIS CHECK
(
VALUE
BETWEEN
1
AND
12
)
;
CREATE
DOMAIN D_PLN_SEMAINE SMALLINT
CONSTRAINT
CKD_SEMAINE CHECK
(
VALUE
BETWEEN
1
AND
53
)
;
CREATE
DOMAIN D_PLN_SEMESTRE SMALLINT
CONSTRAINT
CKD_SEMESTRE CHECK
(
VALUE
BETWEEN
1
AND
2
)
;
CREATE
DOMAIN D_PLN_TRIMESTRE SMALLINT
CONSTRAINT
CKD_TRIMESTRE CHECK
(
VALUE
BETWEEN
1
AND
4
)
;
CREATE
DOMAIN D_PLN_RANG DECIMAL
(
10
,4
)
CONSTRAINT
CKD_RANG CHECK
(
VALUE
>
0
)
;
/* ============================================================ */
/* Table : TR_PLN_ANNEE_PAN */
/* ============================================================ */
create
table
TR_PLN_ANNEE_PAN
(
PAN_ID T_D_PLN_ANNEE not
null
,
PAN_BISEXTILE T_D_PLN_BOOLEEN not
null
,
constraint
PK_TR_PLN_ANNEE_PAN primary
key
(
PAN_ID)
)
;
/* ============================================================ */
/* Table : TR_PLN_MOIS_PMS */
/* ============================================================ */
create
table
TR_PLN_MOIS_PMS
(
PMS_ID T_D_PLN_MOIS not
null
,
PMS_NOM_MOIS T_D_PLN_LIB_COURT not
null
,
constraint
PK_TR_PLN_MOIS_PMS primary
key
(
PMS_ID)
)
;
/* ============================================================ */
/* Table : TR_PLN_JOUR_MOIS_PJM */
/* ============================================================ */
create
table
TR_PLN_JOUR_MOIS_PJM
(
PJM_ID T_D_PLN_JOUR_MOIS not
null
,
constraint
PK_TR_PLN_JOUR_MOIS_PJM primary
key
(
PJM_ID)
)
;
/* ============================================================ */
/* Table : TR_PLN_SEMAINE_PSM */
/* ============================================================ */
create
table
TR_PLN_SEMAINE_PSM
(
PSM_ID T_D_PLN_SEMAINE not
null
,
constraint
PK_TR_PLN_SEMAINE_PSM primary
key
(
PSM_ID)
)
;
/* ============================================================ */
/* Table : TR_PLN_JOUR_FERIE_MOBILE_PFM */
/* ============================================================ */
create
table
TR_PLN_JOUR_FERIE_MOBILE_PFM
(
PFM_ID T_D_PLN_ENTIER_AUTOINC not
null
,
PFM_LIBELLE char
(
64
)
not
null
,
constraint
PK_TR_PLN_JOUR_FERIE_MOBILE_PF primary
key
(
PFM_ID)
)
;
/* ============================================================ */
/* Table : TR_PLN_JOUR_SEMAINE_PJS */
/* ============================================================ */
create
table
TR_PLN_JOUR_SEMAINE_PJS
(
PJS_ID T_D_PLN_JOUR_SEMAINE not
null
,
PJS_LIBELLE T_D_PLN_LIB_COURT not
null
,
constraint
PK_TR_PLN_JOUR_SEMAINE_PJS primary
key
(
PJS_ID)
)
;
/* ============================================================ */
/* Table : TR_PLN_JOUR_ANNEE_PJA */
/* ============================================================ */
create
table
TR_PLN_JOUR_ANNEE_PJA
(
PJA_ID T_D_PLN_JOUR_AN not
null
,
constraint
PK_TR_PLN_JOUR_ANNEE_PJA primary
key
(
PJA_ID)
)
;
/* ============================================================ */
/* Table : TR_PLN_TRIMESTRE_PTR */
/* ============================================================ */
create
table
TR_PLN_TRIMESTRE_PTR
(
PTR_ID T_D_PLN_TRIMESTRE not
null
,
PTR_LIBELLE T_D_PLN_LIB_COURT not
null
,
constraint
PK_TR_PLN_TRIMESTRE_PTR primary
key
(
PTR_ID)
)
;
/* ============================================================ */
/* Table : TR_PLN_SEMESTRE_PST */
/* ============================================================ */
create
table
TR_PLN_SEMESTRE_PST
(
PST_ID T_D_PLN_SEMESTRE not
null
,
PST_LIBELLE T_D_PLN_LIB_COURT not
null
,
constraint
PK_TR_PLN_SEMESTRE_PST primary
key
(
PST_ID)
)
;
/* ============================================================ */
/* Table : TR_PLN_INTERVALLE_TRAVAIL_PIT */
/* ============================================================ */
create
table
TR_PLN_INTERVALLE_TRAVAIL_PIT
(
PIT_HEURE_DEBUT T_D_PLN_HEURE not
null
,
PIT_MINUTE_DEBUT T_D_PLN_MINUTE not
null
default
0
,
PIT_HEURE_FIN T_D_PLN_HEURE not
null
,
PIT_MINUTE_FIN T_D_PLN_MINUTE not
null
default
0
,
constraint
PK_TR_PLN_INTERVALLE_TRAVAIL_P primary
key
(
PIT_HEURE_DEBUT, PIT_MINUTE_DEBUT)
)
;
/* ============================================================ */
/* Table : T_PLN_JOUR_PJR */
/* ============================================================ */
create
table
T_PLN_JOUR_PJR
(
PJR_DATE T_D_PLN_DATE not
null
,
PAN_ID T_D_PLN_ANNEE null
,
PMS_ID T_D_PLN_MOIS null
,
PJM_ID T_D_PLN_JOUR_MOIS null
,
PSM_ID T_D_PLN_SEMAINE null
,
PJS_ID T_D_PLN_JOUR_SEMAINE null
,
PJA_ID T_D_PLN_JOUR_AN null
,
PTR_ID T_D_PLN_TRIMESTRE null
,
PST_ID T_D_PLN_SEMESTRE null
,
PJR_RANG_JOUR T_D_PLN_ENTIER_NAT_LONG not
null
,
PJR_RANG_SEMAINE T_D_PLN_RANG null
,
PJR_RANG_MOIS T_D_PLN_RANG null
,
PJR_RANG_TRIMESTRE T_D_PLN_RANG null
,
PJR_RANG_SEMESTRE T_D_PLN_RANG null
,
PJR_RANG_AN T_D_PLN_RANG null
,
PJR_ALEA T_D_PLN_ENTIER_NAT_LONG not
null
,
constraint
PK_T_PLN_JOUR_PJR primary
key
(
PJR_DATE)
)
;
/* ============================================================ */
/* Table : TR_PLN_JOUR_FERIE_FIXE_PJF */
/* ============================================================ */
create
table
TR_PLN_JOUR_FERIE_FIXE_PJF
(
PMS_ID T_D_PLN_MOIS not
null
,
PJM_ID T_D_PLN_JOUR_MOIS not
null
,
PJF_LIBELLE T_D_PLN_LIB_LONG not
null
,
constraint
PK_TR_PLN_JOUR_FERIE_FIXE_PJF primary
key
(
PMS_ID, PJM_ID)
)
;
/* ============================================================ */
/* Table : TJ_PJRPFM */
/* ============================================================ */
create
table
TJ_PJRPFM
(
PJR_DATE T_D_PLN_DATE not
null
,
PFM_ID T_D_PLN_ENTIER_AUTOINC not
null
,
constraint
PK_TJ_PJRPFM primary
key
(
PJR_DATE, PFM_ID)
)
;
/* ============================================================ */
/* Table : TJ_PJSPIT */
/* ============================================================ */
create
table
TJ_PJSPIT
(
PJS_ID T_D_PLN_JOUR_SEMAINE not
null
,
PIT_HEURE_DEBUT T_D_PLN_HEURE not
null
,
PIT_MINUTE_DEBUT T_D_PLN_MINUTE not
null
default
0
,
constraint
PK_TJ_PJSPIT primary
key
(
PJS_ID, PIT_HEURE_DEBUT, PIT_MINUTE_DEBUT)
)
;
alter
table
T_PLN_JOUR_PJR
add
constraint
FK_T_PLN_JO_L_PJRPAN_TR_PLN_A foreign
key
(
PAN_ID)
references
TR_PLN_ANNEE_PAN (
PAN_ID)
;
alter
table
T_PLN_JOUR_PJR
add
constraint
FK_T_PLN_JO_L_PJRPMS_TR_PLN_M foreign
key
(
PMS_ID)
references
TR_PLN_MOIS_PMS (
PMS_ID)
;
alter
table
T_PLN_JOUR_PJR
add
constraint
FK_T_PLN_JO_L_PJRPJM_TR_PLN_J foreign
key
(
PJM_ID)
references
TR_PLN_JOUR_MOIS_PJM (
PJM_ID)
;
alter
table
T_PLN_JOUR_PJR
add
constraint
FK_T_PLN_JO_L_PJRPSM__TR_PLN_S foreign
key
(
PSM_ID)
references
TR_PLN_SEMAINE_PSM (
PSM_ID)
;
alter
table
T_PLN_JOUR_PJR
add
constraint
FK_T_PLN_JO_L_PJRPJS_TR_PLN_J foreign
key
(
PJS_ID)
references
TR_PLN_JOUR_SEMAINE_PJS (
PJS_ID)
;
alter
table
T_PLN_JOUR_PJR
add
constraint
FK_T_PLN_JO_L_PJRPJA_TR_PLN_J foreign
key
(
PJA_ID)
references
TR_PLN_JOUR_ANNEE_PJA (
PJA_ID)
;
alter
table
T_PLN_JOUR_PJR
add
constraint
FK_T_PLN_JO_L_PJRPTR_TR_PLN_T foreign
key
(
PTR_ID)
references
TR_PLN_TRIMESTRE_PTR (
PTR_ID)
;
alter
table
T_PLN_JOUR_PJR
add
constraint
FK_T_PLN_JO_L_PJRPST_TR_PLN_S foreign
key
(
PST_ID)
references
TR_PLN_SEMESTRE_PST (
PST_ID)
;
alter
table
TR_PLN_JOUR_FERIE_FIXE_PJF
add
constraint
FK_TR_PLN_J_L_PJFPJM_TR_PLN_J foreign
key
(
PJM_ID)
references
TR_PLN_JOUR_MOIS_PJM (
PJM_ID)
;
alter
table
TR_PLN_JOUR_FERIE_FIXE_PJF
add
constraint
FK_TR_PLN_J_L_PJFPMS_TR_PLN_M foreign
key
(
PMS_ID)
references
TR_PLN_MOIS_PMS (
PMS_ID)
;
alter
table
TJ_PJRPFM
add
constraint
FK_TJ_PJRPF_L_PJRPFM_T_PLN_JO foreign
key
(
PJR_DATE)
references
T_PLN_JOUR_PJR (
PJR_DATE)
;
alter
table
TJ_PJRPFM
add
constraint
FK_TJ_PJRPF_L_PFMPJR_TR_PLN_J foreign
key
(
PFM_ID)
references
TR_PLN_JOUR_FERIE_MOBILE_PFM (
PFM_ID)
;
alter
table
TJ_PJSPIT
add
constraint
FK_TJ_PJSPI_L_PJSPIT_TR_PLN_J foreign
key
(
PJS_ID)
references
TR_PLN_JOUR_SEMAINE_PJS (
PJS_ID)
;
alter
table
TJ_PJSPIT
add
constraint
FK_TJ_PJSPI_L_PITPJS_TR_PLN_I foreign
key
(
PIT_HEURE_DEBUT, PIT_MINUTE_DEBUT)
references
TR_PLN_INTERVALLE_TRAVAIL_PIT (
PIT_HEURE_DEBUT, PIT_MINUTE_DEBUT)
;
/* n'oubliez pas d'indexer toutes les colonnes constituant clefs primaires et clefs étrangères !!! */
/* ============================================================ */
/* Nom de la base : PLN_PLANNING */
/* Nom de SGBD : SQL 2 - standard ISO 1992 */
/* Date de création : 04/08/2002 17:50 */
/* ============================================================ */
-- version sans les domaines (les contraintes de domaine sont
-- reportées en contraintes de table)
/* ============================================================ */
/* Table : TR_PLN_ANNEE_PAN */
/* ============================================================ */
create
table
TR_PLN_ANNEE_PAN
(
PAN_ID smallint
not
null
constraint
CKC_PAN_ID_TR_PLN_A check
(
PAN_ID between
1
and
9999
)
,
PAN_BISEXTILE bit
not
null
,
constraint
PK_TR_PLN_ANNEE_PAN primary
key
(
PAN_ID)
)
;
/* ============================================================ */
/* Table : TR_PLN_MOIS_PMS */
/* ============================================================ */
create
table
TR_PLN_MOIS_PMS
(
PMS_ID smallint
not
null
constraint
CKC_PMS_ID_TR_PLN_M check
(
PMS_ID between
1
and
12
)
,
PMS_NOM_MOIS char
(
32
)
not
null
,
constraint
PK_TR_PLN_MOIS_PMS primary
key
(
PMS_ID)
)
;
/* ============================================================ */
/* Table : TR_PLN_JOUR_MOIS_PJM */
/* ============================================================ */
create
table
TR_PLN_JOUR_MOIS_PJM
(
PJM_ID smallint
not
null
constraint
CKC_PJM_ID_TR_PLN_J check
(
PJM_ID between
1
and
31
)
,
constraint
PK_TR_PLN_JOUR_MOIS_PJM primary
key
(
PJM_ID)
)
;
/* ============================================================ */
/* Table : TR_PLN_SEMAINE_PSM */
/* ============================================================ */
create
table
TR_PLN_SEMAINE_PSM
(
PSM_ID smallint
not
null
constraint
CKC_PSM_ID_TR_PLN_S check
(
PSM_ID between
1
and
53
)
,
constraint
PK_TR_PLN_SEMAINE_PSM primary
key
(
PSM_ID)
)
;
/* ============================================================ */
/* Table : TR_PLN_JOUR_FERIE_MOBILE_PFM */
/* ============================================================ */
create
table
TR_PLN_JOUR_FERIE_MOBILE_PFM
(
PFM_ID integer
not
null
constraint
CKC_PFM_ID_TR_PLN_J check
(
PFM_ID >=
1
)
,
PFM_LIBELLE char
(
64
)
not
null
,
constraint
PK_TR_PLN_JOUR_FERIE_MOBILE_PF primary
key
(
PFM_ID)
)
;
/* ============================================================ */
/* Table : TR_PLN_JOUR_SEMAINE_PJS */
/* ============================================================ */
create
table
TR_PLN_JOUR_SEMAINE_PJS
(
PJS_ID smallint
not
null
constraint
CKC_PJS_ID_TR_PLN_J check
(
PJS_ID between
1
and
7
)
,
PJS_LIBELLE char
(
32
)
not
null
,
constraint
PK_TR_PLN_JOUR_SEMAINE_PJS primary
key
(
PJS_ID)
)
;
/* ============================================================ */
/* Table : TR_PLN_JOUR_ANNEE_PJA */
/* ============================================================ */
create
table
TR_PLN_JOUR_ANNEE_PJA
(
PJA_ID smallint
not
null
constraint
CKC_PJA_ID_TR_PLN_J check
(
PJA_ID between
1
and
366
)
,
constraint
PK_TR_PLN_JOUR_ANNEE_PJA primary
key
(
PJA_ID)
)
;
/* ============================================================ */
/* Table : TR_PLN_TRIMESTRE_PTR */
/* ============================================================ */
create
table
TR_PLN_TRIMESTRE_PTR
(
PTR_ID smallint
not
null
constraint
CKC_PTR_ID_TR_PLN_T check
(
PTR_ID between
1
and
4
)
,
PTR_LIBELLE char
(
32
)
not
null
,
constraint
PK_TR_PLN_TRIMESTRE_PTR primary
key
(
PTR_ID)
)
;
/* ============================================================ */
/* Table : TR_PLN_SEMESTRE_PST */
/* ============================================================ */
create
table
TR_PLN_SEMESTRE_PST
(
PST_ID smallint
not
null
constraint
CKC_PST_ID_TR_PLN_S check
(
PST_ID between
1
and
2
)
,
PST_LIBELLE char
(
32
)
not
null
,
constraint
PK_TR_PLN_SEMESTRE_PST primary
key
(
PST_ID)
)
;
/* ============================================================ */
/* Table : TR_PLN_INTERVALLE_TRAVAIL_PIT */
/* ============================================================ */
create
table
TR_PLN_INTERVALLE_TRAVAIL_PIT
(
PIT_HEURE_DEBUT smallint
not
null
constraint
CKC_PIT_HEURE_DEBUT_TR_PLN_I check
(
PIT_HEURE_DEBUT between
0
and
24
)
,
PIT_MINUTE_DEBUT smallint
not
null
default
0
constraint
CKC_PIT_MINUTE_DEBUT_TR_PLN_I check
(
PIT_MINUTE_DEBUT between
0
and
60
)
,
PIT_HEURE_FIN smallint
not
null
constraint
CKC_PIT_HEURE_FIN_TR_PLN_I check
(
PIT_HEURE_FIN between
0
and
24
)
,
PIT_MINUTE_FIN smallint
not
null
default
0
constraint
CKC_PIT_MINUTE_FIN_TR_PLN_I check
(
PIT_MINUTE_FIN between
0
and
60
)
,
constraint
PK_TR_PLN_INTERVALLE_TRAVAIL_P primary
key
(
PIT_HEURE_DEBUT, PIT_MINUTE_DEBUT)
)
;
/* ============================================================ */
/* Table : T_PLN_JOUR_PJR */
/* ============================================================ */
create
table
T_PLN_JOUR_PJR
(
PJR_DATE datetime
not
null
,
PAN_ID smallint
null
constraint
CKC_PAN_ID_T_PLN_JO check
(
PAN_ID between
1
and
9999
)
,
PMS_ID smallint
null
constraint
CKC_PMS_ID_T_PLN_JO check
(
PMS_ID between
1
and
12
)
,
PJM_ID smallint
null
constraint
CKC_PJM_ID_T_PLN_JO check
(
PJM_ID between
1
and
31
)
,
PSM_ID smallint
null
constraint
CKC_PSM_ID_T_PLN_JO check
(
PSM_ID between
1
and
53
)
,
PJS_ID smallint
null
constraint
CKC_PJS_ID_T_PLN_JO check
(
PJS_ID between
1
and
7
)
,
PJA_ID smallint
null
constraint
CKC_PJA_ID_T_PLN_JO check
(
PJA_ID between
1
and
366
)
,
PTR_ID smallint
null
constraint
CKC_PTR_ID_T_PLN_JO check
(
PTR_ID between
1
and
4
)
,
PST_ID smallint
null
constraint
CKC_PST_ID_T_PLN_JO check
(
PST_ID between
1
and
2
)
,
PJR_RANG int
not
null
constraint
CKC_PJR_RANG_T_PLN_JO check
(
PJR_RANG >=
1
)
,
PJR_RANG_SEMAINE decimal
(
10
,4
)
null
,
PJR_RANG_MOIS decimal
(
10
,4
)
null
,
PJR_RANG_TRIMESTRE decimal
(
10
,4
)
null
,
PJR_RANG_SEMESTRE decimal
(
10
,4
)
null
,
PJR_RANG_AN decimal
(
10
,4
)
null
,
PJR_ALEA int
not
null
constraint
CKC_PJR_ALEA_T_PLN_JO check
(
PJR_ALEA >=
1
)
,
constraint
PK_T_PLN_JOUR_PJR primary
key
(
PJR_DATE)
)
;
/* ============================================================ */
/* Table : TR_PLN_JOUR_FERIE_FIXE_PJF */
/* ============================================================ */
create
table
TR_PLN_JOUR_FERIE_FIXE_PJF
(
PMS_ID smallint
not
null
constraint
CKC_PMS_ID_TR_PLN_J check
(
PMS_ID between
1
and
12
)
,
PJM_ID smallint
not
null
constraint
CKC_PJM_ID_TR_PLN_J check
(
PJM_ID between
1
and
31
)
,
PJF_LIBELLE varchar
(
256
)
not
null
,
constraint
PK_TR_PLN_JOUR_FERIE_FIXE_PJF primary
key
(
PMS_ID, PJM_ID)
)
;
/* ============================================================ */
/* Table : TJ_PJRPFM */
/* ============================================================ */
create
table
TJ_PJRPFM
(
PJR_DATE datetime
not
null
,
PFM_ID integer
not
null
constraint
CKC_PFM_ID_TJ_PJRPF check
(
PFM_ID >=
1
)
,
constraint
PK_TJ_PJRPFM primary
key
(
PJR_DATE, PFM_ID)
)
;
/* ============================================================ */
/* Table : TJ_PJSPIT */
/* ============================================================ */
create
table
TJ_PJSPIT
(
PJS_ID smallint
not
null
constraint
CKC_PJS_ID_TJ_PJSPI check
(
PJS_ID between
1
and
7
)
,
PIT_HEURE_DEBUT smallint
not
null
constraint
CKC_PIT_HEURE_DEBUT_TJ_PJSPI check
(
PIT_HEURE_DEBUT between
0
and
24
)
,
PIT_MINUTE_DEBUT smallint
not
null
default
0
constraint
CKC_PIT_MINUTE_DEBUT_TJ_PJSPI check
(
PIT_MINUTE_DEBUT between
0
and
60
)
,
constraint
PK_TJ_PJSPIT primary
key
(
PJS_ID, PIT_HEURE_DEBUT, PIT_MINUTE_DEBUT)
)
;
alter
table
T_PLN_JOUR_PJR
add
constraint
FK_T_PLN_JO_L_PJRPAN_TR_PLN_A foreign
key
(
PAN_ID)
references
TR_PLN_ANNEE_PAN (
PAN_ID)
;
alter
table
T_PLN_JOUR_PJR
add
constraint
FK_T_PLN_JO_L_PJRPMS_TR_PLN_M foreign
key
(
PMS_ID)
references
TR_PLN_MOIS_PMS (
PMS_ID)
;
alter
table
T_PLN_JOUR_PJR
add
constraint
FK_T_PLN_JO_L_PJRPJM_TR_PLN_J foreign
key
(
PJM_ID)
references
TR_PLN_JOUR_MOIS_PJM (
PJM_ID)
;
alter
table
T_PLN_JOUR_PJR
add
constraint
FK_T_PLN_JO_L_PJRPSM__TR_PLN_S foreign
key
(
PSM_ID)
references
TR_PLN_SEMAINE_PSM (
PSM_ID)
;
alter
table
T_PLN_JOUR_PJR
add
constraint
FK_T_PLN_JO_L_PJRPJS_TR_PLN_J foreign
key
(
PJS_ID)
references
TR_PLN_JOUR_SEMAINE_PJS (
PJS_ID)
;
alter
table
T_PLN_JOUR_PJR
add
constraint
FK_T_PLN_JO_L_PJRPJA_TR_PLN_J foreign
key
(
PJA_ID)
references
TR_PLN_JOUR_ANNEE_PJA (
PJA_ID)
;
alter
table
T_PLN_JOUR_PJR
add
constraint
FK_T_PLN_JO_L_PJRPTR_TR_PLN_T foreign
key
(
PTR_ID)
references
TR_PLN_TRIMESTRE_PTR (
PTR_ID)
;
alter
table
T_PLN_JOUR_PJR
add
constraint
FK_T_PLN_JO_L_PJRPST_TR_PLN_S foreign
key
(
PST_ID)
references
TR_PLN_SEMESTRE_PST (
PST_ID)
;
alter
table
TR_PLN_JOUR_FERIE_FIXE_PJF
add
constraint
FK_TR_PLN_J_L_PJFPJM_TR_PLN_J foreign
key
(
PJM_ID)
references
TR_PLN_JOUR_MOIS_PJM (
PJM_ID)
;
alter
table
TR_PLN_JOUR_FERIE_FIXE_PJF
add
constraint
FK_TR_PLN_J_L_PJFPMS_TR_PLN_M foreign
key
(
PMS_ID)
references
TR_PLN_MOIS_PMS (
PMS_ID)
;
alter
table
TJ_PJRPFM
add
constraint
FK_TJ_PJRPF_L_PJRPFM_T_PLN_JO foreign
key
(
PJR_DATE)
references
T_PLN_JOUR_PJR (
PJR_DATE)
;
alter
table
TJ_PJRPFM
add
constraint
FK_TJ_PJRPF_L_PFMPJR_TR_PLN_J foreign
key
(
PFM_ID)
references
TR_PLN_JOUR_FERIE_MOBILE_PFM (
PFM_ID)
;
alter
table
TJ_PJSPIT
add
constraint
FK_TJ_PJSPI_L_PJSPIT_TR_PLN_J foreign
key
(
PJS_ID)
references
TR_PLN_JOUR_SEMAINE_PJS (
PJS_ID)
;
alter
table
TJ_PJSPIT
add
constraint
FK_TJ_PJSPI_L_PITPJS_TR_PLN_I foreign
key
(
PIT_HEURE_DEBUT, PIT_MINUTE_DEBUT)
references
TR_PLN_INTERVALLE_TRAVAIL_PIT (
PIT_HEURE_DEBUT, PIT_MINUTE_DEBUT)
;
/* n'oubliez pas d'indexer toutes les colonnes constituant clefs primaires et clefs étrangères !!! */
/* ============================================================ */
/* ALIMENTATION DES REFERENCES DE LA BASE PLANNING */
/* ============================================================ */
/* ============================================================ */
/* ALIMENTATION DES REFERENCES DE LA BASE PLANNING */
/* ============================================================ */
/* insertion des données table TR_PLN_MOIS_PMS */
INSERT
INTO
TR_PLN_MOIS_PMS VALUES
(
1
, 'janvier'
)
INSERT
INTO
TR_PLN_MOIS_PMS VALUES
(
2
, 'février'
)
INSERT
INTO
TR_PLN_MOIS_PMS VALUES
(
3
, 'mars'
)
INSERT
INTO
TR_PLN_MOIS_PMS VALUES
(
4
, 'avril'
)
INSERT
INTO
TR_PLN_MOIS_PMS VALUES
(
5
, 'mai'
)
INSERT
INTO
TR_PLN_MOIS_PMS VALUES
(
6
, 'juin'
)
INSERT
INTO
TR_PLN_MOIS_PMS VALUES
(
7
, 'juillet'
)
INSERT
INTO
TR_PLN_MOIS_PMS VALUES
(
8
, 'août'
)
INSERT
INTO
TR_PLN_MOIS_PMS VALUES
(
9
, 'septembre'
)
INSERT
INTO
TR_PLN_MOIS_PMS VALUES
(
10
, 'octobre'
)
INSERT
INTO
TR_PLN_MOIS_PMS VALUES
(
11
, 'novembre'
)
INSERT
INTO
TR_PLN_MOIS_PMS VALUES
(
12
, 'décembre'
)
/* insertion des données table TR_PLN_JOUR_SEMAINE_JSM */
INSERT
INTO
TR_PLN_JOUR_SEMAINE_PJS VALUES
(
1
, 'lundi'
)
INSERT
INTO
TR_PLN_JOUR_SEMAINE_PJS VALUES
(
2
, 'mardi'
)
INSERT
INTO
TR_PLN_JOUR_SEMAINE_PJS VALUES
(
3
, 'mercredi'
)
INSERT
INTO
TR_PLN_JOUR_SEMAINE_PJS VALUES
(
4
, 'jeudi'
)
INSERT
INTO
TR_PLN_JOUR_SEMAINE_PJS VALUES
(
5
, 'vendredi'
)
INSERT
INTO
TR_PLN_JOUR_SEMAINE_PJS VALUES
(
6
, 'samedi'
)
INSERT
INTO
TR_PLN_JOUR_SEMAINE_PJS VALUES
(
7
, 'dimanche'
)
/* insertion des données table TR_PLN_JOUR_MOIS_PJM */
INSERT
INTO
TR_PLN_JOUR_MOIS_PJM VALUES
(
1
)
INSERT
INTO
TR_PLN_JOUR_MOIS_PJM VALUES
(
2
)
INSERT
INTO
TR_PLN_JOUR_MOIS_PJM VALUES
(
3
)
INSERT
INTO
TR_PLN_JOUR_MOIS_PJM VALUES
(
4
)
INSERT
INTO
TR_PLN_JOUR_MOIS_PJM VALUES
(
5
)
INSERT
INTO
TR_PLN_JOUR_MOIS_PJM VALUES
(
6
)
INSERT
INTO
TR_PLN_JOUR_MOIS_PJM VALUES
(
7
)
INSERT
INTO
TR_PLN_JOUR_MOIS_PJM VALUES
(
8
)
INSERT
INTO
TR_PLN_JOUR_MOIS_PJM VALUES
(
9
)
INSERT
INTO
TR_PLN_JOUR_MOIS_PJM VALUES
(
10
)
INSERT
INTO
TR_PLN_JOUR_MOIS_PJM VALUES
(
11
)
INSERT
INTO
TR_PLN_JOUR_MOIS_PJM VALUES
(
12
)
INSERT
INTO
TR_PLN_JOUR_MOIS_PJM VALUES
(
13
)
INSERT
INTO
TR_PLN_JOUR_MOIS_PJM VALUES
(
14
)
INSERT
INTO
TR_PLN_JOUR_MOIS_PJM VALUES
(
15
)
INSERT
INTO
TR_PLN_JOUR_MOIS_PJM VALUES
(
16
)
INSERT
INTO
TR_PLN_JOUR_MOIS_PJM VALUES
(
17
)
INSERT
INTO
TR_PLN_JOUR_MOIS_PJM VALUES
(
18
)
INSERT
INTO
TR_PLN_JOUR_MOIS_PJM VALUES
(
19
)
INSERT
INTO
TR_PLN_JOUR_MOIS_PJM VALUES
(
20
)
INSERT
INTO
TR_PLN_JOUR_MOIS_PJM VALUES
(
21
)
INSERT
INTO
TR_PLN_JOUR_MOIS_PJM VALUES
(
22
)
INSERT
INTO
TR_PLN_JOUR_MOIS_PJM VALUES
(
23
)
INSERT
INTO
TR_PLN_JOUR_MOIS_PJM VALUES
(
24
)
INSERT
INTO
TR_PLN_JOUR_MOIS_PJM VALUES
(
25
)
INSERT
INTO
TR_PLN_JOUR_MOIS_PJM VALUES
(
26
)
INSERT
INTO
TR_PLN_JOUR_MOIS_PJM VALUES
(
27
)
INSERT
INTO
TR_PLN_JOUR_MOIS_PJM VALUES
(
28
)
INSERT
INTO
TR_PLN_JOUR_MOIS_PJM VALUES
(
29
)
INSERT
INTO
TR_PLN_JOUR_MOIS_PJM VALUES
(
30
)
INSERT
INTO
TR_PLN_JOUR_MOIS_PJM VALUES
(
31
)
/* insertion des données table TR_PLN_JOUR_FERIE_MOBILE_PFM */
INSERT
INTO
TR_PLN_JOUR_FERIE_MOBILE_PFM (
PFM_ID, PFM_LIBELLE)
VALUES
(
1
, 'Pâques'
)
INSERT
INTO
TR_PLN_JOUR_FERIE_MOBILE_PFM (
PFM_ID, PFM_LIBELLE)
VALUES
(
2
, 'Ascension'
)
INSERT
INTO
TR_PLN_JOUR_FERIE_MOBILE_PFM (
PFM_ID, PFM_LIBELLE)
VALUES
(
3
, 'Pentecôte'
)
/* insertion des données table TR_PLN_JOUR_FERIE_FIXE_PJF */
INSERT
INTO
TR_PLN_JOUR_FERIE_FIXE_PJF VALUES
(
1
, 1
, 'jour de l''an'
)
INSERT
INTO
TR_PLN_JOUR_FERIE_FIXE_PJF VALUES
(
5
, 1
, 'fête du travail'
)
INSERT
INTO
TR_PLN_JOUR_FERIE_FIXE_PJF VALUES
(
5
, 8
, 'armistice 1945'
)
INSERT
INTO
TR_PLN_JOUR_FERIE_FIXE_PJF VALUES
(
7
, 14
, 'fête nationale'
)
INSERT
INTO
TR_PLN_JOUR_FERIE_FIXE_PJF VALUES
(
8
, 15
, 'Assomption'
)
INSERT
INTO
TR_PLN_JOUR_FERIE_FIXE_PJF VALUES
(
11
, 1
, 'Toussaint (fête des morts)'
)
INSERT
INTO
TR_PLN_JOUR_FERIE_FIXE_PJF VALUES
(
11
, 11
, 'armistice de 1918'
)
INSERT
INTO
TR_PLN_JOUR_FERIE_FIXE_PJF VALUES
(
12
, 25
, 'Noël'
)
/* insertion des données table TR_PLN_TRIMESTRE_PTR */
INSERT
INTO
TR_PLN_TRIMESTRE_PTR VALUES
(
1
,'Premier trimestre'
)
INSERT
INTO
TR_PLN_TRIMESTRE_PTR VALUES
(
2
,'Second trimestre'
)
INSERT
INTO
TR_PLN_TRIMESTRE_PTR VALUES
(
3
,'Troisième trimestre'
)
INSERT
INTO
TR_PLN_TRIMESTRE_PTR VALUES
(
4
,'Quatrième trimestre'
)
/* insertion des données table TR_PLN_SEMESTRE_PST */
INSERT
INTO
TR_PLN_SEMESTRE_PST VALUES
(
1
,'Premier semestre'
)
INSERT
INTO
TR_PLN_SEMESTRE_PST VALUES
(
2
,'Second semestre'
)
/****************************************************************************
SCRIPTS TRANSACT SQL DES PROCÉDURES STOCKÉES POUR ALIMENTATION DES DONNÉES
****************************************************************************/
CREATE
PROCEDURE
SP_PLN_CREATE_ONE_YEAR @AN INTEGER
AS
/*****************************************************************************
TITRE : PROCEDURE STOCKÉE MS SQL Server "SP_PLN_CREATE_ONE_YEAR"
AUTEUR : Frédéric BROUARD - 2002-09-06
ARGUMENT : @AN INTEGER entier représentant l'année du calendrier dont les
dates sont à stocker
APPELÉE : par la procédure stockée SP_PLN_CREATE_YEAR qui vérifie les
années à créer
******************************************************************************/
-- NOTA les dates sont calculées pour une seule année en débordant
-- éventuellement de quelques jours pour stocker des semaines entières
DECLARE
@JOUR DATETIME
DECLARE
@JOUR_DE_LAN DATETIME
DECLARE
@JOUR_DEBUT DATETIME
DECLARE
@JOUR_FIN DATETIME
DECLARE
@i integer
DECLARE
@JOUR_FIN_SEMAINE DATETIME
DECLARE
@leapYear bit
DECLARE
@ALEA INTEGER
SET
NOCOUNT ON
-- indique de commencer la numérotation des jours de semaine à lundi
SET
DATEFIRST 1
-- foamt ISO des dates
SET
DATEFORMAT YMD
SET
TRANSACTION
ISOLATION
LEVEL
SERIALIZABLE
BEGIN
TRANSACTION
TRAN_INS_DATES
-- création des dates
SET
@JOUR_DE_LAN =
CAST
(
CAST
(
@AN AS
VARCHAR
(
4
))
+
'-01-01'
AS
DATETIME
)
SET
@JOUR_DEBUT =
DATEADD(
DAY
, -
6
, @JOUR_DE_LAN)
-- soit le 26 décembre an - 1
SET
@JOUR_FIN =
CAST
(
CAST
(
@AN +
1
AS
VARCHAR
(
4
))
+
'-01-01'
AS
DATETIME
)
SET
@JOUR_FIN =
DATEADD(
DAY
, 5
, @JOUR_FIN)
-- soit le 6 janvier an + 1
SET
@JOUR =
@JOUR_DEBUT
-- vérification de présence des années pour débordement des dates
-- l'année avant @AN existe-t-elle ?
SET
@i =
DATEPART(
YEAR
, @JOUR_DEBUT)
IF
NOT
EXISTS
(
SELECT
*
FROM
TR_PLN_ANNEE_PAN
WHERE
PAN_ID =
@i)
BEGIN
IF
@i %
4
=
0
SET
@leapYear =
1
ELSE
SET
@leapYear =
0
INSERT
INTO
TR_PLN_ANNEE_PAN
VALUES
(
@i, @leapYear)
IF
@@ERROR
<>
0
BEGIN
GOTO
LBL_ERROR RETURN
END
END
-- l'année après @AN existe-t-elle ?
SET
@i =
DATEPART(
YEAR
, @JOUR_FIN)
IF
NOT
EXISTS
(
SELECT
*
FROM
TR_PLN_ANNEE_PAN
WHERE
PAN_ID =
@i)
BEGIN
IF
@i %
4
=
0
SET
@leapYear =
1
ELSE
SET
@leapYear =
0
INSERT
INTO
TR_PLN_ANNEE_PAN
VALUES
(
@i, @leapYear)
IF
@@ERROR
<>
0
BEGIN
GOTO
LBL_ERROR RETURN
END
END
-- création de l'année @AN
IF
NOT
EXISTS
(
SELECT
*
FROM
TR_PLN_ANNEE_PAN
WHERE
PAN_ID =
@AN)
BEGIN
IF
@AN %
4
=
0
SET
@leapYear =
1
ELSE
SET
@leapYear =
0
INSERT
INTO
TR_PLN_ANNEE_PAN
VALUES
(
@AN, @leapYear)
IF
@@ERROR
<>
0
BEGIN
GOTO
LBL_ERROR RETURN
END
END
WHILE
@JOUR <
@JOUR_FIN
BEGIN
SET
@ALEA =
RAND
()
*
100000
IF
NOT
EXISTS
(
SELECT
*
FROM
T_PLN_JOUR_PJR
WHERE
PJR_DATE =
@JOUR)
BEGIN
INSERT
INTO
T_PLN_JOUR_PJR (
PJR_DATE, PAN_ID, PMS_ID, PJM_ID, PJS_ID, PJA_ID, PJR_RANG_JOUR, PJR_ALEA)
VALUES
(
@JOUR,
DATEPART(
YEAR
, @JOUR)
,
DATEPART(
MONTH
, @JOUR)
,
DATEPART(
DAY
, @JOUR)
,
DATEPART(
WEEKDAY
, @JOUR)
,
DATEPART(
DAYOFYEAR
, @JOUR)
,
CAST
(
@JOUR AS
INTEGER
)
,
@ALEA)
IF
@@ERROR
<>
0
BEGIN
GOTO
LBL_ERROR RETURN
END
END
SET
@JOUR =
DATEADD(
DAY
, 1
, @JOUR)
END
/* Update des trimestres et semestres */
UPDATE
T_PLN_JOUR_PJR
SET
PTR_ID =
1
, PST_ID =
1
WHERE
DATEPART(
MONTH
, PJR_DATE)
BETWEEN
1
AND
3
AND
PAN_ID =
@AN AND
PTR_ID IS
NULL
IF
@@ERROR
<>
0
BEGIN
GOTO
LBL_ERROR RETURN
END
UPDATE
T_PLN_JOUR_PJR
SET
PTR_ID =
2
, PST_ID =
1
WHERE
DATEPART(
MONTH
, PJR_DATE)
BETWEEN
4
AND
6
AND
PAN_ID =
@AN AND
PTR_ID IS
NULL
IF
@@ERROR
<>
0
BEGIN
GOTO
LBL_ERROR RETURN
END
UPDATE
T_PLN_JOUR_PJR
SET
PTR_ID =
3
, PST_ID =
2
WHERE
DATEPART(
MONTH
, PJR_DATE)
BETWEEN
7
AND
9
AND
PAN_ID =
@AN AND
PTR_ID IS
NULL
IF
@@ERROR
<>
0
BEGIN
GOTO
LBL_ERROR RETURN
END
UPDATE
T_PLN_JOUR_PJR
SET
PTR_ID =
4
, PST_ID =
2
WHERE
DATEPART(
MONTH
, PJR_DATE)
BETWEEN
10
AND
12
AND
PAN_ID =
@AN AND
PTR_ID IS
NULL
IF
@@ERROR
<>
0
BEGIN
GOTO
LBL_ERROR RETURN
END
-- création des semaines de l'année
-- calage premier jour de semaine
SET
@JOUR =
@JOUR_DE_LAN
IF
DATEPART(
WEEKDAY
, @JOUR)
>
4
BEGIN
WHILE
DATEPART(
WEEKDAY
, @JOUR)
<>
1
SET
@JOUR =
DATEADD(
DAY
, +
1
, @JOUR)
END
ELSE
BEGIN
WHILE
DATEPART(
WEEKDAY
, @JOUR)
<>
1
SET
@JOUR =
DATEADD(
DAY
, -
1
, @JOUR)
END
SET
@i =
1
WHILE
DATEADD(
DAY
, 7
, @JOUR)
<
@JOUR_FIN
BEGIN
UPDATE
T_PLN_JOUR_PJR
SET
PSM_ID =
@i
WHERE
PJR_DATE BETWEEN
@JOUR AND
DATEADD(
DAY
, 6
, @JOUR)
IF
@@ERROR
<>
0
BEGIN
GOTO
LBL_ERROR RETURN
END
SET
@JOUR =
DATEADD(
DAY
, 7
, @JOUR)
SET
@i =
@i +
1
END
SET
NOCOUNT OFF
COMMIT
TRANSACTION
TRAN_INS_DATES
RETURN
LBL_ERROR:
ROLLBACK
TRANSACTION
TRAN_INS_DATES
GO
/******************************************************************************
TITRE : PROCEDURE STOCKÉE MS SQL Server "SP_PLN_CREATE_YEAR"
AUTEUR : Frédéric BROUARD - 2002-09-06
ARGUMENT : @AN INTEGER entier dans les limites 1900 / 200 représentant
l'année du calendrier dont les dates sont à stocker
APPELS : la procédure stockée SP_PLN_CREATE_ONE_YEAR qui créé une année
de date
******************************************************************************/
CREATE
PROCEDURE
SP_PLN_CREATE_YEAR @AN INTEGER
AS
-- alimente les tables :
-- TR_PLN_JOUR_ANNEE_PJA
-- TR_PLN_ANNEE_PAN
-- TR_PLN_JOUR_PJR
-- TR_PLN_SEMAINE_PSM
-- vérifaction des limites d'utilisation
-- limite de calcul : année comprise entre 1601 et 2399
IF
@AN <
1901
OR
@AN >
2099
BEGIN
DECLARE
@TXT_ERROR VARCHAR
(
300
)
SET
@TXT_ERROR =
'Il n''est pas possible de calculer le calendrier pour des années hors de la plage 1900 / 2100.'
+
' En l''occurrence vous avez tenté de calculer les dates de l''année %d.'
RAISERROR (
@TXT_ERROR, 16
, 1
, @AN)
RETURN
END
--DECLARE @JOUR_DE_LAN DATETIME
DECLARE
@MIN_AN INTEGER
DECLARE
@MAX_AN INTEGER
DECLARE
@i integer
--DECLARE @JOUR_FIN_SEMAINE DATETIME
--DECLARE @leapYear bit
SET
NOCOUNT ON
SET
DATEFORMAT YMD
--SET @JOUR_DE_LAN = CAST(CAST(@AN AS VARCHAR(4)) + '-01-01' AS DATETIME)
SET
TRANSACTION
ISOLATION
LEVEL
SERIALIZABLE
BEGIN
TRANSACTION
-- création des jours de l'année de 1 à 366 si cela n'est pas déjà fait.
IF
NOT
EXISTS
(
SELECT
1
FROM
TR_PLN_JOUR_ANNEE_PJA
HAVING
COUNT
(*)
=
366
)
BEGIN
SET
@i =
1
WHILE
@i <=
366
BEGIN
INSERT
INTO
TR_PLN_JOUR_ANNEE_PJA
VALUES
(
@i)
IF
@@ERROR
<>
0
BEGIN
ROLLBACK
RETURN
END
SET
@i =
@i +
1
END
END
-- création des numéros de semaine de 1 à 53 si cela n'est pas déjà fait.
IF
NOT
EXISTS
(
SELECT
1
FROM
TR_PLN_SEMAINE_PSM
HAVING
COUNT
(*)
=
53
)
BEGIN
SET
@i =
1
WHILE
@i <=
53
BEGIN
INSERT
INTO
TR_PLN_SEMAINE_PSM
VALUES
(
@i)
IF
@@ERROR
<>
0
BEGIN
ROLLBACK
RETURN
END
SET
@i =
@i +
1
END
END
COMMIT
TRANSACTION
-- l'année à créer a-t-elle déjà été créée ?
IF
EXISTS
(
SELECT
1
FROM
T_PLN_JOUR_PJR
GROUP
BY
PJR_DATE
HAVING
COUNT
(*)
>=
365
)
-- oui, alors retour
RETURN
-- non, alors on regarde si d'autres années ont été créées
SELECT
@MIN_AN =
MIN
(
PAN_ID)
, @MAX_AN =
MAX
(
PAN_ID)
FROM
T_PLN_JOUR_PJR
GROUP
BY
PAN_ID
HAVING
COUNT
(*)
>=
365
-- le calendrier est vide : une seule année est à créer
IF
@MIN_AN IS
NULL
AND
@MAX_AN IS
NULL
BEGIN
EXEC
SP_PLN_CREATE_ONE_YEAR @AN
RETURN
END
-- le calendrier est déjà rempli et l'année à créer si situe
-- avant les années déjà créées
IF
@AN <
@MIN_AN
BEGIN
WHILE
@AN <
@MIN_AN
BEGIN
EXEC
SP_PLN_CREATE_ONE_YEAR @AN
SET
@AN =
@AN +
1
END
RETURN
END
-- le calendrier est déjà rempli et l'année à créer si situe
-- après les années déjà créées
IF
@AN >
@MAX_AN
BEGIN
WHILE
@AN >
@MIN_AN
BEGIN
EXEC
SP_PLN_CREATE_ONE_YEAR @AN
SET
@AN =
@AN -
1
END
RETURN
END
GO
Exemple d'appel du script :
SP_PLN_CREATE_YEAR 2000 => création des dates de l'année 2000 du 26/12/1999 au 6/1/2001
SP_PLN_CREATE_YEAR 2010 => création des dates de toutes les années entre 2001 et 2010
REQUÊTE DE MISE A JOUR DES RANGS
********************************
-- maj du rang des années
UPDATE
T_PLN_JOUR_PJR
SET
PJR_RANG_AN =
CAST
(
(
CAST
(
PAN_ID AS
FLOAT
)
-
1899
.0
)
+
(
CAST
(
PJR_RANG_JOUR AS
FLOAT
)
-
(
SELECT
CAST
(
PJR2.PJR_RANG_JOUR AS
FLOAT
)
FROM
T_PLN_JOUR_PJR PJR2
WHERE
PJR2.PJM_ID =
1
AND
PJR2.PMS_ID =
1
AND
PJR2.PAN_ID =
PJR.PAN_ID +
1
))
/
(
SELECT
CAST
(
COUNT
(*)
AS
FLOAT
)
FROM
T_PLN_JOUR_PJR PJR3
WHERE
PJR3.PAN_ID =
PJR.PAN_ID)
AS
DECIMAL
(
10
,4
))
FROM
T_PLN_JOUR_PJR PJR
-- maj du rang des mois
UPDATE
T_PLN_JOUR_PJR
SET
PJR_RANG_MOIS =
CAST
(
(
CAST
(
PAN_ID AS
FLOAT
)
-
1900
.0
)
*
12
+
PMS_ID +
1
+
(
CAST
(
PJR_RANG_JOUR AS
FLOAT
)
-
(
SELECT
CAST
(
PJR2.PJR_RANG_JOUR AS
FLOAT
)
FROM
T_PLN_JOUR_PJR PJR2
WHERE
PJR2.PJM_ID =
1
AND
PJR2.PMS_ID =
(
PJR.PMS_ID %
12
)
+
1
AND
PJR2.PAN_ID =
CASE
WHEN
PJR.PMS_ID +
1
=
13
THEN
PJR.PAN_ID +
1
ELSE
PJR.PAN_ID
END
))
/
(
SELECT
CAST
(
COUNT
(*)
AS
FLOAT
)
FROM
T_PLN_JOUR_PJR PJR3
WHERE
PJR3.PMS_ID =
PJR.PMS_ID
AND
PJR3.PAN_ID =
PJR.PAN_ID)
AS
DECIMAL
(
10
,4
))
FROM
T_PLN_JOUR_PJR PJR
-- maj du rang des semaines
UPDATE
T_PLN_JOUR_PJR
SET
PJR_RANG_SEMAINE =
CAST
(
CAST
(
PSM_ID AS
FLOAT
)
+
51
+
(
SELECT
SUM
(
MAX_PSM_ID)
FROM
(
SELECT
MAX
(
PSM_ID)
AS
MAX_PSM_ID
FROM
T_PLN_JOUR_PJR
WHERE
PAN_ID <
PJR.PAN_ID
GROUP
BY
PAN_ID)
T )
+
(
CAST
(
PJS_ID AS
FLOAT
)
-
1
)
/
7
)
AS
DECIMAL
(
10
,4
))
FROM
T_PLN_JOUR_PJR PJR
-- maj du rang des trimestres
UPDATE
T_PLN_JOUR_PJR
SET
PJR_RANG_TRIMESTRE =
CAST
((
CAST
(
PAN_ID AS
FLOAT
)
-
1900
)
*
4
+
PTR_ID
-- + nombre de jours écoulés
+
(
CAST
((
SELECT
COUNT
(*)
FROM
T_PLN_JOUR_PJR PJR2
WHERE
PJR2.PTR_ID =
PJR.PTR_ID
AND
PJR2.PAN_ID =
PJR.PAN_ID
AND
PJR2.PJR_DATE <=
PJR.PJR_DATE)
AS
FLOAT
)
-
1
)
-- divisé par le total de jours
/
(
CAST
((
SELECT
COUNT
(*)
FROM
T_PLN_JOUR_PJR PJR3
WHERE
PJR3.PTR_ID =
PJR.PTR_ID
AND
PJR3.PAN_ID =
PJR.PAN_ID)
AS
FLOAT
))
AS
DECIMAL
(
10
,4
))
FROM
T_PLN_JOUR_PJR PJR
-- maj du rang des semestres
UPDATE
T_PLN_JOUR_PJR
SET
PJR_RANG_SEMESTRE =
CAST
((
CAST
(
PAN_ID AS
FLOAT
)
-
1900
)
*
2
+
PST_ID
-- + nombre de jours écoulés
+
(
CAST
((
SELECT
COUNT
(*)
FROM
T_PLN_JOUR_PJR PJR2
WHERE
PJR2.PST_ID =
PJR.PST_ID
AND
PJR2.PAN_ID =
PJR.PAN_ID
AND
PJR2.PJR_DATE <=
PJR.PJR_DATE)
AS
FLOAT
)
-
1
)
-- divisé par le total de jours
/
(
CAST
((
SELECT
COUNT
(*)
FROM
T_PLN_JOUR_PJR PJR3
WHERE
PJR3.PST_ID =
PJR.PST_ID
AND
PJR3.PAN_ID =
PJR.PAN_ID)
AS
FLOAT
))
AS
DECIMAL
(
10
,4
))
FROM
T_PLN_JOUR_PJR PJR
À titre indicatif voici les nombres de semaines dans les années allant de 1900 à 2030
ainsi que le cumul du nombre de semaines depuis le 1er janvier 1900
ANNEE JOUR_DE_LAN JOUR_SEMAINE NOMBRE_SEMAINE CUMUL_SEMAINE
------ ----------- ------------ -------------- -------------
1900 1900-01-01 lundi 52 52
1901 1901-01-01 mardi 52 104
1902 1902-01-01 mercredi 52 156
1903 1903-01-01 jeudi 53 209
1904 1904-01-01 vendredi 53 262
1905 1905-01-01 dimanche 52 314
1906 1906-01-01 lundi 52 366
1907 1907-01-01 mardi 52 418
1908 1908-01-01 mercredi 53 471
1909 1909-01-01 vendredi 53 524
1910 1910-01-01 samedi 52 576
1911 1911-01-01 dimanche 52 628
1912 1912-01-01 lundi 52 680
1913 1913-01-01 mercredi 52 732
1914 1914-01-01 jeudi 53 785
1915 1915-01-01 vendredi 53 838
1916 1916-01-01 samedi 52 890
1917 1917-01-01 lundi 52 942
1918 1918-01-01 mardi 52 994
1919 1919-01-01 mercredi 52 1046
1920 1920-01-01 jeudi 53 1099
1921 1921-01-01 samedi 53 1152
1922 1922-01-01 dimanche 52 1204
1923 1923-01-01 lundi 52 1256
1924 1924-01-01 mardi 52 1308
1925 1925-01-01 jeudi 53 1361
1926 1926-01-01 vendredi 53 1414
1927 1927-01-01 samedi 52 1466
1928 1928-01-01 dimanche 52 1518
1929 1929-01-01 mardi 52 1570
1930 1930-01-01 mercredi 52 1622
1931 1931-01-01 jeudi 53 1675
1932 1932-01-01 vendredi 53 1728
1933 1933-01-01 dimanche 52 1780
1934 1934-01-01 lundi 52 1832
1935 1935-01-01 mardi 52 1884
1936 1936-01-01 mercredi 53 1937
1937 1937-01-01 vendredi 53 1990
1938 1938-01-01 samedi 52 2042
1939 1939-01-01 dimanche 52 2094
1940 1940-01-01 lundi 52 2146
1941 1941-01-01 mercredi 52 2198
1942 1942-01-01 jeudi 53 2251
1943 1943-01-01 vendredi 53 2304
1944 1944-01-01 samedi 52 2356
1945 1945-01-01 lundi 52 2408
1946 1946-01-01 mardi 52 2460
1947 1947-01-01 mercredi 52 2512
1948 1948-01-01 jeudi 53 2565
1949 1949-01-01 samedi 53 2618
1950 1950-01-01 dimanche 52 2670
1951 1951-01-01 lundi 52 2722
1952 1952-01-01 mardi 52 2774
1953 1953-01-01 jeudi 53 2827
1954 1954-01-01 vendredi 53 2880
1955 1955-01-01 samedi 52 2932
1956 1956-01-01 dimanche 52 2984
1957 1957-01-01 mardi 52 3036
1958 1958-01-01 mercredi 52 3088
1959 1959-01-01 jeudi 53 3141
1960 1960-01-01 vendredi 53 3194
1961 1961-01-01 dimanche 52 3246
1962 1962-01-01 lundi 52 3298
1963 1963-01-01 mardi 52 3350
1964 1964-01-01 mercredi 53 3403
1965 1965-01-01 vendredi 53 3456
1966 1966-01-01 samedi 52 3508
1967 1967-01-01 dimanche 52 3560
1968 1968-01-01 lundi 52 3612
1969 1969-01-01 mercredi 52 3664
1970 1970-01-01 jeudi 53 3717
1971 1971-01-01 vendredi 53 3770
1972 1972-01-01 samedi 52 3822
1973 1973-01-01 lundi 52 3874
1974 1974-01-01 mardi 52 3926
1975 1975-01-01 mercredi 52 3978
1976 1976-01-01 jeudi 53 4031
1977 1977-01-01 samedi 53 4084
1978 1978-01-01 dimanche 52 4136
1979 1979-01-01 lundi 52 4188
1980 1980-01-01 mardi 52 4240
1981 1981-01-01 jeudi 53 4293
1982 1982-01-01 vendredi 53 4346
1983 1983-01-01 samedi 52 4398
1984 1984-01-01 dimanche 52 4450
1985 1985-01-01 mardi 52 4502
1986 1986-01-01 mercredi 52 4554
1987 1987-01-01 jeudi 53 4607
1988 1988-01-01 vendredi 53 4660
1989 1989-01-01 dimanche 52 4712
1990 1990-01-01 lundi 52 4764
1991 1991-01-01 mardi 52 4816
1992 1992-01-01 mercredi 53 4869
1993 1993-01-01 vendredi 53 4922
1994 1994-01-01 samedi 52 4974
1995 1995-01-01 dimanche 52 5026
1996 1996-01-01 lundi 52 5078
1997 1997-01-01 mercredi 52 5130
1998 1998-01-01 jeudi 53 5183
1999 1999-01-01 vendredi 53 5236
2000 2000-01-01 samedi 52 5288
2001 2001-01-01 lundi 52 5340
2002 2002-01-01 mardi 52 5392
2003 2003-01-01 mercredi 52 5444
2004 2004-01-01 jeudi 53 5497
2005 2005-01-01 samedi 53 5550
2006 2006-01-01 dimanche 52 5602
2007 2007-01-01 lundi 52 5654
2008 2008-01-01 mardi 52 5706
2009 2009-01-01 jeudi 53 5759
2010 2010-01-01 vendredi 53 5812
2011 2011-01-01 samedi 52 5864
2012 2012-01-01 dimanche 52 5916
2013 2013-01-01 mardi 52 5968
2014 2014-01-01 mercredi 52 6020
2015 2015-01-01 jeudi 53 6073
2016 2016-01-01 vendredi 53 6126
2017 2017-01-01 dimanche 52 6178
2018 2018-01-01 lundi 52 6230
2019 2019-01-01 mardi 52 6282
2020 2020-01-01 mercredi 53 6335
2021 2021-01-01 vendredi 53 6388
2022 2022-01-01 samedi 52 6440
2023 2023-01-01 dimanche 52 6492
2024 2024-01-01 lundi 52 6544
2025 2025-01-01 mercredi 52 6596
2026 2026-01-01 jeudi 53 6649
2027 2027-01-01 vendredi 53 6702
2028 2028-01-01 samedi 52 6754
2029 2029-01-01 lundi 52 6806
2030 2030-01-01 mardi 52 6858
En prime, voici comment calculer les dates fériées mobiles des fêtes chrétiennes, le programme est une procédure écrite pour Transact SQL :
******************************************************************************
calcul des dates fériées chrétiennes dans le calendrier grégorien
******************************************************************************
Algorithme conçu par Claus Tøndering .
Version 2.0 - 11 Nov 1998
Copyright and disclaimer
------------------------
This document is Copyright (C) 1998 by Claus Tondering.
E-mail: claus@tondering.dk.
The document may be freely distributed, provided this
copyright notice is included and no money is charged for
the document.
This document is provided "as is". No warranties are made as
to its correctness.
This algorithm is based in part on the algorithm of Oudin (1940) as
quoted in "Explanatory Supplement to the Astronomical Almanac",
P. Kenneth Seidelmann, editor.
People who want to dig into the workings of this algorithm, may be
interested to know that
G is the Golden Number-1
H is 23-Epact (modulo 30)
I is the number of days from 21 March to the Paschal full moon
J is the weekday for the Paschal full moon (0=Sunday, 1=Monday,
etc.)
L is the number of days from 21 March to the Sunday on or before
the Paschal full moon (a number between -6 and 28
******************************************************************************
Le lundi de Pâques est déterminé par :
dimanche de Pâques + 1 jour
Le jeudi de l'Ascension est déterminé par :
dimanche de Pâques + 39 jours
Le lundi de Pentecôte est déterminé par :
dimanche de Pâques + 50 jours
CREATE
PROCEDURE
SP_PLN_ADD_JOUR_FERIE_MOBILE_CHRETIEN @AN INT
AS
SET
DATEFORMAT YMD
DECLARE
@G INT
DECLARE
@I INT
DECLARE
@J INT
DECLARE
@C INT
DECLARE
@H INT
DECLARE
@L INT
DECLARE
@JourPaque INT
DECLARE
@MoisPaque INT
DECLARE
@DimPaque DATETIME
DECLARE
@LunPaque DATETIME
DECLARE
@JeuAscension DATETIME
DECLARE
@LunPentecote DATETIME
SET
@G =
@AN %
19
SET
@C =
@AN /
100
SET
@H =
(
@C -
@C /
4
-
(
8
*
@C +
13
)
/
25
+
19
*
@G +
15
)
%
30
SET
@I =
@H -
(
@H /
28
)
*
(
1
-
(
@H /
28
)
*
(
29
/
(
@H +
1
))
*
((
21
-
@G)
/
11
))
SET
@J =
(
@AN +
@AN /
4
+
@I +
2
-
@C +
@C /
4
)
%
7
SET
@L =
@I -
@J
SET
@MoisPaque =
3
+
(
@L +
40
)
/
44
SET
@JourPaque =
@L +
28
-
31
*
(
@MoisPaque /
4
)
SET
DATEFORMAT YMD
SET
@DimPaque =
CAST
(
CAST
(
@AN AS
VARCHAR
(
4
))
+
'-'
+
CAST
(
@MoisPaque AS
VARCHAR
(
2
))
+
'-'
+
CAST
(
@JourPaque AS
VARCHAR
(
2
))
AS
DATETIME
)
SET
@LunPaque =
DATEADD(
DAY
, 1
, @DimPaque)
SET
@JeuAscension =
DATEADD(
DAY
, 39
, @DimPaque)
SET
@LunPentecote =
DATEADD(
DAY
, 50
, @DimPaque)
SELECT
'Dimanche'
AS
JOUR, 'Pâques'
AS
FETE, @DimPaque AS
DATE_FETE
UNION
SELECT
'Lundi'
AS
JOUR, 'Pâques'
AS
FETE, @LunPaque AS
DATE_FETE
UNION
SELECT
'Jeudi'
AS
JOUR, 'Ascension'
AS
FETE, @JeuAscension AS
DATE_FETE
UNION
SELECT
'Lundi'
AS
JOUR, 'Pentecôte AS FETE, @LunPentecote AS DATE_FETE
GO
7. Pour en savoir plus sur le sujet▲
LIVRES :
Developping Time-Oriented Database Applications in SQL - Ricahrds T. Snodgrass - Morgan Kauffmann - 2000
Temporal Data and the Relational Model - C. J. Date, Hugh Darwen, Nikos A. Lorentzos - Morgan Kauffmann - 2000
WEB :
http://membres.lycos.fr/urbainmartin/temps.et.calendriers/ http://pchapelin.free.fr/calrep/index.htm http://perso.easynet.fr/~cerf/calendar/calendar.htm http://www.bdl.fr/minitel/ http://www.geocities.com/Paris/Louvre/9647/histoire.htm http://lwh.free.fr/pages/algo/calendriers/calendriers.htm http://www.altcal.com/propcal.html http://www.cite-sciences.fr/francais/ala_cite/act_educ/education/createurs/temps/tintrod.htm http://perso.wanadoo.fr/b.villemin/bissexti.html