Travailler autour de la limite de JOIN JOINDRE dans MySQL en imbriquant des sous-requêtes les unes dans les autres

I figured out that you can get around the 61 table join limit in MySQL by using subqueries. https://stackoverflow.com/a/20134402/2843690

J'essaie de comprendre comment l'utiliser facilement dans un programme sur lequel je travaille pour obtenir une liste détaillée de produits de Magento (mais je pense que la réponse à cette question pourrait s'appliquer à beaucoup de situations où eav est impliqué). Les tables qui doivent être jointes ressemblent à ceci:

catalog_product_entity
+-----------+----------------+
| entity_id | entity_type_id |
+-----------+----------------+
|         1 |              4 |
|         2 |              4 |
|         3 |              4 |
|         4 |              4 |
|         5 |              4 |
|         6 |              4 |
|         7 |              4 |
|         8 |              4 |
|         9 |              4 |
+-----------+----------------+

catalog_product_entity_int
+----------+----------------+--------------+-----------+-------+
| value_id | entity_type_id | attribute_id | entity_id | value |
+----------+----------------+--------------+-----------+-------+
|        1 |              4 |            2 |         1 |   245 |
|        2 |              4 |            3 |         1 |   250 |
|        3 |              4 |            4 |         1 |   254 |
|        4 |              4 |            2 |         2 |   245 |
|        5 |              4 |            3 |         2 |   249 |
|        6 |              4 |            4 |         2 |   253 |
|        7 |              4 |            2 |         3 |   247 |
|        8 |              4 |            3 |         3 |   250 |
|        9 |              4 |            4 |         3 |   254 |
+----------+----------------+--------------+-----------+-------+

eav_attribute
+--------------+----------------+----------------+--------------+
| attribute_id | entity_type_id | attribute_code | backend_type |
+--------------+----------------+----------------+--------------+
|            1 |              4 | name           | varchar      |
|            2 |              4 | brand          | int          |
|            3 |              4 | color          | int          |
|            4 |              4 | size           | int          |
|            5 |              4 | price          | decimal      |
|            6 |              4 | cost           | decimal      |
|            7 |              4 | created_at     | datetime     |
|            8 |              3 | name           | varchar      |
|            9 |              3 | description    | text         |
+--------------+----------------+----------------+--------------+

eav_attribute_option
+-----------+--------------+
| option_id | attribute_id |
+-----------+--------------+
|       245 |            2 |
|       246 |            2 |
|       247 |            2 |
|       248 |            3 |
|       249 |            3 |
|       250 |            3 |
|       251 |            4 |
|       252 |            4 |
|       253 |            4 |
|       254 |            4 |
+-----------+--------------+

eav_attribute_option_value
+----------+-----------+-------------------+
| value_id | option_id |       value       |
+----------+-----------+-------------------+
|       15 |       245 | Fruit of the Loom |
|       16 |       246 | Hanes             |
|       17 |       247 | Jockey            |
|       18 |       248 | White             |
|       19 |       249 | Black             |
|       20 |       250 | Gray              |
|       21 |       251 | Small             |
|       22 |       252 | Medium            |
|       23 |       253 | Large             |
|       24 |       254 | Extra Large       |
+----------+-----------+-------------------+

Le programme que j'écris génère des requêtes SQL qui ressemblent à ceci:

SELECT cpe.entity_id
, brand_int.value as brand_int, brand.value as brand
, color_int.value as color_int, color.value as color
, size_int.value as size_int, size.value as size

FROM catalog_product_entity as cpe

LEFT JOIN catalog_product_entity_int as brand_int
ON (cpe.entity_id = brand_int.entity_id
AND brand_int.attribute_id = 2)
LEFT JOIN eav_attribute_option as brand_option
ON (brand_option.attribute_id = 2
AND brand_int.value = brand_option.option_id)
LEFT JOIN eav_attribute_option_value as brand
ON (brand_option.option_id = brand.option_id)

LEFT JOIN catalog_product_entity_int as color_int
ON (cpe.entity_id = color_int.entity_id
AND color_int.attribute_id = 3)
LEFT JOIN eav_attribute_option as color_option
ON (color_option.attribute_id = 3
AND color_int.value = color_option.option_id)
LEFT JOIN eav_attribute_option_value as color
ON (color_option.option_id = color.option_id)

LEFT JOIN catalog_product_entity_int as size_int
ON (cpe.entity_id = size_int.entity_id
AND size_int.attribute_id = 4)
LEFT JOIN eav_attribute_option as size_option
ON (size_option.attribute_id = 4
AND size_int.value = size_option.option_id)
LEFT JOIN eav_attribute_option_value as size
ON (size_option.option_id = size.option_id)
;

Il était relativement facile d'écrire le code pour générer la requête, et la requête était assez facile à comprendre; Cependant, il est assez facile d'atteindre la limite de la jonction de 61 tables, ce que j'ai fait avec mes données réelles. Je crois que le calcul indique que 21 attributs de type entier dépasseraient la limite, et ce avant que je commence même à ajouter des attributs varchar, text et decimal.

Donc la solution que j'ai trouvée consistait à utiliser des sous-requêtes pour surmonter la limite de 61 tables.

Une façon de le faire est de regrouper les jointures dans les sous-requêtes de 61 jointures. Et ensuite tous les groupes seraient joints. Je pense que je peux comprendre ce que les requêtes sql devraient ressembler, mais il semble difficile d'écrire le code pour générer les requêtes. Il existe un autre problème (quoique théorique) en ce sens que l'on pourrait de nouveau violer la limite de la table 61 s'il y avait suffisamment d'attributs. En d'autres termes, si j'ai 62 groupes de 61 tables, il y aura une erreur MySQL. Évidemment, on pourrait contourner cela en regroupant ensuite les groupes de groupes en 61. Mais cela rend le code encore plus difficile à écrire et à comprendre.

Je pense que la solution que je veux est d'imbriquer des sous-requêtes dans les sous-requêtes de sorte que chaque sous-requête utilise une seule jointure de 2 tables (ou une table et une sous-requête). Intuitivement, il semble que le code serait plus facile à écrire pour ce genre de requête. Malheureusement, penser à ce que ces requêtes devraient ressembler fait mal à mon cerveau. C'est pourquoi j'ai besoin d'aide.

À quoi ressemblerait une telle requête MySQL?

3
@Axel catalog_product_flat nécessite un réindex pour être actuel, je crois. Catalog_product_flat ne possède pas non plus toutes les informations que je veux. En ce qui concerne les collections magento, je ne suis pas très familier avec le fonctionnement interne de magento. Et jusqu'à présent, j'ai trouvé que les requêtes MySQL directes étaient significativement plus rapides que l'utilisation de magento ou des API.
ajouté l'auteur The Phil Lee, source
Aussi, je suis juste curieux. Même si ce n'est pas pratique dans ce cas, cela pourrait être utile pour quelque chose plus tard. À tout le moins, je pense que cela pourrait être intéressant.
ajouté l'auteur The Phil Lee, source
Si votre objectif principal est "d'obtenir une liste détaillée de produits de Magento", pourquoi ne pas inclure les attributs que vous voulez interroger dans l'index de la table, alors faites simplement un select * from catalog_product_flat_1 ? Soit cela, ou tirer parti des modèles Magento existants pour créer une collection, et sélectionnez sur les attributs que vous souhaitez afficher. Votre solution SQL est une approche inefficace à mon avis.
ajouté l'auteur Axel, source
Je suppose que cela dépend de votre objectif. Si vous construisez une extension, vous devriez tirer parti des méthodes existantes que Magento fournit dans ses modèles de base. Je recommande d'interroger tout en utilisant les collections Magento, puis en imprimant le SQL Magento généré en utilisant: $ collection-> getSelect() -> order ('rand ()'); . Cela vous montrera le SQL exact utilisé par Magento pour rassembler toutes les différentes données et les compiler dans un ensemble de résultats unique.
ajouté l'auteur Axel, source

1 Réponses

Vous avez raison de dire que joindre trop d'attributs via une conception EAV est susceptible de dépasser la limite des jointures. Même avant cela, il y a probablement une limite pratique de jointures car le coût de tant de jointures est de plus en plus élevé géométriquement. Cela dépend de la capacité de votre serveur, mais il est probable qu'il soit inférieur à 61.

L'interrogation d'un modèle de données EAV pour produire un résultat comme s'il était stocké dans un modèle relationnel conventionnel (une colonne par attribut) est problématique.

Solution: ne le faites pas avec une jointure par attribut, ce qui signifie que vous ne pouvez pas vous attendre à produire le résultat dans un format conventionnel ligne par entité uniquement avec SQL.

Je ne suis pas intimement familier avec le schéma Magento, mais je peux déduire de votre requête que quelque chose comme ça pourrait fonctionner:

SELECT cpe.entity_id
, o.value AS option
, v.value AS option_value
FROM catalog_product_entity AS cpe
INNER JOIN catalog_product_entity_int AS i 
  ON cpe.entity_id = i.entity_id AND i.attribute_id IN (2,3,4)
INNER JOIN eav_attribute_option AS o 
  ON i.value = o.option_id AND i.attribute_id = o.attribute_id
INNER JOIN eav_attribute_option_value AS v
  ON v.option_id = o.option_id;

Le prédicat IN (2,3,4, ...) est l'endroit où vous spécifiez plusieurs attributs. Il n'est pas nécessaire d'ajouter plus de jointures pour obtenir plus d'attributs. Ils sont simplement renvoyés sous forme de lignes plutôt que de colonnes.

Cela signifie que vous devez écrire le code de l'application pour récupérer toutes les lignes de cet ensemble de résultats et les mapper dans les champs d'un seul objet.

D'après les commentaires de @Axel, il semble que Magento fournisse des fonctions auxiliaires pour consommer cet ensemble de résultats et le mapper en un objet.

2
ajouté
Pas exactement ce que je cherchais, mais cela serait en réalité beaucoup plus facile à mettre en œuvre. Merci. Pour mémoire, j'utilise Amazon RDS, et même une micro-instance a peu de problème avec une jointure de 61 tables (ou même une sous-requête de 61 tables jointes avec une sous-requête de 61 tables) autant que je sache. Je suppose que c'est relatif, car faire des requêtes similaires via Magento ou son API est ridiculement lent. De plus, d'après ce que j'ai pu voir en parcourant les logs de MySQL, Magento ne prend pas votre approche plus sensée et utilise en fait une tonne de JOINs compliq
ajouté l'auteur The Phil Lee, source
Le problème fondamental avec ceci est qu'il renvoie seulement les identifiants et pas les valeurs des tables qui doivent être jointes.
ajouté l'auteur gshauger, source