Applying a WHERE after CASE statement? - mysql

I'm using a CASE statement to build my query for some dates filtering, but after getting the proper CASE syntax, I can't manage to a apply a WHERE statement.
So, I was wondering, how may I do so?
My actual code is:
SELECT id, fecha_inicio,
CASE WHEN eventos.fecha_fin IS NULL
THEN DATE_ADD(eventos.fecha_inicio, INTERVAL 45 DAY)
ELSE fecha_fin
END as fecha_fin_new
FROM eventos
WHERE DATE_FORMAT(fecha_fin_new, '%Y-%m') >= '2006-01'

WHERE can only be used with table values. To process SELECT aliases you have to use HAVING.
SELECT id, fecha_inicio,
CASE WHEN eventos.fecha_fin IS NULL
THEN DATE_ADD(eventos.fecha_inicio, INTERVAL 45 DAY)
ELSE fecha_fin
END as fecha_fin_new
FROM eventos
HAVING DATE_FORMAT(fecha_fin_new, '%Y-%m') >= '2006-01'
You can also replace your CASE expression with:
IFNULL(fecha_fin, DATE_ADD(eventos.fecha_inicio, INTERVAL 45 DAY))

You don't need - and it's not efficient - to do this conversion to apply a WHERE or HAVING condition. You can alter the condition to work on table columns:
WHERE ( eventos.fecha_fin IS NULL
AND DATE_ADD(eventos.fecha_inicio, INTERVAL 45 DAY) >= '2006-01-01'
)
OR ( eventos.fecha_fin IS NOT NULL
AND fecha_fin >= '2006-01-01'
)
which can be further simplified/rewritten to:
WHERE eventos.fecha_fin IS NULL
AND eventos.fecha_inicio >= DATE_SUB('2006-01-01', INTERVAL 45 DAY)
OR eventos.fecha_fin >= '2006-01-01'
So, the query can be written as:
SELECT e.id, e.fecha_inicio,
COALESCE( e.fecha_fin, DATE_ADD(e.fecha_inicio, INTERVAL 45 DAY) )
AS fecha_fin_new
FROM eventos AS e
WHERE e.fecha_fin IS NULL
AND e.fecha_inicio >= DATE_SUB('2006-01-01', INTERVAL 45 DAY)
OR e.fecha_fin >= '2006-01-01'
This way:
the e.fecha_fin IS NULL AND ... OR ... condition is checked first and if there indexes that can be used for this condition, the query will be efficient. All other calculations are performed on the selected rows.
the computed column - whether with CASE, IFNULL() or CALESCE() doesn't matter - is not calculated for all rows of the table, only for the selected ones.
the DATE_FORMAT() function is not needed to be applied, not once.

Related

Counting all rows in column with two different date conditions

I'm trying to turn two count queries with date conditions (the ones below) into one query.
SELECT COUNT(*) as yesterday FROM orders WHERE DATE(timedate) = DATE(NOW() - INTERVAL 1 DAY)
SELECT COUNT(*) as yesterday FROM orders WHERE DATE(timedate) = DATE(NOW() - INTERVAL 2 DAY)
Following the advice of another answer I created the following, but that doesn't seem to work syntax-wise, and I'm not quite sure why. Is there another way to do this? I can't find a similar question on this
SELECT
SUM(IF(DATE(timedate) = DATE(NOW() - INTERVAL 1 DAY))) AS testcount1,
SUM(IF(DATE(timedate) = DATE(NOW() - INTERVAL 2 DAY))) AS testcount2
FROM
orders
You're missing the output values for the IF expression. Also you should use CURRENT_DATE() so you don't need to convert to a DATE:
SELECT
SUM(IF(DATE(timedate) = CURRENT_DATE() - INTERVAL 1 DAY, 1, 0)) AS testcount1,
SUM(IF(DATE(timedate) = CURRENT_DATE() - INTERVAL 2 DAY, 1, 0)) AS testcount2
FROM
orders
Note that MySQL treats boolean expressions as 1 (true) or 0 (false) in a numeric context, so you can actually SUM the expression without needing the IF:
SELECT
SUM(DATE(timedate) = CURRENT_DATE() - INTERVAL 1 DAY) AS testcount1,
SUM(DATE(timedate) = CURRENT_DATE() - INTERVAL 2 DAY) AS testcount2
FROM
orders
You want conditional aggregation. I would phrase the query as follows:
SELECT
SUM(
timedate >= CURRENT_DATE - INTERVAL 1 DAY
and timedate < CURRENT_DATE
) AS testcount1,
SUM(
timedate >= CURRENT_DATE - INTERVAL 2 DAY
and timedate < CURRENT_DATE- INTERVAL 1 DAT
) AS testcount2
FROM orders
Details:
this uses a nice feature of MySQL, that evaluates false/true conditions as 0/1 in numeric context
no date functions are applied on the timedate column : instead, we do litteral date comparisons. This is much more efficient, since the database can possibly take advantage of an index on the datetime column
You might also want to add a WHERE clause to the query:
WHERE
timedate >= CURRENT_DATE - INTERVAL 2 day
AND timedate< CURRENT_DATE

How do I subtract two results from a SELECT statement within that statement?

This pulls back two int values of yesterday and today. I'd like to subtract the two results from within the statement in a third column called difference:
SELECT (
SELECT COUNT(*)
FROM collectors_users
WHERE DATE(dateadded) = CURDATE() - INTERVAL 1 DAY
) AS yesterday, COUNT(*) AS today
FROM collectors_users
WHERE DATE(dateadded) = CURDATE()
You need to repeat the expressions. SQL (in general) does not allow you to re-use column aliases in the same SELECT. You can simplify the logic to:
SELECT SUM(DATE(dateadded) = CURDATE() - INTERVAL 1 DAY) AS yesterday,
SUM(DATE(dateadded) = CURDATE()) as today,
(SUM(DATE(dateadded) = CURDATE()) -
SUM(DATE(dateadded) = CURDATE() - INTERVAL 1 DAY)
) as diff
FROM collectors_users
WHERE dateadded >= CURDATE() - INTERVAL 1 DAY AND
dateadded < CURDATE() + INTERVAL 1 DAY;
Note that the logic for the WHERE clause covers two days. Also, it does not use DATE(). This would allow the query to use an index, if available.

Cannot spot the difference between these two queries

Trying to clean up a long query I have come up with a modified version, but now the old query with certain parameters returns 6 rows, but my version returns none. I have been straining my eyes to spot the difference but cannot find any. Here are the WHERE clauses from the two versions with identical set of parameters:
The ugly (but working) version:
SELECT ...
FROM table as a
WHERE 1=1
and a.title LIKE '%Manager%'
and a.status='Approved'
and (a.effected_date<=now())
and (
(DATE_ADD(a.effected_date, INTERVAL 30 DAY) >= now() AND a.is_hotjob=0)
or
(DATE_ADD(a.effected_date, INTERVAL 30 DAY) >= now() AND a.is_hotjob=1)
)
My cleaned (but broken) version:
SELECT ...
FROM `table` AS `a`
WHERE CONCAT(`a`.`title`, ' | ', `a`.`job_detail_section`) LIKE '%Manager%' AND
`a`.`status` = 'Approved' AND
`a`.`effected_date` <= '2013-12-30' AND
(`a`.`effected_date` >= '2013-11-30' OR `a`.`is_hotjob` = '1')
You got the last AND/OR backwards:
`a`.`effected_date` <= '2013-12-30' OR
(`a`.`effected_date` >= '2013-11-30' AND `a`.`is_hotjob` = '1')
EDIT: after reformatting, the answer looks more like:
SELECT ...
FROM `table` AS `a`
WHERE CONCAT(`a`.`title`, ' | ', `a`.`job_detail_section`) LIKE '%Manager%' AND
`a`.`status` = 'Approved' AND
`a`.`effected_date` <= now() AND
`a`.`effected_date` >= DATE_ADD(a.effected_date, INTERVAL 30 DAY)
and POSSIBLY:
AND `a`.`is_hotjob` IN (0,1)
As a substitute of-
(DATE_ADD(a.effected_date, INTERVAL 30 DAY) >= now() AND a.is_hotjob=0)
or
(DATE_ADD(a.effected_date, INTERVAL 30 DAY) >= now() AND a.is_hotjob=1)
You could use the following-
(`a`.`effected_date` >= '2013-11-30' AND `a`.`is_hotjob` in (0, 1))
But according to the additional info i.e. a.is_hotjob has value either 0 or 1 then there is no need to mention it in the condition at all. So basically the following format will be sufficient-
`a`.`effected_date` >= '2013-11-30'

Case in MySql when calculated values are empty for a row

I've got a long MySql string that is intended to calculate a daily percentage of a certain value. However, if there is nothing for a specific day, it just skips that day and goes to the next one. I need it to spit out a "0" for the day that it usually skips. Thanks for your help!
SELECT day(timestamp), CASE when
round(count(w_comp_current_1+W_comp_current_2)*10/86400*100,1) as 'run_time2' iS NULL
then '0'
ELSE round(count(w_comp_current_1+W_comp_current_2)*10/86400*100,1) as 'run_time2' END
FROM location.db WHERE timestamp between subdate(curdate(), interval 1 month)
and curdate() AND (w_comp_current_1+w_comp_current_2) > 45
GROUP BY MONTH(Timestamp), DAY(Timestamp)
ORDER BY Timestamp
New query using calendar table:
Select date_format(calendar.timestamp,'%b-%e') as 'Month-Day', round(count(w_comp_current_1+W_comp_current_2)*10/86400*100,1) as 'run_time2' from calendar
Left Join courthouse on calendar.timestamp = courthouse.timestamp
WHERE calendar.timestamp between subdate(curdate(), interval 1 month) and curdate() and calendar.timestamp > '2013-10-03%' AND (w_comp_current_1+w_comp_current_2) > 45
GROUP BY MONTH(calendar.Timestamp), DAY(calendar.Timestamp) ORDER BY calendar.Timestamp
You have the alias for the case column twice and both times in the wrong place. It should only be given after the case's END statement:
SELECT day(TIMESTAMP), CASE
WHEN round(count(w_comp_current_1 + W_comp_current_2) * 10 / 86400 * 100, 1) IS NULL
THEN '0'
ELSE round(count(w_comp_current_1 + W_comp_current_2) * 10 / 86400 * 100, 1)
END AS 'run_time2'
FROM location.db
WHERE TIMESTAMP BETWEEN subdate(curdate(), interval 1 month) AND curdate()
AND (w_comp_current_1 + w_comp_current_2) > 45
GROUP BY MONTH(TIMESTAMP), DAY(TIMESTAMP)
ORDER BY TIMESTAMP

What is REALLY inside that field?

In MYSQL I have a '0' inserted into a DATETIME field called DT.
making it a "0000-00-00 00:00:00".
I need to compare DT to NOW() and tell if the result is in the past or not.
Some dates have real values, and some dates have a '0' (as said get the "0000-00-00 00:00:00").
How can I check that column DT + INTERVAL 3 MONTH < NOW() ?
Thanks!
It seems like you just want to ignore the 0 dates, so you can simply filter out the 0 dates in your WHERE clause like this:
SELECT * FROM mytable
WHERE DT != '0000-00-00 00:00:00'
AND DATE_ADD(DT, INTERVAL 3 MONTH) < NOW()
Here's the optimized version of that query. You should try to put your functions on the right side of the criteria so that MySQL can utilize indexes:
SELECT * FROM mytable
WHERE DT != '0000-00-00 00:00:00'
AND DT < DATE_ADD(NOW(), INTERVAL -3 MONTH)
You can use the DATE_ADD() function to get what you are looking for:
SELECT * FROM mytable WHERE DATE_ADD(`DT`, INTERVAL 3 MONTH) < NOW();
This will return all columns where (DT + INTERVAL 3 MONTH) < NOW()