Je développe ma question ici: Construire une requête avec JOIN, mais filtrer et trier cette table en premier Maintenant, l'objectif est devenu plus compliqué et je ne peux pas le résoudre.
Voici le problème:
Je construis un composant personnalisé dans Joomla 3.x. J'ai deux tables dans mon modèle:
table persons
id name
1 Peter
2 Paul
3 Mary
table cars
id personid make price color
1 1 BMW 10,000 red
2 1 Audi 8,000 blue
3 1 BMW 6,000 white
4 2 BMW 21,000 silver
5 2 Renault 9,500 black
6 3 Seat 4,200 green
Maintenant, j'aimerais construire une requête sur le retour des personnes, de leur voiture, de son prix et de sa couleur.
Voici la partie la plus délicate: s’ils ont plusieurs voitures, la requête doit sélectionner la BMW dont le prix est le plus bas:
Peter BMW 6,000 white
Paul BMW 21,000 silver
Mary Seat 4,200 green
J'ai essayé avec ...
SELECT p.name, c.make, MIN(c.price), c.color
FROM persons AS p
LEFT JOIN cars AS c ON c.personid = p.id
GROUP BY p.id
mais les résultats sont faux:
Peter BMW 6,000 red (color change!)
Paul BMW 9,500 silver (price drop!)
Mary Seat 4,200 green (well...)
voir le violon: http://sqlfiddle.com/#!9/d93f41/2/
Comment puis-je contrôler quelle voiture est liée à la personne qui a plus d'une voiture?
Joomla ne gère pas très bien les sous-requêtes car il n'y a pas de vraie méthode $ query-> subQuery. Dans ce cas, il est plus facile de formater la requête en tant que requête MySQL standard et de laisser Joomla traiter les résultats. La requête ci-dessous renverra le résultat approprié pour Peter et Paul ayant des BMW, avec le prix le plus bas et les couleurs correctes.
$db = JFactory::getDbo();
$query = $db->getQuery(true);
$sql = "SELECT p.name, c.make, c.price, c.color FROM #__persons AS p LEFT JOIN #__cars AS c ON c.personid = p.id WHERE c.make = 'BMW' AND c.price = (SELECT MIN(price) FROM #__cars WHERE make = 'BMW' AND personid = p.id)";
$db->setQuery($sql);
$rows = $db->loadObjectList();
La solution de Terry Carter ne fournit pas la fonctionnalité/l'ensemble de résultats souhaité ET elle transmet la fausse notion selon laquelle Joomla "ne gère pas bien les sous-requêtes". S'il est vrai qu'il n'y a pas de méthode appelée subQuery()
, Joomla fournit une syntaxe propre pour imbriquer des requêtes à l'intérieur d'autres requêtes.
La question la plus criante de la requête de Terry est qu’elle ignore toutes les personnes qui n’ont pas de BMW
à vendre. Ce n'est pas la logique souhaitée décrite dans la question. L'ensemble de résultats attendus devrait inclure une ligne pour Mary
qui contient l'élément non BMW le moins cher.
De plus, ma solution implémente INNER JOIN au lieu de LEFT JOIN, de sorte que tout nom existant dans persons
et ne comportant pas de ligne dans cars
sera exclu du jeu de résultats. (Si la logique opposée est souhaitée, utilisez bien sûr un LEFT JOIN.) En d'autres termes, un LEFT JOIN générera potentiellement des lignes avec NULL
valeurs dans make
, price
et color
.
SQL brut: ( démonstration de DB-Fiddle.com )
SELECT name, make, price, color
FROM persons p
INNER JOIN cars ON p.id = personid
WHERE price = IFNULL(
(SELECT MIN(price) FROM cars WHERE personid = p.id AND make = 'BMW'),
(SELECT MIN(price) FROM cars WHERE personid = p.id)
)
Les choses à noter:
c.price
, Mais MySQL sait déjà dans quel tableau il doit être dessiné.cars
n'est jamais utilisé pour identifier les colonnes de la requête, j'ai choisi d'omettre l'alias c
.c
et d'ajouter c.
À chacune des colonnes de la requête, veillez à ne PAS écrire c.make = 'BMW'
Dans la première sous-requête, car modifiera la logique et endommagera les résultats - make
dans ce contexte doit faire référence à la valeur make
de la sous-requête.IFNULL()
est un choix judicieux pour la requête, car la deuxième sous-requête n'est exécutée que si la première sous-requête ne renvoie aucune ligne qualifiante. Ce bloc d’état garantit que vous obtenez la valeur BMW la plus basse, à moins qu’il n’y ait pas de BMW pour la personne concernée, auquel cas vous retombez au "prix le plus bas" pour cette personne.Maintenant, pour utiliser les méthodes de construction de requêtes de Joomla pour créer la requête ... Comme pour la grande majorité de mes messages de cette communauté, je vais emballer mon extrait avec un vidage de requête et quelques astuces de diagnostic pour aider les chercheurs à comprendre et à déboguer leurs propres projets. . Veuillez noter que vous ne devriez jamais afficher les requêtes générées ni les erreurs mysql auprès du public, car cela peut conduire des personnes coquines à affiner les attaques malveillantes sur votre système.
try {
$db = JFactory::getDbo();
$bmw_subquery = $db->getQuery(true)
->select("MIN(price)")
->from("cars")
->where("personid = p.id")
->where("make = " . $db->q("BMW"));
$all_subquery = $db->getQuery(true)
->select("MIN(price)")
->from("cars")
->where("personid = p.id");
$query = $db->getQuery(true)
->select("name, make, price, color")
->from("persons p")
->innerJoin("cars ON p.id = personid")
->where("price = IFNULL(($bmw_subquery), ($all_subquery))");
$db->setQuery($query);
JFactory::getApplication()->enqueueMessage($query->dump(), 'info');
if (!$result = $db->loadAssocList()) {
echo "<p>No Rows Found</p>";
} else {
echo "<pre>";
var_export($result);
echo "</pre>";
}
} catch (Exception $e) {
JFactory::getApplication()->enqueueMessage("Query Syntax Error: " . $e->getMessage(), 'error'); // don't show $e->getMessage() to public
}
Cela servira la requête rendue décrite ci-dessus et le jeu de résultats souhaité:
array (
0 =>
array (
'name' => 'Peter',
'make' => 'BMW',
'price' => '6000',
'color' => 'white',
),
1 =>
array (
'name' => 'Paul',
'make' => 'BMW',
'price' => '21000',
'color' => 'silver',
),
2 =>
array (
'name' => 'Mary',
'make' => 'Seat',
'price' => '4200',
'color' => 'green',
),
)
Quelques mots d'adieu:
Je me serais attendu à ce que les tables de la question portent les noms des caractères de préfixe aléatoires de Joomla. Il est vivement recommandé d’utiliser ces préfixes et ajuster la syntaxe du générateur de requêtes Joomla signifie uniquement que les préfixes #__
, Tels que #__cars
Et #__persons
Sont associés à des tables pourrait s'appeler abcde_cars
et abcde_persons
.
Si quelqu'un est voué à écrire du code laconique, la sous-requête $bmw_subquery
Peut être générée en clonant la sous-requête $all_subquery
Puis en chaînant la condition de clause where supplémentaire.
$all_subquery = $db->getQuery(true)
->select("MIN(price)")
->from("cars")
->where("personid = p.id");
$bmw_subquery = (clone($all_subquery))->where("make = " . $db->q("BMW"));
// ^--------------------^-- parentheses are essential
$query = $db->getQuery(true)
->select("name, make, price, color")
->from("persons p")
->innerJoin("cars ON p.id = personid")
->where("price = IFNULL(($bmw_subquery), ($all_subquery))");
* Les parenthèses sont obligatoires pour que vous "cloniez puis enchaînez" au lieu de "enchaîner à l'original puis cloner". Si vous supprimez les parenthèses, vous constaterez que les deux sous-requêtes seront identiques et qu'elles incluront AND make = 'BMW'
.