MySQL summing points from various tables - mysql

I am trying to create an SQL statement that will essentially calculate points due to a variety of conditions. For example, I have tables: users, followers, pictures
followers and pictures have a user_id, and users has a column called "about"
I want to do some sort of select where I get the sum of:
1) If users.about is not empty, that is worth 100 points
2) If the user owns at least one pictures.type = "something", that is worth 50 points
3) For every follower that is associated with that user, give 20 points each
So this SQL statement should select the sum of those three calculations. After, I want to use this SQL to create a view that essentially lists the user_id and its associated score.
I have tried various permutations but nothing is working. How would I do this? Thanks!

Here is one way:
SELECT sum(points) FROM (
SELECT 100 points FROM users WHERE about <> '' AND user_id = ####
UNION
SELECT 50 points FROM users WHERE EXISTS (SELECT * FROM pictures WHERE user_id = ####)
UNION
SELECT 20 points FROM followers WHERE user_id = ####
)
Here is a second way:
SELECT IF(about <> '', 100, 0)
+ IF(EXISTS (SELECT * FROM pictures WHERE pictures.user_id = users.user_id), 50, 0)
+ (SELECT SUM(20) FROM followers WHERE followers.user_id = users.user_id) points
FROM users
WHERE user_id = ####

Related

Group by with join and subquery cannot see parent table alias

I am building a question / answer panel.
Tables:
users - typical with id, name, etc...,
Replies - user's replies,
Ratings - another user's ratings (1 or -1) related to a reply, every rating got a row
I need to get one row per user with related table having only 1 row for each user who rated his answer once. He got 20 points for each one. I also need when the rater sent 2 or more ratings for 60 points (not included in example) , but if I have solution I can extend it.
Problem with the following query: subquery cant see "user" alias, it says user.id is not found.
Thanks in advance!
SELECT user.*, (
SELECT COUNT(*) * 20
FROM (
SELECT SUM(rating_value) AS rv
FROM Ratings
LEFT JOIN Replies ON Replies.id = Ratings.replyId
WHERE rating_value > 0
AND Replies.userId = user.id
GROUP BY Ratings.userId
HAVING (rv = 1)
) ss
) AS points
FROM users user

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;

MySQL Join Query - joining tables into themselves many times

I have 4 queries I need to excecute in order to suggest items to users based on items they've already expressed an interest in:
Select 5 random items the user already likes
SELECT item_id
FROM user_items
WHERE user_id = :user_person
ORDER BY RAND()
LIMIT 5
Select 50 people who like the same items
SELECT user_id
FROM user_items
WHERE user_id != :user_person
AND item_id = :selected_item_list
LIMIT 50
SELECT all items that the original user likes
SELECT item_id
FROM user_items
WHERE user_id = :user_person
SELECT 5 items the user doesn't already like to suggest to the user
SELECT item_id
FROM user_items
WHERE user_id = :user_id_list
AND item_id != :item_id_list
LIMIT 5
What I would like to know is how would I excecute this as one query?
There are a few reasons for me wanting to do this:
at the moment, I have to excecute the 'select 50 people' query 5 times and pick the top 50 people from it
I then have to excecute the 'select 5 items' query 50 * (number of items initial user likes)
Once the query has been excecuted, I intend to store the query result in a cookie (if the user gives consent to me using cookies, otherwise they don't get the 'item suggestion' at all) with the key being a hash of the query, meaning it will only fire once a day / once a week (that's why I return 5 suggestions and select a key at random to display)
Basically, if anybody knows how to write these queries as one query, could you show me and explain what is going on in the query?
This will select all items you need:
SELECT DISTINCT ui_items.item_id
FROM user_items AS ui_own
JOIN user_items AS ui_others ON ui_own.item_id = ui_others.item_id
JOIN user_items AS ui_items ON ui_others.user_id = ui_items.user_id
WHERE ui_own.user_id = :user_person
AND ui_others.user_id <> :user_person
AND ui_items.item_id <> ui_own.item_id
(please, check if result are exact same with you version - I tested it on a very small fake data set)
Next you just cache this list and show 5 items randomly, because ORDER BY RAND() is VERY inefficient (non-deterministic query => no caching)
EDIT: Added the DISTINCT to not show duplicate rows.
You can also return a most popular suggestions in descending popularity order by removing DISTINCT and adding the following code to the end of the query:
GROUP BY ui_items.item_id
ORDER BY COUNT(*) DESC
LIMIT 20
To the end of the query which will return the 20 most popular items.

Combining 2 tables- Subquery returns more than 1 row

When i join 2 tables i am getting an error
select * from picture where id=(SELECT id FROM user where age= 40 )
#1242 - Subquery returns more than 1 row
What is the best way to join these 2 large data tables? I want to have all the results
Replace = by IN
select * from picture where id in (select id from user where age = 40)
Note, this is only "semi-joining" the two tables, i.e. you will only get pictures as results, not a picture-user relation. If you're actually trying to do the latter, then you'll have to inner join the two tables:
select * from picture p
join user u on p.id = u.id -- are you sure about this join condition?
where u.age = 40
In the where clause, when you assign a value to id, SQL expects a single value. The problem is that the second query SELECT id FROM user where age= 40 returns more than one row.
What you can do is
select *
from picture
where id in
(select id from user where age = 40)
or
select *
from picture p, user u
where p.id = u.id
and u.age = 40
Try using 'IN' instead of '='
select * from picture where id IN (SELECT id FROM user where age= 40 )
From what Im guessing out of what Im seeing,
You have a "picture" table and a "user" table.
Table names should be plurals. Make it "pictures" and "users".
Define your relationships first. In your case, one user has_many pictures and one picture belongs_to one user.(One picture [on a peaceful world] wont belong to many users).
In your pictures table, have an "user_id" column and populate that column with the appropriate user_ids.
Now,
You want all the pictures of users whose ages are exactly 40.
You can do a,
SELECT *
FROM pictures
WHERE user_id IN (SELECT user_id
FROM users
WHERE user_age = 40)
Was that your requirement?. Do I make any sense?

MySQL nested query counting

A bit of background info; this is an application that allows users to created challenges and then vote on those challenges (bog standard userX-vs-userY type application).
The end goal here is to get a list of 5 users sorted by the number of challenges they have won, to create a type of leaderboard. A challenge is won by a user if it's status = expired and the user has > 50 votes for that challenge (challenges expire after 100 votes in total).
I'll simplify things a bit here, but essentially there are three tables:
users
id
username
...
challenges
id
issued_to
issued_by
status
challenges_votes
id
challenge_id
user_id
voted_for
So far I have an inner query which looks like:
SELECT `challenges`.`id`
FROM `challenges_votes`
LEFT JOIN `challenges` ON (`challenges`.`id` = `challenges_votes`.`challenge_id`)
WHERE `voted_for` = 1
WHERE `challenges`.`status` = 'expired'
GROUP BY `challenges`.`id`
HAVING COUNT(`challenges_votes`.`id`) > 50
Which in this example would return challenge IDs that have expired and where the user with ID 1 has > 50 votes for.
What I need to do is count the number of rows returned here, apply it to each user from the users table, order this by the number of rows returned and limit it to 5.
To this end I have the following query:
SELECT `users`.`id`, `users`.`username`, COUNT(*) AS challenges_won
FROM (
SELECT `challenges`.`id`
FROM `challenges_votes`
LEFT JOIN `challenges` ON (`challenges`.`id` = `challenges_votes`.`challenge_id`)
WHERE `voted_for` = 1
GROUP BY `challenges`.`id`
HAVING COUNT(`challenges_votes`.`id`) > 0
) AS challenges_won, `users`
GROUP BY `users`.`id`
ORDER BY challenges_won
LIMIT 5
Which is kinda getting there but of course the voted_for user ID here is always 1. Is this even the right way to go about this type of query? Can anyone shed any light on how I should be doing it?
Thanks!
I guess the following script will solve your problem:
-- get the number of chalenges won by each user and return top 5
SELECT usr.id, usr.username, COUNT(*) AS challenges_won
FROM users usr
JOIN (
SELECT vot.challenge_id, vot.voted_for
FROM challenges_votes vot
WHERE vot.challenge_id IN ( -- is this check really necessary?
SELECT cha.id -- if any user is voted 51 he wins, so
FROM challenges cha -- why wait another 49 votes that won't
WHERE cha.status = 'expired' -- change the result?
) --
GROUP BY vot.challenge_id
HAVING COUNT(*) > 50
) aux ON (aux.voted_for = usr.id)
GROUP BY usr.id, usr.username
ORDER BY achallenges_won DESC LIMIT 5;
Please allow me to propose a small consideration to the condition to close a challenge: if any user wins after 51 votes, why is it necessary to wait another 49 votes that will not change the result? If this constraint can be dropped, you won't have to check challenges table and this can improve the query performance -- but, it can worsen too, you can only tell after testing with your actual database.