I have 3 tables:
tb_user
tb_addquestion
tb_answer
The following queries return count of questions per user (query 1) and answers per user (query 2). I need to combine the results to a single output.
How can I achieve this with the help of a single query?
Count of questions per user (query1)
SELECT tb_addquestion.userid,
COUNT(*) AS count
FROM tb_addquestion
LEFT JOIN tb_user
ON tb_user.userid = tb_addquestion.userid
GROUP BY tb_addquestion.userid
HAVING count > 0 AND
count < 15
Answers per user (query2)
SELECT tb_answer.userid,
COUNT(*) AS count
FROM tb_answer
LEFT JOIN tb_user
ON tb_user.userid = tb_answer.userid
GROUP BY tb_answer.userid
HAVING count > 0 AND
count < 15
You could use UNION:
SELECT tb_addquestion.userid,
COUNT(*) AS count,
'questions' AS type
FROM tb_addquestion
LEFT JOIN tb_user
ON tb_user.userid = tb_addquestion.userid
GROUP BY tb_addquestion.userid
HAVING count > 0 AND
count < 15
UNION
SELECT tb_answer.userid,
COUNT(*) AS count,
'answers' AS type
FROM tb_answer
LEFT JOIN tb_user
ON tb_user.userid = tb_answer.userid
GROUP BY tb_answer.userid
HAVING count > 0 AND
count < 15
Typically we would perform the aggregations in separate subqueries, and then join the main table to each of those subqueries:
SELECT
u.userid,
COALESCE(aq.q_cnt, 0) AS q_cnt,
COALESCE(a.a_cnt, 0) AS a_cnt
FROM tb_user u
LEFT JOIN
(
SELECT userid, COUNT(*) AS q_cnt
FROM tb_addquestion
GROUP BY userid
HAVING q_cnt > 0 AND q_cnt < 15
) aq
ON aq.userid = u.userid
LEFT JOIN
(
SELECT userid, COUNT(*) AS a_cnt
FROM tb_answer
GROUP BY userid
HAVING a_cnt > 0 AND a_cnt < 15
) a
ON a.userid = u.userid
Related
I have a following statistics table:
id | date | user_id | price | additional_price
My query looks like this:
SELECT month, sum(price), sum(additional_price)
FROM statistics s
INNER JOIN users u on s.user_id = u.id
AND u.confirmed = 1
GROUP BY MONTH(date);
The problem is that there are rows in this table where price is equal to 0, but the additional_price is greater than 0, where users are not confirmed, or don't exist anymore. I want to count them into the output sum. Something like:
SELECT t.month, sum(t.price), sum(t.additional_price)
FROM (
(SELECT month, sum(price), sum(additional_price)
FROM statistics s
INNER JOIN users u on s.user_id = u.id
WHERE u.confirmed = 1
AND s.price > 0
GROUP BY MONTH(date))
UNION
(SELECT month, sum(price), sum(additional_price)
FROM statistics s
WHERE price = 0
GROUP BY MONTH(date))
) AS t
GROUP BY MONTH(t.date);
It will work, that's the result I want to achieve. But the query is slow, it's big, and unmaintainable. The main issue I'm having with this is that I can't use relations on a table like that:
$statistics = Statistic::join('users');
$statisticsForZeroPrice = Statistic::forZeroPrice();
$result = DB::query()->fromSub($statistics->union($statisticsForZeroPrice), 't')
->with('user') // will not work
->groupBy('t.date')
->get();
Is there a simpler solution to this issue?
I am guessing that you want a left join. From the limited information in your question, something like this:
SELECT month, sum(price), sum(additional_price)
FROM statistics s LEFT JOIN
users u
ON s.user_id = u.id AND u.confirmed = 1
GROUP BY MONTH(date);
It is unclear which table should be first in the left join.
SELECT month, sum(price), sum(additional_price)
FROM statistics s
LEFT JOIN users u on s.user_id = u.id
WHERE u.confirmed = 1 OR price = 0
GROUP BY MONTH(date);
What about this?
SELECT month, sum(price), sum(additional_price)
FROM statistics s
INNER JOIN users u on s.user_id = u.id
WHERE (u.confirmed = 1 AND s.price > 0) OR (u.confirmed = 0 AND s.price = 0)
GROUP BY MONTH(date);
Your 2 unioned queries differ only in the conditions of the WHERE clause.
Why not combine them like this:
SELECT MONTH(date) month, SUM(price), SUM(additional_price)
FROM statistics s INNER JOIN users u
ON s.user_id = u.id
WHERE (u.confirmed = 1 AND s.price > 0) OR (s.price = 0)
GROUP BY MONTH(date);
I want to create query that would show is the certain section is passed by certain account_id. It means all lessons of the section is checked = 1(true). I tried this solution:
SELECT count(*) as checked,(
SELECT count(*)
FROM lessons
WHERE section_id = 1
GROUP BY section_id
) as cnt, (checked = cnt) as passed
FROM lessons l
LEFT JOIN progress p ON l.id = p.lesson_id
WHERE l.section_id = 1 AND p.account_id = 3 AND checked = 1
GROUP BY l.section_id
But it returns error:
#1054 unknown 'cnt' column in field list.
What do I do wrong?
The unknown 'cnt' is generated from (checked=cnt) as passed. Try this
SELECT checked, cnt, (checked=cnt) as passed FROM (SELECT count(*) as checked,(
SELECT count(*)
FROM lessons
WHERE section_id = 1
GROUP BY section_id
) as cnt
FROM lessons l
LEFT JOIN progress p ON l.id = p.lesson_id
WHERE l.section_id = 1 AND p.account_id = 3 AND checked = 1
GROUP BY l.section_id) tblA
Note that you should move all conditions on the p.* columns to the ON clause. Otherwise you will convert the LEFT JOIN to an INNER JOIN and COUNT(*) will always be the same as in your subquery. However you don't even need that subquery - You can get the same value with COUNT(p.lesson_id) instead. It will ignore all rows with NULL.
SELECT
COUNT(*) as cnt,
COUNT(p.lesson_id) as checked,
COUNT(*) = COUNT(p.lesson_id) as passed
FROM lessons l
LEFT JOIN progress p
ON p.lesson_id = l.id
AND p.account_id = 3
AND p.checked = 1
WHERE l.section_id = 1
SELECT count(*) as checked, (SELECT count(*) FROM lessons
WHERE section_id = 1 GROUP BY section_id) as cnt,
((SELECT count(*) FROM lessons WHERE section_id = 1 GROUP BY section_id) = checked) pass
FROM lessons l LEFT JOIN progress p ON l.id = p.lesson_id
WHERE l.section_id = 1 AND p.account_id = 3 AND checked = 1 GROUP BY l.section_id
Thanks to #aynber. Though I'm not sure if this solution isn't too bulky.
I have a query that LEFT JOINS another table to group rows. I am trying to only select records from the first table "googleimage" where the user_id is a certain number. (user_id is a column in "googleimage" table.
SELECT g.*
FROM googleimage g
LEFT JOIN (SELECT image_id, COUNT(*) AS cnt
FROM googleimagefound WHERE status = 0
GROUP BY image_id) gf ON gf.image_id = g.id
ORDER BY COALESCE(cnt, 0) DESC");
I have tried adding a new ON statement beiside the
gf ON gf.image_id = g.id
I have also tried changing
SELECT g.*
FROM googleimage g
to
SELECT g.*
FROM googleimage WHERE user_id = 1 g
but none of them seem to work, any help will be appriciated
Try changing you query a bit like below
SELECT g.*
FROM googleimage g
LEFT JOIN (SELECT image_id, COUNT(*) AS cnt
FROM googleimagefound WHERE status = 0
GROUP BY image_id) gf ON gf.image_id = g.id
WHERE g.user_id = 1 <-- add this condition
ORDER BY COALESCE(gf.cnt, 0) DESC;
these is my DB table data,{ i have maintained only one Table}
I need to fetch max 3 data from each start_date,
give me any idea to develop query,,
SELECT a.*
FROM Table1 a
INNER JOIN Table1 b ON a.start_date = b.start_date AND a.event_id <= b.event_id
GROUP BY a.event_id,a.start_date
HAVING COUNT(*) <= 3
ORDER BY a.start_date
I may suggest you this query -
SELECT * FROM (
SELECT t1.*, COUNT(*) pos FROM table t1
LEFT JOIN table t2
ON t2.start_date = t1.start_date AND t2.event_id <= t1.event_id
GROUP BY
t1.start_date AND t1.event_id
) t
WHERE
pos <= 3;
It selects 3 rows with min event_id in a start_date group.
I am running two queries to my database for pagination reasons. As such, each query is nearly identical. My COUNT(*) query is not returning the number of results that the non-count query is. I'm baffled as to why this is the case. The queries are below.
SELECT p.host_id, p.rating_support, p.rating_tech, MAX(p.rating_overall) AS rating_overall, p.publish_rating, h.name, prices.price, prices.term_duration
FROM plans p
INNER JOIN hosts AS h ON h.id = p.host_id
INNER JOIN (SELECT plan_id, price, term_duration FROM prices WHERE price > 0 AND price < 50 AND term_duration = 1) prices ON prices.plan_id = p.id
WHERE p.published = 1 AND h.published = 1
GROUP BY p.host_id
ORDER BY rating_overall desc LIMIT 0, 12
SELECT COUNT(*) AS count
FROM plans p
INNER JOIN hosts AS h ON h.id = p.host_id
INNER JOIN (SELECT plan_id, price, term_duration FROM prices WHERE price > 0 AND price < 50 AND term_duration = 1) prices ON prices.plan_id = p.id
WHERE p.published = 1 AND h.published = 1
GROUP BY p.host_id
I'm not an expert at MySQL. Besides the count not providing the correct number of results, the non-count query works perfectly.
Any light on this problem would be great.
With the help of Dems' comment (hunt down and upvote him somewhere :), I created this query. Notice that I removed the subquery, because it seemed unnecessary:
SELECT
COUNT( DISTINCT p.host_id )
FROM plans p
INNER JOIN hosts h ON h.id = p.host_id
INNER JOIN prices ON prices.plan_id = p.id
AND prices.price > 0
AND prices.price < 50
AND prices.term_duration = 1
WHERE p.published = 1
AND h.published = 1
My original answer:
To get the number of total row, you have to wrap the GROUP BY query into an outer SELECT:
SELECT COUNT(*)
FROM (
SELECT NULL -- we are just counting, so we need no actual data -> a bit faster
FROM plans p
INNER JOIN hosts h ON h.id = p.host_id
INNER JOIN prices ON prices.plan_id = p.id
AND prices.price > 0
AND prices.price < 50
AND prices.term_duration = 1
WHERE p.published = 1
AND h.published = 1
GROUP BY p.host_id
) AS all_rows_without_data
Or you could use SQL_CALC_FOUND_ROWS + FOUND_ROWS()
http://dev.mysql.com/doc/refman/5.0/en/information-functions.html#function_found-rows
A SELECT statement may include a LIMIT clause to restrict the number
of rows the server returns to the client. In some cases, it is
desirable to know how many rows the statement would have returned
without the LIMIT, but without running the statement again. To obtain
this row count, include a SQL_CALC_FOUND_ROWS option in the SELECT
statement, and then invoke FOUND_ROWS() afterward:
First, simply select the required rows, but add SQL_CALC_FOUND_ROWS:
SELECT SQL_CALC_FOUND_ROWS
p.host_id, p.rating_support, p.rating_tech,
MAX(p.rating_overall) AS rating_overall,
p.publish_rating, h.name, prices.price, prices.term_duration
FROM plans p
INNER JOIN hosts AS h ON h.id = p.host_id
INNER JOIN prices ON prices.plan_id = p.id
AND prices.price > 0
AND prices.price < 50
AND prices.term_duration = 1
WHERE p.published = 1 AND h.published = 1
GROUP BY p.host_id
ORDER BY rating_overall desc
LIMIT 0, 12;
Second, get the number of rows that would have been returned if there weren't a LIMIT statement in the first query:
SELECT FOUND_ROWS();
Update: SQL_CALC_FOUND_ROWS + FOUND_ROWS() doesn't seem very reliable, always returs zero for unknown reason (not just me: FOUND_ROWS() keeps returning 0 ):
http://sqlfiddle.com/#!2/7304d/8
The result of your second query would return the same number of rows, but the first row won't give you back the total.
The results would give the count for each group per row:
3
5
1
6
etc.
etc.
To get the result into one row, use COUNT(DISTINCT p.host_id) or array_sum() in PHP on the full result set.