Mysql Tag Query - Repeated values - mysql

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

Related

SQL SELECT and GROUP

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

MySQL foreign key multi join

I have this table setup (simplified):
ads
- id
- brand_id
brands
- id
tags
- taggable_type
- taggable_id
- tag
The taggable_type in the tags table will be either "Ad" or "Brand", the taggable_id will identify the ad_id or brand_id respectively, and then the tag is a string.
Ads inherit their brand's tags, and I'm trying to write a search query that pulls up a set of ads for a given set of tags, where those tags belong to either the ad itself or the brand associated with that ad.
These tables can all be fairly large, so I need it to be efficient.
Here's what I have (this isn't working)
SELECT
a.*
FROM
ads a
JOIN
((
SELECT
*
FROM
tags
WHERE
tag IN ({$tags})
AND taggable_type = "Ad"
) t FULL JOIN (
SELECT
*
FROM
tags
WHERE
tag IN ({$tags})
AND taggable_type = "Brand"
)) tt
ON (t.taggable_id = a.id) OR (tt.taggable_id = a.brand_id);
For starters, I'm getting an error on the full join. I've also tried an inner join and a left join, and it's still not working. I feel like I'm just doing something fundamentally stupid. Any help?
Like this it should work
SELECT DISTINCT
ads.*
FROM
ads
LEFT JOIN brands
ON ads.brand_id = brands.id
INNER JOIN tags
ON (
tags.taggable_type = 'brand'
AND brands.id = tags.taggable_id)
OR (
tags.taggable_type = 'ad'
and ads.id = tags.taggable_id)
WHERE
tags.tag IN ('tag 1', 'tag 2', 'tag 7')
SQL Fiddle
But you might also want to think about your database structure again.
Perhaps a setup like the following would suit you better.
ads(id, brand_id)
brands(id)
tags(id)
tags_ads(tag_id, ad_id)
tags_brands(tag_id, brand_id)
Would have the benefit of being able to assign a tag to more than one brand or ad. Or to a brand and an ad...

Count(*) won't display when result is 0 when using group by

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

Exclusion of rows with many-to-many relationships

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/

Select from table1 WHERE table2 contains ALL search parameters

I have two tables (notes and tags). Tags has a foreign key to notes. There may be several tag records to a single note record.
I'm trying to select only the notes that contain all of the desired tags.
SELECT notes.*, tags.* FROM notes LEFT JOIN tags ON notes.id = tags.note_id
WHERE {my note contains all three tags I would like to search on}
Using WHERE tag.name IN ('fruit','meat','vegetable') will bring back all the notes that have a "fruit", "meat", OR "vegetable" tag. I only want to return notes that have all three "fruit", "meat", AND "vegetable" tags.
I'm ok to bring back multiple records (the query above would yield a record for each tag).
I need help with my where clause. Is it possible to do this without a sub-select?
Assuming tags(note_id, tag) is declared UNIQUE or PK, then you can use:
SELECT note_id, COUNT(tag) FROM tags
WHERE tag IN ('fruit', 'vegetable', 'meat')
GROUP BY note_id
HAVING COUNT(tag) >= 3
Further answer based on OP's comment below. To get all tags for the records that match:
SELECT * FROM tags
INNER JOIN
(
SELECT note_id, COUNT(tag) FROM tags
WHERE tag IN ('fruit', 'vegetable', 'meat')
GROUP BY note_id
HAVING COUNT(tag) >= 3
) search_results
ON search_results.note_id = tags.note_id
Without a subselect, as per request:
SELECT notes.*
FROM notes
JOIN tags
ON tag.note = notes.id
AND tag.name IN ('fruit','meat','vegetable')
GROUP BY
notes.id
HAVING COUNT(*) = 3
More efficient method would be:
SELECT notes.*
FROM (
SELECT to.note
FROM tags to
WHERE to.name = 'meat'
AND EXISTS
(
SELECT NULL
FROM tags ti
WHERE ti.note = to.note
AND to.name IN ('fruit', 'vegetable')
LIMIT 1, 1
)
) t
JOIN notes
ON note.id = t.note
The trick here is to put the search on the most selective tag ('meat' in my example) on the first place.
If it is not too late, wouldn't it be better to have a NoteTag table - so you will have notes, tags, notetag tables and you can use simple queries and AND operator to find what you want ?)