MAX() in UPDATE query with field does not work - mysql

I try to update multiple rows at the same time but amount can have different starting values.
I want to subtract x amount from amount without going below 0. I used UNSIGNED for the amount field in order to protect the number never to fall below 0.
The following query refuses to update all rows that fall below 0 because of the UNSIGNED state:
UPDATE `table`
SET `amount` = `amount` - 35
WHERE `id`
IN (26984, 131881, 985550, 985569, 985586, 1086766, 1189724)
In case amount is for example 12, if you subtract 35, it falls below 0 and refuses to update. But I need it to update to 0.
Then I tried to think of MAX() to help me not fall below 0, but this does not work:
UPDATE `table`
SET `amount` = MAX(`amount` - 35, 0)
WHERE `id`
IN (26984, 131881, 985550, 985569, 985586, 1086766, 1189724)
Any idea how to make this work and to make sure the value never falls below 0?

Thanks, this is how it worked in the end:
UPDATE `table`
SET `amount` = GREATEST(`amount` - 35, 0)
WHERE `id`
IN (26984, 131881, 985550, 985569, 985586, 1086766, 1189724)
And discarding UNSIGNED from amount

Related

Add amount in mysql with a certain limit

I'm using MySQL query
update `table_1`
set `amount` = `amount` + 120
where `id` = 1;
If I run this multiple time will add amount.
Can I set the amount column to a certain amount like 1000?
Means amount will not be added when it comes 1000
Use the LEAST() function to put a cap on the amount.
UPDATE table_1
SET amount = LEAST(amount + 120, 1000)
WHERE id = 1

Not able to optimize query

SELECT MIN(classification) AS classification
,MIN(START) AS START
,MAX(next_start) AS END
,SUM(duration) AS seconds
FROM ( SELECT *
, CASE WHEN (duration < 20*60) THEN CASE WHEN (duration = -1) THEN 'current_session' ELSE 'session' END
ELSE 'break'
END AS classification
, CASE WHEN (duration > 20*60) THEN ((#sum_grouping := #sum_grouping +2)-1)
ELSE #sum_grouping
END AS sum_grouping
FROM ( SELECT *
, CASE WHEN next_start IS NOT NULL THEN TIMESTAMPDIFF(SECOND, START, next_start) ELSE -1 END AS duration
FROM ( SELECT id, studentId, START
, (SELECT MIN(START)
FROM attempt AS sub
WHERE sub.studentId = main.studentId
AND sub.start > main.start
) AS next_start
FROM attempt AS main
WHERE main.studentId = 605
ORDER BY START
) AS t1
) AS t2
WHERE duration != 0
) AS t3
GROUP BY sum_grouping
ORDER BY START DESC, END DESC
Explanation and goal
The attempt table records a student's attempt at some activity, during a session. If two attempts are less than 20 minutes apart, we consider those to be the same session. If they are more than 20 minutes apart, we assume they took a break.
My goal with this query is to take all of the attempts and condense them down in a list of sessions and breaks, with the start time of each session, the end time (defined as the start of the subsequent session), and how long the session was. The classification is whether it is a session, a break, or the current session.
The above query does all of that, but is too slow. How can I improve the performance?
How the current query works
The innermost queries select an attempt's start time and the subsequent attempt's start time, along with the duration between those values.
Then, the #sum_grouping and sum_grouping are used to split the attempts into the sessions and breaks. #sum_grouping is only ever increased when an attempt is more than 20 minutes long (i.e. a break), and it is always increased by 2. However, sum_grouping is set to a value of one less than that for that "break". If an attempt is less than 20 minutes long, then the current #sum_grouping value is used, without modification. As a result, all breaks are distinct odd values, and all sessions (whether of 1 or more attempt) end up as distinct even numbers. This allows the GROUP BY portion to correctly separate the attempts into sessions and breaks.
Example:
Attempt type #sum_grouping sum_grouping
non-break 0 0
non-break 0 0
break 2 1
break 4 3
non-break 4 4
break 6 5
As you can see, all the breaks will be grouped by sum_grouping separately with distinct odd values and all the non-breaks will be grouped together as sessions with the even values.
The MIN(classification) simply forces "current session" to be returned when both "session" and "current session" are present within a grouped row.
OUTPUT OF SHOW CREATE TABLE attempt
CREATE TABLE attempt (
id int(11) NOT NULL AUTO_INCREMENT,
caseId int(11) NOT NULL DEFAULT '0',
eventId int(11) NOT NULL DEFAULT '0',
studentId int(11) NOT NULL DEFAULT '0',
activeUuid char(36) NOT NULL,
start timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
end timestamp NULL DEFAULT NULL,
outcome float DEFAULT NULL,
response varchar(5000) NOT NULL DEFAULT '',
PRIMARY KEY id),
KEY activeUuid activeUuid),
KEY caseId caseId,activeUuid),
KEY end end),
KEY start start),
KEY studentId studentId),
KEY attempt_idx_studentid_stat_id studentId,start,id),
KEY attempt_idx_studentid_stat studentId,start
) ENGINE=MyISAM AUTO_INCREMENT=298382 DEFAULT CHARSET=latin1
(This is not a proper Answer, but here goes anyway.)
Try not to nest 'derived' tables.
I see a lot of syntax errors.
Move from MyISAM to InnoDB.
INDEX(a, b) handles situations where you need INDEX(a), so DROP the latter.

Why Index (on primary key column) is not used?

I have a date table, which has a column date (PK). The CREATE script is here:
CREATE TABLE date_table (
date DATE
,year INT(4)
,month INT(2)
,day INT(2)
,month_pad VARCHAR(2)
,day_pad VARCHAR(2)
,month_name VARCHAR(10)
,year_month_index INT(6)
,year_month_hypname VARCHAR(7)
,year_month_name VARCHAR(15)
,week_day_index INT(1)
,day_name VARCHAR(9)
,week INT(2)
,week_interval VARCHAR(13)
,weekend_fl INT(1)
,quarter_num INT(1)
,quarter_num_pad VARCHAR(2)
,quarter_name VARCHAR(2)
,year_quarter_index INT(6)
,year_quarter_name VARCHAR(7)
,PRIMARY KEY (date)
);
Now I would like select rows from this table with dynamic values, using such as LAST_DAY() or DATE_SUB(DATE_FORMAT(SYSDATE(),'%Y-01-01'), INTERVAL X YEAR), etc.
When one of my queries failed and didn't execute in 30 secs, I knew something was fishy, and it looks like the reason is that the index on the primary key column is not used. Here are my results (sorry for using an image instead of copying the queries, but I thought it's concise enough for this purpose, and the queries are short/simple enough):
First of all, it's strange that the BETWEEN works differently than using >= and <=. Secondly, it looks like the index is only used for constant values. If you look closely, you can see that on the right side (where >= and <= is used), it shows ~9K rows, which is half of the rows in the table (the table has about ~18k rows, dates from 2000-01-01 to `2050-12-31).
SYSDATE() returns the time at which it executes. This differs from the behavior for NOW(), which returns a constant time that indicates the time at which the statement began to execute. (Within a stored function or trigger, NOW() returns the time at which the function or triggering statement began to execute.)
-- https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_sysdate
That is, the Optimizer does not see this as a "constant". Otherwise, the Optimizer eagerly evaluates any "constant expressions", then tries to take advantage of knowing the value.
See also the sysdate_is_now option.
Bottom line: Don't use SYSDATE() for normal datetime usage; use NOW() or CURDATE().
Looks like if I use CURRENT_DATE() (or NOW()) instead of SYSDATE(), it's working. Both of these queries:
SELECT *
FROM date_table t
WHERE 1 = 1
AND t.ddate >= LAST_DAY(CURRENT_DATE()) AND t.ddate <= LAST_DAY(CURRENT_DATE());
SELECT *
FROM date_table t
WHERE 1 = 1
AND t.ddate >= LAST_DAY(NOW()) AND t.ddate <= LAST_DAY(NOW());
Give the same result, which is this:
I will accept my answer as a solution, but I'm still looking for an explanation. I thought it might has to do something with SYSDATE() not being a DATE, but NOW() is also not a DATE...
EDIT: Forgot to add, BETWEEN is also working as I see.

Retrieve one out of every n records

I have a table containing thousands of records representing the temperature of a room in a certain moment. Up to now I have been rendering a client side graph of the temperature with JQuery. However, as the amount of records increases, I think it makes no sense to provide so much data to the view, if it is not going to be able to represent them all in a single graph.
I would like to know if there exists a single MySQL query that returns one out of every n records in the table. If so, I think I could get a representative sample of the temperatures measured during a certain lapse of time.
Any ideas? Thanks in advance.
Edit: add table structure.
CREATE TABLE IF NOT EXISTS `temperature` (
`nid` int(10) unsigned NOT NULL COMMENT 'Node identifier',
`temperature` float unsigned NOT NULL COMMENT 'Temperature in Celsius degrees',
`timestamp` int(10) unsigned NOT NULL COMMENT 'Unix timestamp of the temperature record',
PRIMARY KEY (`nid`,`timestamp`)
)
You could do this, where the subquery is your query, and you add a row number to it:
SET #rows=0;
SELECT * from(
SELECT #rows:=#rows+1 AS rowNumber,nid,temperature,`timestamp`
FROM temperature
) yourQuery
WHERE MOD(rowNumber, 5)=0
The mod would choose every 5th row: The 5 here is your n. so 5th row, then 10th, 15th etc.
Not really sure what your asking but you have multiple options
You can limit your results to n (n representing the amount of temperatures you want to display)
just a simple query with the limit in the end:
select * from tablename limit 1000
You could use a time/date restraint so you display only the results of the last n days.
Here is an example that uses date functions. The following query selects all rows with a date_col value from within the last 30 days:
mysql> SELECT something FROM tbl_name
-> WHERE DATE_SUB(CURDATE(),INTERVAL 30 DAY) <= date_col;
You could select an average temperature of a certain period, the shorter the period the more results you'll get. You can group by date, yearweek, month etc. to "create the periods"

phpMyAdmin incorrect results count

Stumbled across potentially a bug(?) within phpMyAdmin, although it's more likely to maybe be my misunderstanding of MySQL, so was hoping someone could shed some light on this behaviour.
Using the following schema
CREATE TABLE IF NOT EXISTS `mlfsql_test` (
`id` int(11) NOT NULL,
`frequency_length` smallint(3) DEFAULT NULL,
`frequency_units` varchar(10) DEFAULT NULL,
`next_delivery_date` date DEFAULT NULL,
`last_created_delivery_date` date DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=5 ;
INSERT INTO `mlfsql_test`
(`id`, `frequency_length`, `frequency_units`, `next_delivery_date`, `last_created_delivery_date`) VALUES
(1, 2, 'week', '2014-06-25', NULL),
(2, 3, 'day', '2014-06-27', NULL),
(3, 1, 'week', '2014-08-08', NULL),
(4, 2, 'day', NULL, '2014-06-26');
I want to determine rows with an upcoming delivery, based on their currently set delivery date, or their last delivery date with the frequency taken into consideration.
Came up with the following query which works fine:
SELECT *, IF (next_delivery_date IS NOT NULL, next_delivery_date,
CASE frequency_units
WHEN 'day' THEN DATE_ADD(last_created_delivery_date, INTERVAL frequency_length DAY)
WHEN 'week' THEN DATE_ADD(last_created_delivery_date, INTERVAL frequency_length WEEK)
WHEN 'month' THEN DATE_ADD(last_created_delivery_date, INTERVAL frequency_length MONTH)
END)
AS next_order_due_date
FROM mlfsql_test
HAVING next_order_due_date IS NULL OR (next_order_due_date BETWEEN CURDATE() AND CURDATE() + INTERVAL 10 DAY)
With the data currently in the table, I am expecting it to return 3 rows, but phpMyAdmin states there are a total of 4 rows of results, although it only displays 3...
I've found that if I add a WHERE clause to my query such as WHERE 1, it'll return the 3 rows and also state that there is a total of 3.
Why does it give an incorrect number of returned rows without the WHERE clause? I'm assuming without one phpMyAdmin assumes that all rows will match, however only returns those that actually did, so the count is wrong? Any help would be appreciated.
Edit: phpMyAdmin Version 4.2.0
This seems to be a bug in phpMyAdmin v4.2.x. I have opened a bug ticket (see Bug #4473). I have also proposed a fix for this bug to them (see PR #1253). You can also apply this patch to fix it in v4.2.4. This is most likely to be fixed in upcoming bugfix release i.e. v4.2.5.
Edit 1: My patch was accepted and this issue is fixed in v4.2.5 (upcoming minor release).