Mysql NOT ON statement - php

i have the following query that pulls posts that belong to a blog
SELECT p.*
FROM `Posts` p
INNER JOIN Blogs b ON b.id = p.blog_id
Is there a MySQL function that does the opposite of "ON"? If i wanted to query posts that didn't belong to a blog in the "Blogs" database table? For instance if the blog was deleted.

You need to use either LEFT JOIN - NULL combo, like this:
SELECT p.*
FROM Posts p
LEFT OUTER JOIN Blogs b
ON b.id = p.blog_id
WHERE b.id IS NULL
... or NOT IN dependent query (with so-called 'anti-join'):
SELECT *
FROM Posts
WHERE blog_id NOT IN (SELECT id FROM Blogs)
I admit I almost exclusively use the first form, but actually they have the similar performance: I'd recommend at least glancing through this article with detailed explanation of similarities and differences between these queries; it's quite an enlightening reading. )

It's all about the JOIN.
SELECT p.*
FROM `Posts` p
LEFT OUTER JOIN Blogs b ON b.id = p.blog_id
WHERE b.Id IS NULL
Should do the trick for you.
Jeff Atwood has a great overview of joins
In this instance, what the LEFT OUTER JOIN does is match all rows in Blogs with all rows in Posts with the same blog_id-Id combo AND all rows in Posts that have no matching Id in Blogs. In this instance, those rows are NULL for the entries in Blogs (as there aren't any!), which is why we filter on b.Id IS NULL
There are some other ways you can achieve this too; for example:
SELECT * FROM Posts WHERE blog_id NOT IN (SELECT Id FROM Blogs)
Effectively, this queries all rows in posts that have a blog_id that isn't in the blog table.

Related

mysql/php: show posts and for each post all comments

I know this question has been asked multiple times (however, I could still not find a solution):
PHP MYSQL showing posts with comments
mysql query - blog posts and comments with limit
mysql structure for posts and comments
...
Basic question: having tables posts, comments, user... can you with one single select statement select and show all posts and all comments (with comment.user, comment.text, comment.timestamp)? How would such a select statement look like? If not, what is the easiest solution?
I also tried to JOIN the comments table with the posts table and use GROUP BY, but I got either only one comment in each row or each comment but also those posts multiple times!?
I tried the solution of the first link (nested mysql_query and then fetch) as well as the second link (with arrays). However, the first caused a bunch of errors (the syntax in that post seems to be not correct and I could not figure out how to solve it) and in the second I had problems with the arrays.
My query looks like this till now:
SELECT p.id, p.title, p.text, u.username, c.country_name, (SELECT SUM(vote_type) FROM votes v WHERE v.post_id = p.id) AS sum_vote_type FROM posts p LEFT JOIN user u ON ( p.user_id = u.id ) LEFT JOIN countries c ON ( c.country_id = u.country_id ) ORDER BY $orderby DESC
I was wondering if this issue was not very common, having posts and comments to show...?
Thank you for every help in advance!
Not knowing your database structure, it should look something like this. Note that you should replace the * characters with more explicit lists of columns you actually need.
SELECT p.*, c.*, u.* FROM posts p
LEFT JOIN comments c ON c.post_id = p.id
LEFT JOIN users u ON u.id = p.author_id
Note that if you're just trying to get counts, sums and things like that it's a good idea to cache some of that information. For instance, you may want to cache the comment count in the post table instead of counting them every query. Only count and update the comment count when adding/removing a comment.
EDIT:
Realized that you also wanted to attach user data to each comment. You can JOIN the same table more than once but it gets ugly. This could turn into a really expensive query. I also am including an example of how to alias columns so it's less confusing:
SELECT p.*, c.*, u.name as post_author, u2.name as comment_author FROM posts p
LEFT JOIN comments c ON c.post_id = p.id
LEFT JOIN users u ON u.id = p.author_id
LEFT JOIN users u2 ON u2.id = c.author_id

Get latest 3 comments for a list of posts

Currently I'm running one query to get all posts for a user, then in the loop for that, I am querying for the latest 3 comments for that particular post. Super inefficient; I'm querying over and over again for every post.
I would like to consolidate my queries so that I query just once for all posts, and just once for all comments for those particular posts. At the moment I have a comma-separated list that I made for all posts for this user (e.g. "1,5,18,9")
posts table:
posts.id
posts.userid
comments table:
comments.id
comments.relid (this is the postid)
comments.userid
The query should use the $posts_list I have, which is the comma-separated list of posts. Or a subselect for all posts for this user, but that seems inefficient since I already have the post list in a string.
Thanks so much for any help!
Try this query -
SELECT p.id, p.userid, c.id, c.userid
FROM posts p
JOIN (
SELECT c1.*, COUNT(*) rank FROM comments c1
LEFT JOIN comments c2
ON c2.relid = c1.relid AND c2.id <= c1.id
GROUP BY c1.relid, c1.id
) c
ON p.id = c.relid
WHERE rank < 4
And add condition you need, i.e. - WHERE p.userid IN (1,5,18,9).

Category post count

I am building a blog with Codeigniter and MySQL. The question I have is this, I have a table with posts and one with categories. I also have a cross reference table with post_categories. What I am trying to do is get all the categories with their names and the number of posts they have under their name.
Example output would be: Hello World(1) Test(0) etc.
What I am having a hard time finding is a SQL query that will join the three tables and get me the counts, and I am also having a hard time wrapping my head around how to make that query.
Here is my table schema:
blgpost
====
id
*Other schema unimportant
blgpostcategories
=================
postid
categoryid
blgcategories
==========
id
name
*Other schema unimportant
This should give you the output you want....
SELECT c.name, COUNT(p.id) FROM
blgcategories c
INNER JOIN blgpostcategories pc ON c.id = pc.categoryid
INNER JOIN blgpost p ON pc.postid = p.id
GROUP BY c.id
You don't need to join the three tables - the blgpost table doesn't have any information in it that you need.
SELECT COUNT(*), blgcategories.name
FROM blgcategories INNER JOIN blgpostcategories
ON blgcategories.id=blgpostcategories.categoryid
GROUP BY blgcategories.id;
SELECT name, COUNT(pc.id)
FROM blgcategories c
LEFT JOIN
blgpostcategories pc
ON pc.categoryid = c.id
GROUP BY
c.id
Using LEFT JOIN will show 0 for empty categories (those without posts linked to them) rather than omitting them.

MySQL Conditional Join Queries (WordPress DB)

So I've been looking through how to get this to work and haven't been able to find it, or really even properly search.
I'm looking to join t1 to t2 where I get ALL results from t1, and it's only joined where t2's column value is "something", the table is set up that there are many items in the t2 column. So if t2's column doesn't equal "something", then just have the other data (but mainly I only want ONE of each value for t1 row, because there will be a max of one t2 row that qualifies per t1 row).
select * t1 LEFT JOIN t2 where t2.column="something" AND t1 conditions.
Any help would be appreciated, the tables are WordPress tables, and the thing I'm asked to do would be easier done without WordPress knowing.
--
Actual code attempt:
$ SELECT * FROM posts LEFT JOIN postmeta ON post_id = id WHERE post_status='publish' AND post_type='portfolio' AND meta_key='rjmeta_video'
$Table 1
$ID Title ....
$----------------------
$5 Some post I need outside of WP
$Table 2
$meta_id post_id meta_key ....
$--------------------------------
$3 5 rjmeta_video
$4 5 _edit_lock
$5 5 _edit_last
I believe this is what you mean:
select distinct t1.* from t1
LEFT JOIN t2 on [condition for join] and t2.column="something"
where [t1 conditions].
This assumes that you are only interested in the t1 fields.
What we don't know:
table schemas
how are they joined? t2.t1_id = t1.id ?
t1 conditions
DISTINCT used above will eliminate duplicate rows if there are multiple matches for the join
EDIT (OP comment response):
SELECT distinct posts.* FROM posts
LEFT JOIN postmeta ON (post_id = id and meta_key='rjmeta_video')
WHERE post_status='publish' AND post_type='portfolio'
This assumes that 'rjmeta_video' is the "something" you were referring to originally

Need help with a multiple table query in mysql

I'm working on building a forum with kohana. I know there is already good, free, forum software out there, but it's for a family site, so I thought I'd use it as a learning experience. I'm also not using the ORM that is built into Kohana, as I would like to learn more about SQL in the process of building the forum.
For my forum I have 4 main tables:
USERS
TOPICS
POSTS
COMMENTS
TOPICS table: id (auto incremented), topic row.
USERS table: username, email, first and last name and a few other non related rows
POSTS table: id (auto incremented), post-title, post-body, topic-id, user-id, post-date, updated-date, updated-by(which will contain the user-id of the person who made the most recent comment)
COMMENTS table: id (auto incremented), post-id, user-id and comment
On the main forum page I would like to have:
a list of all of the topics
the number of posts for each topic
the last updated post, and who updated it
the most recently updated topic to be on top, most likely an "ORDER BY updated-date"
Here is the query I have so far:
SELECT topics.id AS topic-id,
topics.topic,
post-user.id AS user-id,
CONCAT_WS(' ', post-user.first-name, post-user.last-name) AS name,
recent-post.id AS post-id,
post-num.post-total,
recent-post.title AS post-title,
recent-post.update_date AS updated-date,
recent-post.updated-by AS updated-by
FROM topics
JOIN (SELECT posts.topic-id,
COUNT(*) AS post-total
FROM POSTS
WHERE posts.topic-id = topic-id
GROUP BY posts.topic-id) AS post-num ON topics.id = post-num.topic-id
JOIN (SELECT posts.*
FROM posts
ORDER BY posts.update-date DESC) AS recent-post ON topics.id = recent-post.topic-id
JOIN (SELECT users.*,
posts.user-id
FROM users, posts
WHERE posts.user-id = users.id) as post-user ON recent-post.user_id = post-user.id
GROUP BY topics.id
This query almost works as it will get all of information for topics that have posts. But it doesn't return the topics that don't have any posts.
I'm sure that the query is inefficient and wrong since it makes two sub-selects to the posts table, but it was the only way I could get to the point I'm at.
Dash is not a valid character in SQL identifiers, but you can use "_" instead.
You don't necessarily have to get everything from a single SQL query. In fact, trying to do so makes it harder to code, and also sometimes makes it harder for the SQL optimizer to execute.
It makes no sense to use ORDER BY in a subquery.
Name your primary key columns topic_id, user_id, and so on (instead of "id" in every table), and you won't have to alias them in the select-list.
Here's how I would solve this:
First get the most recent post per topic, with associated user information:
SELECT t.topic_id, t.topic,
u.user_id, CONCAT_WS(' ', u.first_name, u.last_name) AS full_name,
p.post_id, p.title, p.update_date, p.updated_by
FROM topics t
INNER JOIN
(posts p INNER JOIN users u ON (p.updated_by = u.user_id))
ON (t.topic_id = p.topic_id)
LEFT OUTER JOIN posts p2
ON (p.topic_id = p2.topic_id AND p.update_date < p2.update_date)
WHERE p2.post_id IS NULL;
Then get the counts of posts per topic in a separate, simpler query.
SELECT t.topic_id, COUNT(*) AS post_total
FROM topics t LEFT OUTER JOIN posts p USING (topic_id)
GROUP BY t.topic_id;
Merge the two data sets in your application.
to ensure you get results for topics without posts, you'll need to use LEFT JOIN instead of JOIN for the first join between topics and the next table. LEFT JOIN means "always return a result set row for every row in the left table, even if there's no match with the right table."
Gotta go now, but I'll try to look at the efficiency issues later.
This is a very complicated query. You should note that JOIN statements will limit your topics to those that have posts. If a topic does not have a post, a JOIN statement will filter it out.
Try the following query.
SELECT *
FROM
(
SELECT T.Topic,
COUNT(AllTopicPosts.ID) NumberOfPosts,
MAX(IFNULL(MostRecentPost.Post-Title, '') MostRecentPostTitle,
MAX(IFNULL(MostRecentPostUser.UserName, '') MostRecentPostUser
MAX(IFNULL(MostRecentPost.Updated_Date, '') MostRecentPostDate
FROM TOPICS
LEFT JOIN POSTS AllTopicPosts ON AllTopicPosts.Topic_Id = TOPICS.ID
LEFT JOIN
(
SELECT *
FROM Posts P
WHERE P.Topic_id = TOPICS.id
ORDER BY P.Updated_Date DESC
LIMIT 1
) MostRecentPost ON MostRecentPost.Topic_Id = TOPICS.ID
LEFT JOIN USERS MostRecentPostUser ON MostRecentPostUser.ID = MostRecentPost.User_Id
GROUP BY T.Topic
)
ORDER BY MostRecentPostDate DESC
I'd use a left join inside a subquery to pull back the correct topic, and then you can do a little legwork outside of that to get some of the user info.
select
s.topic_id,
s.topic,
u.user_id as last_updated_by_id,
u.user_name as last_updated_by,
s.last_post,
s.post_count
from
(
select
t.id as topic_id,
t.topic,
t.user_id as orig_poster,
max(coalesce(p.post_date, t.post_date)) as last_post,
count(*) as post_count --would be p.post_id if you don't want to count the topic
from
topics t
left join posts p on
t.id = p.topic_id
group by
t.topic_id,
t.topic,
t.user_id
) s
left join posts p on
s.topic_id = p.topic_id
and s.last_post = p.post_date
and s.post_count > 1 --0 if you're using p.post_id up top
inner join users u on
u.id = coalesce(p.user_id, s.orig_poster)
order by
s.last_post desc
This query does introduce coalesce and left join, and they are very good concepts to look into. For two arguments (like used here), you can also use ifnull in MySQL, since it is functionally equivalent.
Keep in mind that that's exclusive to MySQL (if you need to port this code). Other databases have other functions for that (isnull in SQL Server, nvl in Oracle, etc., etc.). I used coalesce so that I could keep this query all ANSI-fied.

Categories