mysql - using joins to get several informations - mysql

I have the following tables:
TABLE article
article_id
title
article_text
user_id
TABLE tag_article
tag_article_id
tag_id
article_id
TABLE tag
tag_id
tag
TABLE user
user_id
What I want is to search all articles that have a string in the title OR in the content OR on a tag.
My current query is the following:
SELECT article.article_id,article.title,user.user_id,article.article_text,
FROM tag,user,article
WHERE (article_text LIKE ? OR title LIKE ? OR tag LIKE ?)
AND article.user_id=user.user_id
GROUP BY article.article_id
ORDER BY article_id DESC

First i woud advise you to use explcit joins since they are much easier to read and less error prone (like missing the join between article and tag you just did):
Second, unless you have %'s around your strings, you should add them in the query:
SELECT a.article_id, a.title, u.user_id, a.article_text,
FROM article a
INNER JOIN user u ON a.user_id = u.user_id
LEFT JOIN tag_article ta ON ta.article_id = a.article_id
LEFT JOIN tag t ON t.tag_id = ta.tag_id
WHERE a.article_text LIKE '%YOURSTRING%'
OR a.title LIKE '%YOURSTRING%'
OR t.tag LIKE '%YOURSTRING%'
GROUP BY a.article_id
ORDER BY a.article_id DESC
I'm using LEFT JOIN's, because in the case you have articles without tags, it will still search in the article columns.

Related

Select from 3 tables with one-to-many relation

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

MySQL - Select by some "tags", include all tags

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?

Selecting for the absence of value in a one-to-many table design

I've been working all day on a problem that I expected to be simple, but is proving incredibly elusive. I suspect I may not be asking the right questions, so please bear with me.
I have a table with a bunch of newspaper articles for a research project. The idea is that researchers can tag individual articles. These tags are stored in a second table. An article can have any number of tags.
I can then select articles with a certain tag by using;
SELECT *,
GROUP_CONCAT(`TAGS`.`TAG`) AS tags
FROM ARTICLES
LEFT JOIN TAGS
ON TAGS.ID = ARTICLES.ID
WHERE TAGS.TAG = 'search term'
GROUP BY ARTICLES.ID;
My problems start when I want to select articles based on the absence of a particular tag. If an article has only one tag, the result is as expected, but if there is more then one tag associated with an article, the tag is simply ommitted.
SELECT *,
GROUP_CONCAT(`TAGS`.`TAG`) AS tags
FROM ARTICLES
LEFT JOIN TAGS
ON TAGS.ID = ARTICLES.ID
WHERE TAGS.TAG != 'search term'
OR TAGS.TAG IS NULL
GROUP BY ARTICLES.ID;
if the original tables where as follows;
ID Name
1 Article #1
2 Article #2
and;
ArticleID Tag
1 New
1 Long
1 Boring
2 Old
2 Long
2 Interesting
Then if I use the above query to select articles where tag != Boring, the results would be;
ArticleID Name Tags
1 Article #1 New, Long
2 Article #2 Old, Long, Interesting
How can I make it exclude the first article altogether, rather then just excluding that tag? Keeping in mind that there are over a hundred thousand articles in the database, what is the most efficient way to do this? I've looked at dozens of other questions and google searches, but selecting for the absence of a tag like this is something I could not find advice on.
On a sidenote, I am currently using a one-to-many table, as each tag appears once for each article it is linked to. I noticed that a lot of people in similar scenarios use a many-to-many design. Is this that much faster then having just a foreign key in the tags table referencing the article table?
Thank you for helping out an SQL noob :).
Try this:
SELECT A.ID,
A.`Name`,
GROUP_CONCAT(`TAGS`.`TAG`) AS tags
FROM ARTICLES A
LEFT JOIN TAGS
ON TAGS.ID = A.ID
WHERE NOT EXISTS( SELECT 1 FROM TAGS
WHERE ID = A.ID
AND Tag = 'search term')
GROUP BY ARTICLES.ID, ARTICLES.`Name`;
I would do it like this:
SELECT *,
GROUP_CONCAT(`TAGS`.`TAG`) AS tags
FROM ARTICLES A
LEFT JOIN TAGS
ON TAGS.ID = ARTICLES.ID
WHERE A.ID NOT IN (
SELECT ArticleID FROM TAGS
WHERE Tag = 'search term'
)
GROUP BY A.ID;
SELECT A.ID,
A.`Name`,
GROUP_CONCAT(`TAGS`.`TAG`) AS tags
FROM ARTICLES A
LEFT JOIN TAGS
ON TAGS.ID = A.ID
LEFT JOIN (
SELECT ArticleID FROM TAGS LKP
WHERE Tag = 'search term'
) SRCH
ON SRCH. ArticleID =A.ID
WHERE SRCH. ArticleID IS NUll

Mysql - search for blogs by tags

I'm using the following query to search for blogs that contain certain words in their titles. Each word is recorded as a unique in the table tags and then referenced to an actual blog in the table tags_titles. t.label is where the actual tag words are stored.
For some reason this query does not produce ay results, unless I input a number in which case it produces all the blogs without filtering. How can I get this to work?
SELECT tt.blog_id, b.title, COUNT(*) AS total_matches
FROM tags_titles AS tt
INNER JOIN tags AS t
ON tt.tag_id = t.tag_id
LEFT JOIN blogs AS b
ON tt.blog_id=b.blog_id
WHERE t.label IN ('boats','planes')
GROUP BY tt.blog_id
ORDER BY total_matches DESC
I think you want a right join rather than a left join and to fix some other details in the query:
SELECT b.blog_id, b.title, COUNT(t.label) AS total_matches
FROM tags_titles tt INNER JOIN
tags t
ON tt.tag_id = t.tag_id RIGHT JOIN
blogs b
ON tt.blog_id=b.blog_id and
t.label IN ('boat','plane')
GROUP BY b.blog_id
ORDER BY total_matches DESC;
You are asking for something at the blog level. However, the join is instead keeping all the tags, rather than the blogs. Once this switches to the blogs, then total_matches counts the number of matching tags to get the count (count(*) would never return 0 in this case, because there would be no row).
If you want at least one match, then include having total_matches > 0.

SQL select rows having several values in common

I'm having a problem to select some articles rows depending on a condition.
Here's my problem : All my articles can have several 'tags' attached, so my structure looks like this :
articles articles_tags tags
¯¯¯¯¯¯¯¯ ¯¯¯¯¯¯¯¯¯¯¯¯¯ ¯¯¯¯
id article_id id
title tag_id name
content
[...]
Now I'd like to select ALL articles which have BOTH tags 2 and 3 for example. I tried this :
SELECT * FROM articles a
JOIN articles_tags at
ON (a.id = at.article_id)
WHERE
at.tag_id IN(2, 3)
GROUP BY article_id
But this will select all articles which have AT LEAST tags IDs #2 or #3 (seems logic after all)
Is there any trick or something to get only the articles having a defined list of tag IDs ?
Thanks you
This problem is called Relational Division
SELECT a.*
FROM articles a
INNER JOIN
(
SELECT at.article_id
FROM articles a
INNER JOIN articles_tags at
ON a.id = at.article_id
WHERE at.tag_id IN(2, 3)
GROUP BY at.article_id
HAVING COUNT(*) = 2
) b ON a.id = b.article_id
SQL of Relational Division