IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Requêtes MySQL Discussion :

Performance des sous-requêtes


Sujet :

Requêtes MySQL

  1. #1
    Membre habitué Avatar de tintin72
    Profil pro
    Inscrit en
    Septembre 2003
    Messages
    663
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Septembre 2003
    Messages : 663
    Points : 177
    Points
    177
    Par défaut Performance des sous-requêtes
    Bonjour,

    J'ai créé une requête qui contient 3 sous requêtes.
    Le résultat retourné par MySQL est bien celui attendu, toutefois je voulais savoir
    si 3 sous requêtes ne risquait pas d'affecter les performances.
    Pour l'instant je n'ai qu'une vingtaine de lignes dans les tables concernées et l'exécution
    est très rapide (0.0026sec environs), mais qu'est ce que ça donnerait avec des milliers/millions de lignes ?

    Voici ma requête commentée:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    SELECT DISTINCT p.id, p.name, p.price, p.vendor_id, o1.offer AS latest_offer, o1.user_id AS latest_user_id, o2.user_id AS latest_user_id, o3.user_id AS test_user_id 
    FROM t_products AS p 
    -- Récupère la dernière offre faite pour ce produit.
    LEFT JOIN  t_offers AS o1 ON o1.product_id=p.id AND o1.date=(SELECT MAX(date) FROM 
    t_offers WHERE product_id=p.id) 
    -- Récupère l'avant dernière offre faite pour ce produit.
    LEFT JOIN  t_offers AS o2 ON o2.product_id=o1.product_id AND 
    o2.date=(SELECT date FROM t_offers WHERE product_id=p.id ORDER BY date DESC LIMIT 1,1) 
    -- Test si l'utilisateur courant ($userId) a fait une offre pour ce produit.
    LEFT JOIN  t_offers AS o3 ON o3.product_id=p.id AND o3.user_id=(
    SELECT user_id FROM t_offers WHERE product_id=p.id AND user_id=$userId LIMIT 1) 
    -- Récupère les produits pour lesquels l'utilisateur courant:
    -- a fait la dernière offre OU a fait l'avant dernière offre OU a fait une offre OU est le vendeur du produit
    WHERE (o1.user_id=$userId OR o2.user_id=$userId OR o3.user_id=$userId OR p.vendor_id=$userId)
    Cette requête est elle correctement formulée ou y a t-il moyen de l'optimiser ?

    Merci d'avance pour vos lumières.

  2. #2
    Modérateur

    Avatar de CinePhil
    Homme Profil pro
    Ingénieur d'études en informatique
    Inscrit en
    Août 2006
    Messages
    16 799
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 60
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Ingénieur d'études en informatique
    Secteur : Enseignement

    Informations forums :
    Inscription : Août 2006
    Messages : 16 799
    Points : 34 031
    Points
    34 031
    Billets dans le blog
    14
    Par défaut
    1) "date" est un mot réservé du langage SQL, la colonne ne devrait pas être nommée ainsi.
    Dans les requêtes ci-dessous, j'ai entouré "date" d'apostrophes inversées pour que MySQL ne fasse pas de confusion.

    2) Il y a deux fois le même alias "latest_user_id" dans votre requête ; bizarre que MySQL l'accepte ainsi et puisse exécuter la requête !

    3) Effectivement, je crains fort qu'avec des centaines de milliiers ou de millions de lignes, vous ayez le temps de boire un café avant d'avoir le résultat de la requête !

    Décortiquons...

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    -- Récupère la dernière offre faite pour ce produit.
    A) Quelle est la date de la dernière offre pour chaque produit ?
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    SELECT product_id, MAX(`date`) AS date_derniere_offre
    FROM t_offers
    GROUP BY product_id
    B) Quel est le dernier offreur pour chaque produit ?
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    SELECT 
    	p.id, 
    	p.name, 
    	p.price, 
    	p.vendor_id, 
    	t1.date_derniere_offre, 
    	o1.offer AS latest_offer, 
    	o1.user_id AS latest_user_id
    FROM t_products p
    LEFT OUTER JOIN t_offers o1 ON o1.product_id = p.id
    	LEFT OUTER JOIN
    	(
    		SELECT product_id, MAX(`date`) AS date_derniere_offre
    		FROM t_offers
    		GROUP BY product_id
    	) t1
    		ON t1.product_id = o1.product_id
    		AND t1.date_derniere_offre = o1.`date`
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    -- Récupère l'avant dernière offre faite pour ce produit.
    C) Quelle est la date de l'avant dernière offre pour chaque produit ?
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    SELECT o2.product_id,
    	MAX(o2.`date`) AS date_avant_derniere_offre
    FROM t_offers o2
    	LEFT OUTER JOIN
    	(
    		SELECT product_id, MAX(`date`) AS date_derniere_offre
    		FROM t_offers
    		GROUP BY product_id
    	) t2
    		ON t2.product_id = o2.product_id
    		AND o2.`date` < t2.date_derniere_offre
    GROUP BY o2.product_id
    D) Quel est l'avant-dernier offreur pour chaque produit ?
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    SELECT o3.product_id,
    	o3.user_id AS last_but_one_user_id,
    	t3.date_avant_derniere_offre
    FROM t_offers o3
    LEFT OUTER JOIN 
    (
    	SELECT o2.product_id,
    		MAX(o2.`date`) AS date_avant_derniere_offre
    	FROM t_offers o2
    		LEFT OUTER JOIN
    		(
    			SELECT product_id, MAX(`date`) AS date_derniere_offre
    			FROM t_offers
    			GROUP BY product_id
    		) t2
    			ON t2.product_id = o2.product_id
    			AND o2.`date` < t2.date_derniere_offre
    	GROUP BY o2.product_id
    ) t3
    	ON t3.product_id = o3.product_id
    	AND t3.date_avant_derniere_offre = o3.`date`
    E) On assemble
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    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
    SELECT 
    	p.id, 
    	p.name, 
    	p.price, 
    	p.vendor_id, 
    	t1.date_derniere_offre, 
    	o1.offer AS latest_offer, 
    	o1.user_id AS latest_user_id,
    	o3.user_id AS last_but_one_user_id,
    	t3.date_avant_derniere_offre
    FROM t_products p
    LEFT OUTER JOIN t_offers o1 ON o1.product_id = p.id
    	LEFT OUTER JOIN
    	(
    		SELECT product_id, MAX(`date`) AS date_derniere_offre
    		FROM t_offers
    		GROUP BY product_id
    	) t1
    		ON t1.product_id = o1.product_id
    		AND t1.date_derniere_offre = o1.`date`
    		LEFT OUTER JOIN t_offers o3
    			ON o3.product_id = t1.product_id
    			LEFT OUTER JOIN 
    			(
    				SELECT o2.product_id,
    					MAX(o2.`date`) AS date_avant_derniere_offre
    				FROM t_offers o2
    					LEFT OUTER JOIN
    					(
    						SELECT product_id, MAX(`date`) AS date_derniere_offre
    						FROM t_offers
    						GROUP BY product_id
    					) t2
    						ON t2.product_id = o2.product_id
    						AND o2.`date` < t2.date_derniere_offre
    				GROUP BY o2.product_id
    			) t3
    				ON t3.product_id = o3.product_id
    				AND t3.date_avant_derniere_offre = o3.`date`
    F) On ajoute le test sur le $userId + les restrictions
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    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
    44
    45
    46
    SELECT 
    	p.id, 
    	p.name, 
    	p.price, 
    	p.vendor_id, 
    	t1.date_derniere_offre, 
    	o1.offer AS latest_offer, 
    	o1.user_id AS latest_user_id,
    	o3.user_id AS last_but_one_user_id,
    	t3.date_avant_derniere_offre,
    	o4.user_id AS test_user_id
    FROM t_products p
    LEFT OUTER JOIN t_offers o1 ON o1.product_id = p.id
    	LEFT OUTER JOIN
    	(
    		SELECT product_id, MAX(`date`) AS date_derniere_offre
    		FROM t_offers
    		GROUP BY product_id
    	) t1
    		ON t1.product_id = o1.product_id
    		AND t1.date_derniere_offre = o1.`date`
    		LEFT OUTER JOIN t_offers o3
    			ON o3.product_id = t1.product_id
    			LEFT OUTER JOIN 
    			(
    				SELECT o2.product_id,
    					MAX(o2.`date`) AS date_avant_derniere_offre
    				FROM t_offers o2
    					LEFT OUTER JOIN
    					(
    						SELECT product_id, MAX(`date`) AS date_derniere_offre
    						FROM t_offers
    						GROUP BY product_id
    					) t2
    						ON t2.product_id = o2.product_id
    						AND o2.`date` < t2.date_derniere_offre
    				GROUP BY o2.product_id
    			) t3
    				ON t3.product_id = o3.product_id
    				AND t3.date_avant_derniere_offre = o3.`date`
    LEFT JOIN t_offers o4 
    	ON o4.product_id = p.id
    	AND o4.user_id = $userId
    WHERE o1.user_id = $userId 
    	OR o3.user_id = $userId
    	OR p.vendor_idc= $userId
    Ça semble plus compliqué mais comme c'est ensembliste, ça devrfait aller plus vite avec un grand nombre de lignes.
    À tester.
    Philippe Leménager. Ingénieur d'étude à l'École Nationale Supérieure de Formation de l'Enseignement Agricole. Autoentrepreneur.
    Mon ancien blog sur la conception des BDD, le langage SQL, le PHP... et mon nouveau blog sur les mêmes sujets.
    « Ce que l'on conçoit bien s'énonce clairement, et les mots pour le dire arrivent aisément ». (Nicolas Boileau)
    À la maison comme au bureau, j'utilise la suite Linux Mageïa !

  3. #3
    Membre habitué Avatar de tintin72
    Profil pro
    Inscrit en
    Septembre 2003
    Messages
    663
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Septembre 2003
    Messages : 663
    Points : 177
    Points
    177
    Par défaut
    Merci pour vos conseils et explications
    1) "date" est un mot réservé du langage SQL, la colonne ne devrait pas être nommée ainsi.
    Oui en effet ce n'est pas très bien vu. Je vais renommer la colonne differemment.
    2) Il y a deux fois le même alias "latest_user_id" dans votre requête
    C'est une erreur de ma part lors de la recopie . Le 2eme alias se nomme en fait sec_last_user_id.

    Par contre en ce qui concerne votre requête j'avoue ne pas bien la comprendre (je ne suis pas expert en MySQL).
    Ce sont des jointures imbriquées ?
    Y aurait il un bon tutorial pour m'aider à comprendre cette logique ?
    Est ce le seul moyen d'optimiser ma requête ?

    Merci d'avance.

  4. #4
    Modérateur

    Avatar de CinePhil
    Homme Profil pro
    Ingénieur d'études en informatique
    Inscrit en
    Août 2006
    Messages
    16 799
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 60
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Ingénieur d'études en informatique
    Secteur : Enseignement

    Informations forums :
    Inscription : Août 2006
    Messages : 16 799
    Points : 34 031
    Points
    34 031
    Billets dans le blog
    14
    Par défaut
    Par contre en ce qui concerne votre requête j'avoue ne pas bien la comprendre (je ne suis pas expert en MySQL).
    Ce sont des jointures imbriquées ?
    Je ne nommerais pas ça des jointures imbriquées mais plutôt enchaînées.

    Prenons un autre exemple...
    Une vile est située dans un département qui est situé dans une région.
    Cela donne le MCD suivant :
    ville -1,1----situer----0,n- departement -1,1----situer----0,n- region

    Cela donne les tables suivantes :
    region (reg_id, reg_nom...)
    departement (dpt_id, dpt_id_region, dpt_nom...)
    ville (vil_id, vil_id_departement, vil_nom...)

    Et on peut faire la requête suivante pour avoir une ville, son département et sa région :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    SELECT v.vil_nom, d.dpt_nom, r.reg_nom
    FROM ville v
    INNER JOIN departement d ON d.dpt_id = v.vil_id_departement
    	INNER JOIN region r ON r.reg_id = d.dpt_id_region
    WHERE v.vil_nom = 'Toulouse'
    En indentant la requête, on peut directement lire le schéma de la BDD.

    Dans la requête que j'ai donnée, c'est pareil. Il y a des jointures sur les sous-requêtes et j'ai indenté le code pour que ce soit plus facile à lire et plus compréhensible. Comme n'importe quel code informatique en fait !
    Philippe Leménager. Ingénieur d'étude à l'École Nationale Supérieure de Formation de l'Enseignement Agricole. Autoentrepreneur.
    Mon ancien blog sur la conception des BDD, le langage SQL, le PHP... et mon nouveau blog sur les mêmes sujets.
    « Ce que l'on conçoit bien s'énonce clairement, et les mots pour le dire arrivent aisément ». (Nicolas Boileau)
    À la maison comme au bureau, j'utilise la suite Linux Mageïa !

  5. #5
    Membre habitué Avatar de tintin72
    Profil pro
    Inscrit en
    Septembre 2003
    Messages
    663
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Septembre 2003
    Messages : 663
    Points : 177
    Points
    177
    Par défaut
    Je comprends mieux en effet mais ce qui me perturbait c'était plutôt l'utilisation des sous requêtes avec les jointures.
    Ceci dit j'utilise également cette requête avec une clause WHERE différente, et là votre méthode ne
    fonctionne plus car je me retrouve avec des doublons.

    Voilà ma requête pour l'instant.
    Note: Le test sur le $userId n'est pas utile ici car il est effectué plus loin dans une boucle.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    SELECT DISTINCT p.id, p.name, o1.offer AS latest_offer, o1.user_id AS latest_user_id, o2.offer AS sec_last_offer, o2.user_id AS 
    sec_last_user_id 
    FROM t_products AS p 
    --Récupère la dernière offre faite pour ce produit.
    LEFT JOIN (SELECT product_id, MAX(`date`) AS latest_date FROM t_offers GROUP BY product_id) AS t1 ON t1.product_id=p.id 
    LEFT JOIN t_offers AS o1 ON o1.product_id=t1.product_id AND o1.`date`=t1.latest_date 
    --Récupère l'avant dernière offre faite pour ce produit.
    LEFT JOIN  t_offers AS o2 ON o2.product_id=o1.product_id AND o2.`date`=(SELECT `date` FROM t_offers WHERE product_id=p.id ORDER 
    BY `date` DESC LIMIT 1,1) 
    --Récupère les produits encore disponibles ($NOW = date et heure courante)
    WHERE p.date_limite > $NOW
    Avec un grand nombre de lignes la sous requête pour récupérer l'avant dernière offre risque de consommer des ressources.
    Toutefois je ne vois pas comment faire autrement car les sous requêtes doivent retourner un résultat unique autrement je
    vais me retrouver avec des doublons.

    Y aurait il un moyen d'optimiser ou une autre façon de faire ?

    D'avance merci.

  6. #6
    Modérateur

    Avatar de CinePhil
    Homme Profil pro
    Ingénieur d'études en informatique
    Inscrit en
    Août 2006
    Messages
    16 799
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 60
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Ingénieur d'études en informatique
    Secteur : Enseignement

    Informations forums :
    Inscription : Août 2006
    Messages : 16 799
    Points : 34 031
    Points
    34 031
    Billets dans le blog
    14
    Par défaut
    Avez-vous essayé d'ajouter DISTINCT après le SELECT de ma requête ?
    Philippe Leménager. Ingénieur d'étude à l'École Nationale Supérieure de Formation de l'Enseignement Agricole. Autoentrepreneur.
    Mon ancien blog sur la conception des BDD, le langage SQL, le PHP... et mon nouveau blog sur les mêmes sujets.
    « Ce que l'on conçoit bien s'énonce clairement, et les mots pour le dire arrivent aisément ». (Nicolas Boileau)
    À la maison comme au bureau, j'utilise la suite Linux Mageïa !

  7. #7
    Membre habitué Avatar de tintin72
    Profil pro
    Inscrit en
    Septembre 2003
    Messages
    663
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Septembre 2003
    Messages : 663
    Points : 177
    Points
    177
    Par défaut
    Oui, mais rien n'y fait. Je récupère toujours des doublons.
    Je pourrais remplacer les LEFT JOIN par des INNER JOIN mais les produits qui n'ont pas encore d'offre seraient éliminés du résultat ce qui n'est pas bon.

    Une idée ?

  8. #8
    Modérateur

    Avatar de CinePhil
    Homme Profil pro
    Ingénieur d'études en informatique
    Inscrit en
    Août 2006
    Messages
    16 799
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 60
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Ingénieur d'études en informatique
    Secteur : Enseignement

    Informations forums :
    Inscription : Août 2006
    Messages : 16 799
    Points : 34 031
    Points
    34 031
    Billets dans le blog
    14
    Par défaut
    DISTINCT ne donnant que des lignes uniques, qu'entends-tu par "doublon" ?
    Philippe Leménager. Ingénieur d'étude à l'École Nationale Supérieure de Formation de l'Enseignement Agricole. Autoentrepreneur.
    Mon ancien blog sur la conception des BDD, le langage SQL, le PHP... et mon nouveau blog sur les mêmes sujets.
    « Ce que l'on conçoit bien s'énonce clairement, et les mots pour le dire arrivent aisément ». (Nicolas Boileau)
    À la maison comme au bureau, j'utilise la suite Linux Mageïa !

  9. #9
    Membre habitué Avatar de tintin72
    Profil pro
    Inscrit en
    Septembre 2003
    Messages
    663
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Septembre 2003
    Messages : 663
    Points : 177
    Points
    177
    Par défaut
    Je récupère toutes les lignes t_offers pour chaque product id.

  10. #10
    Modérateur

    Avatar de CinePhil
    Homme Profil pro
    Ingénieur d'études en informatique
    Inscrit en
    Août 2006
    Messages
    16 799
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 60
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Ingénieur d'études en informatique
    Secteur : Enseignement

    Informations forums :
    Inscription : Août 2006
    Messages : 16 799
    Points : 34 031
    Points
    34 031
    Billets dans le blog
    14
    Par défaut
    Alors décompose comme je l'ai fait dans mon message #2 et regarde à quel moment les lignes se multiplient.

    Normalement, avec ma requête A, tu ne dois avoir qu'une ligne par produit.
    Est-ce qu'à la requête B c'est toujours le cas ?
    Philippe Leménager. Ingénieur d'étude à l'École Nationale Supérieure de Formation de l'Enseignement Agricole. Autoentrepreneur.
    Mon ancien blog sur la conception des BDD, le langage SQL, le PHP... et mon nouveau blog sur les mêmes sujets.
    « Ce que l'on conçoit bien s'énonce clairement, et les mots pour le dire arrivent aisément ». (Nicolas Boileau)
    À la maison comme au bureau, j'utilise la suite Linux Mageïa !

  11. #11
    Membre habitué Avatar de tintin72
    Profil pro
    Inscrit en
    Septembre 2003
    Messages
    663
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Septembre 2003
    Messages : 663
    Points : 177
    Points
    177
    Par défaut
    La requête A c'est ok, c'est à la requête B que les "doublons" apparaissent.

  12. #12
    Modérateur

    Avatar de CinePhil
    Homme Profil pro
    Ingénieur d'études en informatique
    Inscrit en
    Août 2006
    Messages
    16 799
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 60
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Ingénieur d'études en informatique
    Secteur : Enseignement

    Informations forums :
    Inscription : Août 2006
    Messages : 16 799
    Points : 34 031
    Points
    34 031
    Billets dans le blog
    14
    Par défaut
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    FROM t_products p
    LEFT OUTER JOIN t_offers o1 ON o1.product_id = p.id
    	LEFT OUTER JOIN
    	(
    		SELECT product_id, MAX(`date`) AS date_derniere_offre
    		FROM t_offers
    		GROUP BY product_id
    	) t1
    		ON t1.product_id = o1.product_id
    		AND t1.date_derniere_offre = o1.`date`
    Essaie de remplacer le deuxième LEFT OUTER JOIN par un INNER JOIN.
    Philippe Leménager. Ingénieur d'étude à l'École Nationale Supérieure de Formation de l'Enseignement Agricole. Autoentrepreneur.
    Mon ancien blog sur la conception des BDD, le langage SQL, le PHP... et mon nouveau blog sur les mêmes sujets.
    « Ce que l'on conçoit bien s'énonce clairement, et les mots pour le dire arrivent aisément ». (Nicolas Boileau)
    À la maison comme au bureau, j'utilise la suite Linux Mageïa !

  13. #13
    Membre habitué Avatar de tintin72
    Profil pro
    Inscrit en
    Septembre 2003
    Messages
    663
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Septembre 2003
    Messages : 663
    Points : 177
    Points
    177
    Par défaut
    Non ça ne va pas, je récupère toujours des "doublons".
    Par contre j'ai trouvé une autre façon de récupérer les avant dernières offres.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    SELECT DISTINCT p.id, p.name, o1.offer AS latest_offer, o1.user_id AS latest_user_id, o2.offer AS sec_last_offer, o2.user_id AS 
    sec_last_user_id 
    FROM t_products AS p 
    --Récupère la dernière offre faite pour ce produit.
    LEFT JOIN (SELECT product_id, MAX(`date`) AS latest_date FROM t_offers GROUP BY product_id) AS t1 ON t1.product_id=p.id 
    LEFT JOIN t_offers AS o1 ON o1.product_id=t1.product_id AND o1.`date`=t1.latest_date 
    --Récupère l'avant dernière offre faite pour ce produit.
    LEFT JOIN (SELECT product_id, MAX(`date`) AS sec_last_date FROM t_offers AS s1  WHERE `date` < (SELECT MAX(`date`) FROM 
    t_offers WHERE product_id=s1.product_id) GROUP BY product_id) AS t2 ON t2.product_id=p.id 
    LEFT JOIN t_offers AS o2 ON o2.product_id=t2.product_id AND o2.`date`=t2.sec_last_date
    --Récupère les produits encore disponibles ($NOW = date et heure courante)
    WHERE p.date_limite > $NOW
    Est ce que la requête est mieux optimisée comme ça ?

  14. #14
    Modérateur

    Avatar de CinePhil
    Homme Profil pro
    Ingénieur d'études en informatique
    Inscrit en
    Août 2006
    Messages
    16 799
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 60
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Ingénieur d'études en informatique
    Secteur : Enseignement

    Informations forums :
    Inscription : Août 2006
    Messages : 16 799
    Points : 34 031
    Points
    34 031
    Billets dans le blog
    14
    Par défaut
    Pas sûr. Je crains fort que la sous-requête dans un WHERE entraîne un parcours complet de la table pour chaque produit alors qu'une requête ensembliste (avec GROUP BY) trouve le résultat pour chaque produit en une seule fois.

    Pourrais-tu nous donner les requêtes de création des tables utilisées et un petit jeu de données pour qu'on puisse tester les solutions proposées et voir ce qui ne va pas ?
    Philippe Leménager. Ingénieur d'étude à l'École Nationale Supérieure de Formation de l'Enseignement Agricole. Autoentrepreneur.
    Mon ancien blog sur la conception des BDD, le langage SQL, le PHP... et mon nouveau blog sur les mêmes sujets.
    « Ce que l'on conçoit bien s'énonce clairement, et les mots pour le dire arrivent aisément ». (Nicolas Boileau)
    À la maison comme au bureau, j'utilise la suite Linux Mageïa !

  15. #15
    Membre habitué Avatar de tintin72
    Profil pro
    Inscrit en
    Septembre 2003
    Messages
    663
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Septembre 2003
    Messages : 663
    Points : 177
    Points
    177
    Par défaut
    Oui le where risque de ralentir la requête, toutefois je crains qu'on ne puisse
    pas y échapper car toutes les requêtes que j'ai vu permettant de récupérer une avant
    dernière donné utilise un where.

    Voici donc les tables + un jeux de données.
    Note: Ce sont des données de travail entrées à la main aussi j'espère qu'il n'y a pas d'erreur dans les dates.
    Je n'ai gardé que les colonnes pertinentes pour ne pas alourdir l'exemple.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    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
    44
    45
    46
    47
    48
    49
    CREATE TABLE `t_products` (
      `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
      `name` VARCHAR(80) NOT NULL ,
      `end_date` DATETIME NOT NULL DEFAULT '0000-00-00 00:00:00' ,
      PRIMARY KEY (`id`) )
    ENGINE = MyISAM AUTO_INCREMENT=0 DEFAULT CHARSET=utf8; 
     
    INSERT INTO `t_products` (id, name, end_date) 
    VALUES 
    (1, "Synthétiseur", "2014-11-29 06:08:13"),
    (2, "Ordinateur portable", "2014-11-29 07:41:09"),
    (3, "Voiture", "2014-11-30 07:41:09"),
    (4, "Jeux video", "2014-11-28 14:37:21"),
    (5, "Téléviseur", "2014-11-10 08:23:40"),
    (6, "Film DVD", "2014-11-29 09:17:50"),
    (7, "Console de jeux", "2014-11-28 08:42:25"),
    (8, "Sac de voyage", "2014-11-30 07:16:03");
     
    CREATE TABLE `t_offers` (
      `product_id` INT UNSIGNED NOT NULL DEFAULT 0 ,
      `user_id` INT UNSIGNED NOT NULL DEFAULT 0 ,
      `offer` DECIMAL(14,5) UNSIGNED NOT NULL ,
      `date` DATETIME NOT NULL DEFAULT '0000-00-00 00:00:00' ) 
    ENGINE = MyISAM DEFAULT CHARSET=utf8;
     
    INSERT INTO `t_offers` (product_id, user_id, offer,`date`) 
    VALUES 
    (5, 612, 500.0000, "2014-11-05 06:08:13"),
    (5, 608, 700.0000, "2014-11-05 14:20:13"),
    (5, 612, 580.0000, "2014-11-06 09:08:13"),
    (5, 608, 680.0000, "2014-11-06 15:08:13"),
    (5, 612, 600.0000, "2014-11-06 17:08:13"),
    (5, 608, 620.0000, "2014-11-06 19:08:13"),
    (5, 612, 620.0000, "2014-11-06 23:08:13"),
    (1, 607, 150.0000, "2014-11-08 06:08:13"),
    (1, 605, 150.0000, "2014-11-08 10:08:13"),
    (1, 608, 160.0000, "2014-11-08 14:20:13"),
    (1, 605, 280.0000, "2014-11-08 22:20:13"),
    (1, 608, 180.0000, "2014-11-09 10:20:13"),
    (1, 605, 260.0000, "2014-11-09 14:20:13"),
    (1, 605, 260.0000, "2014-11-10 10:20:13"),
    (1, 612, 200.0000, "2014-11-10 15:08:13"),
    (2, 607, 300.0000, "2014-11-09 16:08:13"),
    (2, 609, 400.0000, "2014-11-09 19:20:13"),
    (2, 607, 320.0000, "2014-11-09 23:08:13"),
    (2, 609, 390.0000, "2014-11-10 05:20:13"), 
    (8, 607, 250.0000, "2014-11-12 15:20:13"),
    (4, 607, 40.0000, "2014-11-11 07:08:13"),
    (4, 606, 70.0000, "2014-11-11 22:02:13");

  16. #16
    Modérateur

    Avatar de CinePhil
    Homme Profil pro
    Ingénieur d'études en informatique
    Inscrit en
    Août 2006
    Messages
    16 799
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 60
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Ingénieur d'études en informatique
    Secteur : Enseignement

    Informations forums :
    Inscription : Août 2006
    Messages : 16 799
    Points : 34 031
    Points
    34 031
    Billets dans le blog
    14
    Par défaut
    Merci.

    Pour ma requête B, je commence par la simplifier pour ne travailler qu'avec la table t_offers et avec une jointure interne, ça fonctionne mieux :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    SELECT 
    	o1.product_id, 
    	t1.date_derniere_offre, 
    	o1.offer AS latest_offer, 
    	o1.user_id AS latest_user_id
    FROM t_offers o1
    INNER JOIN 
    (
    	SELECT product_id, MAX(`date`) AS date_derniere_offre
    	FROM t_offers
    	GROUP BY product_id
    ) t1
    	ON t1.product_id = o1.product_id
    	AND t1.date_derniere_offre = o1.`date`
    Pour la requête C, la jointure était à l'envers, ce qui entraînait la récupération de toutes les offres.
    Voici la correction :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    SELECT t2.product_id, 
    	t2.date_derniere_offre,
    	MAX(o2.`date`) AS date_avant_derniere_offre
    FROM 
    (
    	SELECT product_id, MAX(`date`) AS date_derniere_offre
    	FROM t_offers
    	GROUP BY product_id
    ) t2
    LEFT OUTER JOIN t_offers o2 
    	ON t2.product_id = o2.product_id
    	AND o2.`date` < t2.date_derniere_offre
    GROUP BY t2.product_id,t2.date_derniere_offre
    J'assemble le tout en faisant deux jointures externes à partir de la table t_products et ça semble bien fonctionner :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    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
    44
    45
    46
    47
    48
    49
    50
    51
    SELECT 
    	p.id AS product_id,
    	p.name AS product_name,
    	d.date_derniere_offre,
    	d.latest_offer,
    	d.latest_user_id,
    	ad.last_but_one_user_id,
    	ad.date_avant_derniere_offre
    FROM t_products p
    LEFT OUTER JOIN
    (
    	SELECT 
    		o1.product_id, 
    		t1.date_derniere_offre, 
    		o1.offer AS latest_offer, 
    		o1.user_id AS latest_user_id
    	FROM t_offers o1
    	INNER JOIN 
    	(
    		SELECT product_id, MAX(`date`) AS date_derniere_offre
    		FROM t_offers
    		GROUP BY product_id
    	) t1
    		ON t1.product_id = o1.product_id
    		AND t1.date_derniere_offre = o1.`date`
    ) d ON d.product_id = p.id
    LEFT OUTER JOIN
    (
    	SELECT 
    		o4.product_id,
    		o4.user_id AS last_but_one_user_id,
    		t3.date_avant_derniere_offre
    	FROM t_offers o4
    	INNER JOIN
    	(
    		SELECT t2.product_id, 
    			MAX(o2.`date`) AS date_avant_derniere_offre
    		FROM 
    		(
    			SELECT product_id, MAX(`date`) AS date_derniere_offre
    			FROM t_offers
    			GROUP BY product_id
    		) t2
    		LEFT OUTER JOIN t_offers o2 
    			ON t2.product_id = o2.product_id
    			AND o2.`date` < t2.date_derniere_offre
    		GROUP BY t2.product_id
    	) t3
    		ON t3.product_id = o4.product_id
    		AND t3.date_avant_derniere_offre = o4.`date`
    ) ad ON ad.product_id = p.id
    Vérifie quand même les résultats mais je crois que cette fois c'est la bonne.
    Philippe Leménager. Ingénieur d'étude à l'École Nationale Supérieure de Formation de l'Enseignement Agricole. Autoentrepreneur.
    Mon ancien blog sur la conception des BDD, le langage SQL, le PHP... et mon nouveau blog sur les mêmes sujets.
    « Ce que l'on conçoit bien s'énonce clairement, et les mots pour le dire arrivent aisément ». (Nicolas Boileau)
    À la maison comme au bureau, j'utilise la suite Linux Mageïa !

  17. #17
    Membre habitué Avatar de tintin72
    Profil pro
    Inscrit en
    Septembre 2003
    Messages
    663
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Septembre 2003
    Messages : 663
    Points : 177
    Points
    177
    Par défaut
    Effectivement je récupère bien les résultats attendus. Bravo
    Toutefois est ce que cette requête va être vraiment plus efficace avec plusieurs milliers de lignes,
    car je m'aperçois qu'elle utilise 5 sous requêtes ce qui risque de prendre beaucoup de ressources non ?

    En tout cas encore un grand merci pour avoir pris le temps de résoudre mon problème.

  18. #18
    Modérateur

    Avatar de CinePhil
    Homme Profil pro
    Ingénieur d'études en informatique
    Inscrit en
    Août 2006
    Messages
    16 799
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 60
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Ingénieur d'études en informatique
    Secteur : Enseignement

    Informations forums :
    Inscription : Août 2006
    Messages : 16 799
    Points : 34 031
    Points
    34 031
    Billets dans le blog
    14
    Par défaut
    Comme je l'ai expliqué plus haut, mettre une sous requête dans un WHERE entraîne généralement l'exécution de la requête pour chaque ligne à tester.

    Le SQL est un langage ensembliste qui travaille sur des données en masse. Lorsqu'on fait un GROUP BY, le SGBD traite toutes les lignes en même temps en faisant sa recherche et/ou ses tris et calculs nécessaires de façon masquée au fur et à mesure du parcours des tables. Il utilise même prioritairement et autant que faire se peut les index plutôt que de parcourir les tables. C'est très rapide.

    Dans ma requête, comme il y a deux fois la sous requête qui cherche la date_derniere_offre, le SGBD ne va l'exécuter qu'une fois et stocker le résultat en mémoire pour l'utiliser quand il en aura besoin.

    En principe, une requête ensembliste est plus rapide qu'une requête séquentielle. Ce n'est pas sensible sur des petites tables mais ça le devient à partir de quelques dizaines de milliers de lignes sur MySQL.
    Philippe Leménager. Ingénieur d'étude à l'École Nationale Supérieure de Formation de l'Enseignement Agricole. Autoentrepreneur.
    Mon ancien blog sur la conception des BDD, le langage SQL, le PHP... et mon nouveau blog sur les mêmes sujets.
    « Ce que l'on conçoit bien s'énonce clairement, et les mots pour le dire arrivent aisément ». (Nicolas Boileau)
    À la maison comme au bureau, j'utilise la suite Linux Mageïa !

  19. #19
    Membre habitué Avatar de tintin72
    Profil pro
    Inscrit en
    Septembre 2003
    Messages
    663
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Septembre 2003
    Messages : 663
    Points : 177
    Points
    177
    Par défaut
    Ok. Encore merci pour ces informations.

  20. #20
    Membre habitué Avatar de tintin72
    Profil pro
    Inscrit en
    Septembre 2003
    Messages
    663
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Septembre 2003
    Messages : 663
    Points : 177
    Points
    177
    Par défaut
    EDIT:
    J'ai trouvé une autre manière d'arriver au même résultat (sans clause WHERE) et avec seulement 2 sous requêtes:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    SELECT DISTINCT p.id, p.name, o1.offer AS latest_offer, o1.user_id AS latest_user_id, o2.offer AS sec_last_offer, o2.user_id AS sec_last_user_id 
    FROM t_products AS p 
    --Récupère la dernière offre (ligne) faite pour ce produit.
    LEFT JOIN (SELECT product_id, MAX(`date`) AS latest_date FROM t_offers GROUP BY product_id) AS t1 ON t1.product_id=p.id 
    LEFT JOIN t_offers AS o1 ON o1.product_id=t1.product_id AND o1.`date`=t1.latest_date 
    --Récupère l'avant dernière offre (ligne) faite pour ce produit.
    LEFT JOIN (SELECT s1.product_id, MAX(s1.date) AS sec_last_date 
               FROM t_offers AS s1  
               INNER JOIN t_offers AS s2
               ON s2.product_id = s1.product_id
               AND s2.`date` > s1.`date`
               GROUP BY s1.product_id) AS t2 ON t2.product_id=p.id        
    LEFT JOIN t_offers AS o2 ON o2.product_id=t1.product_id AND o2.`date`=t2.sec_last_date

+ Répondre à la discussion
Cette discussion est résolue.
Page 1 sur 2 12 DernièreDernière

Discussions similaires

  1. A propos des sous-requêtes
    Par vincent-v dans le forum Langage SQL
    Réponses: 8
    Dernier message: 20/08/2009, 18h40
  2. [AC-2003] Création d'index dans des sous-requêtes
    Par buzz73 dans le forum Requêtes et SQL.
    Réponses: 4
    Dernier message: 06/08/2009, 14h33
  3. Afficher des sous-requêtes dans une requête
    Par ZashOne dans le forum Sql*Plus
    Réponses: 6
    Dernier message: 16/04/2008, 10h07
  4. Problème avec des sous requêtes
    Par nicocolt dans le forum Requêtes
    Réponses: 2
    Dernier message: 10/10/2007, 15h19
  5. exprimer des sous requêtes
    Par Smix007 dans le forum SQL
    Réponses: 2
    Dernier message: 12/07/2007, 18h07

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo