I. Prérequis▲
Ceci s'adresse au lecteur qui a pris connaissance de l'article précédent : Gérer un troupeau de moutons avec Access, nous y avons décrit les différents formulaires pour collecter les données.
II. Les données du modèle▲
Elles sont réparties dans différentes tables du modèle de données. Celles que nous utiliserons dans ce tutoriel sont encadrées :
Les données de la base exemple ne sont sans doute pas représentatives d'une exploitation réelle. Elles sont là pour permettre de vérifier que les programmes calculent correctement.
III. Une fonction pour déterminer la catégorie d'un ovin à une date donnée▲
Pour l'analyse de l'évolution du troupeau, nous devons distinguer les ovins selon qu'ils sont mâles ou femelles, et adultes ou non.
III-A. Terminologie▲
| Sexe | Âge | Catégorie |
| Femelle | <1 an | agnelle |
| Femelle | ≥ 1 an | brebis |
| Mâle | <1 an | agneau |
| Mâle | ≥ 1 an | bélier |
III-B. Code de la fonction Categorie()▲
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
Public Function Categorie(OvinPK As Long, DateDemande As Date) As String
On Error GoTo GestionErreurs
Dim iSexe As Integer
Dim dNaissance As Date
Dim d1erAnniv As Date
'Son sexe
iSexe = DLookup("tSexesFK", "tOvins", "tOvinsPK=" & OvinPK)
'Sa date de naissance
dNaissance = DLookup("DateNaiss", "tOvins", "tOvinsPK=" & OvinPK)
If dNaissance > DateDemande Then Categorie = "Pas né !": Exit Function
'Son 1er anniversaire
d1erAnniv = DateSerial(Year(dNaissance) + 1, Month(dNaissance), Day(dNaissance))
'Sa catégorie à la date demandée
If d1erAnniv > DateDemande Then '1° Traitement si pas encore adulte
If iSexe = 1 Then
Categorie = "Agnelle": Exit Function
Else
Categorie = "Agneau": Exit Function
End If
Else '2° Traitement si adulte
If iSexe = 1 Then
Categorie = "Brebis": Exit Function
Else
Categorie = "Bélier"
End If
End If
GestionErreurs:
Select Case Err.Number
Case 0 'pas d'erreur
Exit Function
Case 94 'Pas trouvé dans tOvins
MsgBox " La clé : " & OvinPK & " n'est pas trouvée dans tOvins", vbInformation
Exit Function
Case Else
MsgBox "Erreur dans Categorie N° " & Err.Number & " " & Err.Description
End Select
End Function
Les commentaires inclus dans le code rendent celui-ci suffisamment compréhensible, même pour un non-initié.
![]() |
Pour un problème de compréhension du code dans un module, placez le curseur n'importe où dans l'instruction et pressez <F1>. L'aide Access s'ouvre alors à la bonne page. |
On peut mettre plusieurs instructions sur une ligne à condition de les séparer par le caractère deux-points.
'comme ceci :
Categorie = "Agnelle": Exit Function
'au lieu de ceci :
Categorie = "Agnelle"
Exit FunctionIV. Le formulaire fInventaire▲
L'idée est de justifier (au sens comptable du terme) la composition du troupeau entre deux dates (par défaut la dernière saison).
IV-A. Présentation▲
Il s'agit d'un formulaire indépendant : il n'a pas de source.
Les cellules en vert sont alimentées soit :
- à l'aide d'une fonction de domaine :

- par combinaison d'autres contrôles :
fInventaire contient deux sous-formulaires (sfInvenSorties et sfInvenEntrees) qui donnent le détail des sorties et des entrées.
IV-B. Les données pour alimenter fInventaire▲
IV-B-1. tInvenEntrees et tInvenEntrees▲
L'ouverture du formulaire provoque la création de deux tables tInvenEntrees et tInvenEntrees.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
Option Compare Database
Option Explicit
Private Sub Form_Open(Cancel As Integer)
Call Rafraichir
End Sub
Public Sub Rafraichir()
Dim ctl As Control
Dim sSql As String
'Recréer la table tInvenEntrees
sSql = "SELECT categorie([tOvinsPK],[DateEntree]) AS Categorie, tOvins.* INTO tInvenEntrees " _
& "FROM tOvins " _
& " WHERE tOvins.DateEntree>=#" & Format([Forms]![fInventaire]![txtDebut], "mm/dd/yy") & "# " _
& "And tOvins.DateEntree<=#" & Format([Forms]![fInventaire]![txtFin], "mm/dd/yy") & "#" _
& "And tOvins.Fictif=False;"
DoCmd.SetWarnings False
DoCmd.RunSQL sSql
DoCmd.SetWarnings True
For Each ctl In Me.Controls
If ctl.Name Like "txt*" Or ctl.Name Like "CTNRsf*" Then ctl.Requery
Next ctl
'Recréer la table tInvenSorties
sSql = "SELECT categorie([tOvinsPK],[DateSortie]) AS Categorie, tOvins.* INTO tInvenSorties " _
& "FROM tOvins " _
& "WHERE tOvins.DateSortie >=#" & Format([Forms]![fInventaire]![txtDebut], "mm/dd/yy") & "# " _
& "AND tOvins.DateSortie <=#" & Format([Forms]![fInventaire]![txtFin], "mm/dd/yy") & "# " _
& "AND tOvins.tCausesSortieFK<>6 " _
& "And tOvins.Fictif=False;"
DoCmd.SetWarnings False
DoCmd.RunSQL sSql
DoCmd.SetWarnings True
End Sub
Commentaires du code
13-17 : par exemple si la période est exprimée comme ceci :

la requête construite à la volée correspond à ceci :
26-31 : la requête construite à la volée correspond à ceci :
Ces deux tables sont supprimées lors de la fermeture du formulaire.
Private Sub Form_Close()
'Supression des tables tInvenEntrees et tInvenSorties
DoCmd.DeleteObject acTable, "tInvenEntrees"
DoCmd.DeleteObject acTable, "tInvenSorties"
End SubNous utiliserons aussi quatre requêtes enregistrées :

IV-B-2. rInvenDebut▲
IV-B-3. rInvenFin▲
IV-B-4. rInvenAgneauxVersBeliers▲
Il s'agit de détecter les mâles qui durant la période sont passés à l'âge adulte.
C'est l'union de deux requêtes :
- la première ramène les mâles présents pendant toute la période qui ont changé de catégorie :

- la seconde ramène les agneaux entrés pendant la période devenus adultes à la fin de celle-ci :
L'union des deux requêtes s'écrit comme ceci
SELECT rInvenDebut.tOvinsPK
FROM rInvenDebut INNER JOIN rInvenFin ON rInvenDebut.tOvinsPK = rInvenFin.tOvinsPK
WHERE (((rInvenDebut.Categorie)="agneau") AND ((rInvenFin.Categorie)="bélier")) UNION SELECT tOvins.tOvinsPK
FROM tOvins INNER JOIN rInvenFin ON tOvins.tOvinsPK = rInvenFin.tOvinsPK
WHERE (((tOvins.DateEntree)>[Formulaires]![fInventaire]![txtDebut]) AND ((tOvins.DateSortie)>[Formulaires]![fInventaire]![txtFin] Or (tOvins.DateSortie) Is Null) AND ((Categorie([tOvins].[tOvinsPK],[tOvins].[DateEntree]))="Agneau") AND ((rInvenFin.Categorie)="Bélier"));IV-B-5. rInvenAgnellesVersBrebis▲
Il s'agit de détecter les femelles qui durant la période sont passées à l'âge adulte.
Même principe que pour les mâles.
L'union des deux requêtes s'écrit comme ceci :
SELECT rInvenDebut.tOvinsPK
FROM rInvenDebut INNER JOIN rInvenFin ON rInvenDebut.tOvinsPK = rInvenFin.tOvinsPK
WHERE (((rInvenDebut.Categorie)="agnelle") AND ((rInvenFin.Categorie)="brebis")) UNION SELECT tOvins.tOvinsPK
FROM tOvins INNER JOIN rInvenFin ON tOvins.tOvinsPK = rInvenFin.tOvinsPK
WHERE (((tOvins.DateEntree)>[Formulaires]![fInventaire]![txtDebut]) AND ((tOvins.DateSortie)>[Formulaires]![fInventaire]![txtFin] Or (tOvins.DateSortie) Is Null) AND ((Categorie([tOvins].[tOvinsPK],[tOvins].[DateEntree]))="Agnelle") AND ((rInvenFin.Categorie)="Brebis"));IV-C. Le sous-formulaire sfInvenSorties▲
IV-C-1. Source▲
SELECT tCausesSortie.CauseSortie, DCount("*","tInvenSorties","Categorie='Agnelle' AND tCausesSortieFK=" & [tCausesSortiePK]) AS Agnelles, DCount("*","tInvenSorties","Categorie='Brebis' AND tCausesSortieFK=" & [tCausesSortiePK]) AS Brebis, DCount("*","tInvenSorties","Categorie='agneau' AND tCausesSortieFK=" & [tCausesSortiePK]) AS Agneaux, DCount("*","tInvenSorties","Categorie='Bélier' AND Fictif = false AND tCausesSortieFK=" & [tCausesSortiePK]) AS Beliers FROM tCausesSortie WHERE (((tCausesSortie.tCausesSortiePK)<>6)) ORDER BY tCausesSortie.OrdreInven;IV-D. Le sous-formulaire sfInvenEntrees▲
V. Le fomulaire fRepro▲
On affiche dans ce formulaire une série de nombres et ratios qui caractérisent les femelles du troupeau pendant une saison (par défaut, la dernière écoulée).
Donne la proportion de femelles qui ont été mises en lutte durant la saison.
Renseigne les agnelages intervenus. Il se peut qu'ils soient supérieurs au nombre de mises en lutte si des femelles ont été inséminées artificiellement ou achetées déjà en cours de gestation.
Renseigne le nombre moyen de nouveau-nés par mise bas.
Nombre de nouveau-nés qui sont morts avant la fin de la saison.
Nombre de nouveau-nés qui ont survécu.
V-A. Présentation▲
Tout comme le formulaire précédent, fRepro n'a pas de source.
Les contrôles du formulaire sont alimentés soit :
- par des fonctions de domaine (sur des requêtes décrites plus bas) ;
- soit par combinaison d'autres contrôles :
Pour éviter les cas où on serait amené à diviser par zéro :
Public Function ZeroToUn(Diviseur As Double) As Double
'Pour éviter de diviser par zéro
If Diviseur = 0 Then
ZeroToUn = 1
Else
ZeroToUn = Diviseur
End If
End FunctionDiviser un nombre par un, ça ne mange pas de pain !
V-B. Les données pour alimenter fRepro▲
Nous utilisons sept requêtes enregistrées pour préparer les données que nous importerons ensuite dans le formulaire à l'aide de fonctions de domaine :

V-B-1. rRepro01FemellesPresentes▲
Remarquez les deux lignes de critères :
Cette requête va ramener deux sortes d'enregistrements :
- ceux qui vérifient les conditions de la 1re ligne ;
ET
- ceux qui vérifient les conditions de la seconde.
Commentaires des colonnes
On ramène uniquement les femelles.
Il s'agit de la catégorie (agnelle ou brebis) au début de la saison.
et
expriment que cette femelle était entrée avant le début et que, si elle est sortie, c'est après le début.
![]() |
Avec Nz() on substitue à la valeur Null éventuelle de DateSortie une date loin dans le futur (en l'occurrence 1/1/2100). Cela nous épargne de devoir traiter un VraiFaux() supplémentaire. |
On exclut du bilan de reproduction les femelles qui ont été destinées à la boucherie.
Il s'agit des femelles entrées dans le troupeau en cours de saison, MAIS évidemment pas les agnelles nouveau-nées de cette saison !
V-B-2. rRepro02MisesEnLutte▲
Parmi les femelles ramenées par la requête rRepro01FemellesPresentes, on retient celles qui ont été mises en lutte durant la période.
V-B-3. rRepro03Agnelages▲
V-B-4. rRepro04Agnele2X▲
Parmi les femelles de la requête rRepro03Agnelages, on ramène celles qui y figurent plus d'une fois.
(Sachant qu'une gestation dure environ 150 jours et qu'un délai de 75 jours sépare une mise bas et la mise en lutte suivante, en pratique deux mises bas par saison est un maximum.)
V-B-5. rRepro05Improductives▲
Cette requête va donc ramener les femelles présentes qui ne se retrouvent pas parmi celles ayant mis bas.
V-B-6. rRepro06AgneauxNes▲
V-B-7. rRepro07AgneauxMorts▲
V-C. Code associé à fRepro▲
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
Option Compare Database
Option Explicit
Private Sub txtDebut_AfterUpdate()
Dim ctl As Control
Me.txtFin = DateSerial(Year(Me.txtDebut) + 1, Month(Me.txtDebut), Day(Me.txtDebut) - 1)
For Each ctl In Me.Controls
If ctl.Name Like "txt####" Then ctl.Requery
Next ctl
End Sub
Private Sub txtFin_AfterUpdate()
Dim ctl As Control
Me.txtDebut = DateSerial(Year(Me.txtFin) - 1, Month(Me.txtFin), Day(Me.txtFin) + 1)
For Each ctl In Me.Controls
If ctl.Name Like "txt####" Then ctl.Requery
Next ctl
End Sub
Commentaires du code
À l'ouverture du formulaire, la période indiquée correspond à la dernière saison écoulée.
L'utilisateur peut changer cette période à la condition que ce qu'il propose corresponde à une durée de 1 an.
6 et 14 : après mise à jour d'une des deux dates, l'autre est automatiquement adaptée pour que l'écart entre début et fin soit d'une année.
7-9 et 15-17 : on réactualise la valeur des contrôles de données (que nous avons pris la précaution de nommer « txt » suivi de quatre chiffres).
VI. Le formulaire fPresentsADate▲
C'est un outil qui consiste à afficher la composition du troupeau à une date choisie par l'utilisateur.
VI-A. Présentation▲
VI-B. La source de fPresentsADate▲
Cette requête ramène donc toutes les colonnes de la table tOvins, la catégorie de l'animal à la date indiquée dans le formulaire, la race en clair et la date de sortie (éventuelle).
Pour les ovins (non fictifs) qui étaient présents à cette date (déjà entrés et pas encore sortis).
Les lignes sont triées de manière à présenter dans l'ordre : les brebis, les agnelles, les béliers et les agneaux et, à l'intérieur des catégories, par numéro de travail.
VI-C. La mise en forme conditionnelle▲
Chaque catégorie s'affiche dans une couleur qui lui est propre
En arrière-plan nous avons une zone de texte indépendante txtCategorie dont la couleur de fond originale correspond à celle choisie pour les agneaux et que nous modifions grâce à la mise en forme conditionnelle en fonction de la valeur de la colonne [Categorie] de chaque enregistrement.
Les autres contrôles sont superposés à cette zone de texte et leur propriété Style fond a la valeur « Transparent » :

Cette astuce donne l'illusion que les lignes du formulaire sont colorées selon la catégorie.
Remarquez que le texte sur les lignes des béliers s'affiche en blanc :
VI-D. Code associé à fPresentsADate▲
Option Compare Database
Option Explicit
Private Sub Form_Open(Cancel As Integer)
Me.Requery
End Sub
Private Sub txtDate_AfterUpdate()
Me.Requery
End SubAprès chaque modification de la date, on provoque la réactualisation du formulaire.
N.B. À l'ouverture, il faut aussi actualiser, sinon on obtiendrait ceci :
Cela tient au fait qu'Access attribue la source à un instant où il n'a pas encore aménagé la valeur par défaut de txtDate. La requête rfPresentsADate ne ramène donc aucun enregistrement (txtDate étant Null).
VII. Téléchargement▲
La base de données au format Access 2000 se trouve ici.
VIII. Remerciements▲
Merci à foster53 qui m'a expliqué l'aspect métier.
Merci à Malick Seck (milkoseck) pour la correction orthographique.
2e partie : l'exploitation des données


































