web-dev-qa-db-fra.com

MySQL JOIN avec LIMIT 1 sur la table jointe

Je veux joindre deux tables, mais seulement 1 enregistrement de table2 par enregistrement sur table1

Par exemple:

SELECT c.id, c.title, p.id AS product_id, p.title
FROM categories AS c
JOIN products AS p ON c.id = p.category_id

Cela me donnerait tous les enregistrements dans products, ce qui n'est pas ce que je veux. Je veux 1 [le premier] produit par catégorie (j'ai une colonne sort dans le champ des produits).

Comment je fais pour faire ça?

49
John Davidson

J'aime plus une autre approche décrite dans une question similaire: https://stackoverflow.com/a/11885521/2215679

Cette approche est préférable, en particulier si vous devez afficher plusieurs champs dans SELECT. Pour éviter Error Code: 1241. Operand should contain 1 column(s) ou double sous-sélectionner pour chaque colonne.

Pour votre situation, la requête devrait ressembler à:

SELECT
 c.id,
 c.title,
 p.id AS product_id,
 p.title AS product_title
FROM categories AS c
JOIN products AS p ON
 p.id = (                                 --- the PRIMARY KEY
  SELECT p1.id FROM products AS p1
  WHERE c.id=p1.category_id
  ORDER BY p1.id LIMIT 1
 )
46
Kostanos

La réponse acceptée par @ goggin13 semble fausse. Les autres solutions fournies à ce jour fonctionneront, mais souffrent du problème n + 1 et, en tant que telles, de la performance.

problème n + 1: s'il y a 100 catégories, il faudrait alors sélectionner 1 pour obtenir les catégories, puis pour chacune des 100 catégories retournées, il faudrait sélectionner pour obtenir les produits de cette catégorie. Ainsi, 101 requêtes SELECT seraient effectuées.

Ma solution alternative résout le problème n + 1 et devrait par conséquent être nettement plus performante puisque seulement 2 sélections sont effectuées.

SELECT
  *
FROM
    (SELECT c.id, c.title, p.id AS product_id, p.title
    FROM categories AS c
    JOIN products AS p ON c.id = p.category_id
    ORDER BY c.id ASC) AS a 
GROUP BY id;
21
Gravy
SELECT c.id, c.title, p.id AS product_id, p.title
FROM categories AS c
JOIN products AS p ON c.id = p.category_id
GROUP BY c.id

Cela retournera les premières données dans les produits (égal à limite 1)

8
Jessé Catrinck

La clause With ferait l'affaire. Quelque chose comme ça:

WITH SELECTION AS (SELECT id FROM products LIMIT 1)
SELECT a.id, c.id, c.title FROM selection a JOIN categories c ON (c.id = a.id);
1
la_kal

Et ça?

SELECT c.id, c.title, (SELECT id from products AS p 
                            WHERE c.id = p.category_id 
                            ORDER BY ... 
                            LIMIT 1)
   FROM categories AS c;
1
Krab

Lorsque vous utilisez postgres, vous pouvez utiliser le DISTINCT ON syntex pour limiter le nombre de colonnes renvoyées par l'une ou l'autre table. 

Voici un exemple du code:

SELECT c.id, c.title, p.id AS product_id, p.title FROM categories AS c JOIN ( SELECT DISTINCT ON(p1.id) id, p1.title, p1.category_id FROM products p1 ) p ON (c.id = p.category_id)
L'astuce n'est pas de joindre directement sur la table avec plusieurs occurrences de l'id, mais plutôt de créer d'abord une table avec une seule occurrence pour chaque id

0
Yaki Klein

En supposant que vous vouliez un produit avec MIN()imial value dans la colonne sort, cela ressemblerait à ceci.

SELECT 
  c.id, c.title, p.id AS product_id, p.title
FROM 
  categories AS c
INNER JOIN (
  SELECT
    p.id, p.category_id, p.title
  FROM
    products AS p
  CROSS JOIN (
    SELECT p.category_id, MIN(sort) AS sort
    FROM products
    GROUP BY category_id
  ) AS sq USING (category_id)
) AS p ON c.id = p.category_id
0
Mchl