COUNT counting something on LEFT JOIN that doesn't exist. Why? - mysql

I'm having a problem.
I have this table called usersbycourse which shows this information:
+------------+-----------------+--------+-----------+-------+-----------------+-----------------+
| instanceid | shortname | userid | firstname | logid | lastaccessdelta | modulesfinished |
+------------+-----------------+--------+-----------+-------+-----------------+-----------------+
| 2 | PJU | 74 | Robin | 766 | 1662246 | 0 |
| 3 | Fundgest-GRHN1A | 75 | Batman | 867 | 1576725 | 0 |
| 3 | Fundgest-GRHN1A | 77 | Abigobeu | 1004 | 610480 | 0 |
+------------+-----------------+--------+-----------+-------+-----------------+-----------------+
and this SQL:
SELECT
mdl_course.id,
mdl_course.shortname,
COUNT(CASE WHEN usersbycourse.modulesfinished = 1 THEN NULL ELSE 1 END) AS studentcount
FROM mdl_course LEFT JOIN usersbycourse ON mdl_course.id = usersbycourse.instanceid
GROUP BY mdl_course.id;
The results from the SQL are:
+----+-----------------+--------------+
| id | shortname | studentcount |
+----+-----------------+--------------+
| 1 | Unity I | 1 |
| 2 | PJU | 1 |
| 3 | Fundgest-GRHN1A | 2 |
| 4 | asdzxc2 | 1 |
+----+-----------------+--------------+
But why? In inside SQL has no Unity I, and no asdzxc2. How do I produce a result like this:
+----+-----------------+--------------+
| id | shortname | studentcount |
+----+-----------------+--------------+
| 1 | Unity I | 0 |
| 2 | PJU | 1 |
| 3 | Fundgest-GRHN1A | 2 |
| 4 | asdzxc2 | 0 |
+----+-----------------+--------------+
?
EDIT:
I want to count only rows having modulesfinished = 0

What you're looking for is SUM rather than COUNT, that is,
SELECT
mdl_course.id,
mdl_course.shortname,
SUM(CASE WHEN usersbycourse.modulesfinished = 0 THEN 1 ELSE 0 END) AS studentcount
FROM mdl_course LEFT JOIN usersbycourse ON mdl_course.id = usersbycourse.instanceid
GROUP BY mdl_course.id;

The problem is because you are using LEFT JOIN some of the values for usersbycourse.modulesfinished are NULL
Something you need to learn is
NULL == something
Is always unknown, not true, not false, just unknown.
So when you try to compare with = 1 your nulls get the ELSE but not because they aren't 1, is because is all the rest.
So if instead you change the condition to
COUNT(CASE WHEN usersbycourse.modulesfinished = 0 THEN 1 ELSE NULL)
Only the TRUE match will get 1, the FALSE and the UNKNOW part ill get NULL and COUNT doesnt count nulls. And that is what you want.

Related

Joining multiple rows with same ID in one

I am having trouble with an SQL query. I have two tables.
My first table:
+------------+-------------+---------------+
| id_mission | Some column | Other column |
+------------+-------------+---------------+
| 1 | ... | ... |
| 2 | ... | ... |
+------------+-------------+---------------+
My second table:
+------------+-------------+---------+
| id_mission | id_category | points |
+------------+-------------+---------+
| 1 | 1 | 3 |
| 1 | 2 | 4 |
| 1 | 3 | 4 |
| 1 | 4 | 8 |
| 2 | 1 | -4 |
| 2 | 2 | 3 |
| 2 | 3 | 1 |
| 2 | 4 | -7 |
+------------+-------------+---------+
And I would like to have this kind of result with my SELECT request
+------------+-------------+--------------+---------------+----------------+
| id_mission | Some column | Other column | id_category 1 | id_category X |
+------------+-------------+--------------+---------------+----------------+
| 1 | ... | ... | ... | ... |
| 2 | ... | ... | ... | ... |
+------------+-------------+--------------+---------------+----------------+
I have tried this with the first two column but it doesn't work, I also tried GROUP_CONCAT, it works but it's not the result I want.
SELECT m.id_mission ,mc.id_category 1,mc1.id_category 2
from mission m
left join mission_category mc on m.id_mission = mc.id_mission
left join mission_category mc1 on m.id_mission = mc1.id_mission
Can someone help me?
You can use conditional aggregation. Assuming that you want to pivot the points value per category:
select
t1.*,
max(case when t2.id_category = 1 then points end) category_1,
max(case when t2.id_category = 2 then points end) category_2,
max(case when t2.id_category = 3 then points end) category_3
from t1
inner join t2 on t2.id_mission = t1.id_mission
group by t1.id_mission
This assumes that id_mission is the primary key of t1 (else, you need to enumerate the columns you want in both the select and group by clauses).

efficient query to group by one field and put all other fields in the row SQL

Imagine the result of a query is something like the following:
+----+---------+--------+
| id | count | type |
+----+---------+--------+
| 1 | 20 | a |
| 1 | 30 | b |
| 1 | 10 | c |
| 2 | 05 | a |
| 2 | 20 | b |
| 2 | 40 | c |
+----+---------+--------+
and the expected result:
+----+---------+--------+------+
| id | a | b | c |
+----+---------+--------+------+
| 1 | 20 | 30 | 10 |
| 2 | 05 | 20 | 40 |
+----+---------+--------+------+
I know some solutions which are complex using Cursor, Variables, Join and etc. I would like to find the most efficient one, otherwise I will handle it from the application layer.
One method uses conditional aggregation:
select id,
sum(case when type = 'a' then count else 0 end) as a,
sum(case when type = 'b' then count else 0 end) as b,
sum(case when type = 'c' then count else 0 end) as c
from t
group by id;

MySQL Data Summary/Counts Table

I'm trying to put together a summary table that has counts of types of mail sent by group.
Hopefully the below is enough to explain what I mean.
Table 1 (senders)
| id | name | group_id |
+----+------+----------+
| 1 | mike | 1 |
| 2 | john | 1 |
| 3 | lucy | 2 |
| 4 | lobo | 3 |
Table 2 (mail)
| id | type | sender_id |
+----+----------+-----------+
| 1 | letter | 1 |
| 2 | postcard | 2 |
| 3 | postcard | 1 |
| 4 | letter | 2 |
| 5 | postcard | 2 |
| 6 | postcard | 4 |
Table 3 (groups)
| id | name | active |
+----+-------+--------+
| 1 | alpha | 1 |
| 2 | black | 1 |
| 3 | cero | 0 |
Ideal result
| group | letter | postcard | parcel |
+-------+--------+----------+--------+
| alpha | 2 | 3 | 0 |
| black | 0 | 0 | 0 |
So I need to get counts per mail type for active groups.
I've been working through examples (only learning MySQL) but when I think of this situation I'm just totally blank.
Have looked at the answers to Joining three tables to get summary data in MySQL but I don't quite understand how to translate the answers to my problem.
Any help is appreciated.
SELECT t.name,
MAX(CASE t.TYPE WHEN 'letter' THEN #CS:=#CS+1 ELSE 0 END ) letter,
MAX(CASE t.TYPE WHEN 'postcard' THEN #CS1:=#CS1+1 ELSE 0 END ) postcard ,
MAX(CASE t.TYPE WHEN 'parcel ' THEN #CS2:=#CS2+1 ELSE 0 END ) parcel
FROM
(SELECT
groups. name,
mail.type
FROM
groups
LEFT JOIN senders ON groups.id = senders.id
LEFT JOIN mail ON senders.id = mail.sender_id ) AS t
,(SELECT #CS:=0) CS ,(SELECT #CS1:=0) CS1 ,(SELECT #CS2:=0) CS2
You put this query
Select count(*) from senders s inner join mail m on s.id = s.sender_id inner join
groups g on s.groups_id = g.id group by m.type

Extending the result set adding columns taken from another table's distinct rows

Good day!
Let's say I have two tables defined as follows:
TABLE USER(id, name, ...other fields that are unimportant)
| id | name |
-------------
| 1 | John |
| 2 | Mary |
| 3 | Luke |
| 4 | Lisa |
TABLE CHOICE(id, uid, kname) where uid is the foreign key to table USER.
Please note: I do NOT know how many distinct kname entries the table CHOICE has. The only thing I know is that they will be limited (say between 0 and 10).
| id | uid | kname |
---------------------
| 1 | 1 | city1 |
| 2 | 1 | city2 |
| 4 | 2 | city2 |
| 5 | 2 | city3 |
| 6 | 4 | city4 |
Is it possible to write a query which returns this table:
| id | name | city1 | city2 | city3 | city4 |
---------------------------------------------
| 1 | John | 1 | 1 | 0 | 0 |
| 2 | Mary | 0 | 1 | 1 | 0 |
| 3 | Luke | 0 | 0 | 0 | 0 |
| 4 | Lisa | 0 | 0 | 0 | 1 |
i. e. the table USER extended with as many additional columns as distinct kname in table CHOICE, labelled with kname and containing a 1 if there exist a row in CHOICE with uid equals to the user id and a zero otherwise.
Something like
SELECT u.id, u.name, (SELECT COUNT(c.uid) FROM CHOICE WHERE c.kname = 'city1') AS city1, (SELECT COUNT(c.uid) FROM CHOICE WHERE c.kname = 'city2') AS city2, (SELECT COUNT(c.uid) FROM CHOICE WHERE c.kname = 'city3') AS city4, (SELECT COUNT(c.uid) FROM CHOICE WHERE c.kname = 'city4') AS city4
FROM USER u
INNER JOIN CHOICE c ON u.id = c.uid
GROUP BY u.id, u.name
No need for subqueries. Usually you would just do
sum(kname = 'city1') as city1
, because the kname = 'city1' equals to true or false, 1 or 0. But you have to deal with possible null values, since you have to left join to get all entries from the users table, even the ones that do not have a corresponding entry in table choice. Therefor the use of the coalesce() function (it returns the first of its parameters, that isn't null).
select
u.id,
u.name,
sum(coalesce(kname, '') = 'city1') as city1,
sum(coalesce(kname, '') = 'city2') as city2,
sum(coalesce(kname, '') = 'city3') as city3,
sum(coalesce(kname, '') = 'city4') as city4
from user u
left join choice c on c.uid = u.id
group by u.id, u.name;
+------+------+-------+-------+-------+-------+
| id | name | city1 | city2 | city3 | city4 |
+------+------+-------+-------+-------+-------+
| 1 | John | 1 | 1 | 0 | 0 |
| 2 | Mary | 0 | 1 | 1 | 0 |
| 3 | Luke | 0 | 0 | 0 | 0 |
| 4 | Lisa | 0 | 0 | 0 | 1 |
+------+------+-------+-------+-------+-------+

Update innerquery result

I have a query and a result as follows.
In the database NULL and 0 represent the same meaning.
Now I want a counter based on Null+0 or 1
Eg:in the following example I want the result like this:
IsVirtual Category counter
NULL+0 3 343+8 = (351 is Total)
Query
select * from
(
Select IsVirtual, Category, count(*) as counter
from [Hardware]
group by IsVirtual, Category
) innercat
Output
+-----------+----------+---------+
| IsVirtual | Category | counter |
+-----------+----------+---------+
| NULL | 3 | 343 |
| 0 | 3 | 8 |
| 1 | 2 | 1 |
| 0 | 1 | 1 |
| NULL | 6 | 119 |
| 0 | 4 | 1 |
| NULL | 1 | 70 |
| 0 | 5 | 9 |
| NULL | 4 | 54 |
| 0 | 2 | 2 |
| NULL | 5 | 41 |
| NULL | 2 | 112 |
| 1 | 1 | 5 |
+-----------+----------+---------+
I think you want this :
SELECT COALESCE(IsVirtual, 0) as [IsVirtual],
Category,
Count(*) as [Counter]
FROM yourtable
GROUP BY COALESCE(IsVirtual, 0),Category
This will give you expected result without using subquery.
try with this
select * from (
Select CASE ISNULL(IsVirtual,0)
WHEN 0 Then 'NULL + 0'
ELSE IsVirtual
END AS IsVirtual, Category, count(*) as counter from [Hardware] group by ISNULL(IsVirtual,0), Category
)innercat
You can also do the same thing by using MAX function
This might help you.
SELECT
max(IsVirtual) as IsVirtual,
Category,
Count(*) as Counter
FROM
yourtable
GROUP BY
Category