Search code examples
mysqlleft-joininner-join

get calculation from two tables related with a third table


i have this table

     table1
|  id  |  name  |
|  1   |  axe   |
|  2   |  bow   |
|  3   |  car   |
|  4   |  dart  |

and these two tables

        table2                                        table3
|  t1_id  |  number  |                        |  t1_id  |  letter  |
|  1      |  5       |                        |  1      |  a       |
|  1      |  6       |                        |  1      |  b       |
|  1      |  2       |                        |  1      |  c       |
|  2      |  2       |                        |  2      |  a       |
|  2      |  2       |                        |  2      |  c       |
|  2      |  3       |                        |  2      |  r       |
|  3      |  8       |                        |  3      |  y       |
|  3      |  3       |                        |  3      |  i       |
|  3      |  1       |                        |  3      |  a       |
|  4      |  8       |                        |  4      |  a       |
|  4      |  9       |                        |  4      |  b       |
|  4      |  10      |                        |  4      |  c       |

where in it t1_id is the table1 id

what i want to do is to get all table1 records having table3 letters a b c and the avg of their numbers like this order by the letter_count DESC first then by the avg_numbers DESC

|  id  |  name  |  letter_count  |  avg_number   |
|  4   |  dart  |  3             |  9            |
|  1   |  axe   |  3             |  4.3333333333 |
|  2   |  bow   |  2             |  2.3333333333 |
|  3   |  car   |  1             |  4            |

the query i expected to work properly was http://www.sqlfiddle.com/#!9/69086b/3/0

SELECT 
  t1.id, 
  t1.name, 
  COUNT(t3.letter) AS letter_count, 
  AVG(t2.number) AS avg_number

FROM 
  table1 t1

INNER JOIN 
  table2 t2
    ON t2.t1_id = t1.id

LEFT JOIN 
  table3 t3
    ON t3.t1_id = t1.id
      AND t3.letter IN ('a', 'b', 'c')

GROUP BY
  t1.id

ORDER BY
  letter_count DESC,
  avg_number DESC

but numbers are totally different and in accurate but the order is correct

i don't want to get the letter_count and avg_number values but i just want to order by them but their values are worrying me with the query performance

i wouldn't notice this weird values because my actual query is

SELECT 
  t1.id, 
  t1.name

FROM 
  table1 t1

INNER JOIN 
  table2 t2
    ON t2.t1_id = t1.id

LEFT JOIN 
  table3 t3
    ON t3.t1_id = t1.id
      AND t3.letter IN ('a', 'b', 'c')

GROUP BY
  t1.id

ORDER BY
  COUNT(t3.letter) DESC,
  AVG(t2.number) DESC

which only gives me proper ordery

|  id  |  name  |
|  4   |  dart  |
|  1   |  axe   |
|  2   |  bow   |
|  3   |  car   |

but after checking the values i was surprsied by the letter_count do i just ignore the values and it wouldn't affect the performance in my big table?


Solution

  • You are aggregating over two different dimensions. This causes a Cartesian product. One way to fix this is to aggregate before joining:

    SELECT t1.id, t1.name, t2.letter_count, t2.avg_number
    FROM table1 t1 INNER JOIN 
         (SELECT t2.t1_id, AVG(t2.number) as avg_number
          FROM table2 t2
          GROUP BY t2.t1_id
         ) t2
         ON t2.t1_id = t1.id LEFT JOIN
         (SELECT t3.t2_id, COUNT(t3.letter) as letter_count
          FROM table3 t3
          WHERE t3.letter IN ('a', 'b', 'c')
          GROUP BY t3.t2_id
         ) t3
         ON t3.t1_id = t1.id
    ORDER BY t3.letter_count DESC, t2.avg_number DESC;