SQL - Too many calls to subquery - mysql

The below query is fairly slow, in terms of the subquery selection for the "skill name". When I run a profile against the SQL execution I am getting far too many queries per line from the ACDCallinformation table against the sub query for skillname.
What is the best way of optimising this SQL query or is there a MySQL tool to help with checking on costs for a SQL query and optimising the script?
SELECT
CASE
WHEN(
SELECT
COUNT(ag.`PKEY`) - COUNT(ag.`ANSWERTIME`)
FROM acdcallinformation ag
WHERE (ag.`COMPLETED`) = 1 AND answertime IS NULL AND DATEofcall = DATE(NOW()) AND ag.skillid = acdcallinformation.skillid
) IS NULL
THEN
0
ELSE
(
SELECT COUNT(ag.`PKEY`) - COUNT(ag.`ANSWERTIME`)
FROM acdcallinformation ag
WHERE (ag.`COMPLETED`) = 1 AND answertime IS NULL AND DATEofcall= DATE(NOW()) AND ag.skillid = acdcallinformation.skillid)
END AS 'Lost Calls',
CASE WHEN COUNT(acdcallinformation.idleonqueue) IS NULL THEN 0 ELSE COUNT(acdcallinformation.idleonqueue) END AS 'Total Calls',
CASE WHEN COUNT(acdcallinformation.`ANSWERTIME`) IS NULL THEN 0 ELSE COUNT(acdcallinformation.`ANSWERTIME`) END AS 'Answered',
(
SELECT
skillinfo.skillname
FROM skillinfo
WHERE skillinfo.pkey = acdcallinformation.skillid
) AS Skill,
SEC_TO_TIME(AVG(TIME_TO_SEC(answertime)- TIME_TO_SEC(firstringonqueue))) AS 'Average Answer Time',
SEC_TO_TIME(AVG(TIME_TO_SEC(IDLEONQUEUE) - TIME_TO_SEC(answertime))) AS 'Average Talk Time'
FROM `acdcallinformation` acdcallinformation
WHERE DATEOFCALL = DATE(NOW())
GROUP BY skill;
Not sure the best way show data:
ACDCALLINFORMATION - number of rows currently 3028
INSTIME PKEY DATEOFCALL CONNECTTIME FIRSTRING SKILLID
2012-07-19 14:50:16 19985 2012-07-19 14:50:16 14:50:16 5
SKILLINFO - Average number of rows is 5-10
INSTIME PKEY SKILLNAME
2012-07-01 13:12:01 1 Calls Outgoing
2012-07-01 13:12:01 2 Call Centre
2012-07-01 13:12:01 3 Accounts
2012-07-01 13:12:01 4 Reception
This is the output expected:
"Lost Calls" "Total Calls" "Answered" "Skill" "Average Answer Time" "Average Talk Time"
"1" "2" "1" "Accounts" "00:00:04" "00:00:01"
"0" "5" "5" "Service" "00:00:07" "00:01:20"

Try this, is using inner joins to improve performance and avoid unnecessary subquerys
SELECT
COALESCE(ag.skillcount, 0) AS 'Lost Calls',
CASE WHEN COUNT(acdcallinformation.idleonqueue) IS NULL THEN 0 ELSE COUNT(acdcallinformation.idleonqueue) END AS 'Total Calls',
CASE WHEN COUNT(acdcallinformation.`ANSWERTIME`) IS NULL THEN 0 ELSE COUNT(acdcallinformation.`ANSWERTIME`) END AS 'Answered',
si.skillname AS Skill,
SEC_TO_TIME(AVG(TIME_TO_SEC(answertime)- TIME_TO_SEC(firstringonqueue))) AS 'Average Answer Time',
SEC_TO_TIME(AVG(TIME_TO_SEC(IDLEONQUEUE) - TIME_TO_SEC(answertime))) AS 'Average Talk Time'
FROM `acdcallinformation` acdcallinformation
LEFT JOIN (
SELECT skillid, COUNT(`PKEY`) - COUNT(`ANSWERTIME`) skillcount
FROM acdcallinformation
WHERE (`COMPLETED`) = 1 AND answertime IS NULL AND DATEofcall = DATE(NOW())
) ag ON AND ag.skillid = acdcallinformation.skillid
LEFT JOIN skillinfo si ON si.pkey = acdcallinformation.skillid
WHERE DATEOFCALL = DATE(NOW())
GROUP BY si.skillname;

It looks like you're trying to ensure that NULLs are converted to 0s. Thus:
SELECT
IFNULL(
(SELECT COUNT(ag.`PKEY`) - COUNT(ag.`ANSWERTIME`) FROM acdcallinformation ag
WHERE (ag.`COMPLETED`) = 1 AND answertime IS NULL
AND DATEofcall = DATE(NOW()) AND ag.skillid = acdcallinformation.skillid
), 0) AS 'Lost Calls',
IFNULL(COUNT(acdcallinformation.idleonqueue), 0) AS 'Total Calls',
IFNULL(COUNT(acdcallinformation.`ANSWERTIME`),0) AS 'Answered',
(
SELECT
skillinfo.skillname
FROM skillinfo
WHERE skillinfo.pkey = acdcallinformation.skillid
) AS Skill,
SEC_TO_TIME(AVG(TIME_TO_SEC(answertime)- TIME_TO_SEC(firstringonqueue))) AS 'Average Answer Time',
SEC_TO_TIME(AVG(TIME_TO_SEC(IDLEONQUEUE) - TIME_TO_SEC(answertime))) AS 'Average Talk Time'
FROM `acdcallinformation` acdcallinformation
WHERE DATEOFCALL = DATE(NOW())
GROUP BY skill;
Although, it might be easier to just convert those NULLs to zeros using the language that consumes this data... just a thought.
Also, my reading of the docs for COUNT make me think that it will never return NULL, thus:
SELECT
(SELECT COUNT(ag.`PKEY`) - COUNT(ag.`ANSWERTIME`) FROM acdcallinformation ag
WHERE (ag.`COMPLETED`) = 1 AND answertime IS NULL
AND DATEofcall = DATE(NOW()) AND ag.skillid = acdcallinformation.skillid
) AS 'Lost Calls',
COUNT(acdcallinformation.idleonqueue) AS 'Total Calls',
COUNT(acdcallinformation.`ANSWERTIME`) AS 'Answered',
(
SELECT
skillinfo.skillname
FROM skillinfo
WHERE skillinfo.pkey = acdcallinformation.skillid
) AS Skill,
SEC_TO_TIME(AVG(TIME_TO_SEC(answertime)- TIME_TO_SEC(firstringonqueue))) AS 'Average Answer Time',
SEC_TO_TIME(AVG(TIME_TO_SEC(IDLEONQUEUE) - TIME_TO_SEC(answertime))) AS 'Average Talk Time'
FROM `acdcallinformation` acdcallinformation
WHERE DATEOFCALL = DATE(NOW())
GROUP BY skill;
Finally, I think you can handle your second query with a JOIN
SELECT
IFNULL(
(SELECT COUNT(ag.`PKEY`) - COUNT(ag.`ANSWERTIME`) FROM acdcallinformation ag
WHERE (ag.`COMPLETED`) = 1 AND answertime IS NULL
AND DATEofcall = DATE(NOW()) AND ag.skillid = acdcallinformation.skillid
), 0) AS 'Lost Calls',
IFNULL(COUNT(acdcallinformation.idleonqueue), 0) AS 'Total Calls',
IFNULL(COUNT(acdcallinformation.`ANSWERTIME`),0) AS 'Answered',
skillinfo.skillname AS Skill,
SEC_TO_TIME(AVG(TIME_TO_SEC(answertime)- TIME_TO_SEC(firstringonqueue))) AS 'Average Answer Time',
SEC_TO_TIME(AVG(TIME_TO_SEC(IDLEONQUEUE) - TIME_TO_SEC(answertime))) AS 'Average Talk Time'
FROM `acdcallinformation` acdcallinformation
INNER JOIN skillinfo ON skillinfo.pkey = acdcallinformation.skillid
WHERE DATEOFCALL = DATE(NOW())
GROUP BY skill;

Try this query. The whole query is just a guess but it would be batter if you provided some data. Also i have used id as primary key you need to replace it with your own key. Avoid using subqueries instead use joins they are much faster. Here is the query.
SELECT
IF(l.LDifference IS NULL,0,r.RDifference) AS 'Lost Calls',
IF(COUNT(acdcallinformation.idleonqueue) IS NULL , 0 , COUNT(acdcallinformation.idleonqueue))AS 'Total Calls',
IF(COUNT(acdcallinformation.`ANSWERTIME`) IS NULL,0,COUNT(acdcallinformation.`ANSWERTIME`))AS 'Answered',
(SELECT skillinfo.skillname FROM skillinfo WHERE skillinfo.pkey = acdcallinformation.skillid) AS Skill,
SEC_TO_TIME(AVG(TIME_TO_SEC(a.answertime)- TIME_TO_SEC(a.firstringonqueue))) AS 'Average Answer Time',
SEC_TO_TIME(AVG(TIME_TO_SEC(a.IDLEONQUEUE) - TIME_TO_SEC(a.answertime))) AS 'Average Talk Time'
FROM acdcallinformation as a
INNER JOIN(
SELECT
(COUNT(ag.`PKEY`) - COUNT(ag.`ANSWERTIME`)) as `LDifference`
FROM acdcallinformation ag
WHERE (ag.`COMPLETED`) = 1 AND answertime IS NULL AND DATEofcall = DATE(NOW()) AND ag.skillid = acdcallinformation.skillid
) as l ON l.id = a.id
INNER JOIN(
SELECT (COUNT(ag.`PKEY`) - COUNT(ag.`ANSWERTIME`)) as `RDifference`
FROM acdcallinformation ag
WHERE (ag.`COMPLETED`) = 1 AND answertime IS NULL AND DATEofcall= DATE(NOW()) AND ag.skillid = acdcallinformation.skillid
) as r ON r.id = a.id
WHERE a.DATEOFCALL = DATE(NOW())
GROUP BY skill;

Try This.
Use INNER JOIN, IF() and try to avoid unnecessary subqueries.
SELECT IFNULL(ag.skillcount, 0) AS 'Lost Calls', COUNT(info.idleonqueue) AS 'Total Calls',
COUNT(info.ANSWERTIME) AS 'Answered', si.skillname AS Skill,
SEC_TO_TIME(AVG(TIME_TO_SEC(answertime)- TIME_TO_SEC(firstringonqueue))) AS 'Average Answer Time',
SEC_TO_TIME(AVG(TIME_TO_SEC(IDLEONQUEUE) - TIME_TO_SEC(answertime))) AS 'Average Talk Time'
FROM acdcallinformation AS info
INNER JOIN (
SELECT skillid, COUNT(PKEY)-COUNT(ANSWERTIME) skillcount
FROM acdcallinformation
WHERE COMPLETED = 1 AND DATEofcall = DATE(NOW()) AND answertime IS NULL
) ag ON ag.skillid = info.skillid
INNER JOIN skillinfo si ON si.pkey = info.skillid
WHERE DATEOFCALL = DATE(NOW())
GROUP BY si.skillname;

Related

MySQL - Slow Query when adding multiple derived tables - Optimization

For my query, the two derived tables at the bottom are causing a crazy slow up for this query. The query, as is, takes about 45-55 seconds to execute.. NOW, when i remove just one of those derived tables (it does not matter which one) the query goes down to 0.1 - 0.3 seconds. My questions; Is there an issue with having multiple derived tables? Is there a better way to execute this? My indexes all seem to be correct, I will also include the explain from this query.
select t.name as team, u.name as "REP NAME",
count(distinct activity.id) as "TOTAL VISITS",
count(distinct activity.account_id) as "UNIQUE VISITS",
count(distinct placement.id) as "COMMITMENTS ADDED",
CASE WHEN
count(distinct activity.account_id) = 0 THEN (count(distinct
placement.id) / 1)
else (cast(count(distinct placement.id) as decimal(10,2)) /
cast(count(distinct activity.account_id) as decimal(10,2)))
end as "UNIQUE VISIT TO COMMITMENT %",
case when o.mode='basic' then count(distinct placement.id) else
count(distinct(case when placement.commitmentstatus='fullfilled'
then placement.id else 0 end))
end as "COMMITMENTS FULFILLED",
case when o.mode='basic' then 1 else
(CASE WHEN
count(distinct placement.id) = 0 THEN (count(distinct(case when
placement.commitmentstatus='fullfilled' then placement.id else 0
end)) / 1)
else (cast(count(distinct(case when
placement.commitmentstatus='fullfilled' then placement.id else 0
end)) as decimal(10,2)) / cast(count(distinct placement.id) as
decimal(10,2)))
end) end as "COMMITMENT TO FULFILLMENT %"
from lpmysqldb.users u
left join lpmysqldb.teams t on t.team_id=u.team_id
left join lpmysqldb.organizations o on o.id=t.org_id
left join (select * from lpmysqldb.activity where
org_id='555b918ae4b07b6ac5050852' and completed_at>='2018-05-01' and
completed_at<='2018-06-01' and tag='visit' and accountname is not
null and (status='active' or status='true' or status='1')) as
activity on activity.user_id=u.id
left join (select * from lpmysqldb.placements where
orgid='555b918ae4b07b6ac5050852' and placementdate>='2018-05-01' and
placementdate<='2018-06-01' and (status IN ('1','active','true') or
status is null)) as placement on placement.userid=u.id
where u.org_id='555b918ae4b07b6ac5050852'
and (u.status='active' or u.status='true' or u.status='1')
and istestuser!='1'
group by u.org_id, t.name, u.id, u.name, o.mode
order by count(distinct activity.id) desc
Thank you for assistance!
I have edited below with changing the two bottom joins from joining on subqueries to joining on the table directly. Still yielding the same result.
This is a SLIGHTLY restructured query of your same. Might be simplified as the last two subqueries are all pre-aggregated for your respective counts and count distincts so you can use those column names directly instead of showing all the count( distinct ) embedded throughout the query.
I also tried to simplify the division by multiplying a given count by 1.00 to force decimal-based precision as result.
select
t.name as team,
u.name as "REP NAME",
Activity.DistIdCnt as "TOTAL VISITS",
Activity.UniqAccountCnt as "UNIQUE VISITS",
Placement.DistIdCnt as "COMMITMENTS ADDED",
Placement.DistIdCnt /
CASE WHEN Activity.UniqAccountCnt = 0
THEN 1.00
ELSE Activity.UniqAccountCnt * 1.00
end as "UNIQUE VISIT TO COMMITMENT %",
case when o.mode = 'basic'
then Placement.DistIdCnt
else Placement.DistFulfillCnt
end as "COMMITMENTS FULFILLED",
case when o.mode = 'basic'
then 1
else ( Placement.DistFulfillCnt /
CASE when Placement.DistIdCnt = 0
then 1.00
ELSE Placement.DistIdCnt * 1.00
END TRANSACTION )
END as "COMMITMENT TO FULFILLMENT %"
from
lpmysqldb.users u
left join lpmysqldb.teams t
on u.team_id = t.team_id
left join lpmysqldb.organizations o
on t.org_id = o.id
left join
( select
user_id,
count(*) as AllRecs,
count( distinct id ) DistIdCnt,
count( distinct account_id) as UniqAccountCnt
from
lpmysqldb.activity
where
org_id = '555b918ae4b07b6ac5050852'
and completed_at>='2018-05-01'
and completed_at<='2018-06-01'
and tag='visit'
and accountname is not null
and status IN ( '1', 'active', 'true')
group by
user_id ) activity
on u.id = activity.user_id
left join
( select
userid,
count(*) AllRecs,
count(distinct id) as DistIdCnt,
count(distinct( case when commitmentstatus = 'fullfilled'
then id
else 0 end )) DistFulfillCnt
from
lpmysqldb.placements
where
orgid = '555b918ae4b07b6ac5050852'
and placementdate >= '2018-05-01'
and placementdate <= '2018-06-01'
and ( status is null OR status IN ('1','active','true')
group by
userid ) as placement
on u.id = placement.userid
where
u.org_id = '555b918ae4b07b6ac5050852'
and u.status IN ( 'active', 'true', '1')
and istestuser != '1'
group by
u.org_id,
t.name,
u.id,
u.name,
o.mode
order by
activity.DistIdCnt desc
FINALLY, your inner queries are querying for ALL users. If you have a large count of users that are NOT active, you MIGHT exclude those users from each inner query by adding those join/criteria there too such as...
( ...
from
lpmysqldb.placements
JOIN lpmysqldb.users u2
on placements.userid = u2.id
and u2.status IN ( 'active', 'true', '1')
and u2.istestuser != '1'
where … ) as placement

CASE statement in SQL giving not proper values

I am having abnormal values when I run this part in my sql code. SQL syntax wise, everything is okay with this?
select
COUNT(CASE WHEN bt.idBillingStatus = 2
THEN 1
ELSE NULL END) AS successfulbillinghits,
SUM(CASE WHEN bt.idBillingStatus = 2
THEN price
ELSE 0.0 END)
AS old_revenue
from table
Overall Query is this. The result of successfulbillinghits should be equal to timesbilled
SELECT
cs.idCustomerSubscription,
cs.msisdn,
pro.name AS promoterName,
c.name AS ClubName,
c.idClub AS ClubID,
o.name AS operatorName,
o.idOperator AS OperatorID,
co.name AS country,
-- cu.customerSince AS CustomerSince,
cs.subscribeddate AS subscribeddate,
-- cs.subscriptionNotificationSent AS SubNotificationSent,
-- cs.eventId AS EventId,
cs.unsubscribeddate AS unsubscribeddate,
cs.firstBillingDate AS FirstBillingDate,
cs.lastBilledDate As LastBilledDate,
cs.lastAttemptDate AS LastAttemptDate,
-- smp.code AS packageName,
-- o.mfactor AS mmfactor,
-- cs.idSubscriptionSource AS SubscriptionChannel,
-- cs.idUnsubscribeSource AS UnsubscriptionChannel,
-- DATE(bt.creationDate) AS BillingCreationDate,
-- bt.price AS pricePerBilling,
-- cs.lastRetryDate As LastRetryDate,
-- cs.lastRenewalDate AS LastRenewalDate,
-- cs.isActive AS ActiveStatus,
-- COUNT(bt.idBillingTransaction) AS BillingAttempts,
curr.idcurreny_symbol AS CurrencyID,
curr.symbol AS currency,
date(bt.creationDate) AS BillingDate,
cs.lastBilledAmount As LastBilledAmount,
cs.timesbilled,
price,
-- sum(price),
-- revenueShareAmountLocal,
-- o.mfactor,
-- count(IFF (bt.idBillingStatus = 2,1,0)) as otherversion,
count(CASE WHEN bt.idBillingStatus = 2
THEN 1
ELSE 0 END) AS successfulbillinghits,
SUM(CASE WHEN bt.idBillingStatus = 2
THEN price
ELSE 0.0 END)
AS old_revenue
FROM
customersubscription cs
LEFT JOIN
billing_transaction bt
ON CONVERT(cs.msisdn USING latin1) = bt.msisdn
AND cs.idClub = bt.idClub
AND bt.creationDate BETWEEN cs.SubscribedDate AND COALESCE(cs.UnsubscribedDate, now())
INNER JOIN customer cu ON (cs.idCustomer = cu.idcustomer)
INNER JOIN operator o ON (o.idoperator = cu.idoperator)
INNER JOIN country co ON (co.`idCountry` = o.idCountry)
INNER JOIN curreny_symbol curr ON (curr.idcurreny_symbol = co.idCurrencySymbol)
LEFT JOIN Promoter pro ON cs.idPromoter = pro.id
INNER JOIN club_operator_relationships cor ON cor.clubId = cs.idClub
INNER JOIN club c ON c.idClub = cs.idClub
-- INNER JOIN operator op ON op.idOperator = cu.idOperator
WHERE
-- (cs.timesbilled > 0 and cs.subscribeddate < '2016-09-01 00:00:00' )
cs.subscribeddate between '2017-04-20 00:00:00' and '2017-04-21 00:00:00'
AND cs.idClub IN (39)
GROUP BY idCustomerSubscription, ClubName, operatorName, promoterName
Successfulbillinghits is much greater than timesbilled in the result
Instead of COUNTuse SUM, as count counts blanks or nulls also
select
SUM(CASE WHEN bt.idBillingStatus = 2
THEN 1
ELSE 0 END) AS successfulbillinghits,
SUM(CASE WHEN bt.idBillingStatus = 2
THEN price
ELSE 0.0 END)
AS old_revenue
from table
Instead of using CASE, you can use WHERE clause with these aggregate functions, e.g.:
SELECT COUNT(*) as `successfulbillinghits`, SUM(price) as `old_revenue`
FROM table bt
WHERE bt.idBillingStatus = 2;

Combine results from 2 SQL Server queries into 2 columns

I currently have 2 SQL queries:
select
SUM(CASE T1.DOCTYPE
WHEN '1' THEN T1.CURTRXAM *1
WHEN '4' THEN T1.CURTRXAM *-1
WHEN '5' THEN T1.CURTRXAM *-1
WHEN '6' THEN T1.CURTRXAM *-1
END) as [Payables TB]
from PM20000 T1
select
sum(PERDBLNC) as [GL Balance]
from GL10110
where ACTINDX = '130'
which return 2 results like this:
Payables TB
1520512.30
GL Balance
-1520512.30
I would like to combine the results into 2 columns and have a variance column like below -
Payables TB GL Balance Variance
1520512.30 -1520512.30 0.00
Thank you
simply
select
(select
SUM(CASE T1.DOCTYPE
WHEN '1' THEN T1.CURTRXAM *1
WHEN '4' THEN T1.CURTRXAM *-1
WHEN '5' THEN T1.CURTRXAM *-1
WHEN '6' THEN T1.CURTRXAM *-1
END) as [Payables TB]
from PM20000 T1) as Payables TB,
(select
sum(PERDBLNC) as [GL Balance]
from GL10110
where ACTINDX = '130') as GL Balance,
0.00 as Variance
You can wrap these into CTE's to reuse the values to compute the difference. With no join condition you will just need to CROSS JOIN, as long as these return just one row each :
WITH Payables AS
(
SELECT
SUM(
CASE
WHEN T1.DOCTYPE IN ('1') THEN T1.CURTRXAM *1
WHEN T1.DOCTYPE IN ('4','5','6') THEN T1.CURTRXAM *-1
-- ? ELSE
END) as [Payables TB]
FR PM20000 T1
),
Balance AS
(
SELECT
SUM(PERDBLNC) as [GL Balance]
FROM GL10110
WHERE ACTINDX = '130'
)
SELECT
Payables.[Payables TB],
Balance.[GL Balance],
Payables.[Payables TB] + Balance.[GL Balance] AS Variance
FROM
Payables, Balance; -- OR Payables CROSS JOIN Balance
Since you seem to be doing the same projection for T1.DOCTYPE 4, 5 and 6 in the first query, you can replace it with a CASE WHEN x IN (...)

How to add another column returned for complicated MySQL query

I have a rather complicated MySQL query that looks like this:
SELECT CONCAT(pe.first, ' ', pe.last) AS Name,
tp.EmpID as 'Empl ID',
DATE_FORMAT(tp.PunchDateTime, '%m-%d-%Y') AS 'Punch Date',
DATE_FORMAT(tp.PunchDateTime, '%W') AS 'Weekday',
TRUNCATE((SUM(UNIX_TIMESTAMP(PunchDateTime) * (1 - 2 * `In-Out`)) / 3600),2)
AS 'Hours Worked'
FROM timeclock_punchlog tp LEFT JOIN prempl01 pe ON tp.EmpID = pe.prempl
WHERE tp.PunchDateTime >= '2013-06-16' and tp.PunchDateTime < '2013-06-23'
AND tp.EmpID = 1588
GROUP BY date(PunchDateTime), EmpID
ORDER BY Name, `Punch Date` ASC
Now I need to add a 6th column. I need to know how long the employee's lunch was. I think it's going to involve a subquery in the select section, because it would be too complicated to get it any other way. Calculating the 'Hours Worked' was complicated because I needed to calculate the (breakout - clockin) + (clockout - breakin) for each day. Now I need to calculate breakin - breakout for each of those days as well. Here is the structure for the current table for one employee for one day.
PunchID EmpID PunchEvent PunchDateTime In-Out
308 1588 clockin 6/17/2013 6:20:48 AM Checked
313 1588 breakout 6/17/2013 12:15:18 PM Unchecked
315 1588 breakin 6/17/2013 12:43:58 PM Checked
319 1588 clockout 6/17/2013 5:00:37 PM Unchecked
I can't figure out how I would add the lunch break time to the above query. Hopefully I have provided all the information needed.
Update: I have put together a working query that does what I want for a specific day and specific employee. Now what I need is for this query to work for all employees for a specific date range(ie week). Here is the query:
SELECT CONCAT(pe.first, ' ', pe.last) AS Name,
tp.EmpID AS 'Empl ID',
DATE_FORMAT(tp.PunchDateTime, '%m-%d-%Y') AS 'Punch Date',
DATE_FORMAT(tp.PunchDateTime, '%W') AS 'Weekday',
TRUNCATE((SUM(UNIX_TIMESTAMP(PunchDateTime) * (1 - 2 * `In-Out`)) / 3600), 2)
AS 'Hours Worked',
(SELECT TIMEDIFF
((SELECT DATE_FORMAT(tpl.PunchDateTime, '%r') as dTime FROM timeclock_punchlog tpl WHERE tpl.PunchEvent = 'breakin' AND tpl.EmpID = 1588 AND DATE(tpl.PunchDateTime) = '2013-06-17'),
(SELECT DATE_FORMAT(tpl.PunchDateTime, '%r') as dTime FROM timeclock_punchlog tpl WHERE tpl.PunchEvent = 'breakout' AND tpl.EmpID = 1588 AND DATE(tpl.PunchDateTime) = '2013-06-17'))) as 'Lunch'
FROM timeclock_punchlog tp LEFT JOIN prempl01 pe ON tp.EmpID = pe.prempl
WHERE DATE(tp.PunchDateTime) = '2013-06-17'
AND tp.EmpID = 1588
GROUP BY date(PunchDateTime), EmpID
ORDER BY Name, `Punch Date` ASC
And the result:
Name Empl ID Punch Date Weekday Hours Worked Lunch
BRUCE COLEMAN 1588 06-17-2013 Monday 10.18 00:28:40
Now if someone can figure out how to make that query work without specifying the employee ID and without specifying an exact date, make it a date range instead.
You should replace both tpl.EmpID = 1588 with tpl.EmpID = tp.EmpID and remove the AND tp.EmpID = 1588.
Edit:
SELECT CONCAT(pe.first, ' ', pe.last) AS Name,
tp.EmpID AS 'Empl ID',
DATE_FORMAT(tp.PunchDateTime, '%m-%d-%Y') AS 'Punch Date',
DATE_FORMAT(tp.PunchDateTime, '%W') AS 'Weekday',
TRUNCATE((SUM(UNIX_TIMESTAMP(PunchDateTime) * (1 - 2 * `In-Out`)) / 3600), 2) AS 'Hours Worked',
(SELECT TIMEDIFF((SELECT DATE_FORMAT(tpl.PunchDateTime, '%r') as dTime FROM timeclock_punchlog tpl WHERE tpl.PunchEvent = 'breakin' AND tpl.EmpID = tp.EmpID AND DATE(tpl.PunchDateTime) = '2013-06-17'),
(SELECT DATE_FORMAT(tpl.PunchDateTime, '%r') as dTime FROM timeclock_punchlog tpl WHERE tpl.PunchEvent = 'breakout' AND tpl.EmpID = tp.EmpID AND DATE(tpl.PunchDateTime) = '2013-06-17'))) as 'Lunch'
FROM timeclock_punchlog tp LEFT JOIN prempl01 pe ON tp.EmpID = pe.prempl
WHERE DATE(tp.PunchDateTime) = '2013-06-17'
GROUP BY date(PunchDateTime), EmpID
ORDER BY Name, `Punch Date` ASC

Is it possible to use a named select in the where clause?

I have a semi-complicated select statement that's building a custom "column" in a query, and the results need to be filtered by the results of this column. Is there some way to refer to this column in a predicate? You'll see what I would like to do commented out in the where clause, filtering on 'On Sale'.
select
p.prod_id,
case
when p.subtitle is null then p.title
else concat(p.title, ': ', p.subtitle)
end as 'Title',
p.issue as 'Issue',
e.abbrv as 'Editor',
p.jobnum as 'Job Number',
p.price as 'Price',
ship.due_date as 'Ship Date',
case
when pi.onsale_minus_ship_date is not null then ship.due_date + interval pi.onsale_minus_ship_date day
else
case
when prs.days_for_shipping != 0 then ship.due_date + interval prs.days_for_shipping day
else ship.due_date + interval 7 day
end
end as 'On Sale',
sale.due_date as 'Bookstore On Sale'
from products p
join schedules ship on ship.prod_id = p.prod_id and ship.milestone = 49
left join schedules sale on sale.prod_id = p.prod_id and sale.milestone = 647
left join editors e on find_in_set(e.id, p.editor)
left join printing_info pi on pi.prod_id = p.prod_id
left join printers prs on prs.id = pi.printer
where p.prod_type in (2, 3, 5, 6) -- physical, non comics (trades, hc, etc.)
--and 'On Sale' >= '$start_date' + interval 2 month
--and 'On Sale' <= '$end_date' + interval 2 month
order by ship.due_date asc, p.title asc
You can do your filtering in the HAVING clause - unfortunately, you can't refer to column aliases in the WHERE clause.
HAVING `On Sale` >= '$start_date' + interval 2 month
AND `On Sale` <= '$end_date' + interval 2 month
http://dev.mysql.com/doc/refman/5.0/en/problems-with-alias.html