LEFT JOIN "Not unique table/alias" - mysql

I want to load all the posts and its tags with one query from my database.I think LEFT JOIN is the appropriate one for that, do you have any other suggestion?
Here is my SQL query:
SELECT * FROM posts, tags, tags_map
LEFT JOIN posts on posts.cid = tags_map.pid
WHERE tags.tag_id = tags_map.tid
It showing an error Not unique table/alias: 'posts' where is wrong, cause i am pointing to a table named 'posts', any idea?

Remove posts table from "FROM" section:
SELECT * FROM tags, tags_map
LEFT JOIN posts on posts.cid = tags_map.pid
WHERE tags.tag_id = tags_map.tid

Do not mix old and new style joins. In fact, always use explicit join syntax. Never use commas in the from clause:
SELECT *
FROM posts p JOIN
tags_map tm
ON p.cid = tm.pid JOIN
tags t
ON t.tag_id = tm.tid;
I'm not sure what the left join is for. If you really need it, add it to this version of the query.

Related

How can I refer to the columns of a table after I used INNER JOIN in MySQL

I asked this question once before but it got deleted because it was unnecessarily long, so I deleted it, edited it so that it would be shorter and to the point and reposted here:
I have the following tables in a MySQL database that are represented in the following image:
I want to get the fields id, id_user, body and post_date from all the posts with one or more specific tags that where originaly in the table posts. To achieve that I try to INNER JOIN
posts to posts_has_tags to tags and get the posts.id, posts.id_user, posts.body, posts.post_date but I get the following error:
Unknown column 'posts.id' in 'field list'
The query I use to INNER JOIN is the following:
SELECT posts.id AS post_id, id_user, body , post_date
FROM posts INNNER JOIN posts_has_tags
ON post_id = posts_id
INNER JOIN tags
ON tags.id = tags_id
Why is posts.id raising an error? Is it beacause I have INNER JOINED the tables and if so what can I do to fix it?
P.S. I know that I didn't post the WHERE statement in the query but the problem is not there so I left it out.
When you are dealing with multiple tables in a query preface each column references with the table that supplies that column. e.g.:
SELECT
posts.id AS post_id ## nb this is a "column alias"
, posts.id_user
, posts.body
, posts.post_date
FROM posts
INNER JOIN posts_has_tags ON posts.id = posts_has_tags.posts_id
INNER JOIN tags ON posts_has_tags.tags_id = tags.id
OR, to make this somewhat easier to prepare you can declare table aliases, then preface each column reference with the relevant alias instead, e.g.:
SELECT
p.id AS post_id
, p.id_user
, p.body
, p.post_date
FROM posts AS p ## nb this is a "table alias"
INNER JOIN posts_has_tags AS pt ON p.id = pt.posts_id
INNER JOIN tags AS t ON pt.tags_id = t.id
Whilst you can take the risk that a specific colum_name may be unique with a set of joined tables, this may not always be true if someone alters a table, so it is "good practice" (in my view) to always preface a column with its table or alias.
https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=223e1f48e64513a375146ca2e8a71343
Note: you alias posts.id as post_id, but that column name already exists in posts_has_tags. This "mixing-up" of columns names is a small example of why it is risky omit table/alias references when writing queries.
You can’t join using an alias. Use posts.id in join and correct the typo INNNER JOIN
and the query syntaxes.
SELECT posts.id AS post_id, id_user, body , post_date
FROM posts
INNER JOIN posts_has_tags ON posts.id = posts_id
INNER JOIN tags ON tags.id = tags_id;
https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=e0cba7e787fa1ab61a926a38b7e98e02

Is there a better way to do this MYSQL statement?

This mysql statement gets posts that have not been flagged by the user.
As it is now, I am getting the flagged post ids, and then not getting posts in that set of ids.
SELECT * FROM posts WHERE posts.id NOT IN
(SELECT p2.id FROM posts p2 LEFT JOIN flagged_posts
ON flagged_posts.user_id = ? WHERE flagged_posts.post_id = p2.id)
I feel there is probably a better (faster) way to do this, for example with just one select and one join, but I am not sure
You can do this using clause EXISTS.
SELECT * FROM posts
WHERE
NOT EXISTS
(SELECT 1
FROM flagged_posts
WHERE flagged_posts.post_id=posts.post_id AND flagged_posts.user_id=?)
Alternatively you can do this using LEFT OUTER JOIN.
SELECT *
FROM posts
LEFT OUTER JOIN flagged_posts ON posts.post_id = flagged_posts.post_id
AND flagged_posts.user_id=?
WHERE flagged_posts.post_id IS NULL

Select documents that are not tagged

Assuming my database has 3 tables:
Documents
Tags
Documents_Tags (the join table)
I know how to find the tags assigned to a document using a LEFT JOIN, but I'm having trouble finding the tags that are not assigned.
SELECT * FROM `documents_tags`
LEFT JOIN `tags` ON `tags`.`id` = `documents_tags`.`tag_id`
WHERE `document_id` = 111;
I've tried different joins, but I keep getting only one record. I thought there was a way to join all the tags and then limit the results to where the document is null?
EDIT: In the above example I need to find all tags not assigned to document 111.
SELECT Tags.name -- or whatever it is
FROM
tags
LEFT JOIN documents_tags dt ON (tags.id = dt.tag_id AND dt.document_id = 111)
WHERE dt.id IS NULL

mysql - select posts all that are not tagged hidden

So I have three tables, one is posts , having columns id,title,content,timestamp . Other is tags, having columns id,tag and third posttags describes one to many relation between posts and tags , having columns postid,tagid .
Now instead of having columns like hidden,featured etc in the table posts to describe whether a post should be visible to all or should be displayed on a special featured page, I thought why not use tags to save time. So what I decided is that all posts that have a tag #featured will be featured and all posts with tag #hidden will be hidden.
Implementing first one was easy as I could use a join query and in my where clause I could mention WHERE tag='featured' and this would get all the featured posts for me.
But take an example of a post tagged #sports and #hidden if I were to use the query
SELECT * FROM posts
INNER JOIN posttags ON posttags.postid = posts.id
INNER JOIN tags ON posttags.tagid = tags.id
WHERE tag !='hidden'
but that'd still return the post tagged hidden since its also tagged sports
PS my question is different from this question : Select a post that does not have a particular tag since it uses tagid directly and I'm unable to achieve same result using double join to check against tag name instead of tagid. And also I wish to retrieve the other tags of the post in same query which is not possible using the method in that question's answers
Group the tags by post, then use the HAVING clause to filter the groups for those that do not contain a 'hidden' tag. Because of MySQL's implicit type conversion and lack of genuine boolean types, one can do:
SELECT posts.*
FROM posts
JOIN posttags ON posttags.postid = posts.id
JOIN tags ON posttags.tagid = tags.id
GROUP BY posts.id
HAVING NOT SUM(tag='hidden')
You can do this with a NOT EXISTS subquery:
SELECT p.*, t.* -- what columns you need
FROM posts AS p
INNER JOIN posttags AS pt
ON pt.postid = p.id
INNER JOIN tags AS t
ON pt.tagid = t.id
WHERE NOT EXISTS
( SELECT *
FROM posttags AS pt_no
INNER JOIN tags AS t_no
ON pt_no.tagid = t_no.id
WHERE t_no.tag = 'hidden'
AND pt_no.postid = p.id
) ;
or the equivalent LEFT JOIN / IS NULL:
SELECT p.*, t.*
FROM posts AS p
LEFT JOIN posttags AS pt_no
INNER JOIN tags AS t_no
ON t_no.tag = 'hidden'
AND pt_no.tagid = t_no.id
ON pt_no.postid = p.id
INNER JOIN posttags AS pt
ON pt.postid = p.id
INNER JOIN tags AS t
ON pt.tagid = t.id
WHERE pt_no.postid IS NULL ;
Thsi type of queries are called anti-semijoins or just anti-joins. It's slightly more complex in your case because the condition (tag='hidden') is in a 3rd table.

Need an alternative to two left joins

Hey guys quick question, I always use left join, but when I left join twice I always get funny results, usually duplicates. I am currently working on a query that Left Joins twice to retrieve the necessary information needed but I was wondering if it were possible to build another select statement in so then I do not need two left joins or two queries or if there were a better way. For example, if I could select the topic.creator in table.topic first AS something, then I could select that variable in users and left join table.scrusersonline. Thanks in advance for any advice.
SELECT * FROM scrusersonline
LEFT JOIN users ON users.id = scrusersonline.id
LEFT JOIN topic ON users.username = topic.creator
WHERE scrusersonline.topic_id = '$topic_id'
The whole point of this query is to check if the topic.creator is online by retrieving his name from table.topic and matching his id in table.users, then checking if he is in table.scrusersonline. It produces duplicate entries unfortunately and is thus inaccurate in my mind.
You use a LEFT JOIN when you want data back regardless. In this case, if the creator is offline, getting no rows back would be a good indication - so remove the LEFT joins and just do regular joins.
SELECT *
FROM scrusersonline AS o
JOIN users AS u ON u.id = o.id
JOIN topic AS t ON u.username = t.creator
WHERE o.topic_id = '$topic_id'
One option is to group your joins thus:
SELECT *
FROM scrusersonline
LEFT JOIN (users ON users.id = scrusersonline.id
JOIN topic ON users.username = topic.creator)
WHERE scrusersonline.topic_id = '$topic_id'
Try:
select * from topic t
left outer join (
users u
inner join scrusersonline o on u.id = o.id
) on t.creator = u.username
If o.id is null, the user is offline.
Would not it be better to match against topic_id in the topics table by moving the condition to the join. I think it will solve your problem, since duplicates come from joining with the topics table:
SELECT * FROM scrusersonline
JOIN users
ON users.id = scrusersonline.id
LEFT JOIN topic
ON scrusersonline.topic_id = '$topic_id'
AND users.username = topic.creator
By the way, LEFT JOIN with users is not required since you seem to search for the intersection between scrusersonline and users