mysql subquery select with field from query - mysql

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;

Related

How to reduce JOINs in subquery?

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 = ?

Sql query within an inner join

i have this mysql statement :
SELECT ca.*, MAX(ca.id), v.*,a.submit_dt from callback_holding ca
inner join valuations v on v.Ref = ca.ref
inner join answer a on a.title = ca.ref
where v.Consultant = '$user' and ca.isholding = 2
GROUP BY ca.ref DESC order by ca.reccomendeddate asc
But the problem is if there is not an entry in "answer" then it doesn't show up in the list. What is the correct way to bring back everything and just "null" if there is nothing in the "answer" table?
Thanks
Your query has several problems. First, you are grouping by the ref column from the callback_holding table, but are selecting non aggregate columns not only from this table, but from other tables. To get around this, you should do the aggregation to find maximum IDs in callback_holding in a subquery, and then join it to the other tables.
Next, you mentioned that if no answer be found, you get back no records. This is the nature of an INNER JOIN, but if you switch the join to answer to use a LEFT JOIN, then no records up to that point in the query will be lost. Note that I used COALESCE(a.submit_dt, 'NA') to display NA in the event that this column from the answer table be NULL. If this column be datetime, then you should use a suitable default value, e.g. NOW().
SELECT ca.*,
v.*,
COALESCE(a.submit_dt, 'NA') AS submit_dt, -- display 'NA' if no answer
t.max_id
FROM callback_holding ca
INNER JOIN
(
SELECT ref, MAX(id) AS max_id
FROM callback_holding
GROUP BY ref
) t
ON t.ref = ca.ref AND
t.max_id = ca.id
INNER JOIN valuations v
ON v.Ref = ca.ref
LEFT JOIN answer a
ON a.title = ca.ref
WHERE v.Consultant = '$user' AND
ca.isholding = 2
ORDER BY ca.reccomendeddate
try with:
SELECT ca.*, MAX(ca.id), v.*,a.submit_dt from callback_holding ca
INNER join valuations v on v.Ref = ca.ref
LEFT join answer a on a.title = ca.ref
WHERE v.Consultant = '$user' and ca.isholding = 2
GROUP BY ca.ref DESC order by ca.reccomendeddate asc

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

My Odd SubSelect, Need a LEFT JOIN Improvement

Here is a sample SQL dump: https://gist.github.com/JREAM/99287d033320b2978728
I have a SELECT that grabs a bundle of users.
I then do a foreach loop to attach all the associated tree_processes to that user.
So I end up doing X Queries: users * tree.
Wouldn't it be much more efficient to fetch the two together?
I've thought about doing a LEFT JOIN Subselect, but I'm having a hard time getting it correct.
Below I've done a query to select the correct data in the SELECT, however I would have to do this for all 15 rows and it seems like a TERRIBLE waste of memory.
This is my dirty Ateempt:
-
SELECT
s.id,
s.firstname,
s.lastname,
s.email,
(
SELECT tp.id FROM tree_processes AS tp
JOIN tree AS t ON (
t.id = tp.tree_id
)
WHERE subscribers_id = s.id
ORDER BY tp.id DESC
LIMIT 1
) AS newest_tree_id,
#
# Don't want to have to do this below for every row
(
SELECT t.type FROM tree_processes AS tp
JOIN tree AS t ON (
t.id = tp.tree_id
)
WHERE subscribers_id = s.id
ORDER BY tp.id DESC
LIMIT 1
) AS tree_type
FROM subscribers AS s
INNER JOIN scenario_subscriptions AS ss ON (
ss.subscribers_id = s.id
)
WHERE ss.scenarios_id = 1
AND ss.completed != 1
AND ss.purchased_exit != 1
AND deleted != 1
GROUP BY s.id
LIMIT 0, 100
This is my LEFT JOIN attempt, but I am having trouble getting the SELECT values
SELECT
s.id,
s.firstname,
s.lastname,
s.email,
freshness.id,
# freshness.subscribers_id < -- Cant get multiples out of the LEFT join
FROM subscribers AS s
INNER JOIN scenario_subscriptions AS ss ON (
ss.subscribers_id = s.id
)
LEFT JOIN ( SELECT tp.id, tp.subscribers_id AS tp FROM tree_processes AS tp
JOIN tree AS t ON (
t.id = tp.tree_id
)
ORDER BY tp.id DESC
LIMIT 1 ) AS freshness
ON (
s.id = subscribers_id
)
WHERE ss.scenarios_id = 1
AND ss.completed != 1
AND ss.purchased_exit != 1
AND deleted != 1
GROUP BY s.id
LIMIT 0, 100
In the LEFT JOIN you are using 'freshness' as the table alias. This in you select you need to additionally state what column(s) you want from it. Since there is only one column (id) you need to add:
freshness.id
to the select clause.
Your ON clause of the left join looks pretty dodgy too. Maybe freshness.id = ss.subscribers_id?
Cheers -

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