简体   繁体   中英

Order by the sum of the count of two fields

Ok, I've got an interesting problem here. I have two tables, tips and votes. Tips looks like this:

tip_id, challenge_id, user_id, ip_address, tip_date, tip_text

Votes looks like this:

tip_id, ip_address, vote_date, vote

Users can post tips on a challenge, then other users can vote on the tip with a thumb up or down. The vote field in votes is a bool, 0 represents a thumb down, 1 is a thumb up. A challenge can have any number of tips.

I've been racking my brain and I just can't get this to work. I need a query that can get the number of up and down votes for a specific tip, the difference of those two numbers (so if there are 2 up votes and 1 down vote the difference would be 1) as a rating, and also the tip data from the tips table, then sort it with the highest rating first. Assume that we have two tips, a and b:

a: 3 up, 2 down (diff: 1) b: 4 up, 1 down (diff: 3)

In this case b would come first since it has a higher rating than a, a larger difference between the number of up and down votes.

So for example, let's say that I have a tip with id 1 which has 4 up votes and 2 down votes, which means a difference of 2, and this tip is linked to challenge id 5, my return might look like this:

up | down | rating | tip_id | tip_date | user_id | tip_text
------------------------------------------------------------
3  | 2    | 1      | 1      | 12345678 | 1       | "this is a tip"

Now this I've actually been able to do, but where I'm having problems is getting multiple rows if there are multiple tips for a challenge. Let's say that challenge id 5 has 2 tips, then I should get a result like this:

up | down | rating | tip_id | tip_date | user_id | tip_text
------------------------------------------------------------
3  | 2    | 1      | 1      | 12345678 | 1       | "this is a tip"
4  | 1    | 3      | 1      | 87654321 | 2       | "this is also a tip"

With each tip getting it's own row in the result. What's happening right now is I'm getting one row with the up and down values being the total count of up and down votes for that challenge, not for the tip. So using the second example above, instead of two separate rows I would get one row with up having a value of 7 and down being 3, the total number of up and down votes for all tips for that challenge.

I'm sure this is going to involve joining and ordering by and grouping by, but I don't care how complicated the query is, I just don't want to do it in PHP. Ultimately I want to feed the query a challenge ID and have it get X rows, where X is the number of tips for that challenge ID, and where each row would contain the counts for just that tip id.

Any help will be appreciated, as I'm running out of hair to pull out. Unfortunately this is the defining point of my project, higher-rated tips need to appear higher in the list.

Edit: Here's my current, almost working query. I made a few adjustments to it after a good night's sleep and got it mostly working, but there's still an issue that I can't explain:

SELECT
  COUNT(CASE WHEN V.vote = 1 THEN 'up' ELSE NULL END) AS up,
  COUNT(CASE WHEN V.vote = 0 THEN 'down' ELSE NULL END) AS down,
  (COUNT(CASE WHEN V.vote = 1 THEN 'up' ELSE NULL END)-COUNT(CASE WHEN V.vote = 0 THEN 'down' ELSE NULL END)) AS rating,
  T.*
  FROM votes V, tips T
  WHERE T.challenge_id = 10
  GROUP BY V.tip_id
  ORDER BY rating DESC

This gets everything I need, all the tips and the votes for a specific challenge, with a new row for each tip (instead of the aggregate count, like I was getting before). The problem is, my up and down votes, and also the rating, is twice what it should be. So if I have a tip which has 3 up votes and 1 down vote, and so a rating of 2, this query will show that I have 6 up votes and 2 down votes for a rating of 4. I suppose I could simply divide the number by two with PHP, but that doesn't fix the issue, it's just a bandaid. Can anyone see why I'm getting twice the number I should be getting? I'm gonna keep messing with it, maybe try a LEFT JOIN, hopefully I figure it out, but it'll be nice to have a few more pairs of eyes.

How about something like this:

SELECT v.up, v.down, (v.up - v.down) AS rating, t.tip_id, t.tip_date, t.user_id, t.tip_text
FROM tips AS t
INNER JOIN votes AS v
ON t.tip_id = v.tip_id
WHERE challenge_id=1
ORDER BY rating DESC;

Then you can set the WHERE clause's challenge_id value in your PHP before passing it to mysql. I think this should give you what you're after, although since I've used an INNER JOIN you need to make sure that every row in "tips" has a row in "votes" or it'll be ignored.

EDIT:

Based on the new info (sorry I didn't read the question properly before, hope I've got it right this time!):

 SELECT
  COUNT(CASE WHEN V.vote = 1 THEN 'up' ELSE NULL END) AS up,
  COUNT(CASE WHEN V.vote = 0 THEN 'down' ELSE NULL END) AS down,
  (COUNT(CASE WHEN V.vote = 1 THEN 'up' ELSE NULL END)-COUNT(CASE WHEN V.vote = 0 THEN 'down' ELSE NULL END)) AS rating,
  T.*
  FROM votes V
  INNER JOIN tips AS T
  ON T.tip_id = V.tip_id
  WHERE T.challenge_id = 1
  GROUP BY V.tip_id
  ORDER BY rating DESC;

I've just tested this in SQLite3 (on simplified tables) and it seems to work ok. The data I used is:

sqlite> select * from votes
   ...> ;
tip_id      vote      
----------  ----------
2           1         
2           1         
2           1         
2           0         
2           0         
3           0         
3           0         
3           0         
3           1         
4           0         
4           0         
4           1         
4           1         
4           1         
4           1    

Which gives the result:

up          down        rating      tip_id      challenge_id  user_id   
----------  ----------  ----------  ----------  ------------  ----------
4           2           2           4           1             4         
3           2           1           2           1             2         
1           3           -2          3           1             3         

Ok, I ended up altering the query that ianhales provided and got what I wanted. With his updated wuery I was getting the proper counts, but because of the INNER JOIN, I wasn't getting tips that had no votes. My changes were simple, but they work perfectly:

SELECT
  COUNT(CASE WHEN V.vote = 1 THEN 'up' ELSE NULL END) AS up,
  COUNT(CASE WHEN V.vote = 0 THEN 'down' ELSE NULL END) AS down,
  (COUNT(CASE WHEN V.vote = 1 THEN 'up' ELSE NULL END)-COUNT(CASE WHEN V.vote = 0 THEN 'down' ELSE NULL END)) AS rating,
  T.*
  FROM tips T
  LEFT JOIN votes V
  ON T.tip_id = V.tip_id
  WHERE T.challenge_id = 10
  GROUP BY T.tip_id
  ORDER BY rating DESC

This gets all the tips, even if they have no votes, and sorts them by rating in descending order, so that higher-rated tips appear first in the list.

You've been fantastic, and you can expect your name to appear in my project's credits. Thank you SO much.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM