MySQL: using Case When Then End for multiple Inner Join tables - mysql

I have a query which returns results using 'Case When Then End' from three tables which are joind. It looks like this:
select f.filename,
sum(case when v.rUser like '%bike%' then 1 else 0 end) as bikeUser,
sum(case when v.rUser like '%Pedestrian%' then 1 else 0 end) as pedestrianUser,
sum(case when d.weather like '%clear%' then 1 else 0 end) as clearWeather
from VMdata v
inner join files f on v.id = f.id
inner join DMdata d on f.id = d.id
where f.filename in (X,Y,Z)
group by f.filename
This works fine, with each resulting-row giving eaither 1 or 0 correctly. The thing to note here is that each table has only one entry(row) for a specific "filename".
Now when I try to add another Inner Join with a table which can have multiple entries(rows) per "filename", the result becomes wrong in a way that only the last 'sum' shows the correct values whereas other 'sums' give wrong values. This second query is:
select f.filename,
sum(case when v.rUser like '%bike%' then 1 else 0 end) as bikeUser,
sum(case when v.rUser like '%Pedestrian%' then 1 else 0 end) as pedestrianUser,
sum(case when d.weather like '%clear%' then 1 else 0 end) as clearWeather,
sum(case when m.extras like '%hat%' then 1 else 0 end) as hatExtras
from VMdata v
inner join files f on v.id = f.id
inner join DMdata d on f.id = d.id
inner join MultiFiledata m on f.id = m.id
where f.filename in (X,Y,Z)
group by f.filename
Any idea to get the right figures for all columns?

If the MultiFiledata table can contain multiple records for any corresponding record in the files table, then you'll need to do the aggregate in a separate query and join that back to the main query.
For example (syntax may not be perfect; concentrate on concept):
select f.filename,
sum(case when v.rUser like '%bike%'
then 1 else 0 end) as bikeUser,
sum(case when v.rUser like '%Pedestrian%'
then 1 else 0 end) as pedestrianUser,
sum(case when d.weather like '%clear%'
then 1 else 0 end) as clearWeather
from VMdata v
inner join files f
on v.id = f.id
inner join DMdata d
on f.id = d.id
inner join (
select id,
sum(case when extras like '%hat%'
then 1 else 0 end) as hatExtras
from MultiFiledata
) m
on f.id = m.id
where f.filename in (X,Y,Z)
group by f.filename;

Your one-to-many join is causing the overall row count to increase, such that when your value=1, you are adding that value more than once. To mitigate this, you may want to use the count function instead, using it to count distinct user IDs. Something like:
Count(distinct case when [logic goes here] then [user ID] else null end) as bikeuser
Then, you are only counting each user once rather than adding each row.

Related

mysql return default value 0 on SUM when there are no results

I am stuck on a query when I add the where clause. As expected I get no results so SUM has nothing to work with. I try to return a default value 0 for countSend and countPending when no rows are matching the where statement.
SELECT
SUM(CASE WHEN orders.status = "send" THEN 1 ELSE 0 END) countSend,
SUM(CASE WHEN orders.status = "pending" THEN 1 ELSE 0 END) countPending
FROM orders
LEFT OUTER JOIN order_posts AS op ON op.order_id = orders.id
LEFT OUTER JOIN posts AS p ON p.id = op.post_id
WHERE (orders.id LIKE "%Shop 3%") OR (p.title LIKE "%Shop 3%")
GROUP BY orders.id
LIMIT 1
I'm not quite sure what you are trying to accomplish, but the GROUP BY may not be necessary. The following will always return one row:
SELECT SUM(o.status = 'send') as countSend,
SUM(o.status = 'pending') as countPending
FROM orders o LEFT OUTER JOIN
order_posts op
ON op.order_id = o.id LEFT OUTER JOIN
posts p
ON p.id = op.post_id
WHERE (o.id LIKE '%Shop 3%') OR (p.title LIKE '%Shop 3%');
If the WHERE clause filters everything out, you will still get one row, with NULL values. Use COALESCE() to return 0 instead:
SELECT COALESCE(SUM(o.status = 'send'), 0) as countSend,
COALESCE(SUM(o.status = 'pending'), 0) as countPending
You can use coalesce() which will return the value the first expression evaluates to, when that value isn't NULL or the value of the second expression. (This also goes on for more than two expressions but in your case it's only two).
SELECT coalesce(sum(CASE
WHEN orders.status = "send" THEN
1
ELSE
0
END),
0) countSend,
coalesce(sum(CASE
WHEN orders.status = "pending" THEN
1
ELSE
0
END),
0) countPending
...

Translate MYSQL query to HQL using multiple JOINS

everyone.
I am using grails 3.3.0.M2 framework with mysql as data-source the following sql query is working as expected
SELECT
c.name,
SUM(CASE
WHEN t.status = 'open' THEN 1
ELSE 0
END) 'open',
SUM(CASE
WHEN t.status = 'pending' THEN 1
ELSE 0
END) 'in progress',
SUM(CASE
WHEN t.status = 'closed' THEN 1
ELSE 0
END) 'closed'
FROM
tickets t
INNER JOIN
users u ON t.user_id = u.id
INNER JOIN
user_coordinations uc ON uc.user_id = u.id
INNER JOIN
coordinations c ON c.id = uc.coordination_id
GROUP BY 1
I translated to HQL using implicit JOIN but I am getting the wrong results, here is the hql query:
SELECT
c.name,
SUM(CASE
WHEN t.status = 'open' THEN 1
ELSE 0
END),
SUM(CASE
WHEN t.status = 'pending' THEN 1
ELSE 0
END),
SUM(CASE
WHEN t.status = 'closed' THEN 1
ELSE 0
END)
FROM
Ticket t, User u, UserCoordination uc, Coordination c
WHERE
MONTH(t.dateCreated) = :month
GROUP BY 1
In order to get the right results stack overflow users help me to understand that the query needs to use explicit JOINS, here the question: Group by a field that does not belongs to the consulted table
Right now I am trying with the following query:
SELECT
c.name,
SUM(CASE
WHEN t.status = 'open' THEN 1
ELSE 0
END),
SUM(CASE
WHEN t.status = 'pending' THEN 1
ELSE 0
END),
SUM(CASE
WHEN t.status = 'closed' THEN 1
ELSE 0
END)
FROM
Ticket t
INNER JOIN
User u
INNER JOIN
UserCoordination uc
INNER JOIN
Coordination c
WHERE
MONTH(t.dateCreated) = :month
GROUP BY 1
But i am getting a com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException with the caused message You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'inner join user_coordinations usercoordi2_ on inner join coordinations coordinat' at line 1
Thanks for your help and time
SELECT new map(
c.name as name,
(CASE
WHEN t.status = 'open' THEN 1
ELSE 0
END) as open,
(CASE
WHEN t.status = 'pending' THEN 1
ELSE 0
END) as pending,
(CASE
WHEN t.status = 'closed' THEN 1
ELSE 0
END) as closed,
SUM(open) as openSum,
SUM(pending) as pendingSum,
SUM(closed) as closedSum
)
FROM
Ticket t
left join t.user u left join u.userCoordination uc left join uc.coordination c
WHERE
MONTH(t.dateCreated) = :month
//GROUP BY 1
What you had had lots missing above is more like what you need, you need
select new map(i.item as item... if you compare the basics of this with what you had and what i tried to do you can see why you had errors.
unsure about your group by it should be group by something. Wasn't sure by inner join if you just meant a join if that was the case leave out all the left join since left join attempts to connect and get any null hasMany relations etc.

SQL select column multiple times with different conditions

I have a table with Incidents that contains column for number injured in the incident and column for number killed in that incident. Incident table is linked with Participants table where you can find a column for age of the person and there is another table ParticipantTypes with column for type(man(id = 1), woman(id = 2) for example).
So, I want to retrieve column number injured, column number killed where type is a man and next to them column number injured, column number killed where type is a woman, all group by age.
select
ip.age as age,
sum(i.number_injured) as injured,
sum(i.number_killed) as killed
from Incidents i
inner join Participants ip on i.id = ip.incident_id
inner join ParticipantTypes ipt on ip.type_id = ipt.id
where ipt.id = 1
group by ip.age
the following query works and retrieve results only for ParticipantTypes.type = 1 (man)
I want it something like
select
ip.age as age,
sum(i.number_injured) as injured where ipt.id = 1,
sum(i.number_killed) as killed where ipt.id = 1,
sum(i.number_injured) as injured where ipt.id = 2,
sum(i.number_killed) as killed where ipt.id = 2,
from Incidents i
inner join Participants ip on i.id = ip.incident_id
inner join ParticipantTypes ipt on ip.type_id = ipt.id
group by ip.age
select
ip.age as age,
sum(case when ipt.id=1 then i.number_injured else 0 end) as injured_man,
sum(case when ipt.id=1 then i.number_killed else 0 end) as killed_man,
sum(case when ipt.id=2 then i.number_injured else 0 end) as injured_woman,
sum(case when ipt.id=2 then i.number_killed else 0 end) as killed_woman
from
Incidents i
inner join
Participants ip on i.id = ip.incident_id
inner join
ParticipantTypes ipt on ip.type_id = ipt.id
group by ip.age
Example result:
age injured_man killed_man injured_woman killed_woman
----------- ----------- ----------- ------------- ------------
30 10 1 3 1
31 12 1 1 6
32 14 2 4 4
Try this....
select
ip.age as age,
sum(case when ipt.id=1then i.number_injured else 0
end) as injured_by_man,
sum(case when ipt.id=1then i.number_killed else 0
end) as killed_by_man,
sum(case when ipt.id=2 then i.number_injured else 0
end) as injured_by_woman,
sum(case when ipt.id=2 then i.number_killed else 0
end) as killed_by_woman
from Incidents i
inner join Participants ip on i.id = ip.incident_id
inner join ParticipantTypes ipt on ip.type_id = ipt.id
group by ip.age
select
ip.age as age,
sum(if(ipt.id=1,i.number_injured,0)) as injured_by_man,
sum(if(ipt.id=1,i.number_killed,0) as killed_by_man,
sum(if(ipt.id=2,i.number_injured,0)) as injured_by_woman,
sum(if(ipt.id=2,i.number_killed,0)) as killed_by_woman
from Incidents i
inner join Participants ip on i.id = ip.incident_id
inner join ParticipantTypes ipt on ip.type_id = ipt.id
group by ip.age
Note: Above mentioned code is not tested. It may contain small syntactic error.
You are filtering out woman records with ipt.id = 1 condition both in inner join and where.
Also, you are grouping by age which is not something you want, as i understood from question. So, your final query should be.
EDIT: As per the updated details in question, following is the query
select
ip.age,
sum(case when ipt.id =1 then i.number_injured else 0 end) as men_injured,
sum(case when ipt.id =1 then i.number_killed else 0 end) as men_killed,
sum(case when ipt.id =2 then i.number_injured else 0 end) as women_injured,
sum(case when ipt.id =2 then i.number_killed else 0 end) as women_killed
from Incidents i
inner join Participants ip on i.id = ip.incident_id
inner join ParticipantTypes ipt on ip.type_id = ipt.id
group by ip.age

MYSQL How to count elements grouped by type

I have a problem with a query:
I have a list of stores, each of these stores has members and there are various categories of membership (Bronze, silver, gold ...)
The tables are: 'shops', 'members', 'membership_cards'.
shops: id, name
members: id, shops_id, membership_id, first_name, last_name
membership_cards: id, description
I need to extract the count of members, grouped by membership of each stores. Can I do this without using a server side language?
The final result should be something like:
Store's name, n°bronze members, n°silver_members, n°gold_members ....
Based on what you provided, you want a query like:
select shopid,
sum(case when c.cardtype = 'Bronze' then 1 else 0 end) as Bronze,
sum(case when c.cardtype = 'Silver' then 1 else 0 end) as Silver,
sum(case when c.cardtype = 'Gold' then 1 else 0 end) as Gold
from shops s left outer join
members m
on s.shopid = m.shopid left outer join
cards c
on c.memberid = m.memberid
group by shopid
If you want to know the number of members, rather than of cards in each group (if members can have more than one card), then replace the sum() expression with:
count(case when c.cardtype = 'Bronze' then m.memberid end)
Without knowing your database schema, it's a bit hard to answer that question, but something like the following should do the job:
SELECT shop.name,
SUM(CASE WHEN membership_cards.category = 'Bronze' THEN 1 ELSE 0 END) AS Bronze,
SUM(CASE WHEN membership_cards.category = 'Silver'THEN 1 ELSE 0 END) AS Silver,
SUM(CASE WHEN membership_cards.category = 'Gold' THEN 1 ELSE 0 END) AS Gold
FROM shops
INNER JOIN members
ON shop.id = members.shopid
INNER JOIN membership_cards
ON members.id = membership_cards.memberid
GROUP BY shop.name
Just change the column names to the names you are using.
SELECT B.name,A.Bronze,A.Silver,A.Gold
FROM
(
SELECT S.id,
SUM(IF(IFNULL(C.cardtype,'')='Bronze',1,0)) Bronze,
SUM(IF(IFNULL(C.cardtype,'')='Silver',1,0)) Silver,
SUM(IF(IFNULL(C.cardtype,'')='Gold' ,1,0)) Gold
FROM shops S
LEFT JOIN members M ON S.id = M.shops_id
LEFT JOIN membership_cards C ON M.membership_id = C.id
GROUP BY S.id
) A
INNER JOIN shops B USING (id);
I used the IFNULL function in case any member has no cards

Mysql COUNT result rows for a related table

I have
users
------------------------
id | name | other_stuff.....
.
engagement
------------------------
user_id | type_code |
type_code is a varchar, but either A, B, C or NULL
[ EDIT for clarity: Users can have many engagements of each type code. SO I want to count how many they have of each. ]
I want to return ALL user rows, but with a count of A, B and C type engagements. E.g.
users_result
------------------------
user_id | user_name | other_stuff..... | count_A | count_B | count_C |
I've done quite a bit of searching, but found the following issues with other solutions:
The "other_stuff..." is actually grouped / concatenated results from a dozen other joins, so it's a bit of a monster already. So I need to be able to just add the additional fields to the pre-existing "SELECT ...... FROM users..." query.
The three additional required bits of data all come from the same engagement table, each with their own condition. I havent found anything to allow me to use the three conditions on the same related table.
Thanks
[edit]
I tried to simplify the question so people didn't have to look through loads of unnecessary stuff, but seems I might not have given enough info. Here is 'most' of the original query. I've taken out a lot of the selected fields as there are loads, but I've left most of the joins in so you can see basically what is actually going on.
SELECT
user.id,
user.first_name,
user.second_name,
GROUP_CONCAT(DISTINCT illness.id ORDER BY illness.id SEPARATOR ',' ) AS reason_for_treatment,
IF(ww_id=1000003, 1,'') as user_refused_program,
Group_CONCAT(DISTINCT physical_activity.name SEPARATOR ', ') AS programme_options,
COUNT(CASE WHEN engagement_item.type_code LIKE 'wm6%' THEN 1 ELSE NULL END) as count_A,
COUNT(CASE WHEN engagement_item.type_code LIKE 'wm12%' THEN 1 ELSE NULL END) as count_B,
COUNT(CASE WHEN engagement_item.type_code LIKE 'wm6%' THEN 1 ELSE NULL END) as count_C
FROM `user`
LEFT JOIN session AS session_induction ON (user.id = session_induction.user_id AND session_induction.session_type_id = 3)
LEFT JOIN stats AS stats_induction ON session_induction.id = stats_induction.session_id
LEFT JOIN session AS session_interim ON (user.id = session_interim.user_id AND session_interim.session_type_id = 4)
LEFT JOIN stats AS stats_interim ON session_interim.id = stats_interim.session_id
LEFT JOIN session AS session_final ON (user.id = session_final.user_id AND session_final.session_type_id = 5)
LEFT JOIN stats AS stats_final ON session_final.id = stats_final.session_id
LEFT JOIN user_has_illness ON user.ID = user_has_illness.user_id
LEFT JOIN illness ON user_has_illness.illness_id = illness.id
LEFT JOIN user_has_physical_activity ON user.ID = user_has_physical_activity.user_id
LEFT JOIN physical_activity ON user_has_physical_activity.physical_activity_id = physical_activity.id
LEFT JOIN engagement_item ON user.ID = engagement_item.user_ID
WHERE (user.INDUCTION_DATE>='2010-06-09' AND user.INDUCTION_DATE<='2011-06-09' AND user.archive!='1' )
GROUP BY user.id, engagement_item.user_id
It's worth mentioning that it works fine - returns all users with all details required. Except for the count_A B and C cols.
[edit added slightly more simplified query below]
Stripped out the unrelated joins and selects.
SELECT
user.id,
user.first_name,
COUNT(CASE WHEN engagement_item.type_code LIKE 'wm6%' THEN 1 ELSE NULL END) as count_A,
COUNT(CASE WHEN engagement_item.type_code LIKE 'wm12%' THEN 1 ELSE NULL END) as count_B,
COUNT(CASE WHEN engagement_item.type_code LIKE 'wm6%' THEN 1 ELSE NULL END) as count_C
FROM `user`
LEFT JOIN engagement_item ON user.ID = engagement_item.user_ID
GROUP BY user.id, engagement_item.user_id
SELECT e.user_id, u.name,
COUNT(CASE type_code WHEN 'A' THEN 1 ELSE NULL END) as count_A,
COUNT(CASE type_code WHEN 'B' THEN 1 ELSE NULL END) as count_B,
COUNT(CASE type_code WHEN 'C' THEN 1 ELSE NULL END) as count_C
FROM engagement e join users u on (e.user_id = u.id)
GROUP BY e.user_id, u.name
I would use COUNT instead of SUM just because that is what it is made for, counting things when not NULL.
SELECT
user.id,
user.first_name,
user.second_name,
GROUP_CONCAT(DISTINCT illness.id ORDER BY illness.id SEPARATOR ',' ) AS reason_for_treatment,
IF(ww_id=1000003, 1,'') as user_refused_program,
Group_CONCAT(DISTINCT physical_activity.name SEPARATOR ', ') AS programme_options,
ei.count_A, ei.count_B, ei.count_C
FROM `user`
LEFT JOIN ( SELECT user_id
, COUNT(CASE WHEN engagement_item.type_code LIKE 'wm6%' THEN 1 ELSE NULL END) as count_A
, COUNT(CASE WHEN engagement_item.type_code LIKE 'wm12%' THEN 1 ELSE NULL END) as count_B
, COUNT(CASE WHEN engagement_item.type_code LIKE 'wm6%' THEN 1 ELSE NULL END) as count_C
FROM engagement_item
GROUP BY userid ) ei
LEFT JOIN session AS session_induction ON (user.id = session_induction.user_id AND session_induction.session_type_id = 3)
LEFT JOIN stats AS stats_induction ON session_induction.id = stats_induction.session_id
LEFT JOIN session AS session_interim ON (user.id = session_interim.user_id AND session_interim.session_type_id = 4)
LEFT JOIN stats AS stats_interim ON session_interim.id = stats_interim.session_id
LEFT JOIN session AS session_final ON (user.id = session_final.user_id AND session_final.session_type_id = 5)
LEFT JOIN stats AS stats_final ON session_final.id = stats_final.session_id
LEFT JOIN user_has_illness ON user.ID = user_has_illness.user_id
LEFT JOIN illness ON user_has_illness.illness_id = illness.id
LEFT JOIN user_has_physical_activity ON user.ID = user_has_physical_activity.user_id
LEFT JOIN physical_activity ON user_has_physical_activity.physical_activity_id = physical_activity.id
LEFT JOIN engagement_item ON user.ID = engagement_item.user_ID
WHERE (user.INDUCTION_DATE>='2010-06-09' AND user.INDUCTION_DATE<='2011-06-09' AND user.archive!='1' )
GROUP BY user.id, engagement_item.user_id, ei.count_A, ei.count_B, ei.count_C
Something like this perhaps?
select e.user_id, u.name,
sum(case e.type_code when 'A' then 1 else 0 end) as count_A,
sum(case e.type_code when 'B' then 1 else 0 end) as count_B,
sum(case e.type_code when 'C' then 1 else 0 end) as count_C
from engagement e join users u on (e.user_id = u.id)
group by e.user_id, u.name
The interesting part is the use of CASE inside the SUM to split the counting into three chunks.