J'ai deux ensembles de filtres de produits disjoints, $special_filters
et $other_filters
. J'essaie de récupérer les produits de la base de données qui correspondent à l'un des filtres de $special_filters
ET l'un des filtres de $other_filters
. Par exemple, si $special_filters = {'New'}
et $other_filters = {'Brakes', 'Tires'}
alors je veux obtenir tous les nouveaux freins et tous les nouveaux pneus, par opposition à toutes les nouvelles pièces, tous les freins et tous les pneus. Voici ce que j'ai appris à travailler dans phpMyAdmin (ici le product_filters
table est un tableau à 2 colonnes concernant product_id
et filter_id
:
SELECT q.product_id FROM (
SELECT product_id FROM `<prefix>_j2store_product_filters`
WHERE filter_id = 'tag1'
) AS q
INNER JOIN `<prefix>_j2store_product_filters` AS t ON q.product_id = t.product_id
WHERE filter_id IN ('tag2', 'tag3')
Tous les produits qui ont tag1
, et soit tag2
ou tag3
. Cependant, ma traduction en Joomla ne semble pas fonctionner.
Je modifie le code de J2Store dans administrator/components/com_j2store/models/products.php
, vers la fin du _sfBuildWhereQuery
, qui configure un objet de requête JDatabase pour récupérer une liste de produits. À ce stade du code, il y a un $filter_ids
tableau qui contient tous les filtres actifs et est l'union de mes deux tableaux de filtres, et je dois ajouter une clause "WHERE" appropriée à l'objet de requête en cours. J'ai simplement essayé de traduire mon code SQL en JDatabase. Voici ma tentative:
$other_ids = array_diff($filter_ids, $special_filters);
if (empty($special_filters) || empty($other_ids)) {
// This is the usual case for the 'OR' operator.
$query->where ( '#__j2store_product_filters.filter_id IN ('
. implode ( ',', $filter_ids ) . ')' );
} else {
// Make sub-sub-query to filter with special filters
$subSubQuery = $db->getQuery(True);
$subSubQuery
->select($db->quoteName('product_id'))
->from($db->quoteName('#__j2store_product_filters'))
->where($db->quoteName('filter_id')
. ' IN (' . implode(',', $special_filters) . ')');
// Filter the result with the other filters in another subquery
$subQuery = $db->getQuery(True);
$subQuery
->select($db->quoteName('s.product_id'))
->from($subSubQuery, 's')
->join('INNER',
$db->quoteName('#__j2store_product_filters', 'f')
. ' ON (' . $db->quoteName('s.product_id')
. ' = ' . $db->quoteName('f.product_id') . ')')
->where($db->quoteName('filter_id')
. ' IN (' . implode(',', $other_ids) . ')');
$query->where('EXISTS (SELECT * FROM (' . $subQuery->__toString() . ') AS t
WHERE ' . $db->quoteName('#__j2store_product_filters.product_id') .
' = ' . $db->quoteName('t.product_id') . ')');
}
Cependant, il renvoie trop de résultats: il renvoie toujours toutes les nouvelles pièces, tous les freins et tous les pneus, au lieu de simplement de nouveaux freins et de nouveaux pneus.
Voici une démonstration de la logique conditionnelle dont j'ai besoin: db-fiddle.com/f/o1UWTKXVTxxwyvrpcupmqu/3
Vous êtes mieux servi à GROUP BY item
puis exécutez votre qualification id_filters
, special_filters
, et other_filters
conditions sur les données agrégées via la clause HAVING.
Pour votre démo db-fiddle ...
SELECT
item
FROM item_tags
GROUP BY item
HAVING
MAX(CASE WHEN tag IN ('Used') THEN 1 ELSE NULL END)
AND MAX(CASE WHEN tag IN ('Brake') THEN 1 ELSE NULL END)
Le 1
est utilisé simplement parce que les résultats satisfaisants doivent être "véridiques" par rapport à NULL
les résultats étant "falsey". Si les deux conditions sont évaluées comme non nulles, vous disposez d'un item
éligible. Facile et propre.
Maintenant, si la micro-optimisation est nécessaire, il peut être avantageux de fusionner les deux listes de filtrage en une seule chaîne et d'ajouter:
WHERE tag IN ($all_csv_filters_as_one)
Je n'ai pas enquêté sur cette clause supplémentaire parce que je n'avais pas de données réalistes à comparer (et je ne suis pas du tout intéressé à le faire non plus). Notez que cela ne remplace pas la clause HAVING, mais préfiltre plutôt les données "agrégées" afin que HAVING n'ait pas à passer au crible autant.
La syntaxe du générateur de requêtes Joomla pour votre requête j2store pourrait ressembler à ceci:
$query = $db->getQuery(true)
->select("product_id")
->from("#__j2store_product_filters")
->group("product_id")
->having([
"MAX(CASE WHEN filter_id IN ({$filter_ids}) THEN 1 ELSE NULL END)",
"MAX(CASE WHEN filter_id IN ({$special_filter_ids}) THEN 1 ELSE NULL END")
]);
p.s.
Si vous souhaitez ajouter conditionnellement plus de clauses HAVING selon que le filtre respectif n'est pas une chaîne vide, vous pouvez appeler de manière conditionnelle une nouvelle méthode HAVING chaque fois que vous en avez une ou plusieurs filter_id
valeurs à inclure. Comme ça...
// build $query with all of the other clauses then after ->group()...
if ($filter_ids)
{
$query->having("MAX(CASE WHEN filter_id IN ({$filter_ids}) THEN 1 ELSE NULL END)");
}
if ($special_ids)
{
$query->having("MAX(CASE WHEN filter_id IN ({$special_ids}) THEN 1 ELSE NULL END)");
}
if ($other_ids)
{
$query->having("MAX(CASE WHEN filter_id IN ({$other_ids}) THEN 1 ELSE NULL END)");
}
Mon problème n'était en fait pas dans le SQL. J'ai fait un peu plus de débogage et j'ai découvert que j'entrais seulement la clause "si vide", c'est pourquoi il faisait le comportement par défaut. Mon problème était que je ne comprenais pas la variable
$filter_ids = $state->productfilter_id;
Je pensais que c'était un tableau, mais en fait c'est une chaîne séparée par des virgules! Une fois que j'ai pris cela en compte, mon code Joomla a fonctionné.
Je suppose que cette question pourrait être supprimée, mais d'un autre côté, le code ci-dessus fournit une solution à ce problème particulier.