Problème de restriction sur jointure MySQL

ViPHP
ViPHP | 928 Messages

24 avr. 2012, 15:20

Bonjour à tous,
je rencontre actuellement un problème qui me bloque en SQL.
J'ai une table de produits (product), chaque produit peut avoir X déclinaisons de produits (product_attribute) sachant qu'il peut n'avoir aucune déclinaison, chaque déclinaison va ensuite pouvoir avoir des caractéristiques par boutique (product_attribute_shop), car il y a une gestion de multi-boutiques, pour mes exemples on considèrera juste le fait de pouvoir désigner une déclinaison par défaut par boutique.
Par exemple mon produit Ipod a trois déclinaisons (Ipod 8go, Ipod 16go et Ipod 32go), sur ma première boutique c'est l'Ipod 16go qui est la déclinaison par défaut, alors que sur la seconde c'est Ipod 32go.

Ce que je dois faire : lister l'ensemble des produits, en récupérant leur déclinaison par défaut s'ils en ont une.

Voici un schéma du problème :

Image

Je me disais qu'à priori cette requête fonctionnerait :
SELECT p.name as product_name, pa.name as product_attribute_name
FROM product p
LEFT JOIN product_attribute pa
   ON pa.id_product = p.id_product
LEFT JOIN product_attribute_shop ps
   ON pa.id_product_attribute = ps.id_product_attribute
   AND ps.id_shop = 1
   AND ps.default_on = 1
Le problème c'est qu'elle me récupère l'ensemble des déclinaisons des produits, sans filtrer uniquement sur celle par défaut, et pour cause la clause de restriction sur cette déclinaison par défaut est faîte dans le second LEFT JOIN. Malheureusement si je sors cette clause dans un WHERE, ça marchera mais il ne me sortira que les produits avec des déclinaisons et pas ceux sans ... Je sèche !

Pour aider, voici un export de ces tables avec quelques données d'exemples :
CREATE TABLE IF NOT EXISTS `product` (
  `id_product` int(11) NOT NULL,
  `name` varchar(45) NOT NULL,
  PRIMARY KEY (`id_product`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

INSERT INTO `product` (`id_product`, `name`) VALUES
(1, 'Ipod'),
(2, 'Macbook');

CREATE TABLE IF NOT EXISTS `product_attribute` (
  `id_product_attribute` int(11) NOT NULL,
  `id_product` int(11) NOT NULL,
  `name` varchar(45) NOT NULL,
  PRIMARY KEY (`id_product_attribute`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

INSERT INTO `product_attribute` (`id_product_attribute`, `id_product`, `name`) VALUES
(1, 1, 'Ipod 8go'),
(2, 1, 'Ipod 16go'),
(3, 1, 'Ipod 32go');

CREATE TABLE IF NOT EXISTS `product_attribute_shop` (
  `id_product_attribute` int(11) NOT NULL,
  `id_shop` int(11) NOT NULL,
  `default_on` tinyint(1) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

INSERT INTO `product_attribute_shop` (`id_product_attribute`, `id_shop`, `default_on`) VALUES
(1, 1, 0),
(1, 2, 0),
(2, 1, 1),
(2, 2, 0),
(3, 1, 0),
(3, 2, 1);
Merci d'avance :)

Avatar du membre
Modérateur PHPfrance
Modérateur PHPfrance | 8758 Messages

24 avr. 2012, 15:48

yop,

et si prend la question a l'envers ?

tu sélectionne les id_product_attribute de product_attribute_shop qui ont default_on=1 et tu remonte sur les autres tables.

ce qui donnerait
select p.name AS product_name, pa.name AS product_attribute_name, id_shop, default_on
from product_attribute_shop
join product_attribute pa using(id_product_attribute)
join product p using(id_product)
where default_on=1
ça me donne en résultat
Ipod 16go et Ipod 32go sur les id_shop 1 & 2 (deux tuples)

@+
Vous n’avez pas les permissions nécessaires pour voir les fichiers joints à ce message.
Il en faut peu pour être heureux ......

Petit nouveau ! | 1 Messages

24 avr. 2012, 15:48

J'ai trouvé deux solutions. Mais je suis pas sûr en terme de perf que ça soit le top :
SELECT p.name AS product_name, pa.name AS product_attribute_name
FROM product p
LEFT JOIN product_attribute pa
   ON pa.id_product = p.id_product
LEFT JOIN product_attribute_shop ps
   ON pa.id_product_attribute = ps.id_product_attribute
   AND ps.id_shop = 1
WHERE  ps.default_on = 1 OR pa.id_product_attribute IS NULL

L'autre solution qui est plus rapide sur ma machine, mais qui je pense avec un grand nombre de produit doit se transformer en gouffre à ressource, est d'utiliser une sous requête :
SELECT
	p.name AS product_name, pav.name AS product_attribute_name
FROM product p
LEFT JOIN
	(SELECT pa.name, ps.*
		FROM product_attribute pa
		INNER JOIN product_attribute_shop ps
   			ON pa.id_product_attribute = ps.id_product_attribute
   			AND ps.id_shop = 1
   			AND ps.default_on = 1
) pav ON pav.id_product = p.id_product

ViPHP
ViPHP | 928 Messages

26 avr. 2012, 10:01

Bonjour,
@moogli : malheureusement cette solution ne permet pas de récupérer les produits qui n'ont pas de déclinaisons, c'est mon soucis :/

@madef : effectivement le OR IS NULL fonctionne très bien !

Merci pour l'aide, c'est réglé, super !

ViPHP
ViPHP | 2577 Messages

26 avr. 2012, 15:46

A priori, je pense qu'il faut que tu mettes une colonne de chaque table dans l'affichage de ta requette. Ca permet de voir ce qui ce passe lorsque l'une des 2 tables ne participe pas (colonne nulle).
De préférence prendre les colonnes qui servent à la synchronisation (clause "on" et "where").

Ca permet de comprendre plus facilement la solution qui t'as été proposée