I am making a QA site, not unlike SO. I would like to list all of the questions that are in my database, count up the number of votes and the number of answers for each question, and then find out whether a particular user has already voted for each question. In the same query.
Let me back up and give a bit of context. I have the following SQL tables:
qa_questions => questionid title text
qa_votes => voteid questionid userid
qa_answers => answerid text questionid userid
The first is a table of questions, which have numerical ids, titles, and text. Users can vote for questions. There are only upvotes. Each user can vote for as many questions as (s)he likes, but only once for each question. Whenever a user votes for a question, an entry is added to the votes table, recording the quesitonid and userid. The answers table stores a numerical id and text for each answer, and also references the question that is being answered and the user who submitted the answer.
To get a list of all questions, counting the number of votes for each question, and showing whether a specific user (e.g. user #1) has voted for the question, I do:
SELECT qa_questions.questionid AS qid, title, text, COUNT( voteid ) AS num_votes,
MAX(CASE WHEN qa_votes.userid=1 THEN 1 ELSE 0 END ) AS voted_for
FROM qa_questions
LEFT JOIN qa_votes ON qa_questions.questionid = qa_votes.questionid
GROUP BY qa_questions.questionid
This works. An example of what this returns:
qid title text num_votes voted_for
1 What is 2 + 2? I want to know the answer to this math question! 2 1
(where the 1 in the voted_for column stands for TRUE, and indicates that the current user has voted for this question).
I can also find out how many answers each question has, by doing this:
SELECT qa_questions.questionid AS qid, COUNT( answerid ) AS num_answers
FROM qa_questions
LEFT JOIN qa_answers ON qa_questions.questionid = qa_answers.questionid
GROUP BY qa_questions.questionid
which returns:
qid num_answers
1 1
2 2
3 0
4 3
Okay, so far so good. Now I want to combine these two tables, so I simply to a join on qid:
SELECT t1.qid, title, TEXT, num_votes, voted_for, num_answers
FROM (
SELECT qa_questions.questionid AS qid, title, text, COUNT( voteid ) AS num_votes,
MAX(CASE WHEN qa_votes.userid=1 THEN 1 ELSE 0 END) AS voted_for
FROM qa_questions
LEFT JOIN qa_votes ON qa_questions.questionid = qa_votes.questionid
GROUP BY qa_questions.questionid
) AS t1
JOIN (
SELECT qa_questions.questionid AS qid, COUNT( answerid ) AS num_answers
FROM qa_questions
LEFT JOIN qa_answers ON qa_questions.questionid = qa_answers.questionid
GROUP BY qa_questions.questionid
) AS t2
ON t1.qid = t2.qid
Ummmm …. yuck. This works, but I can’t help feeling that it is much uglier than it needs to be … is there a way that I can simplify this?
You can simply “stack” these joins… This will be drastically more performant.