29th Mar 2022 9 minutes de lecture Comment trouver tous les employés sous chaque responsable en SQL Tihomir Babic sql apprendre sql requêtes récursives Table des matières Table des employés Compter tous les employés sous chaque responsable Recherche de tous les subordonnés directs sous chaque responsable Trouver les subordonnés directs et indirects sous chaque responsable Avez-vous déjà été chargé de créer un organigramme, c'est-à-dire de trouver les employés de chaque responsable dans une entreprise ? En SQL, vous pouvez le faire plus facilement que vous ne le pensez ! Cet article vous montrera comment obtenir la réponse en utilisant une requête récursive. Dans le monde de l'entreprise, il est très courant de répertorier tous les employés subordonnés à un responsable donné. Cette tâche est-elle suffisamment difficile pour justifier un article entier ? Si vous vous représentez la situation sous la forme d'un organigramme, cela semble assez simple : trouvez le responsable, dressez la liste de tous ses subordonnés, et c'est tout. Quel est le problème ? Le problème est que SQL fonctionne avec des tableaux, pas avec des organigrammes. Lorsqu'un organigramme est traduit en tableau, il peut devenir un peu difficile d'obtenir ce que vous voulez. La technique que je vais utiliser dans cet article s'appuie principalement sur les jointures SQL et les requêtes récursives. Si vous avez besoin d'un peu de pratique (ou d'explications plus complètes), consultez nos cours sur Les jointures en SQL et les Requêtes récursives. Examinons d'abord comment un organigramme est généralement représenté dans une table adaptée aux requêtes SQL. Table des employés Nous allons utiliser une table nommée employee qui comporte les colonnes suivantes : employee_id: L'identifiant de l'employé. first_name: Le prénom de l'employé. last_name: Le nom de famille de l'employé. manager_id: L'identifiant du responsable de l'employé. Dans cet exemple, il y a dix employés. Examinons les enregistrements : employee_idfirst_namelast_namemanager_id 4529NancyYoung4125 4238JohnSimon4329 4329MartinaCandreva4125 4009KlausKoch4329 4125MafaldaRanieriNULL 4500JakubHrabal4529 4118MoiraAreas4952 4012JonNilssen4952 4952SandraRajkovic4529 4444SeamusQuinn4329 Rien de compliqué ici, juste une liste d'employés. La caractéristique principale est la colonne manager_id, qui contient l'identifiant du responsable de chaque employé. Par exemple : employee_idfirst_namelast_namemanager_id 4529NancyYoung4125 Nancy Young (employee_id 4529) a un responsable. L'identifiant de son responsable est 4125, indiqué dans la colonne manager_id. Si vous recherchez cette valeur dans la colonne employee_id, vous découvrirez qu'il s'agit de l'identifiant de Mafalda Ranieri. Maintenant, jetons un coup d'oeil à Mme Mafalda Ranieri : employee_idfirst_namelast_namemanager_id 4125MafaldaRanieriNULL Il y a une valeur NULL dans la colonne manager_id, ce qui signifie que Mafalda Ranieri n'a pas de responsable au-dessus d'elle - c'est-à-dire qu'elle est la présidente de la société. La logique veut que chaque fois qu'il y a une valeur NOT NULL dans la colonne manager_id, l'employé a un responsable. Bien entendu, il est possible qu'un employé soit un responsable et qu'il ait également un supérieur dans la hiérarchie. Compter tous les employés sous chaque responsable Faisons cet exercice d'échauffement. En utilisant uniquement la table employee, comment comptez-vous tous les employés de chaque responsable ? Voici comment vous pourriez faire, l'auto-jointure de la table étant l'astuce magique : SELECT sup.employee_id, sup.first_name, sup.last_name, COUNT (sub.employee_id) AS number_of_employees FROM employee sub JOIN employee sup ON sub.manager_id = sup.employee_id GROUP BY sup.employee_id, sup.first_name, sup.last_name; Si vous n'êtes pas trop familier avec les auto-jointures, consultez cet article qui explique les auto-jointures avec des exemples. Lors de l'auto-jointure de la table employee, j'ai créé deux alias pour qu'il soit plus facile de suivre ce que je fais. Une table aura l'alias sub (pour l'employé subalterne) ; l'alias de l'autre sera sup (pour l'employé supérieur). Comme je cherche des données analytiques sur les responsables, j'ai demandé à la requête de retourner les colonnes employee_id, first_name, et last_name de la table sup. Le code compte ensuite le nombre d'employés en utilisant la fonction COUNT() sur la colonne sub.employee_id. Vous obtiendriez le même résultat si vous utilisiez sup.employee_id à la place. Je voulais simplement utiliser la table sub pour expliquer cette étape de la manière suivante : "Aha, la table sub est pour les employés subalternes, donc, logiquement, je compte le nombre de subalternes dans cette table". Comme je l'ai dit plus tôt, j'ai fait une auto-jointure sur la table employee en utilisant deux alias. La condition de jointure est sub.manager_id = sup.employee_id. Cela semble logique, car la valeur de la colonne manager_id est l'identifiant du responsable en tant qu'employé et sera naturellement dans la colonne employee_id. J'ai utilisé la fonction d'agrégation COUNT(), et je dois donc regrouper le résultat par les colonnes employee_id, first_name et last_name. Lorsque vous exécuterez le code, vous obtiendrez ce résultat : employee_idfirst_namelast_namenumber_of_employees 4125MafaldaRanieri2 4329MartinaCandreva3 4529NancyYoung2 4952SandraRajkovic2 Le tableau montre quatre responsables et le nombre de leurs subordonnés (number_of_employees). Recherche de tous les subordonnés directs sous chaque responsable La recherche des subordonnés directs est similaire au problème précédent. Il est logique de penser que si j'ai trouvé le nombre d'employés subordonnés, je peux aussi trouver leurs noms. Pour tout vous dire, la solution de cet exercice n'est qu'une variation du code précédent : SELECT sub.employee_id AS subordinate_id, sub.first_name AS subordinate_first_name, sub.last_name AS subordinate_last_name, sup.employee_id AS superior_id, sup.first_name AS superior_first_name, sup.last_name AS superior_last_name FROM employee sub JOIN employee sup ON sub.manager_id = sup.employee_id ORDER BY superior_id; Le principe est le même : une auto-jointure est faite sur la table employee à la colonne sub.manager_id = sup.employee_id. Pour ce faire, j'utilise à nouveau les alias sub et sup. D'abord, je prends les colonnes sub.employee_id, sub.first_name, et sub.last_name. Je les renomme pour qu'elles reflètent le fait que les données concernent les employés subalternes. Je fais la même chose avec ces colonnes pour la table sup, mais cette fois le nom reflète la position de l'employé en tant que supérieur. Enfin, le résultat est ordonné par la colonne superior_id: subordinate_idsubordinate_first_namesubordinate_last_namesuperior_idsuperior_first_namesuperior_last_name 4329MartinaCandreva4125MafaldaRanieri 4529NancyYoung4125MafaldaRanieri 4238JohnSimon4329MartinaCandreva 4444SeamusQuinn4329MartinaCandreva 4009KlausKoch4329MartinaCandreva 4500JakubHrabal4529NancyYoung 4952SandraRajkovic4529NancyYoung 4118MoiraAreas4952SandraRajkovic 4012JonNilssen4952SandraRajkovic Il semble que le tableau montre ce que j'avais prévu ; nous avons les noms des employés et ceux de leurs supérieurs. Mais mon œil de lynx d'ex-auditeur remarque qu'il n'y a que neuf employés, alors que je sais qu'il y a dix employés dans l'entreprise. Comment cela se fait-il ? La raison en est Mafalda Ranieri ; elle est la présidente de l'entreprise, donc elle n'a pas de supérieur et n'est subordonnée à personne. Elle est représentée par une valeur NULL dans la colonne manager_id: employee_idfirst_namelast_namemanager_id 4125MafaldaRanieriNULL Elle est absente du résultat de la requête en raison de la nature de l'auto-jointure ; j'ai utilisé JOIN (c'est-à-dire INNER JOIN). Ce type de jointure ne renvoie pas les rangées contenant des valeurs NULL. Si vous souhaitez que ces lignes apparaissent également dans le résultat de votre requête, vous devrez utiliser LEFT JOIN au lieu de JOIN; tout le reste de la requête reste inchangé. Cette partie du code ressemblera à ceci : ... FROM employee sub LEFT JOIN employee sup ON sub.manager_id = sup.employee_id ... L'exécution du code modifié retournera tous les employés, même ceux qui n'ont pas de supérieur : subordinate_idsubordinate_first_namesubordinate_last_namesuperior_idsuperior_first_namesuperior_last_name 4125MafaldaRanieriNULLNULLNULL 4529NancyYoung4125MafaldaRanieri 4329MartinaCandreva4125MafaldaRanieri 4009KlausKoch4329MartinaCandreva 4238JohnSimon4329MartinaCandreva 4444SeamusQuinn4329MartinaCandreva 4952SandraRajkovic4529NancyYoung 4500JakubHrabal4529NancyYoung 4118MoiraAreas4952SandraRajkovic 4012JonNilssen4952SandraRajkovic Cependant, ce résultat peut encore être insatisfaisant car il ne renvoie que les subordonnés directs. Dans les organisations plus complexes, certains responsables ont des subordonnés directs qui gèrent d'autres employés. Ces employés ont un responsable direct, mais ils répondent également au responsable de leur responsable. Est-il possible de choisir un responsable et d'obtenir la liste de tous ses subordonnés directs et indirects en SQL ? Bien sûr que oui, avec un peu d'aide de notre ami, la requête récursive. Si vous n'êtes pas très familier avec le concept des requêtes récursives, il est conseillé de lire cet article expliquant le fonctionnement des requêtes récursives avant de continuer. Trouver les subordonnés directs et indirects sous chaque responsable Dans la table employee, nous avons un employé nommé Nancy Young. Elle n'a qu'un seul supérieur, le président de la société. Nancy est un cadre supérieur ; naturellement, elle a des subordonnés. Ses subordonnés ont également leurs propres subordonnés. Ce que je voudrais vous montrer, c'est comment obtenir tous les subordonnés, directs et indirects, d'un responsable - Nancy Young dans ce cas. Pour ce faire, je vais utiliser un CTE récursif. Le code qui me permettra d'obtenir ce que je veux est le suivant : WITH RECURSIVE subordinate AS ( SELECT employee_id, first_name, last_name, manager_id, 0 AS level FROM employee WHERE employee_id = 4529 UNION ALL SELECT e.employee_id, e.first_name, e.last_name, e.manager_id, level + 1 FROM employee e JOIN subordinate s ON e.manager_id = s.employee_id ) SELECT s.employee_id, s.first_name AS subordinate_first_name, s.last_name AS subordinate_last_name, m.employee_id AS direct_superior_id, m.first_name AS direct_superior_first_name, m.last_name AS direct_superior_last_name, s.level FROM subordinate s JOIN employee m ON s.manager_id = m.employee_id ORDER BY level; En syntaxe SQL standard, si votre intention est d'avoir un CTE récursif, vous commencez à écrire le code en écrivant WITH RECURSIVE. Remarque importante ! Si vous utilisez SQL Server, cela ne fonctionnera pas ; vous devrez écrire la requête ci-dessus sans RECURSIVE. En d'autres termes, la première ligne du code doit ressembler à ceci : WITH subordinate AS ( ... Maintenant, revenons à l'explication de ce que fait le code ci-dessus. Il crée un CTE récursif nommé subordinate. La première instruction SELECT de ce CTE renvoie les colonnes de la table employee. J'ai également ajouté une nouvelle colonne, level. Nancy Young sera le responsable de niveau 0 ; vous verrez plus tard l'utilité de cette colonne. Comme l'identifiant de l'employé de Nancy Young est 4529, j'ai ajouté cet identifiant dans la clause WHERE. Je veux que le résultat de cette déclaration SELECT soit "fusionné" avec le résultat de la deuxième déclaration SELECT. Pour ce faire, les deux instructions SELECT doivent avoir le même nombre de colonnes dans le résultat. Pour que l'opération UNION ALL ait un sens, je placerai les colonnes employee_id, first_name, last_name, et manager_id dans le second SELECT. La dernière colonne de la déclaration sera la valeur de level de la première déclaration SELECT (qui est 0). Nous ajouterons un 1 à cette valeur à chaque récursion, ce qui renverra les niveaux de hiérarchie. Cela sera utile pour trier les données et pour suivre facilement qui est le responsable de qui. J'ai relié la table employee avec l'ETC lui-même. Je traite l'ETC comme une table (ce qu'elle est), je lui donne un alias et je relie les deux tables sur e.manager_id = s.employee_id. Enfin, j'arrive à l'instruction SELECT à l'extérieur du CTE. Dans cette partie du code, j'ai relié l'ETC lui-même avec la table employee. Elle sélectionne d'abord les colonnes employee_id, first_name, et last_name dans le CTE, car j'utilise le CTE comme source pour les données des subordonnés. J'ai également renommé ces colonnes en conséquence, pour éviter toute confusion. L'étape suivante consiste à sélectionner les mêmes colonnes dans la table employee ; ces colonnes contiendront les données des responsables directs des employés. Enfin, le résultat est ordonné par la colonne level, ce qui permet de trier les employés de manière hiérarchique. Une requête aussi longue ne pouvait renvoyer qu'un beau résultat ; jetez-y un coup d'œil par vous-même : employee_idsubordinate_first_namesubordinate_last_namedirect_superior_iddirect_superior_first_namedirect_superior_last_namelevel 4529NancyYoung4125MafaldaRanieri0 4500JakubHrabal4529NancyYoung1 4952SandraRajkovic4529NancyYoung1 4118MoiraAreas4952SandraRajkovic2 4012JonNilssen4952SandraRajkovic2 Le tableau nous montre tous les subordonnés directs et indirects de Nancy Young ainsi que leurs supérieurs directs. Examinez le tableau et vous verrez que Nancy Young a deux subordonnés directs : Jakub Hrabal et Sandra Rajkovic. Jakub n'a pas de subordonnés, mais Sandra en a deux, Moira Areas et Jon Nilssen. Vous voyez ? Il n'est pas si facile d'obtenir quelque chose que l'on pourrait penser être simple. Heureusement, les requêtes récursives sont très pratiques dans des moments comme celui-ci. Tags: sql apprendre sql requêtes récursives