.. _chap-hql: ############## Le langage HQL ############## Voici donc maintenant une présentation des aspects essentiels de HQL. Mon but n'étant pas de ré-écrire la documentation en ligne, et donc d'être exhaustif, le contenu qui suit se concentre sur les principes, avec des exemples illustratifs. ************************** S1: HQL, aspects pratiques ************************** Supports complémentaires : * `Diapos pour la session "S1 : HQL, aspects pratiques" `_ * Vidéo associée : https://mediaserver.cnam.fr/permalink/v125f35950e76wmhuozw/ .. * Vidéo associée : https://avc.cnam.fr/univ-r_av/avc/courseaccess?id=2036 Pour tester HQL, je vous recommande d'utiliser, sous Eclipse, le *plugin* *Hibernate Tools*. Voici comment l'installer, et quelques instructions d'utilisation. * Sous Eclipse, dans le menu *Help*, accédez au choix *Marketplace* (ou *Software search and updates*, selon les versions). * Lancez une recherche sur *Hibernate Tools*. * Lancez l'installation, puis relancez Eclipse quand elle est terminée. Une fois le *plugin* installé, vous pouvez ouvrir des *vues* Eclipse. Dans le menu *Windows*, puis *Show view*, choisissez l'option *Hibernate* comme le montre la figure suivante: .. _hibertools: .. figure:: ../figures/hibertools.png :width: 60% :align: center Lancement des fenêtres de Hibernate Tools Configuration ============= Lancez tout d'abord la fenêtre *Hibernate Configurations*. Vous pouvez créer des configurations (soit, essentiellement, le contenu d'un fichier ``hibernate.cfg.xml``). Une configuration peut être créée à partir d'un projet existant, ce qui revient à utiliser les paramètres Hibernate déjà définis pour le projet. Quand vous êtes sur l'onglet *Configuration* de la nouvelle fenêtre, des boutons sur la droite permettent de créer une nouvelle configuration; pour une configuration existante, le bouton droit donne accès à l'édition de la configuration, au lancement d'un éditeur HQL, etc. .. _hibertools-config: .. figure:: ../figures/hibertools-config.png :width: 60% :align: center Configuration d'une session Hibernate Le plus simple est donc d'indiquer le projet associé à la configuration, et d'utiliser le fichier de *mapping* ``hibernate.cfg.xml`` de ce projet. Cela assure que les classes persistantes et la connexion à la base sont automatiquement détectées. L'outil vous permet également de créer ou modifier un fichier de configuration avec une interface graphique asez agréable. Utilisation =========== Une fois la configuration créée, vous pouvez lancer un éditeur HQL et y saisir des requêtes. Pour l'éditeur HQL, sélectionnez votre configuration de session, et utilisez soit le bouton droit, soit l'icône "HQL" que vous voyez sur la barre d'outils. Vous pouvez saisir des requêtes HQL dans l'éditeur, et les exécuter avec la flèche verte. Trois autres vues sont utiles: * *Hibernate Dynamic SQL Preview* vous montre la/les requête(s) SQL lancée(s) par Hibernate pour évaluer la requête HQL; très utile pour comprendre comment Hibernate matérialise le graphe requis par une requête HQL. * *Hibernate Query Result* vous donne simplement le résultat de la requête HQL. * *Query parameters* sert à définir la valeur des paramètres pour les requêtes qui en comportent. Pour résumer, votre environnement de test HQL devrait ressembler à la figure ci-dessous. .. _hibertools-views: .. figure:: ../figures/hibertools-views.png :width: 100% :align: center Les fenêtres Hibernate Tools en action Une dernière astuce: les requêtes HQL renvoient en général des *objets*, et Hibernate Tools affiche la *référence* de ces objets, ce qui n'est pas très parlant. Pour consulter plus facilement le résultat des requêtes, vous pouvez ajouter une clause ``select`` (optionnelle en HQL):: select film.titre, film.annee from Film as film HQL et java ============ L'intégration de HQL à Java se fait par des méthodes de la ``Session``. Le code suivant recherche un (ou plusieurs) film(s) par leur titre. .. code-block:: java public List parTitre(String titre) { Query q = session.createQuery("from Film f where f.titre= :titre"); q.setString ("titre", titre); return q.list(); } La clause ``select`` est optionnelle en HQL, et offre peu d'intérêt dans le cadre de l'application java car on cherche en général à récupérer automatiquement des instances des classes *mappées*, ce qui implique de récupérer tous les attributs de chaque ligne. Commme en jdbc, on peut introduire dans la requête des *paramètres* (ci-dessus, le titre) en les préfixant par ":" ("?" est également accepté). Hibernate se charge de protéger la syntaxe de la requête, par exemple en ajoutant des barres obliques devant les apostrophes et autres caractères réservés. Voici une requête un peu plus complète, tout en étant formulée plus concisément. .. code-block:: java session.createQuery("from Film as film where film.titre like :titre and film.annee < :annee") .setString ("titre", "%er%") .setInteger("annee", 2000) .list(); On applique la technique dite de "chaînage des méthodes", chaque méthode ``set`` renvoyant l'objet-cible, auquel on peut donc appliquer une nouvelle méthode, et ainsi de suite, pour une syntaxe beaucoup plus concise. Notez également que l'affectation des paramètres tient compte de leur type: Hibernate propose des ``setDate()``, ``setInteger()``, ``setTimestamp()``, etc. On peut également utiliser comme paramètre un objet persistant, comme le montre l'exemple suivant: .. code-block:: java // bergman est une instance de Artiste Artiste bergman = ...; session.createQuery("from Film as film where film.realisateur= :mes") .setEntity ("mes", bergman) .list(); HQL offre la possibilité de *paginer* les résultats, une option souvent utile dans un contexte d'application web. L'exemple suivant retourne les lignes 10 à 19 du résultat. .. code-block:: java session.createQuery("from Film") .setFirstResult (10) .setMaxResults(10) .list(); La méthode ``list()`` de l'objet ``query`` est la plus générale pour exécuter une requête et constituer le résultat. Si vous êtes sûr que ce dernier ne contient qu'un seul objet, vous pouvez utiliser ``uniqueResult()``. .. code-block:: java session.createQuery("from Film where titre='Vertigo'") .uniqueResult(); Attention, une exception est levée si plus d'une ligne est trouvée. Cette méthode ne devrait être appliquée que pour des recherches portant sur des attributs déclarés ``unique`` dans le schéma de la base de données (dont la clé primaire). .. note:: À partir de maintenant je donne les requêtes HQL indépendamment du code java, dans la forme "pure" que vous pouvez tester sous Hibernate Tools. *************** S2: base de HQL *************** Supports complémentaires : * `Diapos pour la session "S2 : Base de HQL" `_ * Vidéo associée : https://mediaserver.cnam.fr/permalink/v125f35950ee5kzsay1m/ .. * Vidéo associée : https://avc.cnam.fr/univ-r_av/avc/courseaccess?id=2037 Rappelons que HQL est un langage de requêtes *objet* qui sert à interroger le graphe (virtuel au départ) des objets java constituant la vue orientée-objet de la base. Autrement dit, on interroge *un ensemble d'objets java* liés par des associations, et pas directement la base relationnelle qui permet de les matérialiser. Hibernate se charge d'effectuer les requêtes SQL pour matérialiser la partie du graphe qui satisfait la requête. Cela donne une syntaxe et une interprétation parfois différente de SQL, même si les deux langages sont en apparence très proches. .. important: Contrairement à un "vrai" langage d'interrogation orienté-objet, HQL ne permet pas d'appeler les méthodes des classes du modèle. Sélection d'objets ================== Toute cette section se concentre sur l'interrogation d'une seule classe. La clause ``from`` ------------------ La forme la plus basique de HQL comprend la clause ``from``, et indique la classe dont l'extension est sujet de la recherche:: from Film Le point intéressant est que, en cas de hiérarchie d'héritage, il est possible d'interroger les super- et sous-classes. La liste des vidéos est obtenue par:: from Video et la liste des films (sous-classe de ``Video``) par:: from FilmV On peut rechercher tous les objets persistants avec:: from java.lang.Object On peut créer des *alias* pour désigner les objets et leur appliquer des traitements de sélection ou projection. La requête suivante définit un alias ``film``: .. code-block:: sql select film.titre from Film as film where film.annee < 2000 Le mot-clé ``as`` est optionnel (comme la clause ``select``). Je l'utilise systématiquement pour plus de clarté. Préfixer un attribut par l'alias n'est indispensable que s'il y a risque d'ambiguité, ce qui n'est pas le cas dans l'exemple ci-dessus. La clause ``where`` ------------------- Les opérateurs de comparaison sont très proches de ceux de SQL: =, <, >, ``between``, ``in``, ``not between``, etc. Les opérateurs ``like`` et ``not like`` s'utilisent comme en SQL et expriment la même chose. Les opérateurs logiques sont les mêmes qu'en SQL: ``and``, ``or`` et ``not``, avec ordre de priorité défini par le parenthésage. De même, HQL dispose d'une clause ``order by`` semblable à celle de SQL. Voici un exemple un peu complet: .. code-block:: sql from Film as film where (titre like '%er%' and annee between 1970 and 2000) or film.genre='Drame' order by film.titre Attention à la valeur ``NULL`` ou ``null``. Comme en SQL, elle indique *l'absence* de valeur. On la teste avec l'expression ``is null``, comme en SQL: .. code-block:: sql from Film as film where film.resume is null .. note:: il existe beaucoup de fonctions pré-définies que peuvent être appliquées aux propriétés des objets: ``size()``, ``minElement()``, ``trunc()``, ``trim()``, etc. Je vous renvoie à la documentation pour une liste complète. Jointures implicites -------------------- Contrairement à SQL où les critères de sélection ne peuvent concerner que les propriétés de la table interrogée, HQL permet la navigation vers des objets associés (et donc vers d'autres tables), avec une syntaxe abrégée et intuitive. On peut par exemple chercher les films dirigés par Clint Eastwood: .. code-block:: sql from Film as film where film.realisateur.nom = 'Eastwood' Cela ne fonctionne *que* pour les associations ``many-to-one`` et ``one-to-one``. Vous ne pouvez pas écrire: .. code-block:: sql from Film as film where film.roles.acteur.nom = 'Eastwood' Attention, il n'y a pas de magie: cette syntaxe nécessite une *jointure* au niveau de la requête SQL générée par Hibernate, et correspond en fait à une manière dite *implicite* d'exprimer une jointure. Voici la requête SQL engendrée par Hibernate. .. code-block:: sql select * from Film film0_ cross join Artiste artiste1_ where film0_.id_realisateur=artiste1_.id and artiste1_.nom='Eastwood' On peut bien entendu exprimer *explicitement* des jointures, avec des options assez riches détaillées dans la section suivante. .. _ex-restrictions: .. admonition:: Exercice: expression de restrictions * Trouver lez films dont le titre comprend le mot 'parrain' * Trouvez tous les drames * Trouvez tous les artistes nés avant 1970 * Trouvez tous les réalisateurs (c-à-d. les artistes qui ont mis en scène un film) ***************** S3: Les jointures ***************** Supports complémentaires : * `Diapos pour la session "S3 : Les jointures" `_ * Vidéo associée : https://mediaserver.cnam.fr/permalink/v125f35950f6ca878jyg/ .. * Vidéo associée : https://avc.cnam.fr/univ-r_av/avc/courseaccess?id=2039 Jusqu'à présent, nous prenons les objets d'une classe, ce qui correspond donc à une recherche de ligne dans une seule table pour les instancier. Comme en SQL, il est nécessaire d'exprimer des *jointures* si l'on veut exprimer des critères de recherche sur les objets associés. Jointures HQL et jointures SQL ============================== Comme en SQL, une jointure s'exprime avec le ``join``. * en HQL, en exprime des jointures par *navigation* en exploitant les associations d'objets définies dans le *mapping*, * en SQL, on applique (conceptuellement) le produit cartésien de deux tables, et on restreint le résultat à des combinaisons de lignes satisfaisant certains critères. Il faut bien réaliser qu'une jointure par HQL qui, en apparence, accède directement aux objets associés, est en fait implantée par Hibernate comme une jointure SQL. Soyons concrets avec un premier exemple: on veut tous les films mis en scène par Clint Eastwood: .. code-block:: sql select film from Film as film join film.realisateur as a where a.nom='Eastwood' On accède donc directement à l'objet associé ``realisateur``, sans mention explicite de la classe ``Artiste``. En sous-main, Hibernate engendre et exécute la requête SQL suivante. .. code-block:: sql select film.* from Film film0_ inner join Artiste artiste1_ on film0_.id_realisateur=artiste1_.id where artiste1_.nom='Eastwood' Vous voyez que la version SQL comprend une contrainte d'égalité entre la clé primaire de ``Artiste`` et la clé étrangère dans ``Film``, contrainte qui n'apparaît pas en HQL puisqu'elle est représentée par une association entre objets dans le graphe virtuel. La définition du *mapping* permet à Hibernate d'engendrer le code SQL correct. On peut adopter en HQL une expression plus proche de la logique SQL, mais moins naturelle du point de vue d'une logique "graphe d'objet". .. code-block:: sql select film from Film as film, Artiste as a where film.realisateur = a and a.nom='Eastwood' *Le code SQL engendré est le même*. Il n'y a donc aucune différence en termes de performance ou d'expressivité. Cette dernière version illustre une autre spécificité "objet" de HQL: on compare directement *l'identité* de deux objets (un artiste, un réalisateur) avec la clause:: where film.realisateur = a et pas, comme en SQL, *l'égalité* des clés primaires de ces objets. Cela étant, il est possible d'effectuer directement une jointure sur les clés en HQL: .. code-block:: sql select film from Film as film, Artiste as a where film.realisateur.id = a.id and a.nom='Eastwood' Ici, ``id`` est un mot-clé représentant la clé primaire, quel que soit son nom réel. Le rôle de la clause ``select`` ------------------------------- Voici un autre exemple de jointure, impliquant une association un à plusieurs. Elle sélectionne tous les films dont un des rôles est *McClane*: .. code-block:: sql select distinct film from Film as film join film.roles as role where role.nom= 'McClane' La clause ``select`` prend ici une importance particulière: dans la mesure où nous accédons aux extensions de deux classes (``Film`` et ``Role``), il est préférable de préciser quels sont les objets que nous plaçons dans le résultat. Sans clause ``select``, on obtient une liste de paires d'objets (un film, un rôle), soit une instance de ``List``. Pour chaque élément de la liste, le premier composant du tableau est un ``Film``, le second un ``Rôle``. De plus, le film est dupliqué autant de fois qu'il a de rôles associés, ce qui rend indispensable l'utilisation du mot-clé ``distinct``. Executez par exemple la requête suivante. .. code-block:: sql select film.titre, role.nom from Film as film join film.roles as role On obtient autant de fois le titre du film qu'il y a de rôles. Un tel résultat est assez laborieux à traiter, et on perd en grande partie les avantages de HQL (à savoir instancier un résultat directement exploitable). Dernier exemple, tous les films où Clint Eastwood joue un rôle: .. code-block:: sql select distinct film from Film as film join film.roles as role join role.pk.acteur as acteur where acteur.nom= 'Eastwood' Notez le chemin ``role.pk.acteur``, exprimant un *chemin* dans le graphe des objets et composants. Je vous invite à consulter la requête SQL engendrée par Hibernate pour cette jointure. La clause ``join`` de HQL ------------------------- Les jointures de HQL sont les mêmes que celles du SQL ANSI. * ``[inner] join``: exprime une jointure standard pour impliquer (pas forcément matérialiser) les objets associés; * ``left [outer] join``: exprime une *jointure externe à gauche*: si par exemple un film n'a pas de metteur en scène, il (le film) fera quand même partie du résultat, avec un objet ``realisateur`` associé à ``null``; * ``right [outer] join`` est la complémentaire de ``left join`` (et a donc peu d'intérêt en pratique). Pour bien voir la différence, exécutez les deux requêtes suivantes: .. code-block:: sql from Film as film inner join film.roles as role et: .. code-block:: sql from Film as film left outer join film.roles as role La seconde devrait vous ramener *aussi* les films pour lesquels aucun rôle n'est connu. Comme indiqué plus haut, cette requête renvoie une instance de ``List``, qu'il faut ensuite décoder, pour obtenir, respectivement, des films et des rôles. Ce qui soulève la question suivante: *si, à partir de l'un des films sélectionnés, je veux accéder aux rôles par la collection* ``film.roles``, *Hibernate a-t-il déjà instancié cette collection, ou va-t-il devoir à nouveau accéder à ces rôles avec une requête SQL?*. La réponse est: une nouvelle requête sera effectuée pour trouver les rôles du film, ce qui semble dommage car la jointure SQL sera effectuée deux fois. *Les requêtes HQL que nous présentons de créent pas de graphe, mais instancient simplement les objets sélectionnés, sans lien entre eux*. Dit autrement: le fait d'effectuer une jointure entre un film et des rôles *n'implique pas* que le graphe *film-rôles* est matérialisé dans le cache de premier niveau. Ce n'est pas si illogique que cela en a l'air, si on prend en compte le fait qu'il est possible d'appliquer des *restrictions* sur les objets sélectionnés, comme dans l'exemple ci-dessous. .. code-block:: sql from Film as film left outer join film.roles as role where role.nom='McClane' Si on matérialisait le graphe à partir des objets sélectionnés, la collection ``roles`` de chaque film serait incomplète puisqu'elle ne comprendrait que ceux dont le nom est ``McClane``. En résumé, il faut bien distinguer deux motivations différentes, et partiellement incompatibles: * la *sélection* d'objets satisfaisant des critères de restriction; * la *matérialisation* d'une partie du sous-graphe de données. Les requêtes HQL que nous présentons ici correspondent à la première motivation. Il est possible d'utiliser HQL *aussi* pour la matérialisation du graphe de données, avec une option ``fetch`` que nous étudierons dans le prochain chapitre. .. important:: Retenez que ces deux motivations peuvent être *incompatibles*: si je *restreins* avec la clause *where* les objets sélectionnés (les films avec un rôle McClane), je ne peux pas obtenir en même temps un graphe complet (tous les rôles des films, dont un des rôles est McClane). En résumé, concentrez-vous pour l'instant sur l'utilisation de HQL pour sélectionner et matérialiser les objets qui vous intéressent, sans chercher à optimiser les performances ou éliminer les redondances de requêtes SQL engendrées. Cela revient à toujours utiliser la clause ``select`` pour garder les objets qui vous intéressent, et à utiliser les jointures pour exprimer des restrictions. Par exemple: .. code-block:: sql select distinct film from Film as film left outer join film.roles as role where role.nom='McClane' Comme en SQL, une autre manière d'exprimer une jointure est d'utiliser des sous-requêtes, qui sont présentées un peu plus loin. .. _ex-jointures: .. admonition:: Exercice: pour quelques jointures de plus * Trouvez les films avec l'acteur Bruce Willis. * Affichez les noms des acteurs ayant joué le rôle de 'Jack Dawson', et le metteur en scène qui les a dirigés. * Affichez la liste des metteurs en scène, et les films qu'ils ont réalisés * Affichez les films dans lesquels le metteur en scène est également un acteur (pensez: "HQL, langage objet"!) * Affichez la liste des genres, et pour chaque genre les films correspondant. *************** S4: Compléments *************** Supports complémentaires : * `Diapos pour la session "S4 : Compléments" `_ * Vidéo associée : à venir Nous avons vu l'essentiel de HQL dans ce qui précède. Voici quelques compléments utiles. Sous-requêtes ============= Comme en SQL, HQL permet l'expression de sous-requêtes, ce qui revient souvent à une nouvelle manière d'exprimer des jointures. Voici donc une nouvelle manière d'obtenir les films dirigés par Clint Eastwood. .. code-block:: sql from Film as film where film.realisateur in (from Artiste where nom='Eastwood') Par rapport à l'expression standard avec un ``join``, il existe une différence qui mérite d'être mentionnée: comme la classe ``Artiste`` n'apparaît que dans la sous-requête, les instances de ``Artiste`` ne figurent pas dans le résultat. Il n'est donc pas nécessaire d'utiliser une clause ``select`` pour se restreindre aux films. Voici une autre version de la requête qui ramène les films dans lesquels figure un rôle 'McClane'. .. code-block:: sql from Film as film where film in (select role.pk.film from Role as role where role.nom='McClane') Et une autre version de la requête qui recherche les films dirigés par Clint Eastwood, cette fois avec une sous-requête ``exists``. .. code-block:: sql select film.titre from Film as film where exists (from Artiste as a where a = film.realisateur and a.nom='Eastwood') Il est bien clair que cette expression est très compliquée par rapport à celle basée sur la jointure par navigation (et en particulier la jointure implicite, voir ci-dessus). Les sous-requêtes en HQL sont tout à fait similaires à celles de SQL. Je n'entre donc pas dans l'énumération des diverses possibilités. Pour rappel, les quantificateurs suivants sont composables avec les opérateurs de comparaison, et s'appliquent à des sous-requêtes qui ramènent plusieurs lignes. * ``any``: vrai si *au moins* une ligne ramenée par la sous-requête satisfait la comparaison; * ``all``: vrai si *toutes* les lignes ramenées par la sous-requête satisfontt la comparaison. Des synonymes pour ``any`` sont ``in`` et ``some``. Voici deux exemples. Le premier recherche les films dont au moins un acteur est né avant 1940. .. code-block:: sql from Film as film where 1940 > any (select role.pk.acteur.anneeNaissance from Role as role where role.pk.film=film) Il est beaucoup plus simple (et plus lisible) d'exprimer ce genre de requête avec un ``join``. L'utilisation de ``all`` (pour: "les films dont *tous* les acteurs sont nés avant 1940") paraît plus justifiée: .. code-block:: sql from Film as film where 1940 > all (select role.pk.acteur.anneeNaissance from Role as role where role.pk.film=film) Comme en SQL, il existe plusieurs syntaxes possibles pour une même requête. Par exemple, celle ci-dessus s'exprime aussi avec ``not exists`` (à vous de jouer). .. _ex-sousrequetes: .. admonition:: Exercice: requêtes imbriquées Les requêtes suivantes doivent être exprimées avec des sous-requêtes * Quels internautes n'ont donné que des notes supérieures à 3? * Quels sont les films dans lesquels joue Brad Pitt? * Et quels sont les films qui n'ont pas obtenu une note de 5? Création d'objet spécifiques ============================ Revenons sur les requêtes *ad hoc* qui constituent des résultats constitués de *projection* sur certains attributs. Voici un exemple .. code-block:: sql select film.titre, film.annee, film.realisateur.prenom, film.realisateur.nom from Film as film Quel est l'intérêt de cette requête? A priori elle implique un travail de décodage fastidieux du résultat, constitué d'une liste de tableaux, chaque tableau contenant 4 objets correspondant aux 4 attributs projetés. Il existe quand même un avantage potentiel: comme la requête ne nécessite pas la création d'objets persistants, on évite le placement de ces objets dans le cache de la session, avec le (modeste) surcoût induit par le test d'existence, et l'insertion dans la table de hachage. On gagne donc (un peu) en performance. Il se peut que dans certains cas le gain vaille le surcroît de programmation nécessaire. Regroupement et agrégation ========================== Le ``group by``, le ``having`` et les fonctions d'agrégation sont identiques en HQL et en SQL (ou, pour être plus précis, elles ne présentent aucune spécificité supplémentaire par rapport à celles que nous avons déjà vues). Quelques exemples suffiront. * La note moyenne des films .. code-block:: sql select avg(note) from Notation * Les films et leur nombre d'acteurs .. code-block:: sql select film, count(*) from Film as film join film.roles as role group by film Notez qu'on aimerait faire ``select film, count(film.roles) from Film as film``, mais HQL n'accepte pas cette syntaxe. * Les metteurs en scène ayant réalisé au moins trois films .. code-block:: sql select artiste.nom, count(*) from Artiste as artiste join artiste.filmsRealises as film group by artiste having count(*) > 3 .. _ex-groupby: .. admonition:: Exercice: requêtes imbriquées * Les internautes et le nombre de films qu'ils ont notés * Donnez les films avec au moins 4 rôles. * Films dont la note moyenne est supérieure à la note moyenne de tous les films. .. commentaires .. _ex-requêtes: .. admonition:: Exercice: expression de requêtes Voici maintenant une liste de demande d'affichage des données sur l'application de votre projet. Pour chacune, regardez attentivement les requêtes SQL engendrées et essayez de comprendre la stratégie appliquée (par défaut, puisque nous n'avons encore rien paramétré à ce sujet) par Hibernate. * Prénom et nom de tous les ouvriers humains * Les gisements de Bouzon dont le coefficient de pureté est supérieur à yyy (remplacez par une valeur réaliste). * Les robots dont le modèle est &TR123 (remplacez par un nom de modèle existant) * Les équipes dont le chef est Paul Machin (remplacez par le nom d'un chef d'équipe existant). * Les équipes dont le chef ne fait pas partie des ouvriers. * Liste des ouvriers travaillant sur le gisement xxx (remplacez par le nom dun gisement existant). * Pour chaque gisement, le nombre d'ouvriers qui lui sont affectés * Les gisements de HzK2 auxquels sont affectées au moins 2 équipes. Ce n'est qu'un échantillon: à vous de compléter si vous le souhaitez.