MySQL subtracting multiple times for same row in update - mysql

I have a table of comments and a table of posts
Whenever a post is deleted, a query runs to subtract the number of comments (that are deleted later) from each user's comment_count
So if a user has 2 comments in a post, and that post is deleted, their balance should have 2 subtracted from it
My query is as follows:
UPDATE users
INNER JOIN comment ON users.id = comment.author
SET comment_count = comment_count - 1
WHERE comment.post = 1
User A has 2 comments with .post = 1, but for some reason that user only gets comment_count subtracted by 1 once, when it should happen twice
I think my syntax is right because when I:
SELECT *
FROM users
INNER JOIN comment ON users.id = comment.author
WHERE comment.post = 1
I get two results for user A
Shouldn't UPDATE be iterating over those two results, subtracting each time?
Can someone explain what I am missing? thank you

If you're going to store the count, use:
UPDATE USERS
SET comment_count = (SELECT COUNT(*)
FROM COMMENT c
WHERE c.author = USERS.id)
...or:
UPDATE USERS u
JOIN (SELECT c.author,
COUNT(*) AS numComments
FROM COMMENT c
GROUP BY c.author) x ON x.author = u.id
SET comment_count = x.numComments
There's no point in relying on two records to subtract twice, when you could perform the operation once.
I prefer not to store such values, because they can be calculated based on records without the hassle of keeping the counts in sync. A view might be a better idea...

Related

List of all products with user's saved products

I'm having an issue trying to figure out a query that will allow me to show a list of all of my product as well as showing whether or not a user has saved any given product.
I have 3 tables involved in the query (users, product_user, product).
I am determing whether or not a user has saved a product by joining the three tables and checking if user_id is null or not with the following query:
SELECT products.*, users.id as 'user_id' FROM products
LEFT JOIN product_user ON products.id = product_user.product_id
LEFT JOIN users ON product_user.user_id = users.id AND users.id =1;
However this returns duplicate rows when the user has saved a product (user_id null version and user_id = 1 version). A distinct statement won't work because the rows aren't distinct in this case. What is best practices to ensure that I only get back distinct products? I need to get back the entire list of products, whether or not the user has saved it.
This is being queried in mysql.
I think this does what you want:
select p.*,
(select pu.user_id
from product_user pu
where pu.product_id = p.id and pu.user_id = 1
limit 1
) as user_id
from products p;
This will return only one row per product. The row will have the user_id -- only once and it has to match whatever you pass in.

Advise for best query performance (MySQL)

I'm looking for the best MySQL query for that situation:
I'm listing 10 last posts of a member.
table for posts:
post_id | uid | title | content | date
The member have the possibility to subscribe to other member posts, so that posts are listed in the same list (sorted by date - same table)
So it's ok to select last posts of userid X and userid Y
But I'd like to allow members to diable display of some posts (the ones he doesn't want to be displayed).
My problem is: how can I make that as simple as possible for MySQL?... I thought about a second table where I put the post ids that the user doesn't want:
table postdenied
uid | post_id
Then make a select like:
select * from posts as p where not exists (select 1 from postdenied as d where d.post_id = p.post_id and d.uid = p.uid) order by date DESC limit 10
I'm right?
Or is there something better?
Thanks
If I understand correctly, the posts.uid column stores the ID of the poster. And the postdenied.uid stores the ID of the user that doesn't want to see a certain post.
If the above assumptions are correct, then your query is fine, except that you should not join on the uid columns, only on the post_id ones. And you should have a parameter or constant the userID (noted as #X in the code below) of the user that you want to show all the posts - except those he has "denied":
select p.*
from posts as p
where not exists
(select 1
from postdenied as d
where d.post_id = p.post_id
and d.uid = #X -- #X is the userID of the specific user
)
order by date DESC
limit 10 ;
Another approach to implementing this would be with a LEFT JOIN clause.
SELECT * FROM posts AS p
LEFT JOIN postdenied as d ON d.post_id = p.post_id and d.uid = p.uid
WHERE d.uid IS NULL
ORDER BY date DESC
LIMIT 10
It's unclear to me whether this would be more amenable to the query optimizer. If you have a large amount of data, it may be worth testing both queries and seeing if one is more performant than the other.
See http://sqlfiddle.com/#!2/be7e3/1
Appreciation to ypercube and Lamak for their feedback on my original answer

Mysql query to select rows and alternatively matching rows on other table

I've got two tables:
user
ID --- Name
posts
ID --- UserID --- Text --- Postdate
Now I want to select all users and every post they've made in the past 15 minutes. I also want to display every user that didn't didn't even make a post with the matching conditions at all.
I'm currently doing it this way:
$user_q = mysql_query("SELECT * FROM user");
while ($a = mysql_fetch_assoc($user_q))
{
$post_q = mysql_query("SELECT * FROM posts WHERE Userid=".$a['ID']." AND Postdate >= DATE_SUB(NOW(), INTERVAL 15 MINUTE)");
//Do anything with this information
}
Do you have any ideas how I can put all this information in just one query? Doing so many queries makes the server running very slow.
What you want is a left outer join:
select u.*, p.*
from users u left outer join
posts p
on u.id = p.userid and
p.Postdate >= DATE_SUB(NOW(), INTERVAL 15 MINUTE);
The left outer join keeps all the rows in the first table. If nothing matches in the second table, using the condition, then the values are NULL for those fields.
EDIT:
If you want to limit this to 50 random users, you can do this at the users level:
select u.*, p.*
from (select u.*
from users u
order by rand()
limit 50
) u left outer join
posts p
on u.id = p.userid and
p.Postdate >= DATE_SUB(NOW(), INTERVAL 15 MINUTE);
The order by rand() makes them random. You could order by anything -- name, date created, whatever. You can even leave the order by out and just take 50 arbitrary rows returned by the database.

MySQL Query -- based on user and business ID's

I am working on a project right now and am rather stumped with a specific sql query I (need) to execute. Let me start off by showing the DB structure I need to pull from.
--posts_table--
ID
post_title
post_text
bus_id
This next table is what is screwing with me. The only way data related to the logged in user is in here is if they have "liked" a specific post -- otherwise there is no data related to that user in this table. Now there could be plenty of data related to a particular post, just generated from other users.
--likes_table--
ID
user_id
post_id
like
What I need this to do is grab all the posts from the post_table above where a specific business id is specified. From there, I need it to grab the "like" column in the likes_table if there is data in there related to the logged in user. If there is no data there, just leave that field null in the query. Below is a query I wrote that works until there is other "like" data in the like_table from other users.
SELECT posts.id, posts.post_text, posts.post_title, likes.post_id, likes.like
FROM posts LEFT JOIN likes ON posts.id = likes.post_id WHERE
posts.bus_id = 1 AND likes.user_id IS NULL OR likes.user_id = 1;
This works up until data has been entered in the table about a specific post being liked by a different user before that user has done anything with that post, whether they like or dislike it. I am not sure if this specific type of query is even possible, any help would be much appreciated.
Edit:
After looking at it again -- I got it, finally. I just needed to add one more AND. Below is the proper query I was looking for.
SELECT posts.id, posts.post_text, posts.post_title, likes.post_id, likes.like
FROM posts LEFT JOIN likes ON posts.id = likes.post_id AND posts.user_id = 1 WHERE
posts.bus_id = 1 AND likes.user_id IS NULL OR likes.user_id = 1;
Ahh, I think I get you -- is it that if a particular post hasn't been commented on by user_id number 1 at all, the row for that doesn't show up at all?
In that case, put your l.user_id=1 into the JOIN condition instead of the WHERE condition --- this will put a NULL in if user_id 1 hasn't liked or disliked a particular post.
SELECT p.id, p.post_text, p.post_title, l.post_id, l.likes
FROM posts p
LEFT JOIN likes l ON p.id = l.post_id AND l.user_id=1
WHERE p.bus_id = 1
The l.user_id IS NULL OR l.user_id=1 has been incorporated into the LEFT JOIN -- it doesn't make rows for the other user_ids.

Counting results from multiple tables with same column

I have a system where, essentially, users are able to put in 3 different pieces of information: a tip, a comment, and a vote. These pieces of information are saved to 3 different tables. The linking column of each table is the user ID. I want to do a query to determine if the user has any pieces of information at all, of any of the three types. I'm trying to do it in a single query, but it's coming out totally wrong. Here's what I'm working with now:
SELECT DISTINCT
*
FROM tips T
LEFT JOIN comments C ON T.user_id = C.user_id
LEFT JOIN votes V ON T.user_id = V.user_id
WHERE T.user_id = 1
This seems to only be getting the tips, duplicated for as many votes or comments there are, even if the votes or comments weren't made by the specified user_id.
I only need a single number in return, not individual counts of each type. I basically want a sum of the number of tips, comments, and votes saved under that user_id, but I don't want to do three queries.
Anyone have any ideas?
Edit: Actually, I don't even technically need an actual count, I just need to know if there are any rows in any of those three tables with that user_id.
Edit 2: I almost have it with this:
SELECT
COUNT(DISTINCT T.tip_id),
COUNT(DISTINCT C.tip_id),
COUNT(DISTINCT V.tip_id)
FROM tips T
LEFT JOIN comments C ON T.user_id = C.user_id
LEFT JOIN votes V ON T.user_id = V.user_id
WHERE T.user_id = 1
I'm testing with user_id 1 (me). I've made 11 tips, voted 4 times, and made no comments. My return is a row with 3 columns: 11, 0, 4. That's the proper count. However, I tested it with a user that hasn't made any tips or comments, but has voted 3 times, that returned 0 for all counts, it should have returned: 0, 0, 3.
The problem that I'm having seems to be that if the table that I'm using for the WHERE clause doesn't have any rows from that user_id, then I get 0 across the board, even if the other tables DO have rows with that user_id. I could use this query:
SELECT
(SELECT COUNT(*) FROM tips WHERE user_id = 2) +
(SELECT COUNT(*) FROM comments WHERE user_id = 2) +
(SELECT COUNT(*) FROM votes WHERE user_id = 2) AS total
But I really wanted to avoid running multiple queries, even if they're subqueries like this.
UPDATE
Thanks to ace, I figured this out:
SELECT
(COUNT(DISTINCT T.tip_id) + COUNT(DISTINCT C.tip_id) + COUNT(DISTINCT V.tip_id)) AS total
FROM users U
LEFT JOIN tips T ON U.user_id = T.user_id
LEFT JOIN votes V ON U.user_id = V.user_id
LEFT JOIN comments C ON U.user_id = C.user_id
WHERE U.user_id = 4
the users table contains the actual information bout the user including, obviously, the user id. I used the user table as the parent, since I could be 100% sure that the user would be present in that table, even if they weren't in the other tables. I got the proper count that I wanted with this query!
As I understand your question. You want to count the total comments + tips + votes for each user. Though is not really clear to me take a look at below query. I added columns for details this is a cross tabs query as someone teach me.
EDITED QUERY:
SELECT
COALESCE(COALESCE(t2.tips,0) + COALESCE(c2.comments,0) + COALESCE(v2.votes,0)) AS `Totals`
FROM parent p
LEFT JOIN (SELECT t.user_id, COUNT(t.tip_id) AS tips FROM tips t GROUP BY t.user_id) t2
ON p.user_id = t2.user_id
LEFT JOIN (SELECT c.user_id, COUNT(c.tip_id) AS comments FROM comments c GROUP BY c.user_id) c2
ON p.user_id = c2.user_id
LEFT JOIN (SELECT v.user_id, COUNT(v.tip_id) AS votes FROM votes v GROUP BY v.user_id) v2
ON p.user_id = v2.user_id
WHERE p.user_id = 1;
Note: This used a parent table in order to get the result of a table which doesn't in other table.
The reason why I use a sub-query in my JOIN is to create a virtual table that will get the sum of tip_id for each table. Also I'm having problem with the DISTINCT using the same query of yours, so I end up with this query.
I know you prefer not using sub-queries, but I failed without a sub-query. For now this is all I can.