I have three tables: posts, tags and posts_has_tags (which facilitate the many-to-many relationship between posts and tags). A post can have any number of tags.
The 'posts' table has the following columns:
idposts
text
The 'tags' table has these:
idtags
name
As for the 'posts_has_tags' table:
posts_idposts
tags_idtags
What I can't do is come up with a query to select all posts except for those which have a tag (or tags) with a certain value in the 'name' column assigned to them. It seems that it should contain a "NOT EXISTS", but I can't really fully wrap my head around it.
Thank you for your help in advance.
EDIT:
Also, is it possible to restrict the result set to certain tags at the same time? For example:
tags for exclusion: a, b
tags for inclusion: c
Post with tags 'a', 'f' doesn't make it into result set (because none if it's tags are included).
Post with tags 'a', 'b', 'c' doesn't make it into result set either (because it's 'a' and 'b' are excluded tags).
Post with tags 'c', 'f' does make it into result set, because 'c' is the included tag.
FINAL EDIT
I finally found a solution that seems to be working and reasonably well-performing: http://www.mysqldiary.com/a-many-to-many-relationship-table-solving-the-exclude-relation-problem/
You can use an anti-join.
SELECT p.*
FROM posts p
LEFT JOIN post_has_tags pt ON (pt.post_id = p.id)
LEFT JOIN tags t ON (t.id = pt.tag_id AND t.name IN ('test','test1','test2'))
WHERE t.id IS NULL
GROUP BY p.id
If you want to force other tags to be included, you do another join.
SELECT p.*
FROM posts p
LEFT JOIN post_has_tags pt ON (pt.post_id = p.id)
LEFT JOIN tags t ON (t.id = pt.tag_id AND t.name IN ('a','b'))
INNER JOIN tags t2 ON (t2.id <> t.id AND t2.id = pt.tag_id AND t2.name IN ('c'))
WHERE t.id IS NULL
GROUP BY p.id
This will prioritize exclusion over inclusion.
If you want to prioritize inclusion then replace the inner join with:
INNER JOIN tags t2 ON (t2.id = pt.tag_id AND t2.name IN ('c'))
SELECT p.*
FROM posts AS p
WHERE NOT EXISTS
( SELECT *
FROM posts_has_tags AS pt
JOIN tags AS t
ON pt.tags_idtags = t.idtags
WHERE pt.posts_idposts = p.idposts
AND t.name = #CertainForbiddenTagName
)
If you have many tag names to forbid, use this instead:
AND t.name IN (List of ForbiddenTagNames)
For your updated 2nd question, just add a similar EXISTS:
AND EXISTS
( SELECT *
...
)
select * from posts where idposts in
(select posts_has_tags.posts_idposts from posts_has_tags
join tags on tags.idtags = posts_has_tags.tags_idtags
where tags.name not in ('value1','value2',...))
I finally found a solution that seems to be working and reasonably well-performing: http://www.mysqldiary.com/a-many-to-many-relationship-table-solving-the-exclude-relation-problem/
Related
I use following to get data from two tables:
SELECT p.id, p.title, p.event_date, a.name, p.location_id
FROM ixrsk_eb_events p
INNER JOIN ixrsk_eb_locations a on p.location_id = a.id
This works fine.
Now in addition, I want to have column headings.
On top of the results: "ID" (col p.id), "Title" (col p.title), "Date" (col p.event_date) and "Location" (col a.name).
And column p.location_id should not be displayed at all (no heading, no data).
How can I do this?
you can use aliases to change the names of the columns you show:
SELECT
p.id AS ID,
p.title AS Title,
p.event_date AS Date,
a.name AS Location
FROM ixrsk_eb_events p
INNER JOIN ixrsk_eb_locations a on p.location_id = a.id
and just leave out the columns you do not want to show up.
You are looking for aliasing the column/expressions. Refer this tutorial for further explanation: http://www.mysqltutorial.org/mysql-alias/
SELECT p.id AS ID,
p.title AS Title,
p.event_date AS Date,
a.name AS Location
-- removed p.location_id
FROM ixrsk_eb_events p
INNER JOIN ixrsk_eb_locations a on p.location_id = a.id
To skip a particular column, you simply need to remove that from the SELECT clause.
Here is my problem:
SELECT *
FROM posts
JOIN tags
ON tags.did = posts.did
JOIN users
ON users.username = posts.username
GROUP by tags.did
Now the result only shows one tag when I have two tags for the did. I want it to show all the tags I have for it kinda like this
a post | tag 1, tag 2
but right now, it's showing it like this
a post | tag 1
And I don't know why it won't show the other tag. I hope this is enough relevant information. (Pardon me, I'm a bit of a SQL beginner right now)
TRY (essential part, not tested )
SELECT p.postid, GROUP_CONCAT(t.tags)
FROM posts p
INNER JOIN tags t ON t.did = p.did
INNER JOIN users u ON u.username = p.username
GROUP by t.did
Pseudocode
SELECT post.Text, Coalesce('',select tag +',' from tags where tags.did=posts.did)
FROM posts
or us Group_concat
group concat
SELECT GROUP_CONCAT(Language) As Languages FROM CountryLanguage WHERE CountryCode = 'THA'
I dont have a mysql to test against but you need something like this.My Sql Coalsce
This has been asked in different ways before, but I can't seem to get something that works for what I need exactly.
The goal here is to make a search query that returns Photos based on tags that are selected. Many tags can be applied to the filter simultaneously, which would need to make it so that the query only returns photos that have ALL of the tags selected. Think of any major web shop where you are narrowing down results after performing a basic keyword search.
Table1: Photos
ID|Title|Description|URL|Created
Table2: PhotosTagsXref
ID|PhotoId|TagId
Table3: PhotosTags
ID|Title|Category
What I have:
SELECT p.* FROM `PhotosTagsXref` AS pt
LEFT JOIN `Photos` AS p ON p.`ID` = pt.`PhotoId`
LEFT JOIN `PhotosTags` AS t ON pt.`TagId` = t.`ID`
WHERE p.`Description` LIKE "%test%" AND
????
GROUP BY p.`ID`
ORDER BY p.`Created` DESC LIMIT 20
The ???? is where I've tried a bunch of things, but stumped. Problem is I can easily find a result set that contains photos with one tag or another, but if applying 2, 3, or 4 tags we'd need to only return photos that have entries for all of those tags in the database. I think this will involve combining result sets but not 100% sure.
Example:
Photo 1 Tags: Blue, White, Red
Photo 2 Tags: Blue
Searching for a photo with tags of 'blue' returns both photos, searching for a photo with tags of 'blue' and 'white' returns only Photo 1.
Supposing the requested set of tags is (red,blue) you can do:
SELECT * FROM `Photos`
WHERE `Description` LIKE "%test%"
AND `ID` IN (
SELECT pt.`PhotoId` FROM `PhotosTagsXref` AS pt
JOIN `PhotosTags` AS t ON pt.`TagId` = t.`ID`
WHERE t.Title in ('red','blue') /* your set here */
GROUP BY pt.`PhotoId` HAVING COUNT(DISTINCT t.`TagId`)=2 /* # of tags */
)
ORDER BY `Created` DESC LIMIT 20
Apparently, the tag set needs to be created dynamically, as well as its count.
Note: I'm counting DISTINCT TagIDs because I don't know your table's constraints. If PhotosTagsXRef had a PK/UNIQUE (PhotoId,TagId) and PhotosTags had a PK/UNIQUE (TagId), then COUNT(*) would suffice.
Admittedly a bit ugly. But assuming that PhotosTags.Category has the 'Blue', 'White', etc, try something along this line.
SELECT p.*
From `Photos` AS p
WHERE p.`Description` LIKE "%test%" AND
AND Exists
( Select 1 FROM `PhotosTagsXref` AS pt
Inner JOIN `PhotosTags` AS t ON pt.`TagId` = t.`ID`
Where pt.`PhotoId` = p.`ID`
And t.Category = 'FirstCatToSearch'
)
AND Exists
( Select 1 FROM `PhotosTagsXref` AS pt
Inner JOIN `PhotosTags` AS t ON pt.`TagId` = t.`ID`
Where pt.`PhotoId` = p.`ID`
And t.Category = 'SecondCatToSearch'
)
AND Exists
( ...
)
...
SELECT p.* FROM `PhotosTagsXref` AS pt
LEFT JOIN `Photos` AS p ON p.`ID` = pt.`PhotoId`
LEFT JOIN `PhotosTags` AS t ON pt.`TagId` = t.`ID`
inner join (select PhotoId from PhotosTagsXref
LEFT JOIN `PhotosTags` AS t
ON pt.`TagId` = t.`ID`
where (t.title = 'cond 1' or t.title = 'cond 2' ...)
--where t.title in (list condition) **this works as well**
having count(1) = (count of conditions) ) filter
on filter.photoID = pt.PhotoID
WHERE p.`Description` LIKE "%test%"
GROUP BY p.`ID`
ORDER BY p.`Created` DESC LIMIT 20
That should work, I made some assumptions on what column to use for the filter and joins, you may need to retool...the inner join functions as a filter and should pull out only records that have the number of matches equal to the total of the number of matches submitted. Now you just need a language to plug in those conditions and condition count values.
Sorry I couldn't think of a better title...
So I have this database where I have a table 'bibitems' (basicly texts) 'tags' (the tags the text is filed under) and 'taglinks' (Associative entity between a bibitem and a tag)
So on my page I want to display every tag, directly followed by the amount of documents that are filed under that tag
so if 2 documents are under 'java' it's say
java (2)
SELECT
tags.id,
naamnl,
COUNT(*) AS 'count'
FROM
tags,
bibitems,
taglinks
WHERE
bibitems.id=taglinks.item_id
AND tag_id=tags.id
GROUP BY
naamnl
This works well when there are documents filed under a certain tag.
But when there's a tag that's unused so far, it won't pop up.
I want it to display java (0) in that case
SELECT tags.id,
tags.naamnl,
COUNT(bibitems.id) AS 'count'
FROM tags
LEFT
OUTER
JOIN taglinks
ON taglinks.tag_id = tags.id
LEFT
OUTER
JOIN bibitems
ON bibitems.id = taglinks.item_id
GROUP
BY tags.id,
tags.naamnl
;
or
SELECT tags.id,
tags.naamnl,
( SELECT COUNT(*)
FROM bibitems
WHERE bibitems.id IN
( SELECT taglinks.item_id
FROM taglinks
WHERE taglinks.tag_id = tags.id
)
)
FROM tags
GROUP
BY tags.id,
tags.naamnl
;
(Take your pick.)
Read on the difference between INNER and OUTER joins.
SELECT tag.id, naamnl, COUNT(taglink.tag_id) AS 'count'
FROM tag
LEFT OUTER JOIN taglink ON taglink.tag_id=tag.id
LEFT OUTER JOIN bibitem ON bibitem.id=taglink.item_id
GROUP BY tag.id, naamnl
Something like thisshould do it. (It is difficult since you did not provide the full table schemas.)
SELECT
i.naamnl,
COALESCE(t.Count, 0) AS Count
FROM
bibitems i
LEFT JOIN
(
SELECT
tag_id,
COUNT(*) AS Count
FROM
taglinks
GROUP BY
item_id
) t
ON
t.tag_id = i.tag_id
I'm working on a tag-based search. The user can search one tag or multiple tags. I've got 3 tables: Content, Content_Tags and Tags. Content_Tags does the link between content and tags. The query has to return all the information of the content retrieved, this includes a concatenated string of all tags from that content.
Using an example from other question I've managed to reach this query:
SELECT content.Name,GROUP_CONCAT(t2.Tag SEPARATOR ' ') FROM content
JOIN content_tags ct1 ON content.ContentID = ct1.ContentID
JOIN tags t1 on ct1.TagID = t1.TagID AND t1.Tag IN('grass','texture')
JOIN content_tags ct2 ON ct2.ContentID = content.ContentID JOIN tags t2 ON ct2.TagID = t2.TagID GROUP BY content.ContentID;
The query works fine when searching for 1 tag, but using IN('grass','texture') the GROUP_CONCAT will return 'grass grass texture texture' (repeating the tags).
I don't know MySQL this deep, how could I fix this problem?
SELECT content.name,
(
SELECT GROUP_CONCAT(cta.tag)
FROM content_tags cta
WHERE cta.contentID = ct.contentID
)
FROM content_tags ct
JOIN content c
ON c.contentId = ct.contentId
WHERE ct.tag IN ('grass', 'texture')
GROUP BY
ct.contentId
HAVING COUNT(*) = 2