Only get users with a specific joined table count - mysql

My SqlFiddle
Seems pretty simple but for the life of me I cannot seem to get the results I want. Keep in mind this is happening on a server that has over 6 million records so it has to be efficient.
I want to get the users who are joined to item table that have status 1 of 3 count or higher only not the users who have status of 1 on an item 2 times has to be >= 3.
SELECT
user.id,
user.name,
item.id as item_id,
itemstatus.item_status,
COUNT(item.status) as status
FROM user
JOIN item ON (user.id = item.user_id)
JOIN itemstatus ON (item.status = itemstatus.id)
WHERE item.status = 1
GROUP BY user.id
My current query above get all with a count. How do I get only the ones that have the 3 or above count. Thanks in advance. I hope I made this clear.

If I understand you correctly, you only want records with COUNT(item.status) greater than or equal to 3, in which case you need to use the HAVING clause after your GROUP BY clause. The HAVING clause is sort of like a WHERE clause for aggregate values.
SELECT
user.id,
user.name,
item.id as item_id,
itemstatus.item_status,
COUNT(item.status) as status
FROM user
JOIN item ON (user.id = item.user_id)
JOIN itemstatus ON (item.status = itemstatus.id)
WHERE item.status = 1
GROUP BY user.id
HAVING COUNT(item.status) >= 3

Related

Return unrated rows from a MySQL database

I have a system where people rate items, and I have two tables, I want to only show the user items they have not rated.
item (i and simplified for this example)
----
item_id, name
1, widget1
2, widget2
I have a rating table, which stores three columns
rating
------
item_id
user_id
rating
So I want to only return results that that user has not yet rated, now I did try this;
psuedo-query
SELECT * FROM item LEFT JOIN rating r ON r.item_id = i.item_id WHERE r.user_id != USER_ID_OF_THE_USER;
However that still returned items that they had rated, as other people had rated the item...
So if I have 100 items in the database, user a has rated 30 and user b has rated 70... then user a should get the 70 items they have to rate, and user b should get the 30 items they havent rated.
My rating table has a compound unique key, so if they rate item_id = 1 once, and rate it again, it just updates the rating value, it doesnt make a new row. One row is inserted for every item that is rated by a user.
This feels like it should be easy and it probably is, but I am stuck.
I'm assuming every user has to rate every item. If so, then you can do this with not exists:
select *
from item i
where not exists (
select 1
from rating r
where i.item_id = r.item_id and r.user_id = ?)
SQL Fiddle Demo
Give the fact you're using mysql, a left join / null check would probably be faster:
select i.*
from item i
left join rating r on i.item_id = r.item_id and r.user_id = ?
where r.user_id is null
More Fiddle
http://explainextended.com/2009/09/18/not-in-vs-not-exists-vs-left-join-is-null-mysql/
Just use an anti-join pattern, like this:
SELECT i.*
FROM item i
LEFT
JOIN rating r
ON r.item_id = i.item_id
AND r.user_id = USER_ID_OF_THE_USER
WHERE r.user_id IS NULL
The outer join returns all rows from item, along with rating by the user. If there is no related row from the rating table, then the values of the columns from rating will be NULL. So all we need is to add a WHERE clause to filter out the rows that had a match.
You can use this query:
SELECT * FROM item i
WHERE NOT EXISTS (
SELECT * FROM rating r
WHERE r.item_id = i.item_id
AND r.user_id = USER_ID_OF_THE_USER);
This will return you those items for which there is no rating of user USER_ID_OF_THE_USER.

Trying to join table on the row with a max value

I feel like I need to use a sub-query but I'm having trouble getting the query to work right
As a scenario, I have an auction_item that has bids on it, there are three bids.
I want to get just the user information for the highest bid on that item.
The info I have is the item_id, so I need to look up bids on that item id, and join the user table on the user_id of the bid row with the highest bid for that item.
SELECT user.* FROM text2bid_users AS user
INNER JOIN (SELECT user_id,MAX(bid_amt) FROM text2bid_bids
WHERE item_id = 11479) AS bid
ON user.user_id = bid.user_id
Problem is it's returning the first row from bids instead of the one with the max bid_amt
Hoping someone can point me in the right direction here
Try the following Query:
select user.* from text2bid_users as user
where user.user_id =(select user_id from text2bid_bids
where item_id = 11479
order by bid_amt desc limit 1);

Correct SELECT/JOIN for "has login user liked"

We're having the following tables:
User (id, name)
Item (id, title, text)
Like (id, itemId, userId)
The Like table stores a has-and-belongs-to-many relationship between Item and User.
What is the most efficient way to select a list from Item and see if the "logged in" user has "liked" that particular Item?
SELECT * FROM Item ORDER BY published DESC LIMIT 10
(+ check if each Item has been liked by known user id, e.g. '123')
Is this done best with a sub-select, join or two individual queries?
The most straight forward way ( if i have understood the question right ) is
select * from Like where itemId = ? and userId = ?;
Assuming you have the itemId you want to check and userId from the session.
It will be good to have a composite index on table Like and columns itemId,userId to have fast returning queries.
Joining is more effective than subselect, database engines are well optimized for joining tables.
The query (I have changed the name of table 'like', because it is a keyword in SQL)
SELECT COUNT(*)
FROM User u
JOIN Likes l ON u.id = l.userID
JOIN Item i ON l.itemid = i.id
WHERE i.title = 'item' AND u.name = 'user'
If the result is greater than 0, the user liked the item.
EDIT:
If you want to display the 10 newest items, and see if the user liked them or no, you can use this query:
SELECT i.id, i.title, IF(l.userID IS NULL, 0, 1) AS Liked
FROM item i
LEFT JOIN Likes l ON i.id = l.itemID AND l.userID = ?
ORDER BY i.published DESC LIMIT 10
It is important to use LEFT JOIN, otherwise you will not get the rows the user has not liked.

Attempting to Join 3 tables in MySQL

I have three tables that are joined. I almost have the solution but there seems to be one small problem going on here. Here is statement:
SELECT items.item,
COUNT(ratings.item_id) AS total,
COUNT(comments.item_id) AS comments,
AVG(ratings.rating) AS rate
FROM `items`
LEFT JOIN ratings ON (ratings.item_id = items.items_id)
LEFT JOIN comments ON (comments.item_id = items.items_id)
WHERE items.cat_id = '{$cat_id}' AND items.spam < 5
GROUP BY items_id ORDER BY TRIM(LEADING 'The ' FROM items.item) ASC;");
I have a table called items, each item has an id called items_id (notice it's plural). I have a table of individual user comments for each item, and one for ratings for each item. (The last two have a corresponding column called 'item_id').
I simply want to count comments and ratings total (per item) separately. With the way my SQL statement is above, they are a total.
note, total is the total of ratings. It's a bad naming scheme I need to fix!
UPDATE: 'total' seems to count ok, but when I add a comment to 'comments' table, the COUNT function affects both 'comments' and 'total' and seems to equal the combined output.
Problem is you're counting results of all 3 tables joined. Try:
SELECT i.item,
r.ratetotal AS total,
c.commtotal AS comments,
r.rateav AS rate
FROM items AS i
LEFT JOIN
(SELECT item_id,
COUNT(item_id) AS ratetotal,
AVG(rating) AS rateav
FROM ratings GROUP BY item_id) AS r
ON r.item_id = i.items_id
LEFT JOIN
(SELECT item_id,
COUNT(item_id) AS commtotal
FROM comments GROUP BY item_id) AS c
ON c.item_id = i.items_id
WHERE i.cat_id = '{$cat_id}' AND i.spam < 5
ORDER BY TRIM(LEADING 'The ' FROM i.item) ASC;");
In this query, we make the subqueries do the counting properly, then send that value to the main query and filter the results.
I'm guessing this is a cardinality issue. Try COUNT(distinct comments.item_id)

How can I get an even distribution using WHERE id IN(1,2,3,4)

I have a query that is pulling users who liked a specific object from a users table. Ratings are stored in a ratings table. The query I have come up with so far looks like this:
SELECT user.id, user.name, user.image
FROM users
LEFT JOIN ratings ON ratings.userid = user.id
WHERE rating.rating > 0
AND rating.objectId IN (1,2,3,4)
I want to be able to put a LIMIT on this query, to avoid returning all the results, when I only need 3 or so results for each ID. If I just put a LIMIT 12 for example, I might get 8 records with one id, and 1 or 2 each for the others - i.e. an uneven distribution across the IDs.
Is there a way to write this query so as to guarantee that (assuming an object has been "liked" at least three times), I get three results for each of the ids in the list?
By setting the row number whit variables, and then filter that result to show only row 1-3 should work
SET #last_objectId = 0;
SET #count_objectId = 0;
SELECT id, name, image FROM (
SELECT
user.id,
user.name,
user.image,
#count_objectId := IF(#last_objectId = rating.objectId, #count_objectId, 0) + 1 AS rating_row_number,
#last_objectId := rating.objectId
FROM users
LEFT JOIN ratings ON (ratings.userid = user.id)
WHERE
rating.rating > 0 AND
rating.objectId IN (1,2,3,4)
ORDER BY rating.objectId
) AS subquery WHERE rating_row_number <= 3;