MySQL Data Summary/Counts Table - mysql

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

Related

MySQL: Finding the most efficient use of INNER JOIN with subquery

I have a working query using INNER JOIN and a subquery but was wondering if there is a more effient way of writing it.
with prl
as
(
SELECT `number`, creator, notes
FROM ratings
INNER JOIN
projects on ratings.project_id = projects.project_id
WHERE ratings.rating = 5 AND projects.active = 1
)
SELECT prl.`number`, creator, notes
FROM prl
INNER JOIN(
SELECT `number`
HAVING COUNT(creator) > 1
)temp ON prl.`number` = temp.`number`
ORDER BY temp.`number`
projects table
project_id| number | creator | active |
| 1 | 3 | bob | 1 |
| 2 | 4 | mary | 1 |
| 3 | 5 | asi | 1 |
rating table
project_id| notes | rating |
| 1 | note1 | 5 |
| 1 | note2 | 5 |
| 3 | note3 | 5 |
| 1 | note4 | 1 |
| 2 | note5 | 5 |
| 3 | note6 | 2 |
result
| number | creator | notes |
| 3 | bob | note1 |
| 3 | bob | note2 |
It seems like you're using MySQL version that support window function. If so, then try this:
SELECT number, creator, notes
FROM
(SELECT p.number, p.creator, r.notes,
COUNT(creator) OVER (PARTITION BY creator) AS cnt
FROM project p
JOIN rating r ON p.project_id=r.project_id
WHERE r.rating=5
AND p.active = 1) v
WHERE cnt=2;
As far as whether this is more efficient, I'm not really sure because it depends in your table indexes but for a small dataset, I assume this will do well.
Demo fiddle

How to replace reference ID with value

Let's say I have two tables:
+------------------------------------+
| `houses` |
+----+-------+-------+-------+-------+
| id | house | room1 | room2 | room3 |
+----+-------+-------+-------+-------+
| 1 | a | 1 | 1 | 2 |
| 2 | b | 1 | 3 | 1 |
| 3 | c | 2 | 2 | 1 |
+----+-------+-------+-------+-------+
+------------------------+
| `status` |
+-------------+----------+
| status_code | status |
+-------------+----------+
| 1 | empty |
| 2 | occupied |
| 3 | full |
+-------------+----------+
Now I want to change room1, room2 and room3's ids to the status from TABLE status like this:
+---------------------------------------------+
| `houses` |
+----+-------+----------+----------+----------+
| id | house | room1 | room2 | room3 |
+----+-------+----------+----------+----------+
| 1 | a | empty | empty | occupied |
| 2 | b | empty | full | empty |
| 3 | c | occupied | occupied | empty |
+----+-------+----------+----------+----------+
I know one solution, but I believe there is an easier way to do the same:
SELECT
h.*,
s1.status AS room1,
s2.status AS room2,
s3.status AS room3
FROM houses h
JOIN status s1 ON h.room1 = s1.status_code
JOIN status s2 ON h.room2 = s2.status_code
JOIN status s3 ON h.room3 = s3.status_code
UPDATE
MySQL pivot table solution is not suitable for me because the above example is a simplified version what I use. There are more than ten different status and writing a 40 line CASE function for a single room is not an opportunity. The status must come from the status table.
You can approach this as
SELECT id,house,
(
CASE
WHEN room1 > 0 THEN (SELECT status FROM status WHERE status_code=room1)
END) AS room1,
(
CASE
WHEN room2 > 0 THEN (SELECT status FROM status WHERE status_code=room2)
END) AS room2,
(
CASE
WHEN room3 > 0 THEN (SELECT status FROM status WHERE status_code=room3)
END) AS room3
FROM houses
Live DEMO

How to spot missing value from two GROUP_CONCAT columns?

I have table All Badges and table User Badge.
The value in User Badge can all be found on the All Badges and each user_id can have multiple User Badge so I use GROUP_CONCAT to group the value based on the user_id. The goal of the User Badge is to allow users to collect all available badges, thus they will need information about the missing badge on their profile.
Here is the All Badges table:
|---------------|-----------|
| Badge_ID | Label |
|---------------|-----------|
| 1 | a |
| 2 | b |
| 3 | c |
| 4 | d |
| 5 | e |
|---------------|-----------|
The User Badge table:
|------------------|---------------|---------------|---------------|
| User_Badge_ID | Badge_Label | user_ID | fk_badge_id |
|------------------|---------------|---------------|---------------|
| 1 | a | 1 | 1 |
| 2 | a | 2 | 1 |
| 3 | b | 1 | 2 |
| 4 | a | 3 | 1 |
| 5 | b | 2 | 2 |
| 6 | c | 2 | 3 |
| 7 | d | 2 | 4 |
|------------------|---------------|---------------|---------------|
I have this query to group the badge on user_id, but I don't know the query to show the missing value.
SELECT
user_badge.user_id AS User_ID
GROUP_CONCAT(user_badge.Badge_Label) AS User_Badge
FROM
User_Badge
The desired result. The missing badge will be added into the new column called Missing Badge.
|---------------------|------------------|------------------|
| User_ID | User_Badge | Missing Badge |
|---------------------|------------------|------------------|
| 1 | a, b | c, d, e |
|---------------------|------------------|------------------|
| 2 | a, b, c, d | e |
|---------------------|------------------|------------------|
| 3 | a | b, c, d, e |
|---------------------|------------------|------------------|
Any ideas to show the missing value from each user_id? Many thanks for your willingness to help.
select s.user_id,
group_concat(case when fk_badge_id is not null then s.label end) 'user_badge',
group_concat(case when fk_badge_id is null then s.label end) 'missing'
from
(
SELECT distinct USER_ID,badge_id,ab.label
FROM USER_BADGE ub
CROSS JOIN ALL_BADGES ab
) s
left join user_badge ub on ub.user_id = s.user_id and ub.fk_badge_id = s.badge_id
group by s.user_id;
Where the sub query s gets all the badges a user could have using a cross join and the main query checks those he/she has or not , the left join returning nulls if has not.
+---------+------------+---------+
| user_id | user_badge | missing |
+---------+------------+---------+
| 1 | a,b | c,d,e |
| 2 | a,b,c,d | e |
| 3 | a | c,d,e,b |
+---------+------------+---------+
3 rows in set (0.00 sec)

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

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.

Querying across 6 tables, is there a better way of doing this?

What I did was, I wanted each user to have their own "unique" numbering system. Instead of auto incrementing the item number by 1, I did it so that Bob's first item would start at #1 and Alice's number would also start at #1. The same goes for rooms and categories. I achieved this by creating "mapping" tables for items, rooms and categories.
The query below works, but I know it can definitely be refactored. I have primary keys in each table (on the "ids").
SELECT unique_item_id as item_id, item_name, category_name, item_value, room_name
FROM
users_items, users_map_item, users_room, users_map_room, users_category, users_map_category
WHERE
users_items.id = users_map_item.map_item_id AND
item_location = users_map_room.unique_room_id AND
users_map_room.map_room_id = users_room.room_id AND
users_map_room.map_user_id = 1 AND
item_category = users_map_category.unique_category_id AND
users_map_category.map_category_id = users_category.category_id AND
users_category.user_id = users_map_category.map_user_id AND
users_map_category.map_user_id = 1
ORDER BY item_name
users_items
| id | item_name | item_location |item_category |
--------------------------------------------------------
| 1 | item_a | 1 | 1 |
| 2 | item_b | 2 | 1 |
| 3 | item_c | 1 | 1 |
users_map_item
| map_item_id | map_user_id | unique_item_id |
----------------------------------------------------
| 1 | 1 | 1 |
| 2 | 1 | 2 |
| 3 | 2 | 1 |
users_rooms
| id | room_name |
----------------------
| 1 | basement |
| 2 | kitchen |
| 3 | attic |
users_map_room
| map_room_id | map_user_id | unique_room_id |
----------------------------------------------------
| 1 | 1 | 1 |
| 2 | 1 | 2 |
| 3 | 2 | 1 |
users_category
| id | room_name |
----------------------
| 1 | antiques |
| 2 | appliance |
| 3 | sporting goods |
users_map_category
| map_room_id | map_user_id | unique_category_id |
----------------------------------------------------
| 1 | 1 | 1 |
| 2 | 1 | 2 |
| 3 | 2 | 1 |
Rewriting your query with explicit JOIN conditions makes it more readable (while doing the same).
SELECT mi.unique_item_id AS item_id
, i.item_name
, c.category_name
, i.item_value
, r.room_name
FROM users_map_item mi
JOIN users_items i ON i.id = mi.map_item_id
JOIN users_map_room mr ON mr.unique_room_id = i.item_location
JOIN users_room r ON r.room_id = mr.map_room_id
JOIN users_map_category mc ON mc.unique_category_id = i.item_category
JOIN users_category c ON (c.user_id, c.category_id)
= (mc.map_user_id, mc.map_category_id)
WHERE mr.map_user_id = 1
AND mc.map_user_id = 1
ORDER BY i.item_name
The result is unchanged. Query plan should be the same. I see no way to improve the query further.
You should use LEFT [OUTER] JOIN instead of [INNER] JOIN if you want to keep rows in the result where no matching rows are found in the right hand table. You may want to move the additional WHERE clauses to the JOIN condition in this case, as it changes the outcome.