Multiple Left Joins have affected my SUM(TIMESTAMPDIFF calculation - mysql

I am currently trying to create a report for the amount of time in total some individuals on my PHPBB3 forum have booked in, for the last week. Initially, I had the following query that worked as expected:
SELECT forum_users.username, SUM(TIMESTAMPDIFF(SECOND, schedule_slots.time_starting, schedule_slots.time_finishing)) AS seconds
FROM forum_users
LEFT JOIN schedule_slots
ON forum_users.user_id = schedule_slots.user_id
AND schedule_slots.time_starting >= (CURDATE() - INTERVAL 1 WEEK)
AND schedule_slots.is_del = 0
AND schedule_slots.channel = 0
WHERE (forum_users.group_id = 8 OR forum_users.group_id = 5 OR forum_users.group_id = 14)
GROUP BY forum_users.username
ORDER BY upper(forum_users.username)
However, when I go to join another table, the timestamp difference ends up being incorrectly calculated (it's higher), here's my newer non working statement:
SELECT forum_users.username, SUM(TIMESTAMPDIFF(SECOND, schedule_slots.time_starting, schedule_slots.time_finishing)) AS seconds, group_concat(DISTINCT forum_user_group.group_id) AS user_groups
FROM forum_users
LEFT JOIN schedule_slots
ON forum_users.user_id = schedule_slots.user_id
AND schedule_slots.time_starting >= (CURDATE() - INTERVAL 1 WEEK)
AND schedule_slots.is_del = 0
AND schedule_slots.channel = 0
LEFT JOIN forum_user_group
ON forum_user_group.user_id = forum_users.user_id
WHERE (forum_users.group_id = 8 OR forum_users.group_id = 5 OR forum_users.group_id = 14 OR forum_users.group_id = 12)
GROUP BY forum_users.username
ORDER BY upper(forum_users.username)
I'm drawing a blank on this one, and your help is greatly appreciated.

The timestamp difference is calculated right, but you multiply it by number of groups the user is in.
I would try:
SELECT forum_users.username, SUM(TIMESTAMPDIFF(SECOND, schedule_slots.time_starting, schedule_slots.time_finishing)) AS seconds,
(select group_concat(DISTINCT group_id) from forum_user_group WHERE forum_user_group.user_id = forum_users.user_id) AS user_groups
FROM forum_users
LEFT JOIN schedule_slots
ON forum_users.user_id = schedule_slots.user_id
AND schedule_slots.time_starting >= (CURDATE() - INTERVAL 1 WEEK)
AND schedule_slots.is_del = 0
AND schedule_slots.channel = 0
WHERE (forum_users.group_id = 8 OR forum_users.group_id = 5 OR forum_users.group_id = 14 OR forum_users.group_id = 12)
GROUP BY forum_users.username, user_groups
ORDER BY upper(forum_users.username)

Related

Selecting an alias with a condition to factor into percentage in mysql

I have a working query but I need to change one line of my select to a different calculation and I'm not sure exactly how I should do it
The current line is Round((sum(curdate() < (d.next_call_date + interval 7 day )) / al.Number_of_dealers) * 100 ,2) as Percentage_up_to_date
This is currently taking the value for Days_within_window and dividing it by total number of dealers. However, I need it to check the next_call_date field in the dealers table and if curdate() < next_call_date + interval 7 day then count it. At this point, all dates are in that window so it should be 100% but it's showing lower percentages becuase of the prior calculation.
It should be saying: Count number of dealers for this CSR where curdate < next_call_date + 7 days, and divide that by total dealers for this CSR.
I tried
Round((sum(al.Number_of_dealers where curdate() < (d.next_call_date + interval 7 day )) / al.Number_of_dealers) * 100 ,2) as Percentage_up_to_date
but the syntax is wrong so i'm getting an error, but that's kind of the logic I'm going for.
Fiddle:
http://sqlfiddle.com/#!9/8be141/1
(Again, target output is 100% at percentage_up_to_date for all records in the fiddle )
Full Query:
select
c.user as UserID,
concat(u.firstn,' ', u.lastn) as name,
count(*) as Number_of_recorded_events,
sum(curdate() < d.next_call_date + interval 7 day) as Days_within_window,
al.Number_of_dealers,
Round((sum(curdate() < (d.next_call_date + interval 7 day )) / al.Number_of_dealers) * 100 ,2) as Percentage_up_to_date
From contact_events c
join users u
on c.user = u.id
join dealers d
on c.dealer_num = d.dealer_num
left join (
SELECT user_id, COUNT(*) AS Number_of_dealers
FROM attr_list AS al
JOIN dealers AS d ON d.csr = al.data
where al.attr_id = 14
GROUP BY user_id) AS al
ON al.user_id = c.user
GROUP BY UserID;
I think I finally understood what you were trying to do:
select
c.user as UserID,
count(*) as NumberOfDailyContacts,
al.NumberOfDealerContacts,
Round((al.NumberOfDealers / al.NumberOfDealerContacts) * 100 ,2) as Percentage_up_to_date
From contact_events c
join users u
on c.user = u.id
join dealers d
on c.dealer_num = d.dealer_num
left join (
SELECT al.user_id,
count(user_id) AS NumberOfDealerContacts,
SUM(CASE WHEN ( d.next_call_date + interval 7 day) THEN 1 ELSE 0 END) AS NumberOfDealers
FROM attr_list AS al
JOIN dealers AS d ON d.csr = al.data
GROUP BY al.user_id) AS al
ON al.user_id = c.user
GROUP BY UserID;
I'm not 100% sure I understand your question, but my intuition is suggesting something like this might be what you are looking for....
Round((sum(IF(curdate() < (d.next_call_date + interval 7 day ), al.Number_of_dealers, 0)) / al.Number_of_dealers) * 100 ,2) as Percentage_up_to_date
....except that seems to result in 200%. (Probably because it doesn't, and couldn't, "distinct" the previous counts' shared dealers?).
UPDATE: From your feedback, I would suggest you can possibly avoid the subquery altogether if you include the JOIN and WHERE from it in the main query, then you could do something like sum(curdate() < (d.next_call_date + interval 7 day )) / count(*).
...though your use of LEFT JOIN makes me question this suggestion.

MySql graph query multiple series aligned to same time x-axis

I have queries that I'm using to make a graph of earnings. But now people are able to earn from two different sources, so I want to separate this out into two lines on the same chart
This one for standard earnings:
SELECT DATE_FORMAT(earning_created, '%c/%e/%Y') AS day, SUM(earning_amount) AS earning_standard
FROM earnings
WHERE earning_account_id = ? AND earning_referral_id = 0 AND (earning_created > DATE_SUB(now(), INTERVAL 90 DAY))
GROUP BY DATE(earning_created)
ORDER BY earning_created
And this one for referral earnings:
SELECT DATE_FORMAT(e.earning_created, '%c/%e/%Y') AS day, SUM(e.earning_amount) AS earning_referral
FROM earnings AS e
INNER JOIN referrals AS r
ON r.referral_id = e.earning_referral_id
WHERE e.earning_account_id = ? AND e.earning_referral_id > 0 AND (e.earning_created > DATE_SUB(now(), INTERVAL 90 DAY)) AND r.referral_type = 0
GROUP BY DATE(e.earning_created)
ORDER BY e.earning_created
How do I get it to run the queries together, so that it outputs two columns/series for the y-axis: earning_standard and earning_referral.
But with them both aligned to the same day column/scale for the x-axis - substituting zero when there are no earnings for a specific series.
You'll need to set both of those queries as subqueries
SELECT DATE_FORMAT(earnings.earning_created, '%c/%e/%Y') AS day,
COALESCE(es.earning_standard, 0) AS earning_standard,
COALESCE(er.earning_referral, 0) AS earning_referral
FROM earnings
LEFT JOIN (SELECT DATE_FORMAT(earning_created, '%c/%e/%Y') AS day,
SUM(earning_amount) AS earning_standard
FROM earnings
WHERE earning_account_id = ?
AND earning_referral_id = 0
AND (earning_created > DATE_SUB(now(), INTERVAL 90 DAY))
GROUP BY DATE(earning_created)) AS es
ON (day = es.day)
LEFT JOIN (SELECT DATE_FORMAT(e.earning_created, '%c/%e/%Y') AS day,
SUM(e.earning_amount) AS earning_referral
FROM earnings AS e
INNER JOIN referrals AS r
ON r.referral_id = e.earning_referral_id
WHERE e.earning_account_id = ?
AND e.earning_referral_id > 0
AND (e.earning_created > DATE_SUB(now(), INTERVAL 90 DAY))
AND r.referral_type = 0
GROUP BY DATE(e.earning_created)) AS er
ON (day = er.day)
WHERE earnings.earning_account_id = ?
ORDER BY day
where I'm assuming earning_account_id = ? is intended to be with a question mark because the language you're using to run the query is replacing it with the actual id before running the query.
SELECT
COALESCE(t1.amount,0) AS link_earnings,
COALESCE(t2.amount,0) AS publisher_referral_earnings,
COALESCE(t3.amount,0) AS advertiser_referral_earnings,
t1.day AS day
FROM
(
SELECT DATE_FORMAT(earning_created, '%c/%e/%Y') AS day, SUM(earning_amount) AS amount
FROM earnings
WHERE earning_referral_id = 0
AND (earning_created > DATE_SUB(now(), INTERVAL 90 DAY))
AND earning_account_id = ?
GROUP BY DATE(earning_created)
) t1
LEFT JOIN
(
SELECT DATE_FORMAT(ep.earning_created, '%c/%e/%Y') AS day, (SUM(ep.earning_amount) * rp.referral_share) AS amount
FROM earnings AS ep
INNER JOIN referrals AS rp
ON ep.earning_referral_id = rp.referral_id
WHERE ep.earning_referral_id > 0
AND (ep.earning_created > DATE_SUB(now(), INTERVAL 90 DAY))
AND ep.earning_account_id = ?
AND rp.referral_type = 0
GROUP BY DATE(ep.earning_created)
) t2
ON t1.day = t2.day
LEFT JOIN
(
SELECT DATE_FORMAT(ea.earning_created, '%c/%e/%Y') AS day, (SUM(ea.earning_amount) * ra.referral_share) AS amount
FROM earnings AS ea
INNER JOIN referrals AS ra
ON ea.earning_referral_id = ra.referral_id
WHERE ea.earning_referral_id > 0
AND (ea.earning_created > DATE_SUB(now(), INTERVAL 90 DAY))
AND ea.earning_account_id = ?
AND ra.referral_type = 1
GROUP BY DATE(ea.earning_created)
) t3
ON t1.day = t3.day
ORDER BY day
Seems to run ok....
You can simply use an outer join to retain earnings even when there is no matching referral, and then conditionally sum depending on whether a referral exists or not:
SELECT DATE_FORMAT(e.earning_created, '%c/%e/%Y') AS day,
SUM(IF(r.referral_id IS NULL, e.earning_amount, 0)) earning_standard,
SUM(IF(r.referral_id IS NULL, 0, e.earning_amount)) earning_referral
FROM earnings e LEFT JOIN referrals r ON r.referral_id = e.earning_referral_id
WHERE e.earning_account_id = ?
AND e.earning_created > CURRENT_DATE - INTERVAL 90 DAY
AND (r.referral_id IS NULL OR r.referral_type = 0)
GROUP BY 1
ORDER BY 1
I've assumed here that earnings.earning_referral_id is never negative, though you can add an explicit test to filter such records if so desired.
I've also changed the filter on earnings.earning_created to base from CURRENT_DATE rather than NOW() to ensure that any earnings created earlier than the current time on the first day of the series are still included—this would typically be what one actually wants, but feel free to change back if not.

Joining four MySQL queries with same columns, different conditions

I have four queries that have 6 columns each. Each query is the same except for the WHERE clause is slightly different in each case. What I would like to see is each queries result for each column next to eachother for comparison.
Example result table headers: time(only one), calls1, calls2, calls3, calls4, work1, work2, work3, work4, tele1, tele2, tele3, tele4, comm1, comm2, comm3, comm4, techs1, techs2,techs3, techs4.
The actual queries are below. Please help me make a comparative query. T
SELECT CONCAT(hour(opened_dt),':',floor(minute(opened_dt)/15)*15) AS time, COUNT(*)/COUNT(DISTINCT DATE(opened_dt)) AS r_calls, ROUND(AVG(work_time),2)/60 AS r_work, ROUND(AVG(tele_time),2)/60 AS r_tele, ROUND(AVG(comm_time),2)/60 AS r_comm, IFNULL(COUNT(*)/COUNT(DISTINCT DATE(opened_dt)),0)/3 AS r_techs
FROM detail_head LEFT JOIN detail_detail ON detail_detail.detail_head_uid = detail_head.detail_head_uid
WHERE call_origins_uid != 5
AND DATE(opened_dt) >= (CURDATE() - INTERVAL 42 DAY)
AND dayname(opened_dt) = 'SUNDAY'
GROUP BY (hour(opened_dt)*100)+floor(minute(opened_dt)/15)
SELECT CONCAT(hour(opened_dt),':',floor(minute(opened_dt)/15)*15) AS time, COUNT(*)/COUNT(DISTINCT DATE(opened_dt)) AS calls, ROUND(AVG(work_time),2)/60 AS work, ROUND(AVG(tele_time),2)/60 AS tele, ROUND(AVG(comm_time),2)/60 AS comm, IFNULL(COUNT(*)/COUNT(DISTINCT DATE(opened_dt)),0)/3 AS techs
FROM detail_head LEFT JOIN detail_detail ON detail_detail.detail_head_uid = detail_head.detail_head_uid
WHERE call_origins_uid != 5
AND DATE(opened_dt) >= (CURDATE() - INTERVAL 42 DAY)
AND dayname(opened_dt) = 'SUNDAY'
AND call_origins_uid = 1
GROUP BY (hour(opened_dt)*100)+floor(minute(opened_dt)/15)
SELECT CONCAT(hour(opened_dt),':',floor(minute(opened_dt)/15)*15) AS time, COUNT(*)/COUNT(DISTINCT DATE(opened_dt)) AS calls, ROUND(AVG(work_time),2)/60 AS work, ROUND(AVG(tele_time),2)/60 AS tele, ROUND(AVG(comm_time),2)/60 AS comm, IFNULL(COUNT(*)/COUNT(DISTINCT DATE(opened_dt)),0)/3 AS techs
FROM detail_head LEFT JOIN detail_detail ON detail_detail.detail_head_uid = detail_head.detail_head_uid
WHERE call_origins_uid != 5
AND DATE(opened_dt) >= (CURDATE() - INTERVAL 42 DAY)
AND dayname(opened_dt) = 'SUNDAY'
AND call_origins_uid = 4
GROUP BY (hour(opened_dt)*100)+floor(minute(opened_dt)/15)
SELECT CONCAT(hour(opened_dt),':',floor(minute(opened_dt)/15)*15) AS time, COUNT(*)/COUNT(DISTINCT DATE(opened_dt)) AS calls, ROUND(AVG(work_time),2)/60 AS work, ROUND(AVG(tele_time),2)/60 AS tele, ROUND(AVG(comm_time),2)/60 AS comm, IFNULL(COUNT(*)/COUNT(DISTINCT DATE(opened_dt)),0)/3 AS techs
FROM detail_head LEFT JOIN detail_detail ON detail_detail.detail_head_uid = detail_head.detail_head_uid
WHERE DATE(opened_dt) >= (CURDATE() - INTERVAL 42 DAY)
AND dayname(opened_dt) = 'SUNDAY'
GROUP BY (hour(opened_dt)*100)+floor(minute(opened_dt)/15)
You could use 'CREATE TEMPORARY TABLE TableName1' for each table then write a query to put the columns in order then drop the tables when your done.

MySQL : Different behaviors depending on WHERE result

I'm modifying an existing project. I want to group 3 mysql request in one.
These 3 request have the same selected data, only the WHERE change.
here's one of the request for exemple :
SELECT COUNT(seg.my_seg1) FROM (
SELECT COUNT(DISTINCT cp.conference_id) as my_seg1 FROM A.Account a
INNER JOIN A.ConferenceParticipant cp ON a.account_id = cp.user_id
INNER JOIN A.Conference cf ON cf.id = cp.conference_id
WHERE cf.`status` = 0
AND DATE_SUB(CURDATE(), INTERVAL 30 DAY) <= cf.creation_timestamp
GROUP BY a.account_id) as seg
WHERE seg.my_seg1 >= 30
The 2 other requests are exactly the same except :
WHERE seg.my_seg1 >= 11 AND seg.my_seg1 <= 30;
and :
WHERE seg.my_seg1 >= 30;
So my question is how can I get 3 different values depending on the WHERE result in the same request ?
Like this you'll have 3 virtual columns:
SELECT
COUNT(IF(seg.my_seg1 >= 30, 1, 0)) AS res1,
COUNT(IF(seg.my_seg1 >= 11 AND seg.my_seg1 < 30, 1, 0)) AS res2
FROM (
SELECT COUNT(DISTINCT cp.conference_id) as my_seg1
FROM A.Account a
JOIN A.ConferenceParticipant cp ON a.account_id = cp.user_id
JOIN A.Conference cf ON cf.id = cp.conference_id
WHERE
cf.`status` = 0
AND DATE_SUB(CURDATE(), INTERVAL 30 DAY) <= cf.creation_timestamp
GROUP BY a.account_id
) AS seg
But you have to revise your filters, you talk about 3 but I only see 2 different ones.

sql select - another select with NULL returns only NULL

I have a like the one below that does a minus from another select. The problem I have is that if the second SELECT (the one to do minus with) returns NULL, the full query returns NULL even if the first query has values. Seems like MySQL thinks 1-NULL=NULL. How can I fix this?
SELECT round(sum(iv.`amount`)) -
(
SELECT round(sum(pay.`amount`)) amountSum
FROM invoice iv
LEFT JOIN invoiceFactoring ivf on ivf.invoiceID=iv.invoiceID
LEFT JOIN user systemuser ON (systemuser.userID=iv.ownerUserID)
LEFT JOIN Payment pay ON (pay.`invoiceID`=iv.`invoiceID`)
WHERE
(iv.invoiceStateID = 2 OR iv.invoiceStateID = 3)
AND
(ivf.`invoiceFactoringProcessID` = 7)
AND (pay.`paymentMethodID` = 1 OR pay.`paymentMethodID` = 2)
AND systemuser.`groupID` = 1
AND iv.`disabled` <> 1
AND ivf.`invoiceExpiryDate` BETWEEN date_add(now(), INTERVAL - 28 DAY) AND date_add(now(), INTERVAL - 21 DAY)
)
FROM invoice iv
LEFT JOIN invoiceFactoring ivf on ivf.invoiceID=iv.invoiceID
LEFT JOIN user systemuser ON (systemuser.userID=iv.ownerUserID)
WHERE
(iv.invoiceStateID = 2 OR iv.invoiceStateID = 3)
AND
(ivf.`invoiceFactoringProcessID` = 7 or ivf.`invoiceFactoringProcessID`)
AND systemuser.`groupID` = 1
AND iv.`disabled` <> 1 /* ta bort de som är inaktiva*/
AND ivf.`invoiceExpiryDate` BETWEEN date_add(now(), INTERVAL - 28 DAY) AND date_add(now(), INTERVAL - 21 DAY)
perhaps you can wrap the "inner" sql with an IFNULL
SELECT round(sum(iv.`amount`)) -
IFNULL((
SELECT round(sum(pay.`amount`)) amountSum
FROM invoice iv
LEFT JOIN invoiceFactoring ivf on ivf.invoiceID=iv.invoiceID
LEFT JOIN user systemuser ON (systemuser.userID=iv.ownerUserID)
LEFT JOIN Payment pay ON (pay.`invoiceID`=iv.`invoiceID`)
WHERE
(iv.invoiceStateID = 2 OR iv.invoiceStateID = 3)
AND
(ivf.`invoiceFactoringProcessID` = 7)
AND (pay.`paymentMethodID` = 1 OR pay.`paymentMethodID` = 2)
AND systemuser.`groupID` = 1
AND iv.`disabled` <> 1
AND ivf.`invoiceExpiryDate` BETWEEN date_add(now(), INTERVAL - 28 DAY)
AND date_add(now(), INTERVAL - 21 DAY)
),0)
FROM invoice iv
LEFT JOIN invoiceFactoring ivf on ivf.invoiceID=iv.invoiceID
LEFT JOIN user systemuser ON (systemuser.userID=iv.ownerUserID)
WHERE
(iv.invoiceStateID = 2 OR iv.invoiceStateID = 3)
AND
(ivf.`invoiceFactoringProcessID` = 7 or ivf.`invoiceFactoringProcessID`)
AND systemuser.`groupID` = 1
AND iv.`disabled` <> 1 /* ta bort de som är inaktiva*/
AND ivf.`invoiceExpiryDate` BETWEEN date_add(now(), INTERVAL - 28 DAY) AND date_add(now(), INTERVAL - 21 DAY)
Doing anything with a null returns null. You need to define what you want to happen if it is null using COALESCE or IFNULL.
Check http://www.w3schools.com/sql/sql_isnull.asp for an example.
Seems like MySQL thinks 1-NULL=NULL.
Yes; and it is correct. "One" minus "unknown" is "unknown". If you want the second subquery's NULL to be treated as 0, then you need to wrap it in a call to the COALESCE operator: change (SELECT ...) to COALESCE((SELECT ...), 0).