web-dev-qa-db-fra.com

Jointure interne avec compte () sur trois tables

Question simple et rapide, j'ai ces tableaux:

//table people
| pe_id | pe_name |
| 1  | Foo  |
| 2  | Bar  |
//orders table
| ord_id | pe_id | ord_title   |
|   1    |   1   | First order |
|   2    |   2   | Order two   |
|   3    |   2   | Third order |
//items table
| item_id | ord_id | pe_id | title  |
|   1     |   1    |   1   | Apple  |
|   2     |   1    |   1   | Pear   |
|   3     |   2    |   2   | Apple  |
|   4     |   3    |   2   | Orange |
|   5     |   3    |   2   | Coke   |
|   6     |   3    |   2   | Cake   |

Il me faut une requête répertoriant toutes les personnes, en comptant le nombre de commandes et le total nombre d'éléments, comme celui-ci:

| pe_name | num_orders | num_items |
| Foo  |    1       |   2       |
| Bar  |    2       |   4       |

Mais je ne peux pas le faire fonctionner! J'ai essayé 

SELECT
    people.pe_name,
    COUNT(orders.ord_id) AS num_orders,
    COUNT(items.item_id) AS num_items
FROM
    people
    INNER JOIN orders ON (orders.pe_id = people.pe_id)
    INNER JOIN items ON items.pe_id = people.pe_id
GROUP BY
    people.pe_id;

Mais cela retourne les valeurs num_* incorrectes:

| name | num_orders | num_items |
| Foo  |    2       |   2       |
| Bar  |    8       |   8       |

J'ai remarqué que si j'essaie de joindre une table à la fois, cela fonctionne:

SELECT
    people.pe_name,
    COUNT(orders.ord_id) AS num_orders
FROM
    people
    INNER JOIN orders ON (orders.pe_id = people.pe_id)
GROUP BY
    people.pe_id;

//give me:
| pe_name | num_orders |
| Foo     |          1 |
| Bar     |          2 |

//and:
SELECT
    people.pe_name,
    COUNT(items.item_id) AS num_items
FROM
    people
    INNER JOIN items ON (items.pe_id = people.pe_id)
GROUP BY
    people.pe_id;
//output:
| pe_name | num_items |
| Foo     |         2 |
| Bar     |         4 |

Comment combiner ces deux requêtes en une?

18
Strae

Il est plus logique de joindre le produit aux commandes qu'aux personnes!

SELECT
    people.pe_name,
    COUNT(distinct orders.ord_id) AS num_orders,
    COUNT(items.item_id) AS num_items
FROM
    people
    INNER JOIN orders ON orders.pe_id = people.pe_id
         INNER JOIN items ON items.ord_id = orders.ord_id
GROUP BY
    people.pe_id;

Joindre les objets aux gens provoque beaucoup de doublons. Par exemple, les gâteaux de l’ordre 3 seront liés à l’ordre 2 via la jonction entre les personnes et vous ne voulez pas que cela se produise!

Alors :

1- Vous avez besoin d'une bonne compréhension de votre schéma. Les articles sont liés aux commandes et non aux personnes. 

2- Vous devez compter les commandes distinctes pour une personne, sinon vous compterez autant d'articles que de commandes.

32
Cyril Gandon

Comme Frank l'a souligné, vous devez utiliser DISTINCT. De plus, puisque vous utilisez des clés primaires composites (ce qui est parfaitement, BTW), vous devez vous assurer que vous utilisez la clé entière dans vos jointures:

SELECT
    P.pe_name,
    COUNT(DISTINCT O.ord_id) AS num_orders,
    COUNT(I.item_id) AS num_items
FROM
    People P
INNER JOIN Orders O ON
    O.pe_id = P.pe_id
INNER JOIN Items I ON
    I.ord_id = O.ord_id AND
    I.pe_id = O.pe_id
GROUP BY
    P.pe_name

Sans I.ord_id = O.ord_id, il reliait chaque ligne d'élément à chaque ligne d'ordre d'une personne.

5
Tom H

j'ai essayé de mettre distinct sur les deux, count (distinct ord.ord_id) comme num_order, count (distinct items.item_id) comme éléments de num

ça marche :)

    SELECT
         people.pe_name,
         COUNT(distinct orders.ord_id) AS num_orders,
         COUNT(distinct items.item_id) AS num_items
    FROM
         people
         INNER JOIN orders ON (orders.pe_id = people.pe_id)
         INNER JOIN items ON items.pe_id = people.pe_id
    GROUP BY
         people.pe_id;

Merci pour le fil ça aide :)

3
Marjunne Romero
select pe_name,count( distinct b.ord_id),count(c.item_id) 
 from people  a, order1 as b ,item as c
 where a.pe_id=b.pe_id and
b.ord_id=c.order_id   group by a.pe_id,pe_name
2
Kamal Deepak

Votre solution est presque correcte. Vous pouvez ajouter DISTINCT:

SELECT
    people.pe_name,
    COUNT(distinct orders.ord_id) AS num_orders,
    COUNT(items.item_id) AS num_items
FROM
    people
    INNER JOIN orders ON (orders.pe_id = people.pe_id)
    INNER JOIN items ON items.pe_id = people.pe_id
GROUP BY
    people.pe_id;
1
Frank

Il faut comprendre ce qu’un joint ou une série de joints fait à un ensemble de données. Avec le message de strae, un pe_id de 1 rejoint la commande correspondante et les éléments sur pe_id = 1 vous donneront les données suivantes pour "sélectionner" parmi:

[partie personnes de la table] [partie commandes de la table] [partie éléments de la table]

| people.pe_id | people.pe_name | orders.ord_id | orders.pe_id | orders.ord_title | item.item_id | item.ord_id | item.pe_id | item.title |

| 1 | Foo | 1 | 1 | Première commande | 1 | 1 | 1 | Apple |
| 1 | Foo | 1 | 1 | Première commande | 2 | 1 | 1 | Poire |

Les jointures proposent essentiellement un produit cartésien de toutes les tables. Vous avez essentiellement le choix de cet ensemble de données et c'est pourquoi vous avez besoin d'un décompte distinct pour orders.ord_id et items.item_id. Sinon, les deux comptages donneront 2 - car vous avez effectivement 2 lignes à sélectionner.

0
Alamgir Mand