left join multiplying values - mysql

I have the following queries -
SELECT COUNT(capture_id) as count_captures
FROM captures
WHERE user_id = 9
...returns 5
SELECT COUNT(id) as count_items
FROM items
WHERE creator_user_id = 9
...returns 22
I tried the following query -
SELECT COUNT(capture_id) as count_captures,
COUNT(items.id) as count_items
FROM captures
LEFT JOIN items ON captures.user_id = items.creator_user_id
WHERE user_id = 9
...but it returns two columns both with 110 as the value. I would want 5 in one column and 22 in the other. What am I doing wrong?

My knee-jerk is a subquery:
select count(capture_id) as count_captures,
(select count(id) as count_items
from items i where i.creator_user_id = captures.user_id) as count_items
from captures
where user_id = 9
I'm not really sure what you can do to avoid this. You're seeing expected (and generally desired behavior).
Of course, if you know that the ID's in both won't repeat themselves, you can use distinct:
SELECT COUNT( DISTINCT capture_id) as count_captures,
COUNT( DISTINCT items.id) as count_items
FROM captures
LEFT JOIN items ON captures.user_id = items.creator_user_id
WHERE user_id = 9

A LEFT JOIN returns each row in the left table with each row in the right table that matches the results. Since all of your id's are the same which produces a Cartesian Product of the table. (5 * 22 = 110).
This is expected to happen.

You could always union the results (warning, untested):
SELECT SUM(sub.count_captures), SUM(sub.count_items)
FROM (SELECT COUNT(capture_id) as count_captures, 0 as count_items
from captures where user_id = 9
UNION
SELECT 0 as count_captures, count(id) as count_items
from items where creator_user = 9) sub

Another way to combine two (seemingly not related) queries into one:
SELECT
( SELECT COUNT(capture_id)
FROM captures
WHERE user_id = 9
)
AS count_captures
, ( SELECT COUNT(id)
FROM items
WHERE creator_user_id = 9
)
AS count_items
There really is no need for subqueries or JOIN in these cases. Although the optimizer may be smart enough to figure that out, I wouldn't try to confuse him.

Related

mysql select statement for multiple table

This is just a test project. I want to know how to select All professors with more than 5 failed students in a subject
I already know how to select All professors with at least 2 subjects with the following query:
SELECT paulin_professors.*,
IFNULL(sub_p.total, 0) num
FROM paulin_professors
LEFT JOIN ( SELECT COUNT(*) total, pau_profid
FROM paulin_profsubject
GROUP BY pau_profid
) sub_p ON (sub_p.pau_profid = paulin_professors.pau_profid)
WHERE sub_p.total >= 2;
I know I'm close but I can't get it to work (All professors with more than 5 failed students in a subject) . Any ideas? TIA
try using SELECT with UNION
select [columnName1],[columnName2] from [Table1] where [condition] union select [columnName1],[columnName2] from [Table1] where [condition] union ....
Looks like can get the professor IDs from the profsubject table and JOIN the studentenrolled table using the subjid for the join. In a similar way to what you had, you can get the count of students who have a grade less than a certain pass/fail threshold (in this case 65).
Then to get a short list, you can select the distinct profids from this derivaed table.
SELECT
distinct pau_profid
FROM
(SELECT
t1.pau_profid,
IFNULL(t2.total_failed, 0) number_failed >= 5
FROM
paulin_profsubject t1
LEFT JOIN
(SELECT
COUNT(*) total_failed,
pau_subjid
FROM
paulin_studentenrolled
WHERE
pau_grade < 65
GROUP BY
pau_subjid
) t2
ON
t1.pau_subjid = t2.pau_subjid
WHERE
number_failed >= 5
) t3;

Complicated SQLite or SQL SUM() between several rows

I need to extend this question: SQLite SUM() between several rows
Before table was
and query was :
SELECT Sum(SERVICE)
FROM (
SELECT ft.*, (
SELECT count(*)
FROM fuel_table ft2
WHERE ft2.note='Your tank was full up.' and ft2.id>=ft.id)
AS numNotesAhead
FROM fuel_table ft)
AS ft
WHERE numNotesAhead=1
but now my fuel table is split and looks like:
I've tried:
SELECT Sum(fuel_table.SERVICE)
FROM (
SELECT ft.*, (
SELECT count(*)
FROM fuel_table ft2
LEFT JOIN note_table ON fuel_table.EnterId = note_table.EnterId
WHERE ft2.note='Your tank was full up.' and ft2.id>=ft.id)
AS numNotesAhead
FROM fuel_table ft)
AS ft
WHERE numNotesAhead=1
but it doesn't works. My app just stops.
*Note there is "_" in name of fuel table.
So there are two issues here - one is the joining of the tables, which is different from your previous problem. The second issue is doing the appropriate summation of the right values, which is already beautifully answered here: SQLite SUM() between several rows.
So, let's look at the JOIN.
Since the fuel_table has the greater number of records, we're going to expect some 'NULL' values from the desired JOIN with the note_table. (And we're not going to lose any records from fuel_table since we're joining on enterId, so we don't need a FULL OUTER JOIN). This means (for a LEFT JOIN), we want the fuel_table to be on the left:
SELECT f.id, f.service, f.enterid, n.note
FROM fuel_table f
LEFT JOIN note_table n
ON f.enterid = n.enterid
This will give us output:
id service enterid note
----------------------------------
2 50 25 Yes
3 20 26 NULL
4 20 35 Yes
8 30 36 NULL
9 15 37 NULL
10 20 42 Yes
So far, so good.
Now - rather than thinking about it - I just looked at the answer to the previous question, and substituted this subquery from the fuel_table from this answer: SQLite SUM() between several rows
which is a very good answer, and I can take no credit for any part of it in the following bit ...
Putting them together you get the ugly but functional query:
SELECT SUM(fq.service) AS total
FROM
(SELECT fq2.*,
(SELECT COUNT(*)
FROM (SELECT f.id, f.service, f.enterid, n.note
FROM fuel_table f
LEFT JOIN note_table n
ON f.enterid = n.enterid) fq1
WHERE fq1.note = 'Yes' AND fq1.id >= fq2.id) AS numNotesAhead
FROM
(SELECT f.id, f.service, f.enterid, n.note
FROM fuel_table f
LEFT JOIN note_table n
ON f.enterid = n.enterid) fq2) fq
WHERE numNotesAhead = 1
which returns output:
total
------
65

Trying to Compare 2 Tables with Multiple Joins

I'm trying to compare 2 tables, but can't seem to get it to work. It's probably something very basic. Here's my code:
Select bin, partno, count(*) from
(SELECT parts.partno, location.bin
FROM inventory
INNER JOIN location
ON inventory.locid = location.locid
INNER JOIN parts
ON inventory.partid = parts.partid
WHERE loc = 'PnP'
UNION
select partno, bin from dlyfeedercontents) t
Group By t.bin, t.partno
HAVING COUNT(*) = 1
ORDER BY bin
Here are the results:
08-01 3052-93-7100-0C6 1
08-01 3052-93-7100-0C6 1
08-01 Test2 1
08-02 3052-90-7100-063 1
08-02 3052-90-7100-063 1
I can't seem to get the Group or Count to acknowledge that there are duplicates (for example, the first 2 lines). The results of the UNION are what I expected.
I'm not quite sure what you are trying to do, but I strongly suspect the problem is using union rather than union all. union removes duplicates. Does this version do what you want?
select bin, partno, count(*)
from ((SELECT parts.partno, location.bin
FROM inventory INNER JOIN
location
ON inventory.locid = location.locid INNER JOIN
parts
ON inventory.partid = parts.partid
WHERE loc = 'PnP'
) union all
(select partno, bin
from dlyfeedercontents
)
) t
Group By t.bin, t.partno
HAVING COUNT(*) = 1
ORDER BY bin;
The issue was a nonprinting character in some of the data (/r)! Thanks for all who helped!

Rewrite MySQL query without using UNION [duplicate]

This question already has answers here:
Get top n records for each group of grouped results
(12 answers)
Get records with highest/smallest <whatever> per group
(2 answers)
Closed 8 years ago.
I have the following query which returns the correct results but I'm sure it's not the best way to get these results...
select * from (
select * from features where feature_area = 0
order by updateStamp desc limit 1
) as feature_1
union all
select * from (
select * from features where feature_area = 1
order by updateStamp desc limit 1
) as feature_2
union all
select * from (
select * from features where feature_area = 2
order by updateStamp desc limit 1
) as feature_3
This returns results which look something like...
id feature_area title updateStamp
--------------------------------------------------------------------
103 0 This is a titleĀ  2014-04-15 09:26:14
102 1 Another title 2014-03-27 14:09:49
98 2 More title 2014-01-21 16:00:55
Could this be improved using joins rather than unions and if so could you point me in the right direction please.
EDIT:
Having looked at the other options pointed out by #Ben it would seem I've already got the quickest query (albeit not that attractive) for my particular purpose. Feel free to correct me if you think I'm wrong though. I'm no expert, hence I'm asking for advice.
select f.* from features f
inner join (
select
feature_area
max(updateStamp) as updateStamp
from
features
where feature_are IN (0,1,2)
group by feature_area
) sq on sq.feature_area = f.feature_area
and sq.updateStamp = f.updateStamp
Hope I read your question correctly.
select *
From features f
inner join ( select feature_area, max(updateStamp) as maxUpdateStamp
from features
Group by feature_area
) as minfeatures
ON minfeatures.feature_area = f.feature_area
AND minfeatures.maxUpdateStamp = f.updateStamp
Assuming proper indexes, it's often most performant to solve this with the anti-join:
SELECT f1.*
FROM features f1
LEFT JOIN features f2
ON f2.feature_area = f1.feature_area
AND f2.updateStamp < f1.updateStamp
WHERE f1.feature_area < 3
AND f2.id IS NULL
ORDER BY f1.feature_area
In cases where there are duplicate rows with same feature_area and highest updateStamp, it will return duplicate rows.
For more explanation of this technique:
Get records with highest/smallest <whatever> per group
with MaxFeature
AS
(
select
feature_area AS feature_area
,max(updateStamp) AS MaxUpdateStamp
from
features
group by
feature_area
)
select
Features.*
from
Features
inner join
Maxfeature
on
Features.feature_area = MaxFeature.feature_area
and
Features.updateStamp = MaxFeature.MaxUpdateStamp
order by
Features.feature_area asc

Query output differs from the expected output

Below query is doing what I need:
SELECT assign.from_uid, assign.aid, assign.message, curriculum.asset,
curriculum.title, curriculum.description
FROM assignment assign
INNER JOIN curriculum_topics_assets curriculum
ON assign.nid = curriculum.asset
WHERE assign.to_uid = 13 AND assign.status = 1
GROUP BY assign.from_uid, assign.to_uid, assign.nid
ORDER BY assign.created DESC
Now I need to get the total count of rows of the result. For example if it is displaying 5 rows the o/p should be like My expected o/p. The query I tried is given below.
SELECT count(description) FROM assignment assign
INNER JOIN curriculum_topics_assets curriculum ON assign.nid = curriculum.asset
WHERE assign.to_uid = 13 AND assign.status = 1
GROUP BY assign.from_uid, assign.to_uid, assign.nid
ORDER BY assign.created DESC
My expected o/p:
count(*)
---------
5
My current o/p:
count(*)
---------
6
2
5
6
6
The easiest solution would be to
place your initial GROUP BY query in a subselect
select the amount of rows retrieved from this subselect
SQL Statement
SELECT COUNT(*)
FROM (
SELECT assign.from_uid
FROM assignment assign
INNER JOIN curriculum_topics_assets curriculum ON assign.nid = curriculum.asset
WHERE assign.to_uid = 13
AND assign.status = 1
GROUP BY
assign.from_uid
, assign.to_uid
, assign.nid
) q
Edit - why doesn't the original query return the results required
It did already prepared what was needed to get the correct result
Your query without grouping returns a resultset of 25 records (6+2+5+6+6)
From these 25 records, you have 5 unique combinations of from_uid, to_uid, nid
Now you don't want to count how many records each combination has (as you did in your example) but how many unique (distinct anyone?) combinations there are.
One solution to this is the subselect I presented but following equivalent statement using a DISTINCT clause might be more comprehensive.
SELECT COUNT(*)
FROM (
SELECT DISTINCT assign.from_uid
, assign.to_uid
, assign.nid
FROM assignment assign
INNER JOIN curriculum_topics_assets curriculum ON assign.nid = curriculum.asset
WHERE assign.to_uid = 13
AND assign.status = 1
) q
Note that my personal preference goes to the GROUP BY solution.
To get the number of rows for a query do:
SELECT COUNT(*) as RowCount FROM (--insert other query here--) s
In you example:
SELECT COUNT(*) as RowCount FROM (SELECT a.from_uid
FROM assignment a
INNER JOIN curriculum_topics_assets c ON a.nid = c.asset
WHERE a.to_uid = 13
AND a.status = 1
GROUP BY a.from_uid, a.to_uid, a.nid
) s
Note that I the dropped the stuff that has no effect on the number of rows to make the query run slightly faster.
You should use COUNT(*) instead of count(description). Look at: http://www.mysqlperformanceblog.com/2007/04/10/count-vs-countcol/