[SQL] FROM (SELECT...) imbriqués. Bien ou pas ?

FROM (SELECT...) imbriqués. Bien ou pas ? [SQL] - SQL/NoSQL - Programmation

Marsh Posté le 23-10-2013 à 15:55:21    

Bonjour,
 
Description du problème général :
 
J'ai des Equipments qui sont dans des Locations. Une Location peut accueillir plusieurs Equipments, un Equipment peut être dans une seule Location à la fois mais je veux conserver l'historique des positions (i.e. equipment1 dans location1 de telle date à telle date, puis dans location2 de telle date à telle date, etc.)
 
J'ai donc une table Equipment, une table Location et une table EquipmentInLocation qui fait le lien entre les deux. Cette table de liaison contient les champs suivants :

  • id (clé primaire)
  • equipment_id (clé étrangère)
  • location_id (clé étrangère)
  • date_connection
  • comment


Ce que je veux, c'est : depuis la page d'une Location (donc location_id est connu et unique au moment de la requête), récupérer la liste des Equipments toujours dans la Location et uniquement ceux-là (pas ceux qui une date_connection plus récente dans une autre Location, ni les anciennes date_connection dans la même Location.
 
Exemple de table EquipmentInLocation :


ID | EQUIPMENT_ID | LOCATION_ID | DATE_CONNECTION | COMMENT
1  |       1      |     1       |     2007        | First Installation
2  |       1      |     2       |     2008        | Sent for repair
3  |       3      |     1       |     2004        | First Installation
4  |       1      |     1       |     2009        | Back from repair
5  |       1      |     3       |     2011        | First Installation


Si location_id = 1, je veux voir que la ligne 3. Et si je retire la ligne 5, seule la 3 et la 4 doivent s'afficher (pas la 1).
 
Requête SQL :
 
J'ai réussi a construire la requête qui me sort les résultats que je veux (testée via PhpMyAdmin). Par contre je ne suis pas certain que la cascade de trois SELECT soit la bonne façon de faire...


SELECT *
FROM (  SELECT *
        FROM(SELECT *
             FROM EQUIPMENTINLOCATION
             ORDER BY date_connection DESC) AS EL
        GROUP BY EL.equipment_id) AS ELD
WHERE ELD.location_id = 46


 
En fait, cette requête fonctionne bien mais déjà elle ne me semble pas très propre et en plus elle me pose un problème parce que je n'arrive pas à l'utiliser avec Doctrine2 (pour ceux que cet aspect de mon problème intéresse, voir mon post sur le forum du site du zéro. Je pose ici en espérant avoir d'autres avis sur l'aspect SQL).
 
Comment feriez-vous à ma place ?  
C'est pas mieux avec des jointures ? Si oui, comment ça marche exactement vu que je ne veux pas faire des jointures sur des champs mais je fais des group by et order by... ?
 
Merci de votre aide !

Reply

Marsh Posté le 23-10-2013 à 15:55:21   

Reply

Marsh Posté le 23-10-2013 à 19:04:57    

Euh alors, pour faire simple c'est tres mal de faire comme ca  :D  
 
Il faut privilegier les jointures :  
- c'est mieux en terme de perf (beaucoup mieux car le SGBD analyse les index a utiliser en fonction des Primary key / Foreign keys). En gros il optimise la facon d'executer la requete en choisissant un plan adapte.
- c'est beaucoup plus lisible !
 
Ensuite, il faut que tu revoies les GROUP BY car j'ai l'impression que c'est pas clair pour toi. Un GROUP BY ne doit servir QUE si tu as une fonction d'agregation (genre COUNT, SUM, AVG, etc). Dans ta requete il ne sert a rien ! Pire, je ne comprends pas comment ca peut fonctionner, car tu fais SELECT * FROM GROUP BY, ce qui n'est pas valide ! Ca explique peut-etre pourquoi ca ne fonctionne pas sur Doctrine2. La regle du GROUP BY c'est : toutes les colonnes qui apparaissent dans le SELECT et qui ne sont pas des fonctions d'agregation doivent apparaitre.
 
Donc mon conseil ce serait de regarder les cours sur les jointures en priorite (comprendre les INNER JOIN et LEFT OUTER JOIN notamment), puis de revoir les GROUP BY.


Message édité par Yonel le 23-10-2013 à 19:05:43
Reply

Marsh Posté le 23-10-2013 à 19:35:18    

Je plussoie sur Yonel, je comprends meme pas comment ca peut marcher.
 
D'autre part, si tu en as la possibilite, tu devrais peut-etre egalement revoir le modele de donnees. Si connaitre l'emplacement courant d'un equipement est utile (et ca a l'air d'etre le cas ici), il serait judicieux d'ajouter un champ a ta table pour l'indiquer, par exemple:
- soit un flag CURRENT de valeur Y/N par exemple que tu rafraichis a chaque fois qu'un equipement bouge
- soit tu rajoutes une date de fin de connexion pour chaque ligne
- Edit: en relisant, en fait le mieux serait probablement de rajouter un champ CURRENT_LOCATION_ID dans ta table equipment.
Dans les deux cas, retourner la liste que tu veux pourrait se faire en un clin d'oeil (modulo les indexs necessaires si tu as beaucoup d'enregistrements).
 
Si tu veux rester sur ton modele (ou que tu n'as pas le choix), le probleme c'est que tu vas etre oblige de faire un parcours complet de la table pour trouver l'instance la plus recente. Bon si t'as pas beaucoup d'enregistrements, tu t'en fous un peu je suppose.
 
Cette requete (pas testee) devrait marcher pour toi:

SELECT *
FROM equipmentinlocation el
WHERE (el.equipment_id, el.date_connection) IN (SELECT equipment_id, MAX(date_connection)
                                                FROM equipmentinlocation
                                                GROUP BY equipment_id);

Ou bien en version plus mieux avec jointure:

SELECT *
FROM equipmentinlocation el
JOIN (SELECT equipment_id, MAX(date_connection) AS dt
      FROM equipmentinlocation
      GROUP BY equipment_id) latest_loc ON latest_loc.equipment_id = el.equipment_id AND latest_loc.dt = el.date_connection;


Message édité par lasnoufle le 23-10-2013 à 19:46:38

---------------
C'était vraiment très intéressant.
Reply

Marsh Posté le 24-10-2013 à 08:47:30    

:whistle:  
 
D'abord merci à vous deux pour vos réponses complètes.
Ensuite, je ne suis pas tellement surpris par ce que vous dites, je me doutais un peu que ma façon de faire était un peu (beaucoup?) bancale (sinon je ne serais pas venu poster ici). Mes connaissances en SQL ne sont peut-être pas à la hauteur de mes besoins, va ptet falloir que je me plonge sérieusement dans un bon tuto. (Pour vous dire, un coup je voulais faire un MAX mais j'arrivais pas à utiliser MAX, du coup j'ai fait un ORDER BY xxx DESC puis un setMaxResult(1)).
 
Et juste pour info, quelques détails sur le pourquoi du GROUP BY dans ce que j'ai fait. En fait je ne voulais garder qu'une ligne par Equipment : celle qui a la date la plus récente. Si j'ai bien compris, par défaut le "GROUP BY equipment_id" parcours ce qu'il y a dans le FROM, et à chaque fois qu'il rencontre un nouvel equipment_id il le garde, et s'il en rencontre un qu'il connait déjà il le passe. Ce qui fait qu'il me gardait les lignes 1 et 3 de mon exemple. Et comme je voulais qu'il me garde les lignes qui ont les dates les plus récentes pour chaque equipment, j'ai trié par date descendante avant de faire le group by.
En trois étapes :
0. Table de départ :


ID | EQUIPMENT_ID | LOCATION_ID | DATE_CONNECTION | COMMENT
1  |       1      |     1       |     2007        | First Installation
2  |       1      |     2       |     2008        | Sent for repair
3  |       3      |     1       |     2004        | First Installation
4  |       1      |     1       |     2009        | Back from repair
5  |       1      |     3       |     2011        | First Installation


1. ORDER BY date_connection DESC


ID | EQUIPMENT_ID | LOCATION_ID | DATE_CONNECTION | COMMENT
5  |       1      |     3       |     2011        | First Installation
4  |       1      |     1       |     2009        | Back from repair
2  |       1      |     2       |     2008        | Sent for repair
1  |       1      |     1       |     2007        | First Installation
3  |       3      |     1       |     2004        | First Installation


2. GROUP BY equipment_id


ID | EQUIPMENT_ID | LOCATION_ID | DATE_CONNECTION | COMMENT
5  |       1      |     3       |     2011        | First Installation
3  |       3      |     1       |     2004        | First Installation


3. WHERE location_id = 1


ID | EQUIPMENT_ID | LOCATION_ID | DATE_CONNECTION | COMMENT
3  |       3      |     1       |     2004        | First Installation


Tadaaaam !! [:eldricht]  C'est bô hein ?  
 
 
Bref, comme dit plus haut, ce matin je vais me plonger dans la doc et étudier les propositions de lasnoufle.
Merci à vous, en tout cas.
 
[edit] Ah oui et pour le modèle de données normalement je n'ai pas trop le choix, je récupère des données déjà existantes mais je vais voir s'il n'y a pas moyen de rajouter un champ date_disconnection quand même.
Pour les performances, on parle ici de quelques centaines de lignes et quelques dizaines d'utilisateurs par semaine donc ce n'est pas dramatique, mais ce n'est pas une raison pour faire les choses comme un goret.


Message édité par blacksad le 24-10-2013 à 08:54:12
Reply

Marsh Posté le 25-10-2013 à 11:08:00    

J'ai finalement réussi à


Message édité par blacksad le 25-10-2013 à 13:10:18
Reply

Marsh Posté le 25-10-2013 à 13:08:31    

J'ai finalement réussi à

Reply

Marsh Posté le 25-10-2013 à 13:17:21    

Petite remarque : on peut parfaitement faire un group by après un FROM puisqu'une requête SQL n'a pas obligatoirement de clause Where ;)
 
Par ailleurs, le group by peut se comporter dans certains cas, comme un DISTINCT (qui lui, se met dans le SELECT); le group by élimine alors les doublons. Sur MySQL, il me semble avoir lu qu'en terme de perfs, GROUP BY était plus rapide que DISTINCT. Ca peut donc être intéressant pour optimiser certaines requêtes même si syntaxiquement et sémantiquement, l'emploi de group by à la place du distinct peut être douteux...


---------------
Astres, outil de help-desk GPL : http://sourceforge.net/projects/astres, ICARE, gestion de conf : http://sourceforge.net/projects/icare, Outil Planeta Calandreta : https://framalibre.org/content/planeta-calandreta
Reply

Marsh Posté le 29-10-2013 à 13:42:32    

C'est fou de faire des jointures comme vous les faites...
une jointure se fait de fichier à fichier , clé à clé... puis dans le where on fait la sélection...
dans ton exemple : que viens faire la ligne 5? id_location <> 1 et quelle est la règle d'exclusion pour la ligne 1 ?  
id_location =1 : la date est au milieu des deux autres...


Message édité par gpl73 le 29-10-2013 à 13:54:59
Reply

Sujets relatifs:

Leave a Replay

Make sure you enter the(*)required information where indicate.HTML code is not allowed