How to reduce JOINs in subquery? - mysql

Here is my current query:
SELECT q.type,
q.author_id author,
q.deleted,
q.deleter_id,
COUNT(DISTINCT a.id) answers_num,
COUNT(DISTINCT v.id) votes_num,
FROM qanda q
LEFT JOIN qanda a ON q.id = a.related
LEFT JOIN votes v on q.id = v.post_id
WHERE q.id = ?
It works well. Just I need to add one more thing (which returns a boolean by EXISTS) in the SELECT statement. This:
EXISTS (SELECT a.id, sum(vot.value) total_votes
FROM qanda q
LEFT JOIN qanda a on q.id = a.related
LEFT JOIN votes vot ON a.id = vot.post_id
WHERE q.type = 0 -- this is a question
AND a.related = 1315 -- getting answers
GROUP BY a.id
HAVING total_votes
ORDER BY total_votes DESC
LIMIT1
) as HasAUpvotedAnswer
Ok, it works well too. But I'm worried about the performance. As you can see, the JOINs of those two queries above (which will be combined finally) are similar. How can make them more optimal?
In other word, how can I combine those two above queries in the most optimal case?

Looking to your code could be that y can use a single query using conditional for sum
SELECT q.type,
q.author_id author,
q.deleted,
q.deleter_id,
COUNT(DISTINCT a.id) answers_num,
COUNT(DISTINCT v.id) votes_num,
IF (SUM(
CASE WHEN q.type = 0 AND a.related = 1315
THEN v.value else 0 END) > 0,1,0)
HasAUpvotedAnswer
FROM qanda q
LEFT JOIN qanda a ON q.id = a.related
LEFT JOIN votes v on q.id = v.post_id
WHERE q.id = ?

Related

can't write the correct syntax for the next query

There are 3 entities besides User : Answer, Question and Votes_on_Answers which contains users votes of answers
Answer is associated with Question table by Many-To-One relationship
I need to write query that sums the amount of votes of answer and sort them but I have diffulty with writing the if statement
select a.id, a.question_id, a.user_id,
case
when sum(voa.vote) = null
then 0
else sum(voa.vote)
end as i
from answer as a left join votes_on_answers as voa on a.id = voa.answer_id
where a.question_id = 1
group by a.id order by i desc;
This query returns:
id question_id i
1 1 3
2 1 -2
3 1 null
In this case you could avoid the case .. when and use ifnul()
select a.id
, a.question_id
, sum(ifnull(voa.vote,0)) as i
from answer as a
left join votes_on_answers as voa on a.id = voa.answer_id
where a.question_id = 1
group by a.id
order by i desc;
or using case
select a.id
, a.question_id
, sum(case when voa.vote is null then 0 else voa.vote end)) as i
from answer as a
left join votes_on_answers as voa on a.id = voa.answer_id
where a.question_id = 1
group by a.id
order by i desc;
You might find that a correlated subquery is simpler and faster . . . and you don't have to worry about null values:
select a.id, a.question_id, a.user_id,
(select coalesce(sum(v.votes), 0)
from votes_on_answers voa
where a.id = voa.answer_id
) as i
from answer a
where a.question_id = 1
order by i desc;
For performance you want an index on votes_on_answers(answer_id).
Avoiding the outer group by is a win from the performance perspective. When there are no matches in voa, the count(*) returns 0.

mysql subquery select with field from query

i have a little problem with a subquery in sql.
her the query
SELECT st.title, count(q.id) as question_count, max(a.id) as maxid,
sum(case when a.answer is not null then 1 else 0 end) as answer_count, g.user_id as game_user_id,
a.game_id as a_game_id, a.modified as finished, (select modified as finished from answers a where a.id = g.maxid limit 1) as subquery
FROM games g
left join answers a on(a.game_id = g.id)
left join questions q on(a.question_id = q.id)
left join sessions s on(s.id = q.session_id)
left join sessiontypes st on(st.id = s.sessiontype_id)
WHERE g.user_id = 21
group by g.id
having(question_count = answer_count)
order by finished DESC;
i want that the subquery returns the modified value from answers where the id is the highest grouped by game.
so i tried to select max(id) as maxid... and the use max id in the subquery. where a.id = maxid. nice try, but dont work.
mysql error is this one: Reference 'maxid' not supported (reference to group function)
can anybody give a hint how to solve that?
Join on a subquery which returns the MAX(answers.id) grouped by the answers.game_id.
Then use that maxid to join on the answers table to get the row of the corresponding answers.id.
Not sure, how your result is supposed to look, in your select I removed a.modified AS finished, and replaced it with the modified column of the row with maxid.
SELECT
st.title,
count(q.id) AS question_count,
sum(
CASE
WHEN a.answer IS NOT NULL THEN
1
ELSE
0
END
) AS answer_count,
g.user_id AS game_user_id,
a.maxid,
a.game_id AS a_game_id,
modifiedAnswer.modified AS finished,
FROM
games g
LEFT JOIN (SELECT MAX(answers.id) AS maxid, game_id FROM answers GROUP BY answers.game_id) AS a ON (a.game_id = g.id)
LEFT JOIN answers AS modifiedAnswer ON modifiedAnswer.id = a.maxid
LEFT JOIN questions q ON (a.question_id = q.id)
LEFT JOIN sessions s ON (s.id = q.session_id)
LEFT JOIN sessiontypes st ON (st.id = s.sessiontype_id)
WHERE
g.user_id = 21
GROUP BY
g.id
HAVING
(
question_count = answer_count
)
ORDER BY
finished DESC;

Can't Count results from Joined Table mySQL

i followed 3 others SO answers, and they say it works.. but here not... here is my problem, i have this QUERY:
SELECT
q.*
FROM relacionamento AS r
INNER JOIN questoes AS q ON r.idquestao = q.id
WHERE tabela = 'disciplina'
GROUP BY q.id
This is there result of query:
Until here, it was perfect.. now i need know how much rows returned... i changed the query to this:
SELECT
COUNT(q.*)
FROM relacionamento AS r
INNER JOIN questoes AS q ON r.idquestao = q.id
WHERE tabela = 'disciplina'
GROUP BY q.id
And got this error:
Why i cant count how much rows did i get?
UPDATE:
I did try do this query also:
SELECT
COUNT(*)
FROM relacionamento AS r
INNER JOIN questoes AS q ON r.idquestao = q.id
WHERE tabela = 'disciplina'
GROUP BY q.id
And get this as result:
You can change it like this:
SELECT COUNT(*) FROM (
SELECT
q.*
FROM relacionamento AS r
INNER JOIN questoes AS q ON r.idquestao = q.id
WHERE tabela = 'disciplina'
GROUP BY q.id
)
Your query is probably better written as:
SELECT q.*
FROM questoes q
WHERE EXISTS (SELECT 1
FROM relacionamento r
WHERE r.idquestao = q.id AND r.tabela = 'disciplina'
);
This should be more efficient than your query, if you have an index on relacionamento(idquestao, tablea) (which you would want anyway for the join). And, it doesn't use select * with group by, which just generally looks wrong.
Then, to get the count, you can just do count(*):
SELECT COUNT(*)
FROM questoes q
WHERE EXISTS (SELECT 1
FROM relacionamento r
WHERE r.idquestao = q.id AND r.tabela = 'disciplina'
);

How Do I Count Number of Males and Females

I have the query as follows:
select d.question, b.response, count(b.response)
from sl_flow a
INNER JOIN ul_attempt_responses b on a.question_id = b.question_id and b.type = 1
INNER JOIN us_attempts c on c.id = b.attempt_id
INNER JOIN ss_questions d on d.id = a.question_id
where a.status = 1
and a.ckm_question = 0
and b.response
group by a.question_id, b.response
order by a.question_order asc
The above gives me the questions that I have in the DB which are active and their responses and counts.
However I need a query that will give me number of males and females that answered each of the question. Therefore, I have another query that gives me the number of males and females which is:
SELECT
concat(a.response, 's') as gender,
count(a.response) as count
FROM
ul_attempt_responses a
INNER JOIN us_attempts b ON a.attempt_id = b.id
WHERE
a.question_id = 6 and a.type = 0 AND trim(a.response) != ''
GROUP by a.response;
I am not sure, how to do so. For the gender, the question_id is 6 and type on the a table has to be 0 (the a table is ul_attempt_responses).
This is what I got so far. However, it appears that the results I am getting may not be consistent:
SELECT
gender.question
,coalesce(sum(case final.Response when 'male' then gender.total end),0) as 'Males'
,coalesce(sum(case final.Response when 'female' then gender.total end),0) as 'Females'
FROM
(SELECT
stats . *,
(CASE concat(stats.userid, stats.QuestionID, stats.type)
WHEN #curType THEN #curRow:=coalesce(#curRow, 0) + 1
ELSE #curROw:=1
AND #curType:=concat(stats.userid, stats.QuestionID, stats.type)
END) + 1 AS rank
FROM
(SELECT
d.question as Question,
a.user_id AS UserID,
c.question_id AS QuestionID,
c.type as Type,
c.response AS Response,
a.campaign_id as campaign_id
FROM
us_attempts a
INNER JOIN ul_attempt_responses c ON a.id = c.attempt_id
RIGHT OUTER JOIN ss_profile_questions d ON c.question_id = d.id AND c.type = 0
LEFT OUTER JOIN sl_profile_flow f ON c.question_id = f.profile_question_id
RIGHT OUTER JOIN us_users g ON g.id = a.user_id
WHERE
f.status = 1
ORDER BY a.user_id , c.question_id , c.type , a.id desc) stats) final
INNER JOIN
(select b.user_id, c.question as question, count(1) as total
from ul_attempt_responses a
INNER JOIN us_attempts b on a.attempt_id = b.id
INNER JOIN ss_questions c on a.question_id = c.id and a.type = 1
group by b.user_id, c.id) gender on final.UserID = gender.user_id
where
final.rank = 2 and final.QuestionID = 6 and final.campaign_id = 3
group by gender.question;
Is there a way I can reduce the above query, or is there a better optimized way?
You could use a combination of sum and case/if to get the counts. Given your full table structures are not clear, I am assuming you have a table (or an SQL that can produce a set of rows) with the following fields:
response_id
question_id
response
Then an SQL such as
select question_id
, response
, sum(if(gender='M',1,0)) as males
, sum(if(gender='F',1,0)) as females
from (select q.question_id
, q.response
, g.response as gender from answers as q
left join answers as g on q.response_id=g.response_id and g.question_id=6
where q.question_id!=6) as t
group by question_id, response
would give you a result of the form
question_id,response,males,females
1,A,1,2
1,B,1,0
2,A,0,1
2,B,1,1
2,C,1,0
To explain the code, the sub query produces a set of rows for each response with the question mapped with the gender question's response. In the main select statement the if statement produces a 1 for the specific gender in the proper column and summing them up gives you how many of that specific gender responded to that question.
EDIT
As suggested by #Strawberry, the shorter version would be
select q.question_id
, q.response_id
, sum(g.response='M') as males
, sum(g.response='F') as females
from answers as q
left
join test as g
on q.response_id = g.response_id
and g.question_id = 6
where q.question_id != 6
group
by q.question_id
, q.response

mysql: multiple join problem

Im trying to select a table with multiple joins, one for the number of comments using COUNT and one to select the total vote value using SUM, the problem is that the two joins affect each other, instead of showing:
3 votes 2 comments
I get 3 * 2 = 6 votes and 2 * 3 comments
This is the query I'm using:
SELECT t.*, COUNT(c.id) as comments, COALESCE(SUM(v.vote), 0) as votes
FROM (topics t)
LEFT JOIN comments c ON c.topic_id = t.id
LEFT JOIN votes v ON v.topic_id = t.id
WHERE t.id = 9
What you're doing is an SQL antipattern that I call Goldberg Machine. Why make the problem so much harder by forcing it to be done in a single SQL query?
Here is how I would really solve this problem:
SELECT t.*, COUNT(c.id) as comments
FROM topics t
LEFT JOIN comments c ON c.topic_id = t.id
WHERE t.id = 9;
SELECT t.*, SUM(v.vote) as votes
FROM topics t
LEFT JOIN votes v ON v.topic_id = t.id
WHERE t.id = 9;
As you have found, combining these two into one query results in a Cartesian product. There may be clever and subtle ways to force it to give you the correct answer in one query, but what happens when you need a third statistic? It's much simpler to do it in two queries.
SELECT t.*, COUNT(c.id) as comments, COALESCE(SUM(v.vote), 0) as votes
FROM (topics t)
LEFT JOIN comments c ON c.topic_id = t.id
LEFT JOIN votes v ON v.topic_id = t.id
WHERE t.id = 9
GROUP BY t.id
or perhaps
SELECT `topics`.*,
(
SELECT COUNT(*)
FROM `comments`
WHERE `topic_id` = `topics`.`id`
) AS `num_comments`,
(
SELECT IFNULL(SUM(`vote`), 0)
FROM `votes`
WHERE `topic_id` = `topics`.`id`
) AS `vote_total`
FROM `topics`
WHERE `id` = 9
SELECT t.*, COUNT(DISTINCT c.id) as comments, COALESCE(SUM(v.vote), 0) as votes
FROM (topics t)
LEFT JOIN comments c ON c.topic_id = t.id
LEFT JOIN votes v ON v.topic_id = t.id
WHERE t.id = 9