MySQL Non-Negative INT Columns - mysql

I want to do the following query:
UPDATE `users` SET balance = (balance - 10) WHERE id=1
But if the balance will become a negative number I want an error to be returned. Any ideas on if this is possible?

If you do
UPDATE `users` SET balance = (balance - 10) WHERE id=1 and balance >=10
You should be able to detect that a row was not modified.
Note that while another answer suggests using an unsigned int column, this may not work:
Create a test table
create table foo(val int unsigned default '0');
insert into foo(val) values(5);
Now we attempt to subtract 10 from our test row:
update foo set val=val-10;
Query OK, 1 row affected, 1 warning (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 1
mysql> select * from foo;
+------------+
| val |
+------------+
| 4294967295 |
+------------+
This was on mysql 5.0.38

You can make the balance field of the users table an unsigned int:
ALTER TABLE `users` CHANGE `balance` `balance` INT UNSIGNED;

This sort of things is done by triggers. MySql have support for triggers only since 5.0.2.
DELIMITER $$
CREATE TRIGGER balance_check BEFORE INSERT ON user FOR EACH ROW
BEGIN
IF new.balance < #limit_value THEN
-- do something that causes error.
-- mysql doesn't have mechanism to block action by itself
END IF;
END $$
DELIMITER ;
Triggers in MySql are quite rudimentary. You have to hack things around to do some things (e.g. cause error).

I dont think you can do this with a simple query. you should use a mysql user defined function that manage that before update the row. or a trigger

Just a tip that wouldn't fit as a comment. I was just trying to subtract 32000 from 32047 (not a negative result) and was getting errors. Also confusing, I was getting BIGINT errors but my subtraction was on a SMALLINT column! (Which still makes no sense.)
If you're getting "out of range" errors even when your "balance" is positive, try adding "limit 1" to the end of your query. Maybe this is a bug in MySQL?
mysql> update posts set cat_id=cat_id-32000 where timestamp=1360870280;
ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '(`xxxxx`.`posts`.`cat_id` - 32000)'
mysql> update posts set cat_id=cat_id-32000 where timestamp=1360870280 limit 1;
Query OK, 1 row affected (6.45 sec)
Rows matched: 1 Changed: 1 Warnings: 0
In my case the timestamp is unique (I just checked to be sure) but not explicitly defined as unique when I created the table. So why is the "limit 1" here necessary? But who cares, it works!

Related

Make a column NULL for insert and NOT NULL for update

This is something that I stumbled across multiple projects, and I feel I'm reinventing the wheel every time:
I have a table that stores user data. Whenever the user is created, I create one row on the table. This row has several NULL fields on creation, because the user just informed some critical information (and other non-critical info is going to be filled later).
But, when the user completes the filling of the data, I want to enforce this cols to be NOT NULL.
Is there any way to make a column NULL for INSERT, but NOT NULL for UPDATE that does not involves triggers? Or any other clever solution?
Thank you
CREATE TABLE users ( regular_column SOMETYPE NOT NULL,
smart_column SOMETYPE NULL,
completed ENUM('no', 'yes') NOT NULL DEFAULT 'no',
CHECK ((completed = 'no') OR (smart_column IS NOT NULL)) );
This row has several NULL fields on creation, because the user just informed some critical information (and other non-critical info is going to be filled later).
In this moment completed = 'no', CHECK constraint is TRUE, smart_column may be NULL.
when the user completes the filling of the data, I want to enforce this cols to be NOT NULL.
In this moment completed = 'yes', and CHECK constraint does not allow NULL value in smart_column.
I.e. setting completed column to 'yes' fixes smart_column - you may alter it but cannot set it to NULL. And you cannot set completed to 'yes' until smart_column is set to a value.
If you use column options in your CREATE TABLE statement, there's no way a column can be both NULLable and NOT NULL. There's no way to distinguish between inserts and updates.
The alternative could be to let the column be nullable, but add a trigger on UPDATE that throws an exception (called a SIGNAL in MySQL terminology) if the column is still NULL after the update.
Here's a quick demo:
mysql> create table mytable (id int primary key, x int);
Query OK, 0 rows affected (0.05 sec)
mysql> delimiter //
mysql> create trigger notnullonupdate before update on mytable
-> for each row
-> begin
-> if NEW.x IS NULL then
-> signal sqlstate '45000'
-> set message_text = 'x must be updated to non-NULL value';
-> end if;
-> end//
mysql> delimiter ;
mysql> insert into mytable set id = 42, x = null;
Query OK, 1 row affected (0.03 sec)
mysql> update mytable set id = 42;
ERROR 1644 (45000): x must be updated to non-NULL value
Even though this is possible, it's kind of a lot of work.
Most developers would just handle this by writing application code to ensure the value is not null before executing an update. Of course the risk of that is if you forget one of the cases in your app that does an update, or if someone does an ad hoc update in the mysql client, it could cause your data to be in an invalid state and you wouldn't know it.

MySql update. difference between procedure results and phpmyadmin sql

I am having difficult getting a procedure to update a table in the way I require. I am using phpmyadmin on my local computer. In phpmyadmin I can put the following code into the SQL tab and one row will be updated:
SET `adjCost` = 22.05 WHERE `Name` LIKE CONCAT('magic', '%') AND `idKey` = '2016fulham02345';
As expected and wanted, IF the name begins with magic AND the idKey is '2016fulham02345' THEN the adjCost is updated to 22.05.
There will be between 2 and 50 rows with the same idKey. The Name will never be repeated in a set with the same idKey.
I created a procedure with the following parameters:
IN idK VARCHAR 255 Charset
IN aName VARCHAR 255 Charset
IN cost FLOAT 5,2
BEGIN
UPDATE `raceresults` SET `adjCost` = cost WHERE `Name` LIKE CONCAT(aName, '%') AND `idKey` = idK;
END
When I run this procedure it updates ALL adjCost where the idKey = idk and (seems) to ignore the name parameter.
I have tried concatenating the name string first:
BEGIN
SELECT CONCAT(aName, '%') INTO #str;
UPDATE `raceresults` SET `adjCost` = cost WHERE `Name` = #str AND `idKey` = idK;
END
but to no avail.
I looked through w3schools, stackoverflow and google and have not been able to find the answer.
My question is:
How can I correct my procedure to get it to work as I would like?
UPDATE: as requested.
CREATE DEFINER=`root`#`localhost` PROCEDURE `importAltUpdateAjdCost`(IN `idK` VARCHAR(255), IN `aName` VARCHAR(255), IN `cost` FLOAT(5,2))
NO SQL
BEGIN
UPDATE `costingPP`
SET `adjCost` = cost
WHERE
`Name` LIKE CONCAT(aName, '%')
AND
`idKey` = idK;
END
To get this, I selected export on my list of procedures on phpmyadmin.
I'm not entirely sure what or how you did, but here's what I did and it instantly worked. Since you didn't specify MySQL version, I used 5.7.
EDIT: Now as I went back to see your procedure creation statement I realised that NO SQL was introduced in MySQL 8.0. Since your procedure clearly is SQL then please remove the NO SQL and re-create the procedure.
I'm leaving my MySQL 5.7 sample here for reference:
1) Created a simple table:
mysql> CREATE TABLE raceresults (
-> idKey VARCHAR(255),
-> Name VARCHAR(255),
-> adjCost FLOAT(5,2)
-> );
Query OK, 0 rows affected (0.06 sec)
2) Here we insert a sample data row:
mysql> INSERT INTO raceresults VALUES ('2016fulham02345', 'magicFlyingHorse', 0.00);
Query OK, 1 row affected (0.01 sec)
3) To create a (STORED) PROCEDURE we have to temporarily set a different delimiter, so query parser wouldn't terminate procedure creation on default semi-colon, as it's used inside the procedure. After delimiter's change we create the procedure and set the delimiter back to semi-colon
mysql> DELIMITER //
mysql> CREATE PROCEDURE update_test(IN idK VARCHAR(255), IN aName VARCHAR(255), IN cost FLOAT(5,2))
-> BEGIN
-> UPDATE `raceresults` SET `adjCost` = cost WHERE `Name` LIKE CONCAT(aName, '%') AND `idKey` = idK;
-> END//
mysql> DELIMITER ;
Query OK, 0 rows affected (0.00 sec)
4) Now let's see how it all works. Before and after the procedure call I'm selecting the rows from database. You can see the cost column value changing:
mysql> SELECT * FROM raceresults;
+-----------------+------------------+---------+
| idKey | Name | adjCost |
+-----------------+------------------+---------+
| 2016fulham02345 | magicFlyingHorse | 0.00 |
+-----------------+------------------+---------+
1 row in set (0.00 sec)
mysql> CALL update_test('2016fulham02345', 'magic', 1.23);
Query OK, 1 row affected (0.02 sec)
mysql> SELECT * FROM raceresults;
+-----------------+------------------+---------+
| idKey | Name | adjCost |
+-----------------+------------------+---------+
| 2016fulham02345 | magicFlyingHorse | 1.23 |
+-----------------+------------------+---------+
1 row in set (0.00 sec)
And now one piece of advise too:
If possible, use only lower case table, column, indexes, functions, procedures, etc... names, while always writing all SQL commands in uppercase (which you did). This is kind of a de facto standard and makes life easier both for you and others reading your code.

time resriction in column

hello I have a datetime column and I would like to put a time restriction on it how would I do this?
For example a range of time from 3:00:00 to 15:00:00 all data that fits this criteria is stored if not throw and error up and stop the entering of data in the column
In MySQL, you'd have to do this with a trigger on INSERT and UPDATE, so if someone tries to enter a value that doesn't meet your criteria, you raise a SIGNAL.
mysql> CREATE TABLE MyTable (
my_datetime DATETIME
);
mysql> DELIMITER ;;
mysql> CREATE TRIGGER MyTable_ins BEFORE INSERT ON MyTable
FOR EACH ROW BEGIN
IF (NOT TIME(NEW.my_datetime) BETWEEN '03:00:00' AND '15:00:00') THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'Time does not fall in the range allowed.'
END IF;
END;;
mysql> DELIMITER ;
I get the error if try to do something I shouldn't:
mysql> INSERT INTO MyTable SET my_datetime = '2017-01-13 18:00:00';
ERROR 1644 (45000): time does not fall in the range allowed
But it works if I choose a time that's allowed:
mysql> INSERT INTO MyTable SET my_datetime = '2017-01-13 11:00:00';
Query OK, 1 row affected (0.00 sec)
I did some digging and some reading. I tested some stuff out on my own server. It doesn't work. Then I found this answer:
CHECK constraint in MySQL is not working
Yep, it accepts a CHECK constraint as valid syntax, then completely ignores it.
And while I was testing and writing up, Bill has posted the correct answer for MySQL. Do what he says.

ON DUPLICATE KEY UPDATE - decrement value in MySQL

The following seems odds to me:
INSERT INTO sometable (UNIQUEVALUE,NUMERICVALUE) VALUES ('valuethatexists','100') ON DUPLICATE KEY UPDATE NUMERICVALUE = NUMERICVALUE+VALUES(NUMERICVALUE);
Assume your NUMERICVALUE is at 0.
The above would change it to 100 - which does work.
If, however, you then input -100, it does not work properly.
INSERT INTO sometable (UNIQUEVALUE,NUMERICVALUE) VALUES ('valuethatexists','-100') ON DUPLICATE KEY UPDATE NUMERICVALUE = NUMERICVALUE+VALUES(NUMERICVALUE);
The above statement should return it to 0. It does not, in my case. It remains at 100.
Am I missing something?
Edit: This goes wrong somewhere else. I am doing this with PHP. The actual code exhibiting this bug looks like this:
Edit 2: This had nothing to do with PHP either. The problem was the NUMERIC value was UNSIGNED in my production environment, meaning VALUES(NUMERICVALUE) was brought from -100 to 0 before it was used.
On my MySQL server (5.7.12), it does work as expected:
mysql> CREATE TABLE sometable (
UNIQUEVALUE VARCHAR(16) NOT NULL PRIMARY KEY,
NUMERICVALUE INT NOT NULL);
Query OK, 0 rows affected (0.01 sec)
mysql> INSERT INTO sometable (UNIQUEVALUE,NUMERICVALUE)
VALUES ('valuethatexists','100')
ON DUPLICATE KEY UPDATE NUMERICVALUE = NUMERICVALUE+VALUES(NUMERICVALUE);
Query OK, 1 row affected (0.01 sec)
mysql> SELECT * FROM sometable;
+-----------------+--------------+
| UNIQUEVALUE | NUMERICVALUE |
+-----------------+--------------+
| valuethatexists | 100 |
+-----------------+--------------+
1 row in set (0.00 sec)
mysql> INSERT INTO sometable (UNIQUEVALUE,NUMERICVALUE)
VALUES ('valuethatexists','-100')
ON DUPLICATE KEY UPDATE NUMERICVALUE = NUMERICVALUE+VALUES(NUMERICVALUE);
Query OK, 2 rows affected (0.00 sec)
mysql> SELECT * FROM sometable;
+-----------------+--------------+
| UNIQUEVALUE | NUMERICVALUE |
+-----------------+--------------+
| valuethatexists | 0 |
+-----------------+--------------+
1 row in set (0.00 sec)
Which version of MySQL are you using? Can you execute the exact statements above and see if you have different results?
While Benjamin's answer is correct, the root of the issue turned out to be the fact that the NUMERICVALUE column was UNSIGNED, so whenever I input -100, it was turned into 0 before it was evaluated as VALUES(NUMERICVALUE). If this is to be considered a bug or not I don't know.
Obviously the result of the final evaluation should not be negative, but I don't know how clever it is to silently turn it into 0. I had logic in place making sure the value in question would never be below 0 anyway by never passing a negative value larger than what was already in the row.

I set a MySQL column to "NOT NULL" but still I can insert an empty value

In MySQL I have a table with Column1 as NOT NULL:
create table myTable
(
Column1 int not null,
Column2 int not null
)
I can still insert an empty value like this:
INSERT INTO `myTable` ( `Column1` , `Column2` )
VALUES ( '66', '' );
How can I make the MySQL column also disallow blankstring?
EMPTY STRINGS
In ORACLE an empty string is used to represent NULL. In virtually everything else, however, an empty string is still a string, and so not NULL.
INTS
In your case you're actually inserting STRINGS into an INT column. This forces an implicit CAST operation.
When your RDBMS is converting the string '' to an INT it must get the value 0. As 0 is not NULL, this gets inserted.
A more valid test would be:
INSERT INTO `plekz`.`countries` (`Column1 ` , `Column2`)
VALUES (66, NULL);
EDIT
Sorry, I only half read your question. You also ask how to stop '' being inserted.
Your first problem is that you're inserting STRINGS and the table is defined as having INT fields. You can put constraints on the data that gets inserted, but these constraints will apply the the value after an conversion to an INT. Unless you want to prevent the value 0 from also being inserted, there is nothing you can do to the table to prevent this scenario.
Your better bet is to address why you are inserting strings in the first place. You could use a stored procedure that takes, and checks, the strings before converting them to INTs and then inserting them. Or, better still, you could make the checks in your client application.
A technically available option is to make the fields CHAR fields, then put a constraint on the fields, preventing '' from being inserted. I would strongly recommend against this.
You're inserting an empty string, not NULL. The constraint is only against NULL values, and it would appear that your database is not coercing empty strings to NULL when it converts them to INT (which raises the additional question of why you're inserting string literals into INT columns...)
MySQL, how to disallow empty string:
Create your table:
mysql> create table yar (val VARCHAR(25) not null);
Query OK, 0 rows affected (0.02 sec)
Create your 'before insert' trigger to check for blankstring and disallow.
mysql> create trigger foo before insert on yar
-> for each row
-> begin
-> if new.val = '' then
-> signal sqlstate '45000';
-> end if;
-> end;$$
Query OK, 0 rows affected (0.01 sec)
Try to insert null and blankstring into your column:
mysql> delimiter ;
mysql> insert into yar values("");
ERROR 1644 (45000): Unhandled user-defined exception condition
mysql> insert into yar values(NULL);
ERROR 1048 (23000): Column 'val' cannot be null
mysql> insert into yar values ("abc");
Query OK, 1 row affected (0.01 sec)
mysql> select * from yar;
+-----+
| val |
+-----+
| abc |
+-----+
1 row in set (0.00 sec)
Finally, Grumble to self and smack the nearest person who was responsible for picking mysql over postgresql.
As Martin mentions, depends on your RDBMS. Oracle treats empty strings as NULLs while others do not. See this SO post.
NULL is not equal to emptiness. In MySQL, there is an additional byte with each column entry to hold the "is null" information. To save space, a column is often defined as "not null" to spare this extra byte if the null status doesn't add any thing to the data model.