I simplified a many-to-many relationship case
with these mockup tables.
Posts:
------------------------------
| id | title | body |
------------------------------
| 1 | One | text1 |
| 2 | Two | text2 |
| 3 | Three | text3 |
------------------------------
Tags:
-------------------
| id | name |
-------------------
| 1 | SQL |
| 2 | GLSL |
| 3 | PHP |
-------------------
Post_tags:
------------------------------
| id | p_id | t_id |
------------------------------
| 1 | 1 | 1 |
| 2 | 1 | 3 |
| 3 | 2 | 1 |
| 3 | 3 | 2 |
------------------------------
My goal is to query POSTS with specific TAGS, which I have no problem with, but I also want to display all related tags to the post not just the one I queried for.
My query looks like this:
SELECT p.Title, p.Body, t.name
FROM Posts p
LEFT JOIN Post_tags pt ON p.id = pt.p_id
LEFT JOIN Tags t ON t.id = pt.t_id
WHERE t.name LIKE '%SQL%'
This gets the posts with the “SQL” tag, but it only joins the posts table with tags where it found the “SQL” string, so the other tag “PHP” associated with the post doesn’t get joined.
Obviously the problem is I’m joining the table on the WHERE clause, but how would one solve this in one query or (preferably with subqueries)?
Currently I’m doing this in two separate queries in my application, one for selecting matching posts and another one that is retreiving full post data. This isn’t so efficient and also seems like a lame solution, and I haven’t found a better yet, so I decided to ask the StackOverflow community.
My old answer is not the shortest, here’s the shortest one:
Output:
So if we group by ID, and check that if at least one (aided by bit_or; Postgresql has this too, aptly named bool_or) of the elements in the group satisfied the ‘%SQL%’ criteria, its bit is ON (aka boolean = true). We can pick that group and we retain all the tags under that group, example, tag id 1 appear on post 1, and post 1 has other tags, which is #3 or PHP. All tags that belong to same Post ID will not be discarded, as we will not be using
WHEREfilter, we will be usingHAVINGfilter instead:We can also rewrite that to this:
BIT_ORis likeIN, orANY, so it’s more semantic than evaluating things bySUMOutput:
Live test: http://www.sqlfiddle.com/#!2/52b3b/26
I’m learning so much on stackoverflow. After my old answer, I’m thinking how to make an equivalent shorter code in Postgresql using windowing function(which MySQL don’t have) via
SUM OVER partition. Then I thought of Postgresql’sbool_or,bool_andandeveryfunction. Then I remember MySQL hasbit_or🙂The last solution using
SUMis just an afterthought, when I thought up thatbit_oris just a semantic of at least one is true, then it’s obvious that you can useHAVING SUM(condition) >= 1too. Now it works on all database 🙂I ended up not solving it by windowing function, the solution above now works on all database 🙂