I have one table (posts) that each have a post_id. I also have a table (tags) that holds a tag_id and a name. To tie these together, I have a table (post_tags) which contains a tag_id FK and a post_id FK.
My goal is to get all post columns, with a concated group of tag_ids (easy, done) and a concated group of tag names (hard, haven't figured this out).
This is what I have so far:
SELECT Group_concat(ids SEPARATOR ','),
(SELECT Group_concat(tags.name SEPARATOR ',')
FROM tags
WHERE tags.tag_id = ids) ta
FROM (SELECT post_tags.tag_id AS ids
FROM posts
LEFT JOIN post_tags
ON posts.post_id = post_tags.post_id) tb
This is very close - it correctly group concats the ids, but it only returns a single tag name. Why is this?
Whilst I'm not clear on what grouping you need, the following should help. This query summarises the tags that belong to each post
SELECT
post_tags.post_id
, group_concat(tags.tag_id SEPARATOR ',') tag_ids
, group_concat(tags.name SEPARATOR ',') tag_names
FROM post_tags
INNER JOIN tags ON post_tags.tag_id = tags.tag_id
GROUP BY post_tags.post_id;
Related
I am trying to make a search system for listings in my database.
The user can enter a search query, which will then be split up by word by word.
The words are then matched to their tag IDs in my database (a tag is just one word).
Then the link table for tags and listing items is searched for listingItemIDs and ListingIDs that contain those tags. The results are then sorted by the amount of tags that a listing contains (listings that contain more tags are shown first).
Finally the listing data is selected.
My issue is that when i join the ordered listing ID results with the listings table, the order of the listings is wiped.
I tried reversing the order of the two tables in the join statement but i kept getting SQL errors.
SELECT * FROM listing
JOIN
(SELECT listingID
FROM listing_item_tags
JOIN (
SELECT tagID FROM tags WHERE tagName IN ("2","1","4")
) as tagIds
ON listing_item_tags.tagID = tagIds.tagID
GROUP BY listingID
ORDER BY COUNT(listingID) DESC
) AS ListingIDS
ON listing.listingID = ListingIDS.listingID
Something like this should suffice...
SELECT l.*
FROM listing l
JOIN
( SELECT listingID
, COUNT(*) total
FROM listing_item_tags lt
JOIN tags t
ON t.tagID = lt.tagID
WHERE t.tagName IN (2,1,4)
GROUP
BY listingID
) x
ON ...
ORDER
BY total DESC
I have a question.In my database I have 3 tables:
Articles:
id title content date
Tags:
id name
Tags_in_news:
id news_id tag_id
Where news_id is foreign key for news table and tag_id is foreign key for tag table...How to select the articles and all tags attached to them?
I create a query but it select a news for each tag:
SELECT * FROM articles join tags_in_news
ON articles.id = tags_in_news.news_id
join tags on tags.id = tags_in_news.tag_id
ORDER BY date DESC
Try GROUP BY article and grouping tags as comma separated value
something like this:
SELECT
date, a.title, GROUP_CONCAT(DISTINCT t.name) as tags_attached
FROM articles a
JOIN tags_in_news tin ON a.id = tin.news_id
JOIN tags t ON t.id = tin.tag_id
GROUP BY a.id
ORDER BY date DESC
Your query is pretty close, and since you are doing joining it will list all the matching rows and you will get multiple rows for article per tag.
In mysql there is a function called group_concat() which you can use along with group by so that all the tags associated with an article is concat by a comma and then display it for each article.
select
a.title,
a.content,
a.date,
group_concat(t.name) as name
from tags_in_news tin
inner join article a on a.id = tin.news_id
inner join tags t on t.id = tin.tag_id
group by a.id
DEMO
I've successfully managed to fetch articles filtering by matching tags in an AND manner.
This is my current code:
SELECT *
FROM articles a
JOIN article_tags a_t ON a_t.article_id = a.id
LEFT JOIN tags t ON t.id = a_t.tag_id
WHERE t.caption IN ('fire', 'water', 'earth')
HAVING COUNT(DISTINCT t.caption) = 3
Where:
articles are the articles I want to fetch, with id, title, etc…
tags are the list of tags, with id and caption
article_tags a relationship table, with article_id and tag_id
Now The problem is that after matching, I want to retrieve all the tags that each article has. Even if they are matched by 3 different ones, one may have 5 tags, other 4 tags, and I want them included in each row. Something like "tag,tag,tag" or whatever I can parse, in some "tags" column.
Any ideas? I can't find a way around it...
You need to join your query as a subquery with a query that returns all the tags and combines them with GROUP_CONCAT().
select a.*, GROUP_CONCAT(DISTINCT t.caption) tags
from (select distinct a.*
from articles a
JOIN article_tags a_t on a_t.article_id = a.id
JOIN tags t on t.id = a_t.tag_id
WHERE t.caption IN ('fire', 'water', 'earth')
GROUP BY a.id
HAVING COUNT(DISTINCT t.caption) = 3) a
JOIN article_tags a_t on a_t.article_id = a.id
JOIN tags t on t.id = a_t.tag_id
GROUP BY a.id
BTW, there's no reason to use LEFT JOIN in your query, because you only care about rows with matches in tags.
I also wonder about the need for DISTINCT in the COUNT() -- do you really allow multiple tag IDs with the same caption?
I am currently trying to retrieve the latest posts along with their related posts (x number for each post). I have the following query in hand:
SELECT id, title, content
(SELECT GROUP_CONCAT(title) FROM posts -- Select title of related posts
WHERE id <> p.id AND id IN (
SELECT p_id FROM tagsmap -- Select reletad post ids from tagsmap
WHERE t_id IN (
SELECT t_id FROM tagsmap -- Select the tags of the current post
WHERE p_id = p.id)
) ORDER BY id DESC LIMIT 0, 3) as related
FROM posts as p ORDER BY id DESC LIMIT 5
My database structure is simple: A posts table. A tags table. And a tagsmap table where I associate posts with tags.
This query works fine (though I don't know its performance since I don't have many rows in the tables -- Maybe an explain could help me but that's not the case right now).
What I really need is to retrieve the ids of the related posts along with their titles.
So I'd like to do SELECT GROUP_CONCAT(title), GROUP_CONCAT(id), but I know that will result in an error. So what is the best way to retrieve the id along with the title in this case? I do not want to rewrite the whole subquery to just retrieve the id. There should be another way.
EDIT
SELECT p1.id, p1.title, p1.content,
group_concat(DISTINCT p2.id) as 'P IDs',
group_concat(DISTINCT p2.title) as 'P titles'
FROM posts as p1
LEFT JOIN tagsmap as tm1 on tm1.p_id = p1.id
LEFT JOIN tagsmap as tm2 on tm2.t_id = tm1.t_id and tm1.p_id <> tm2.p_id
LEFT JOIN posts as p2 on p2.id = tm2.p_id
GROUP BY p1.id
ORDER BY p1.id desc limit 5;
At the end this is the query that I've used. I removed the Where clause because it is unnecessary and used LEFT JOIN rather that JOIN because otherwise it would ignore the posts without tags. And finally added DISTINCT to group_concat because it was concatenating duplicate rows (If for example a post had multiple common tags with a related post it would result in a duplicate concatenation).
The query above works perfectly. Thanks for all.
Okay - this will work, and it has the added advantage of eliminating the sub queries (which can slow you down when you get lots of records):
SELECT p1.id, p1.title, p1.content,
group_concat( p2.id) as 'P IDs',
group_concat( p2.title) as 'P titles'
FROM posts as p1
JOIN tagsmap as tm1 on tm1.p_id = p1.id
JOIN tagsmap as tm2 on tm2.t_id = tm1.t_id and tm1.p_id <> tm2.p_id
JOIN posts as p2 on p2.id = tm2.p_id
WHERE p2.id <> p1.id
GROUP BY p1.id
ORDER BY p1.id desc limit 5;
What we're doing here is selecting what you want from the first version of posts, joining them to the tagsmap by their post.id, doing a self join to tagsmap by tag id to get all the related tags, and then joining back to another posts (p2) to get the posts that are pointed to by those related tags.
Use GROUP BY to discard the dups from all that joining, and you're there.
like this?
SELECT id, title, content
(SELECT GROUP_CONCAT(concat(cast(id as varchar(10)), ':', title)) FROM posts -- Select title of related posts
WHERE id <> p.id AND id IN (
SELECT p_id FROM tagsmap -- Select reletad post ids from tagsmap
WHERE t_id IN (
SELECT t_id FROM tagsmap -- Select the tags of the current post
WHERE post_id = p.id)
) ORDER BY id DESC LIMIT 0, 3) as related
FROM posts as p ORDER BY id DESC LIMIT 5
Okay, so I have a case where I want to do a Group Concat, but also use the same column that's in the Group Concat as a Where clause further in the statement, but not exclude the results in the Group Concat because of the Where. Got it? Hahah here's what I mean:
SELECT posts.id, post.post_date,
GROUP_CONCAT(DISTINCT terms.name ORDER BY terms.name DESC SEPARATOR ';')
FROM posts
INNER JOIN terms on terms.post_id = posts.id
WHERE terms.name = "kittens";
So I want to get the posts where the post's terms.name = "kittens", however the post can have multiple terms.name, such as "kittens", "cats", "cuteness", etc etc. So in the Group Concat I want the result to be "kittens;cats;cuteness" (each of these being a separate entry related by posts.id). However with "kittens" in the Where clause, all the Group Concat returns is "kittens".
If I said "WHERE terms.name = 'kittens' OR terms.name = 'cats'", the Group Concat returns "kittens;cats"... but I only want to be searching by one terms.name and get the rest of the terms in the Group Concat.
How can I get around this?
Use Having instead of Where. But you also need to include terms.name in select clause like this :
SELECT posts.id, post.post_date, terms.name,
GROUP_CONCAT(DISTINCT terms.name ORDER BY terms.name DESC SEPARATOR ';')
FROM posts
INNER JOIN terms on terms.post_id = posts.id
HAVING terms.name = "kittens";
The query you need is:
SELECT p.id, p.post_date,
GROUP_CONCAT(DISTINCT t.name ORDER BY t.name DESC SEPARATOR ';')
FROM posts p
INNER JOIN terms s ON s.post_id = p.id # "s" from "search"
INNER JOIN terms t ON t.post_id = p.id
WHERE s.name = "kittens"
GROUP BY p.id
How it works
If I understand correctly the question, you need to select all the terms of the posts that have the term "kitten". It requires joining the table terms twice: once to get all the posts that have the term "kittens" and once again to get the terms of the posts selected this way.
This is what the query does: it joins the table posts aliased as "p" ("p" from "post") with table terms aliased as "s" ("s" from "search") and selects only the pairs (post, term) where "term" is "kittens". Then it joins the table terms again as "t" ("t" from "term") and matches the posts having the term "kittens" with all their terms.
The GROUP BY clause creates one group from all the rows having the same posts.id. Since id is the PK of table posts there is no need to add posts.post_date to the GROUP BY clause, p.post_date is functionally dependent on p.id.