Joining 3 tables with n:m relationship, want to see nonmatching rows

江枫思渺然 提交于 2020-01-25 09:11:06

问题


For this problem, consider the following 3 tables:

Event
id (pk)
title

Event_Category
event_id (pk, fk)
category_id (pk, fk)

Category
id (pk)
description

Pretty trivial I guess... :) Each event can fall into zero or more categories, in total there are 4 categories. In my application, I want to view and edit the categories for a specific event. Graphically, the event will be shown together with ALL categories and a checkbox indicating whether the event falls into the category. Changing and saving the choice will result in modifocation of the intermediate table Event_Category. But first: how to select this for a specific event? The query I need will in fact always return 4 rows, the number of categories present.

Following returns only the entries for the categories the event with id=11 falls into. Experimenting with outer joins did not give more rows in the result.

SELECT e.id, c.omschrijving 
FROM Event e
  INNER JOIN Event_Categorie ec ON e.id = ec.event_id
  INNER JOIN Categorie c ON c.id = ec.categorie_id
WHERE e.id = 11

Or should I start with the Category table in the query? Hope for some hints :) TIA, Klaas

UPDATE: Yes I did but still have not found the answer. But I have simplified the issue by omitting the Event table from the query because this table is only used to view the Event descriptions.

SELECT * from Categorie c LEFT JOIN Event_Categorie ec ON c.id = ec.categorie_id WHERE ec.event_id = 11;

The simplified 2-table query only uses the lookup table and the link table but still returns only 2 rows instead of the total of 4 rows in the Categorie table. My guess would be that the WHERE clause is applied after the joining, so the rows not joined to the link table are excluded. In my application I solved the issues by using a subquery but I still would like to know what is the best solution.


回答1:


What you want is the list of all categories, plus information about whether that category is in the list of categories of your event.

So, you can do:

SELECT 
    * 
FROM 
    Category 
    LEFT JOIN Event_Category ON category_id = id 
WHERE 
    event_id = 11

and event_id column will be NULL on the categories that are not part of your event.

You can also create a column (named has_category below) that you will use to see if the event has this category instead of comparing with NULL:

SELECT 
    *,
    event_id IS NOT NULL AS has_category
FROM 
    Category 
    LEFT JOIN Event_Category ON category_id = id 
WHERE 
    event_id = 11

EDIT: This seems exactly what you say you are doing on your edit. I tested it and it seems correct. Are you sure you are running this query, and that rows with NULL are not somehow ignored?




回答2:


The query

SELECT * FROM Categorie;

returns 4 rows:

+----+--------------+-------------------------------------+--------------------------------------+
| id | omschrijving | afbeelding                          | afbeelding_klein                     |
+----+--------------+-------------------------------------+--------------------------------------+
|  1 | Creatief     | images/categorieen/creatief420k.jpg | images/categorieen/creatief190k.jpg  |
|  2 | Sportief     | images/categorieen/sportief420k.jpg | images/categorieen/sportief190kr.jpg |
|  4 | Culinair     | images/categorieen/culinair420k.jpg | images/categorieen/culinair190k.jpg  |
|  5 | Spirit       | images/categorieen/spirit420k.jpg   | images/categorieen/spirit190k.jpg    |
+----+--------------+-------------------------------------+--------------------------------------+
4 rows in set (0.00 sec)

BUT: The query

SELECT * 
FROM Categorie 
LEFT JOIN Event_Categorie ON categorie_id = id 
WHERE event_id = 11;

returns 2 rows:

+----+--------------+-------------------------------------+-------------------------------------+----------+--------------+
| id | omschrijving | afbeelding                          | afbeelding_klein                    | event_id | categorie_id |
+----+--------------+-------------------------------------+-------------------------------------+----------+--------------+
|  1 | Creatief     | images/categorieen/creatief420k.jpg | images/categorieen/creatief190k.jpg |       11 |            1 |
|  4 | Culinair     | images/categorieen/culinair420k.jpg | images/categorieen/culinair190k.jpg |       11 |            4 |
+----+--------------+-------------------------------------+-------------------------------------+----------+--------------+
2 rows in set (0.00 sec)

So I still need the subquery... and the LEFT JOIN is not effective in showing all rows of the CAtegorie table, regardless whether there is a match with the link table.

This query, however, does what I want it to do:

SELECT * 
FROM Categorie c 
LEFT JOIN (SELECT * FROM Event_Categorie ec WHERE ec.event_id = 11 ) AS subselect ON  subselect.categorie_id = c.id;

Result:

+----+--------------+-------------------------------------+--------------------------------------+----------+--------------+
| id | omschrijving | afbeelding                          | afbeelding_klein                     | event_id | categorie_id |
+----+--------------+-------------------------------------+--------------------------------------+----------+--------------+
|  1 | Creatief     | images/categorieen/creatief420k.jpg | images/categorieen/creatief190k.jpg  |       11 |            1 |
|  2 | Sportief     | images/categorieen/sportief420k.jpg | images/categorieen/sportief190kr.jpg |     NULL |         NULL |
|  4 | Culinair     | images/categorieen/culinair420k.jpg | images/categorieen/culinair190k.jpg  |       11 |            4 |
|  5 | Spirit       | images/categorieen/spirit420k.jpg   | images/categorieen/spirit190k.jpg    |     NULL |         NULL |
+----+--------------+-------------------------------------+--------------------------------------+----------+--------------+
4 rows in set (0.00 sec)



回答3:


The issue is that you have filtered the results by the eventid. As you can see in your results, two of the categories (Sportief and Spirit) do not have events. So the correct SQL statement (using SQL Server syntax; some translation may be required) is:

SELECT * 
FROM Categorie 
LEFT JOIN Event_Categorie ON categorie_id = id 
WHERE (event_id IS NULL) OR (event_id = 11);



回答4:


Finally I found the right query, no subselect is necessary. But the WHERE clause works after the joining and therefore is no part of the join anymore. THe solution is extending the ON clause with an extra condition. Now all 4 rows are returned with NULL for the non-matching Categories!

SELECT * 
FROM Categorie  
LEFT JOIN Event_Categorie ON categorie_id = id AND event_id = 11;

So the bottom line is that putting an extra condition in the ON clause has different effect than filtering out rows by the same condition in the WHERE clause!



来源:https://stackoverflow.com/questions/8013861/joining-3-tables-with-nm-relationship-want-to-see-nonmatching-rows

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!