MySQL Set 0 if null (SUM CASE) doesn't work - mysql

I want to count of condition from all employees of each area, which conditions has 2 parameters, WELL AND UNWELL. Like this,
conditions | area | count_conditions
Well AREA1 1
UNWELL AREA1 0
Well AREA2 5
UNWELL AREA2 1
...
This is the closest so far.
SELECT a.conditions, k.area,
SUM(CASE WHEN a.conditions IS NOT NULL THEN 1 ELSE 0 END) AS count_conditions
FROM tb_attended a
INNER JOIN tb_employees k ON a.nrp = k.nrp
AND a.date = '2020-07-20'
GROUP BY k.area, a.conditions
My code above is running well, but if there is no entry of UNWELL OR WELL in some area, then that conditions does not appear. Like this.
conditions | area | count_conditions
Well AREA1 1
Well AREA2 5
UNWELL AREA2 1
...
This is example data that I use,
SQL Fiddle
Any suggest?
Thank you.

First you need a CROSS join of the distinct areas to the distinct conditions and then LEFT joins of the tables:
SELECT t1.condition, t2.area,
COUNT(k.nrp) AS count_conditions
FROM (SELECT DISTINCT `condition` FROM tb_attended) t1
CROSS JOIN (SELECT DISTINCT Area FROM tb_employees) t2
LEFT JOIN tb_attended a ON a.condition = t1.condition
LEFT JOIN tb_employees k ON k.area = t2.area AND a.nrp = k.nrp AND a.date = '2020-07-20'
GROUP BY t1.condition, t2.area
ORDER BY t2.area, t1.condition DESC
See the demo.

Use left join
SELECT a.conditions, k.area,
SUM(CASE WHEN a.conditions IS NOT NULL THEN 1 ELSE 0 END) AS count_conditions
FROM tb_attended a
left JOIN tb_employees k
ON a.nrp = k.nrp
AND a.date = '2020-07-20'
GROUP BY k.area, a.conditions

You can use LEFT JOIN without redundant conditional statement, with SUM() aggregation :
SELECT a.conditions, k.area,
SUM(a.conditions IS NOT NULL) AS count_conditions
FROM tb_attended a
LEFT JOIN tb_employees k
ON a.nrp = k.nrp
AND a.date = '2020-07-20'
GROUP BY k.area, a.conditions

I think you want a left join:
SELECT a.conditions, k.area, COUNT(a.conditions) AS count_conditions
FROM tb_employees k LEFT JOIN
tb_attended a
ON a.nrp = k.nrp AND a.date = '2020-07-20'
GROUP BY k.area, a.conditions;
EDIT:
I see. You want to get all rows, even those with no matches. Use a CROSS JOIN to generate the combinations of areas and conditions that you want. Then use LEFT JOIN to match to the existing data:
SELECT c.condition, e.area, COUNT(a.nrp) AS count_conditions
FROM (SELECT DISTINCT a.condition
FROM tb_attended a
) c CROSS JOIN
(SELECT DISTINCT area
FROM tb_employees
) e LEFT JOIN
tb_employees k
ON k.area = e.area LEFT JOIN
tb_attended a
ON a.nrp = k.nrp AND
a.condition = c.condition AND
a.date = '2020-07-20'
GROUP BY c.condition, e.area;
Here is a db<>fiddle.

Related

How to return the count of an aggregate?

I am trying to do something pretty simple but I am drawing a blank on what I am doing wrong. I have a query that returns a last name, an id, and a count of how many times that last name has viewed the id. I want a result of just one row that counts how many different id's were viewed.
I apologize if this has been asked before, I tried looking but I may have not been searching the right terms.
Select a.employeeLastName, COUNT(Distinct d.id) as counter, COUNT(a.employeeId) as avCount
From tblEmployees a
LEFT JOIN tblEvents b on b.employeeId = a.employeeId
LEFT JOIN tblEventsType c on c.typeId = b.typeId
LEFT JOIN assets d on d.id = b.assetId
WHERE a.employeeId = 138050
AND d.type = 'Article'
and c.typeId = 40
group by a.employeeId, d.id, a.employeeLastName
ORDER BY avCount DESC
The result of that code is a row for each id. I need one row that counts all the id's returned.
Edit:
new result
empID counter avCount
138050 1 24
138050 1 23
138050 1 20
138050 1 14
138050 1 7
You need to group by a.employeeId, a.employeeLastName only and count distinct ids:
Select a.employeeId, a.employeeLastName, COUNT(DISTINCT d.id) as counter
From tblEmployees a
LEFT JOIN tblEvents b on b.employeeId = a.employeeId
LEFT JOIN tblEventsType c on c.typeId = b.typeId
LEFT JOIN assets d on d.id = b.assetId
WHERE a.employeeId = 138050
AND d.type = 'Article'
and c.typeId = 40
group by a.employeeId, a.employeeLastName

i want to display all records from 2 or more queries which are not in left queries too

i have queries like this
SET #curr_date = '2017-03-23';
SELECT
curr_week.mid AS MID,
curr_week.EDC AS Merchant_Name ,
COALESCE(curr_week.amount,0) AS Total_Amount_Curr_Week,
COALESCE(curr_week.total_trx,0) AS Total_Trx_Curr_Week,
COALESCE(curr_week.total_user,0) AS Total_User_Curr_Week,
COALESCE(last_week.amount,0) AS Total_Amount_Last_Week,
COALESCE(last_week.total_trx,0) AS Total_Trx_Last_Week,
COALESCE(last_week.total_user,0) AS Total_User_Last_Week
FROM
(
SELECT a.*, b.total_user
FROM
(
SELECT a1.owner_name AS MID, m.name AS EDC,SUM(t1.amount) AS amount, COUNT(t1.id) AS total_trx
FROM members m
JOIN accounts a1 ON a1.member_id = m.id
JOIN transfers t1 ON a1.id = t1.to_account_id
WHERE DATE(t1.DATE) = (#curr_date - INTERVAL 1 DAY)
GROUP BY a1.owner_name
) AS a
JOIN
(-- get total user
SELECT COUNT(r.ecash_no) AS total_user, r.mid, r.merchant_name
FROM
(
SELECT a.`owner_name` AS ecash_no,
a1.owner_name AS MID,
m.name AS merchant_name
FROM accounts a1
JOIN transfers t1 ON a1.id = t1.to_account_id
JOIN members m ON a1.member_id = m.id
JOIN accounts a ON a.id = t1.from_account_id
WHERE DATE(t1.date) = (#curr_date - INTERVAL 1 DAY)
GROUP BY a.owner_name,m.`name`
) AS r
GROUP BY r.mid
) AS b ON a.mid = b.mid
) AS curr_week
JOIN
(
-- last week
SELECT a.*, b.total_user
FROM
(
SELECT a1.owner_name AS MID, m.name AS EDC,SUM(t1.amount) AS amount, COUNT(t1.id) AS total_trx
FROM members m
JOIN accounts a1 ON a1.member_id = m.id
JOIN transfers t1 ON a1.id = t1.to_account_id
WHERE DATE(t1.DATE) = (#curr_date - INTERVAL 1 DAY) - INTERVAL 1 WEEK
GROUP BY a1.owner_name
) AS a
JOIN
(-- get total user
SELECT COUNT(r.ecash_no) AS total_user, r.mid, r.merchant_name
FROM (
SELECT a.`owner_name` AS ecash_no,
a1.owner_name AS MID,
m.name AS merchant_name
FROM accounts a1
JOIN transfers t1 ON a1.id = t1.to_account_id
JOIN members m ON a1.member_id = m.id
JOIN accounts a ON a.id = t1.from_account_id
WHERE DATE(t1.date) = (#curr_date - INTERVAL 1 DAY) - INTERVAL 1 WEEK
GROUP BY a.owner_name,m.`name`
) AS r
GROUP BY r.mid
) AS b ON a.mid = b.mid
) AS last_week ON curr_week.mid = last_week.mid
how can i retrieve all EDC value from joined queries like that.
because if i use join , it displayed only the same values.
and if i use left join, it follows the value from the left query
is there any way to display everything with join?
You can simulate a full outer join of the two tables using the following:
SELECT COALESCE(a.ColA, b.ColA) AS ColA,
COALESCE(a.ColB, b.ColB) AS ColB
FROM tableA a
LEFT JOIN tableB b ON a.ColA = b.ColA
UNION
SELECT COALESCE(a.ColA, b.ColA) AS ColA,
COALESCE(a.ColB, b.ColB) AS ColB
FROM tableA a
RIGHT JOIN tableB b ON a.ColA = b.ColA;
Note: I've assumed that only ColA is the join column. You can add ColB as a join column as well, or use only ColB as a join column. This really depends on the design of your table, but the general approach I gave should still work.
Output:
Demo here:
Rextester

Select 2 counts from a join query

This query always returns "num_of_accounts" the same as num_of_users
but if i remove the second join with lists_user_assignment
num_of_accounts is correct
select lists.*, count(lists_account_assignment.id) as num_of_accounts, count(lists_user_assignment.id) as num_of_users
from lists
left join lists_account_assignment on lists.id = lists_account_assignment.lists_id
left join lists_user_assignment on lists.id = lists_user_assignment.lists_id
where lists.tenant_id = 1
group by lists.id
Not sure if this is the right way to get 2 counts in a query. Also, if this query will be expensive or not. Would appreciate help.
Try using a COUNT(DISTINCT ..):-
SELECT lists.*,
Count(DISTINCT lists_account_assignment.id) AS num_of_accounts,
Count(DISTINCT lists_user_assignment.id) AS num_of_users
FROM lists
LEFT JOIN lists_account_assignment
ON lists.id = lists_account_assignment.lists_id
LEFT JOIN lists_user_assignment
ON lists.id = lists_user_assignment.lists_id
WHERE lists.tenant_id = 1
GROUP BY lists.id
try this..
SELECT L.*, count(Acc.id) AS NoOfAccounts
FROM lists L
LEFT JOIN lists_account_assignment Acc
ON L.id = Acc.lists_id
LEFT JOIN lists_user_assignment U
ON L.id = U.lists_id
AND L.tenant_id = 1
GROUP BY L.id

SELECT all rows by WHERE clause

I have the following query which works (without the "WHERE stats.dt" part). I get all users with their data.
My problem is that this query of course results in rows ONLY with users that have stats.dt > $timestampnow-$maxdays_data). But I need ALL users but their values of SUM(upload) or SUM(download) need only to be fetched when stats.dt is larger than tstamp-maxdays. The other rows with values of upload and download where stats.dt is smaller than what I need, can be ignored.
An example would be that the user with nodeid 2 would not be selected because his dt is too small. I do want the user to be selected but just not with data or upload values (they can be 0).
The stats table looks like this
nodeid | dt | upload | download
----------------------------------------
1 | 1381699533 | 345345 | 42324234
1 | 1382899152 | 7575 | 574234
1 | 1380699533 | 764534 | 7235232
2 | 1372899152 | 71455 | 124123
I don't know where to start looking how to solve this so maybe somebody out there can point me in the right direction. Thanks!
SELECT b.id, b.lastname, b.name, c.balance, a.maxdebt, b.warndata, b.warndownload, b.warnupload, b.warndebt, b.cutoffdata, b.cutoffdownload, b.cutoffupload, b.cutoffdebt, b.data, b.download, b.upload, b.warning, b.access, b.cutoffstop
FROM (
SELECT customers.id AS id, SUM(tariffs.value) AS maxdebt
FROM tariffs
LEFT JOIN assignments ON tariffs.id = assignments.tariffid
RIGHT JOIN customers ON assignments.customerid = customers.id
GROUP BY id
) a
JOIN (
SELECT customers.id AS id, UPPER(lastname) AS lastname, customers.name AS name, SUM(stats.upload+stats.download) AS data, SUM(stats.download) AS download, SUM(stats.upload) AS upload, customers.cutoffstop, warndata, warndownload, warnupload, warndebt, cutoffdata, cutoffdownload, cutoffupload, cutoffdebt, nodes.warning, nodes.access
FROM customers
LEFT JOIN nodes ON customers.id = nodes.ownerid
LEFT JOIN stats ON nodes.id = stats.nodeid
LEFT JOIN customerwarnings ON customers.id = customerwarnings.id
WHERE stats.dt > ($timestampnow-$maxdays_data)
GROUP BY id
) b ON a.id = b.id
JOIN (
SELECT customerid, SUM(cash.value) AS balance
FROM cash
GROUP BY customerid
) c ON b.id = c.customerid
Here's a brute force way of doing it. It can almost certainly be simplified, but without knowing more about the table and foreign key structures it's hard to be sure.
What I've done is replace sum(stats.download) with sum(case when stats.dt > ($timestampnow-$maxdays_data) then s.download end) and similarly for upload. I've also changed the join on b to be an outer join:
Select
b.id,
b.lastname,
b.name,
c.balance,
a.maxdebt,
b.warndata,
b.warndownload,
b.warnupload,
b.warndebt,
b.cutoffdata,
b.cutoffdownload,
b.cutoffupload,
b.cutoffdebt,
b.data,
b.download,
b.upload,
b.warning,
b.access,
b.cutoffstop
From (
Select
c.id,
sum(t.value) as maxdebt
From
tariffs t
left join
assignments a
on t.id = a.tariffid
right join
customers
on a.customerid = c.id
Group by
c.id
) a left outer join (
Select
c.id,
upper(lastname) as lastname,
c.name,
sum(s.upload + s.download) as data,
sum(case when stats.dt > ($timestampnow-$maxdays_data) then s.download end) as download,
sum(case when stats.dt > ($timestampnow-$maxdays_data) then s.upload end) as upload,
c.cutoffstop,
warndata,
warndownload,
warnupload,
warndebt,
cutoffdata,
cutoffdownload,
cutoffupload,
cutoffdebt,
n.warning,
n.access
From
customers c
left join
nodes n
on c.id = n.ownerid
left join
stats s
on n.id = s.nodeid
left join
customerwarnings w
on c.id = w.id
Group By
c.id
) b
On a.id = b.id
inner join (
Select
customerid,
sum(cash.value) as balance
From
cash
Group By
customerid
) c
on a.id = c.customerid

Performance of the SELECTs

I need some help performing my SELECTs. I made an SQL fiddle to show you the database.
I need to perform two queries but they didn't work fine:
First:
SELECT s.id, s.day_of_week, s.title
FROM slots s
LEFT JOIN bookings_has_slots bhs
ON s.id = bhs.slot_id
LEFT JOIN bookings b
ON bhs.booking_id = b.id
WHERE NOT EXISTS (
SELECT null
FROM bookings_has_slots bhs2
LEFT JOIN bookings b2
ON bhs2.booking_id = b2.id
WHERE b.date = '2018-01-27'
)
AND s.service_id = 3
AND s.day_of_week = DAYOFWEEK('2018-01-27');
Returns:
id day_of_week title
3 7 Après-midi (14h30 - 17h00)
But I expect:
no results, because the three slots possibilities for this day_of_week on the same date are taken.
Second:
SELECT DISTINCT b3.date AS unavailable_date
FROM bookings b3
LEFT JOIN bookings_has_slots bhs3
ON bhs3.booking_id = b3.id
LEFT JOIN slots s3
ON s3.id = bhs3.slot_id
WHERE NOT EXISTS (
SELECT null
FROM slots s
LEFT JOIN bookings_has_slots bhs
ON s.id = bhs.slot_id
LEFT JOIN bookings b
ON bhs.booking_id = b.id
WHERE NOT EXISTS (
SELECT null
FROM bookings_has_slots bhs2
LEFT JOIN bookings b2
ON bhs2.booking_id = b2.id
WHERE b.date = b2.date
)
AND s.service_id = 3
AND s.day_of_week = s3.day_of_week
);
Returns:
unavailable_date
2018-01-17
2018-01-31
2018-01-27
2018-02-03
But I expect:
unavailable_date
2018-01-17
2018-01-31
2018-01-27
Because here, there are two others slots available for the "2018-02-03" not taken by any other bookings.
Here is the sql fiddle :
http://sqlfiddle.com/#!9/89e46/9
Thanks for any help.
It is not the answer yet.
But Could you confirm that this will fix your 1st query:
SELECT s.id, s.day_of_week, s.title
FROM slots s
LEFT JOIN (
SELECT slot_id, booking_id
FROM bookings_has_slots bhs
INNER JOIN bookings b
ON b.id = bhs.booking_id
AND b.date = '2018-02-03'
) bhs
ON s.id = bhs.slot_id
WHERE s.day_of_week = DAYOFWEEK('2018-02-03')
AND bhs.booking_id IS NULL;
2nd query:
http://sqlfiddle.com/#!9/53a998/1
SELECT d.date unavailable_date, d.used_slots
FROM (
SELECT b.date, COUNT(b.id) used_slots
FROM bookings b
GROUP BY b.date
) d
LEFT JOIN slots s
ON s.day_of_week = DAYOFWEEK(d.date)
GROUP BY d.date
HAVING d.used_slots = COUNT(s.id);