-3

I've normalized my DB but can't seem to return the data I'm looking for in the right way.

I've got 5 tables:

  1. Resources (5 resources)
  2. Topics (10 topics)
  3. Chapters (10 chapters)
  4. Topics-to-Resources (18 topic to resource links)
  5. Topics-to-Chapters (18 topic to chapter links)

Check out this SQL Fiddle...

I need to collect all the records in the Resources table and group each of them with their corresponding topics and chapters (from the Topics-to-Resources and Topics-to-Chapters tables)

Can anyone suggest the right SQL query to return the 5 resource records with their attending topics and chapters?

I've tried JOINS with GROUP BY and this condenses the recordset to the 5 resources but not with all the other information I need (topics and chapters).

SELECT * FROM TOPICS, CHAPTERS, RESOURCES AS RES
INNER JOIN TOPICS_to_RESOURCE AS TR ON RES.RES_ID = TR.TR_RESID
INNER JOIN TOPICS_to_CHAPTER AS TCH ON TR.TR_TID = TCH.TCH_TID
GROUP BY RES.RES_ID
philipxy
  • 14,867
  • 6
  • 39
  • 83
nbardach
  • 123
  • 1
  • 10
  • 1
    What have you tried so far? Post your query. – Eric Oct 06 '17 at 23:22
  • Sorry, I meant to include it in the SQL Fiddle comments. Moving it here... – nbardach Oct 06 '17 at 23:34
  • If a many-to-many, or multiple one-to-many, relationship is involved you will likely need to use subqueries. – Uueerdo Oct 06 '17 at 23:34
  • Updated question with a sample of the JOIN and GROUP BY query I've used. Can you show me an example of what a subquery structure would look like? – nbardach Oct 06 '17 at 23:40
  • Note that tr_id serves no useful purpose – Strawberry Oct 07 '17 at 00:01
  • Why are you grouping? GROUP BY partitions a table into groups/tables from which you can select columns that are single-valued per group or from which you can select functions of the values in columns you didn't group by, eg their SUM or COUNT. – philipxy Oct 07 '17 at 06:56
  • Hi. Please read & act on [mcve]. You haven't explained or given example data for what output you want per input. (No wonder you can't find a solution, but we can't find one either, although we might guess it.) Finish these sentences: Row (RES_ID, RES_Name) in Resources states resource RES_ID has name RES_Name. Row (...) in ... states ... . Row (RES_ID, ...) in the query result states resource RES_ID has ... and RES_ID=r.RES_ID and r.RES_ID has name r.RES_Name and ... r.RES_ID=tr.TR_RESID and ... . https://stackoverflow.com/a/33952141/3404097 https://stackoverflow.com/a/23842061/3404097 – philipxy Oct 07 '17 at 07:07
  • @Uueerdo & nbardach One does not need to know any constraints (nor need constraints be declared) in order to query. One just has to know when rows go into tables. (Knowing constraints allows additoinal query variants, when one can write expressions that extract values from single-column single-row tables.) Also no normalization is needed to query. – philipxy Oct 07 '17 at 07:14
  • Why are you grouping? GROUP BY partitions a table into groups/tables from which you can select columns that are single-valued per group or from which you can select functions of the values in columns you didn't group by, eg their SUM or COUNT. You are using the word "group" in your text in an everyday yet vague sense that doesn't explain and isn't clear and if anything is trying to express "paired" or "accompanied in the same row as". The more you force yourself to use technical terms about business entities & relationships and database things (tables, rows, columns, values) the better. – philipxy Oct 07 '17 at 07:22
  • 1
    Comma means CROSS JOIN but binds looser than the various JOINs. Don't mix it with them. – philipxy Oct 07 '17 at 07:26
  • @philipxy, thanks for the feedback. I'm new at this and trying to do the best I can to formulate my questions correctly. I've posted a new related question that is hopefully better: https://stackoverflow.com/questions/46626600/adding-href-links-to-a-distinct-group-concat – nbardach Oct 08 '17 at 01:29

2 Answers2

2

I can't really distinguish what you're trying to achieve, but it sounds like you're simply looking to obtain a table that shows every chapter with its topic and resource.

If so, then the following SQL:

select * from resources r
JOIN topics_to_resource ttr ON ttr.tr_resid = r.res_id
JOIN topics t on t.t_id = ttr.tr_tid
JOIN topics_to_chapter ttc on ttc.tch_tid = t.t_id
JOIN chapters ch ON ch.ch_id = tch_chid
ORDER BY r.res_id;

will return just that, as per http://sqlfiddle.com/#!9/ddf252/12

Or, ignoring the join IDs in the select:

select r.res_id, r.res_name, t.t_id, t.t_name, ch.ch_id, ch.ch_name from resources r
JOIN topics_to_resource ttr ON ttr.tr_resid = r.res_id
JOIN topics t on t.t_id = ttr.tr_tid
JOIN topics_to_chapter ttc on ttc.tch_tid = t.t_id
JOIN chapters ch ON ch.ch_id = tch_chid
ORDER BY r.res_id, t.t_id, ch.ch_id

as per http://sqlfiddle.com/#!9/ddf252/14

If that's not what you're looking for, could you elaborate a little on what results you're looking to see?

Edit: To return a more concise list with all associated records

select 
CONCAT(r.res_id,': ',r.res_name) 'Resources', 
GROUP_CONCAT(CONCAT(' (',t.t_id,': ',t.t_name,')')) 'Topics', 
GROUP_CONCAT(CONCAT(' (',ch.ch_id,': ',ch.ch_name,')')) 'Chapters'
from resources r
JOIN topics_to_resource ttr ON ttr.tr_resid = r.res_id
JOIN topics t on t.t_id = ttr.tr_tid
JOIN topics_to_chapter ttc on ttc.tch_tid = t.t_id
JOIN chapters ch ON ch.ch_id = tch_chid
GROUP BY r.res_id
ORDER BY r.res_id, t.t_id, ch.ch_id

As per http://sqlfiddle.com/#!9/ddf252/30

Finally, to group these by chapter and topic:

select 
CONCAT(res_id,': ',res_name) 'Resources', 
GROUP_CONCAT(`chapters` order by chapters separator '\n') as 'Content'
FROM
  (SELECT r.res_id 'res_id',
          r.res_name 'res_name', 
          t.t_id 't_id',
          t.t_name 't_name',
          CONCAT(t.t_name,': (',GROUP_CONCAT(ch.ch_name ORDER BY t.t_name separator ','),')') 'Chapters'
    FROM resources r
      JOIN topics_to_resource ttr ON ttr.tr_resid = r.res_id
      JOIN topics t on t.t_id = ttr.tr_tid
      JOIN topics_to_chapter ttc on ttc.tch_tid = t.t_id
      JOIN chapters ch ON ch.ch_id = tch_chid
    GROUP BY res_id, t_id
    ORDER BY r.res_id, t.t_id, ch.ch_id) as t
GROUP BY res_id

As seen here: http://sqlfiddle.com/#!9/ddf252/85

I've checked the results over, and they look fine - but double check, as it's gone a bit like MySQL Inception in my head (it's past 1am here)

Further addition: Distinct values per resource

    select CONCAT(r.res_id,': ',r.res_name) 'Resources', GROUP_CONCAT(distinct t_name separator ',') 'Topics', 
GROUP_CONCAT(distinct ch.ch_name separator ',') 'Chapters'
from resources r
JOIN topics_to_resource ttr ON ttr.tr_resid = r.res_id
JOIN topics t on t.t_id = ttr.tr_tid
JOIN topics_to_chapter ttc on ttc.tch_tid = t.t_id
JOIN chapters ch ON ch.ch_id = tch_chid
GROUP BY r.res_id
ORDER BY r.res_id, t.t_id, ch.ch_id

See http://sqlfiddle.com/#!9/ddf252/88

Chris J
  • 1,441
  • 9
  • 19
  • Thanks, Chris! Sorry for the lack of clarity. I'm looking to return each resource (there are 5) grouped with all the multiple topics and multiple chapters they correspond to. HTH! – nbardach Oct 06 '17 at 23:44
  • In other words, it would be your second query but, instead of returning 55 records it would return 5 (one for each resource). – nbardach Oct 06 '17 at 23:46
  • Hi - I've added a third query - hope that helps – Chris J Oct 06 '17 at 23:49
  • I think that's the one!! Hold tight... just running some quick tests... – nbardach Oct 06 '17 at 23:54
  • Is there any way to modify this 3rd query so that only distinct topics and chapters are listed? – nbardach Oct 07 '17 at 00:04
  • I've added another version that I think should make it clearer – Chris J Oct 07 '17 at 00:26
  • Super grateful for your help, Chris! Actually, regarding that last one, I was hoping for the 3rd one you did but with distinct topics and distinct chapters. The same way you broke it out but with distinct values in the Topics field and distinct values in the Chapters field. Hope that helps explain. Thanks again!! – nbardach Oct 07 '17 at 00:44
  • Ok, added a distinct option – Chris J Oct 07 '17 at 00:55
  • Running out but will check this in a few hours and report back. THANK YOU!! – nbardach Oct 07 '17 at 01:00
  • @nbardach Please edit clarifications into your question, don't put them in comments. Otherwise your "question" in your post is not the question you want an answer to. Also (because of that intended role) comments are ephemeral. (People shouldn't answer unclear questions, they should comment on the question that it is not clear.) – philipxy Oct 07 '17 at 07:30
  • Chris, working with the Distinct option you proposed and it's returning the right records but I'm struggling with how to convert the GROUP_CONCATs into lists of links while keeping them Distinct. I posted another question for this: https://stackoverflow.com/questions/46626600/adding-href-links-to-a-distinct-group-concat but would love your suggestions if you can figure out how. I tried to embedd a CONCAT within the GROUP_CONCAT but then I lost the Distinct. LMK. Thanks!! Noah – nbardach Oct 08 '17 at 01:23
  • Hi Noah - I have posted an answer on [your question](https://stackoverflow.com/questions/46626600/mysql-adding-href-links-to-a-distinct-group-concat/46630808#46630808) - hope that helps – Chris J Oct 08 '17 at 11:54
0

This is non-standard syntax:

SELECT *
...
GROUP BY RES.RES_ID

Here you have asked for every column (select *) but only specified one column under group by. ONLY MySQL allows this non-standard GROUP BY syntax which is controlled by server settings. Those default settings have recently changed and you may find many our queries using non-standard GROUP BY could fail in future.

A standards compliant GROUP BY clause specifies ALL "non-aggregating" columns. Meaning you must specify every column not using SUM/COUNT/AVG/MIN/MAX etc.


SELECT * FROM TOPICS, CHAPTERS, RESOURCES AS RES
INNER JOIN TOPICS_to_RESOURCE AS TR ON RES.RES_ID = TR.TR_RESID
INNER JOIN TOPICS_to_CHAPTER AS TCH ON TR.TR_TID = TCH.TCH_TID
GROUP BY RES.RES_ID

That query has 2 forms of join syntax. **FROM t1,t2,t3 ** is ancient and unwise and is NOT good practice. Also; never "combine" this old syntax form with the more recent join syntax in a single query as that can lead to query errors.

Just do NOT use commas between tables in the from clause this simple step will make you use the better syntax all he time.

By the way FROM TOPICS, CHAPTERS, RESOURCES AS RES with nothing to limit these in a where clause will produce:

Multiply every row in TOPICS by every row in CHAPTERS by every row in RESOURCES. In other words a "Cartesian product". In more recent syntax your query translates to:

SELECT * FROM TOPICS
CROSS JOIN CHAPTERS
CROSS JOIN RESOURCES AS RES
INNER JOIN TOPICS_to_RESOURCE AS TR ON RES.RES_ID = TR.TR_RESID
INNER JOIN TOPICS_to_CHAPTER AS TCH ON TR.TR_TID = TCH.TCH_TID
GROUP BY RES.RES_ID
Paul Maxwell
  • 33,002
  • 3
  • 32
  • 51