Subtract number from enum - mysql

My number column in data table has enum values:
1, 2, 3, 4, 5, 101, 102, 103, 104, 105
I want to set values of number = number - 100 for all rows using query:
UPDATE data SET data.number = (data.number - 100) WHERE data.number > 100
But it does not work on enum data.

This is a little complicated.
Mysql doesn'z allow number to be cast from ENUM, because the says that numbers should not be used as ENUM value.
but as you need the value if the ENUM field you can convert it into characters.
The final step is to convert that text into a number.
Schema (MySQL v5.7)
CREATE TABLE IF NOT EXISTS `data` (
number ENUM('1', '2', '3', '4', '5', '101', '102', '103', '104', '105')
) ;
INSERT INTO `data` VALUES ( '103');
UPDATE `data` SET number = CAST(CAST(`number` AS CHAR) AS UNSIGNED) - 100 WHERE CAST(CAST(`number` AS CHAR) AS UNSIGNED) >= 100;
Query #1
SELECT * FROM `data`;
| number |
| ------ |
| 3 |
View on DB Fiddle

You can only do arithmetic operations on cardinal numbers (integer, float, double, decimal, date). An enum datatype is nominal.
You could try casting it within SQL but if this operation should be possible the issue is that you have chosen the wrong data type.
That you are exclusively using digits to represent states should have been a red flag.

But it does not work on enum data.
Of course. The string values of ENUM datatype are not the values stored in table data body - it contains 2-byte index of the value only, and according string values list is stored in table definition area which is not available for changing by any DML operation. Only DDL (ALTER TABLE) may change ENUM value string representation definition.
The posted query will treate data.number as index value - so you must get the error (source values are 1-10, final values will be negative whereas ENUM index values are UNSIGNED).

Related

MySQL AVG() of varchar possible?

I'm doing an assignment in a database-course at my uni using MySQL. We've made a database of a golf club, where we (among other things) can store players results from different games.
Now I'm writing the report for the assignment, and I'm trying to prove that it's better to use an int rather than a varchar to store the results. If an int is used, disqualified players will get a NULL-value as a result. If a varchar would've been used, they would've got a string: "disqualified".
The things I'm wondering are:
Does MySQL automatically convert varchar to int if avg() is used (for example) when a calculation is about to happen?
If it does, does that slow down the database a lot (compared to if an int would've been used)?
Is it possible to do calculations on string-values? E.g. if the result-attribute is a varchar containing "52", "68", "72", can the average be calculated?
What will happen if I've got the strings listed above plus a result that is "disqualified"? Will it ignore that string like it would've ignored a NULL of an int?
Your questions can be answered by runing simple tests:
drop table if exists golf;
create table golf(id int, int_col int, char_col varchar(50));
insert into golf(id, int_col, char_col) values
(1, 10, '10'),
(2, 20, '20');
select avg(int_col), avg(char_col) from golf;
Result:
avg(int_col) | avg(char_col)
15,0000 | 15
http://rextester.com/NNAZ9432
As you can see AVG over a VARCHAR column returns the expected result.
Now add a row with NULL and 'disqualified'
drop table if exists golf;
create table golf(id int, int_col int, char_col varchar(50));
insert into golf(id, int_col, char_col)values
(1, 10, '10'),
(2, 20, '20'),
(2, NULL, 'disqualified');
select avg(int_col), avg(char_col) from golf;
Now the results are different:
avg(int_col) | avg(char_col)
15,0000 | 10
http://rextester.com/RXOQAZ69820
The reoson is: While NULL is ignored by AVG, 'disqualified' is converted to 0 and the result is (10 + 20 + 0) / 3 = 10.
To test the performance you can create a big table with dummy data. In MariaDB with the sequence plugin this can be done easily:
drop table if exists golf;
create table golf(id mediumint primary key, int_col smallint, char_col varchar(50));
insert into golf(id, int_col, char_col)
select seq id
, floor(rand(1)*1000) int_col
, floor(rand(1)*1000) char_col
from seq_1_to_1000000;
AVG over INT:
select avg(int_col) from golf;
-- query time: 187 msec
AVG over VARCHAR:
select avg(char_col) from golf;
-- query time: 203 msec
Last but not least: You should not use string types for numeric values. One more reason is sorting. If you try to sort numbers stored as strings you will get something like [10, 2, 22, 3].
You should also not use one column for different information types. In your case you could define one more column like status with values 'finished' or 'disqualified'. Another possible way is to have a flag column disqualified with values 0 or 1.

mysql CONVERT() and CAST() to integer are increasing the value by 1

I'm trying to select records from a phone-call table where the value of an ENUM string field called Call_Rating is less than the integer value 4. The Call_Rating field can only contain the values '0','1','2','3','4','5'. Whenever I use CONVERT(Call_Rating, UNSIGNED INTEGER) or CAST(Call_Rating AS UNSIGNED), the values of the Call_Rating field are increased by 1. Why is it doing this and is there a way to avoid it other than just manually subtracting 1 from the CALL() or CAST() functions?
Also, this is an old DB that was set-up by someone else and is still in use by various systems, so some way of getting around this issue without changing the DB schema would be useful.
Create Table
CREATE TABLE `member_calls` (
`CallID` int(10) NOT NULL AUTO_INCREMENT,
`Call_Rating` enum('0','1','2','3','4','5') CHARACTER SET latin1 NOT NULL DEFAULT '0',
PRIMARY KEY (`CallID`)
) ENGINE=MyISAM AUTO_INCREMENT=538616 DEFAULT CHARSET=utf8;
Data in Table
INSERT INTO `member_calls`
(`CallID`, `Call_Rating`)
VALUES
(510515, '4'),
(510909, '0'),
(538614, '3'),
(538615, '5');
Select Statement
SELECT `CallID`, `Call_Rating`, CAST(`Call_Rating` AS UNSIGNED) AS 'Casted', CONVERT(`Call_Rating`, UNSIGNED INTEGER) AS 'Converted'
FROM `member_calls`
WHERE CONVERT(`Call_Rating`, SIGNED INTEGER) < 4
OR CAST(`Call_Rating` AS UNSIGNED) < 4;
Given Results
CallID Call_Rating Casted Converted
510909 0 1 1
Expected Results
CallID Call_Rating Casted Converted
510909 0 0 0
538614 3 3 3
Edit 2016-5-17
Thank you everyone for your input. I now understand why the issue was happening. Basically the ENUM was being treated like an array and CAST()/CONVERT() were returning the index of the array rather than the value. The best solution to this would be to change the ENUM to an INT field, but that is not desirable in my situation since the DB is being used live, and altering the data type could cause issues elsewhere. For that reason, lserni's solution was the most useful for me.
The direct conversion of ENUM to INTEGER yields the index of that ENUM, and since they start from 1, the first element is 0 and becomes 1, etc.
It's not increasing by 1 at all: it's returning an integer value that by chance seems as it's the correct value plus 1. But it could be anything else. If you had an enum of '1','2','3','4','5', without the '0', then the result would appear to be correct, even if it really isn't.
Either run a double convert passing from CHAR, or an implicit convert again after converting to CHAR:
SELECT CONVERT(CONVERT(Call_Rating, CHAR(1)), UNSIGNED), 0+CONVERT(Call_Rating, CHAR(1)), 0+Call_Rating, CAST(Call_Rating AS UNSIGNED) from member_calls;
+--------------------------------------------------+---------------------------------+---------------+-------------------------------+
| CONVERT(CONVERT(Call_Rating, CHAR(1)), UNSIGNED) | 0+CONVERT(Call_Rating, CHAR(1)) | 0+Call_Rating | CAST(Call_Rating AS UNSIGNED) |
+--------------------------------------------------+---------------------------------+---------------+-------------------------------+
| 2 | 2 | 3 | 3 |
+--------------------------------------------------+---------------------------------+---------------+-------------------------------+
I can't give a detailed explanation of what is happening here, but it's likely to do with the fact that you are using an ENUM with numeric values, instead of an INT type, which seems like the sane choice.
The mySQL manual strongly recommends against this:
We strongly recommend that you do not use numbers as enumeration values, because it does not save on storage over the appropriate TINYINT or SMALLINT type, and it is easy to mix up the strings and the underlying number values (which might not be the same) if you quote the ENUM values incorrectly. If you do use a number as an enumeration value, always enclose it in quotation marks. If the quotation marks are omitted, the number is regarded as an index. See Handling of Enumeration Literals to see how even a quoted number could be mistakenly used as a numeric index value.
mySQL is probably using the numeric index instead of the ENUM value and that's causing the weirdness.
Just switch to a proper INT field.
You should get that ENUM is not string or integer that is enum.
To me that is the type you should avoid in DB as much as possible.
Here is fiddle tha illustrate why that happens to you:
http://sqlfiddle.com/#!9/de53b2/1
as you can see when mysql casting ENUM to int - that returns something like index of saved value in array(enum) but not the value casted to int.
But just to illustrate mysql power functionality you can run this query to get expected result:
http://sqlfiddle.com/#!9/e2cf5/3
SELECT `CallID`, `Call_Rating`,
ELT(CAST(`Call_Rating` AS UNSIGNED),'0','1','2','3','4','5') AS 'Casted',
ELT(CONVERT(`Call_Rating`, SIGNED INTEGER),'0','1','2','3','4','5') AS 'Converted'
FROM `member_calls`
HAVING `Casted` < 4
OR `Converted` < 4;
But anyway that is not best solution.
I think you should better redesign your db schema.

MySql SET datatye

i try to make column in my database table , with set data type to store in it data in this fomat " 10,2,44" i made SET column like this SET('A' , 'B' , 'C')
but when i try to insert data in it i got this response
Error Code: 1265. Data truncated for column 'setcol' at row 1
and this is my query
INSERT INTO `voting`.`questionnaires` (`name`, `cat_id`, `init_date`, `end_date`, `setcol`) VALUES ('sad', '2', '2008-02-02', '2008-02-02', 'A, B');
should i use specific format ? and does set accept repetition ?
To set your desired value you would store a 3 (without quotes) instead of 'A,B'. That is because values in a SET field are stored numerically in bitwise fashion. So each value in a set definition corresponds to on/off bits in a binary number.
Therefore you statement would be:
INSERT INTO `voting`.`questionnaires` (`name`, `cat_id`, `init_date`, `end_date`, `setcol`) VALUES ('sad', '2', '2008-02-02', '2008-02-02', 3);
For example, from the MySQL Documentation:
For a column specified as SET('a','b','c','d'), the members have the
following decimal and binary values.
So since A has a value of 1 and B has a value of 2, add them together and you get 3. Or to set C and D you would store 12, etc.
For this reason, it's ill-advised to have numbers as members of a SET field (as you hinted you might) since storing '5' has a completely different meaning than storing 5.

MySQL Order By doesn't work on Concat(enum)

Currently we have an interessting problem regarding the sort order of MySQL in an enum-field. The fields enum entries have been sorted in the order we want it. Just to be save, we added a CONCAT around it, so it would be cast to char and ordered in alphabetical order, just as suggested by the MySQL-reference (MySQL Reference - Enum)
Make sure that the column is sorted lexically rather than by index number by coding ORDER BY CAST(col AS CHAR) or ORDER BY CONCAT(col).
But that didn't produce the expected results, so we started to investigate further. It seems that the order by statement doesn't work on a combination of enum and the concat function. I've wrote the following sample script, which should show my point:
CREATE TABLE test (
`col1` enum('a','b','c') COLLATE utf8_bin DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
INSERT INTO test
VALUES ('b'), ('c'), ('a');
SELECT * FROM test; -- b, c, a
SELECT * FROM test ORDER BY col1 ASC; -- a, b, c
SELECT * FROM test ORDER BY CAST(col1 AS CHAR) ASC; -- a, b, c
SELECT * FROM test ORDER BY CAST(col1 AS BINARY) ASC; -- a, b, c
SELECT * FROM test ORDER BY CONCAT(col1) ASC; -- b, c, a - This goes wrong
I am currently suspecting some kind of problem with the collation/encoding, but I'm not sure. My databases default encoding is also utf8. The MySQL version is 5.6.12 but it seems to be reproduceable with MySQL 5.1. The storage engine is MyIsam but it also occurs with the memory engine.
Any help would be appreciated.
Update:
As it seems the problem is produced only in MySQL 5.6 and by the collation of the column. With the first CREATE TABLE statement, the queries work fine.
CREATE TABLE test (
`col1` enum('a','b','c') COLLATE utf8_general_ci DEFAULT NULL
)
With the second they don't.
CREATE TABLE test (
`col1` enum('a','b','c') COLLATE utf8_bin DEFAULT NULL
)
The collation of the table and/or database don't seem to affect the queries. The queries can be tested in this SQL Fiddle
Strange,it works in this fiddle.Do you have a trigger or something?
http://sqlfiddle.com/#!2/0976a/2
BUT,in 5.6 goes haywire:
http://sqlfiddle.com/#!9/0976a/1
Mysql bug,probably.
More,if you input the values in the enum in the "proper" order it works:
http://sqlfiddle.com/#!9/a3784/1
IN the doc:
ENUM values are sorted based on their index numbers, which depend on
the order in which the enumeration members were listed in the column
specification. For example, 'b' sorts before 'a' for ENUM('b', 'a').
As per the document:
Under the Handling of Enumeration Literals section, it states that:
If you store a number into an ENUM column, the number is treated as
the index into the possible values, and the value stored is the
enumeration member with that index. (However, this does not work with
LOAD DATA, which treats all input as strings.) If the numeric value is
quoted, it is still interpreted as an index if there is no matching
string in the list of enumeration values. For these reasons, it is not
advisable to define an ENUM column with enumeration values that look
like numbers, because this can easily become confusing.
For example, the following column has enumeration members with string values of '0', '1', and '2', but numeric index values of 1, 2, and 3:
numbers ENUM('0','1','2')
If you store 2, it is interpreted as an
index value, and becomes '1' (the value with index 2). If you store
'2', it matches an enumeration value, so it is stored as '2'. If you
store '3', it does not match any enumeration value, so it is treated
as an index and becomes '2' (the value with index 3).
mysql> INSERT INTO t (numbers) VALUES(2),('2'),('3');
mysql> SELECT * FROM t;
+---------+
| numbers |
+---------+
| 1 |
| 2 |
| 2 |
+---------+
In your case:
INSERT INTO test
VALUES ('2'), ('3'), ('1');
Index value of '2' is 2, '3' is 3 and '1' is 1.
So the output is 2,3,1

ISSUE: Mysql converting Enum to Int

I have a very simple rating system in my database where each rating is stored as an enum('1','-1'). To calculate the total I tried using this statement:
SELECT SUM(CONVERT(rating, SIGNED)) as value from table WHERE _id = 1
This works fine for the positive 1 but for some reason the -1 are parsed out to 2's.
Can anyone help or offer incite?
Or should I give up and just change the column to a SIGNED INT(1)?
this is what you want
select enum+0 as enum
This conversion to int in MySQL for enum is only possible:
CAST(CAST(`rating` AS CHAR) AS SIGNED) as value from table WHERE _id = 1
Yes, I'd suggest to change the type of the column. The issue becomes clear when you read the doc about enum type (which strongly recommends not to use numbers as enumeration values!) - the index of the enum item is returned, not the enum value itself.
Ok guys,
Just had a bit of a mere of a time with this one. I learned that i shouldn't use ENUMs where integers are the values. However We had years worth of data and i couldn't alter the database.
This bad boy worked (turning it into a character, then into a signed int).
CAST(CAST(`rating` AS CHAR) AS SIGNED) as value from table WHERE _id = 1
use
SELECT SUM( IF( columnname >0, CAST( columnname AS CHAR ) , NULL ) ) AS vals
FROM `tableName`
I wouldn't use enum here too, but it is still possible in this case to get what is needed
Creating table:
CREATE TABLE test (
_id INT PRIMARY KEY,
rating ENUM('1', '-1')
);
Filling table:
INSERT INTO test VALUES(1, "1"), (2, "1"), (3, "-1"), (4, "-1"), (5, "-1");
Performing math operations on enums converts them to indexes, so it is possible just to scale the result value:
SELECT
SUM(3 - rating * 2)
FROM
test;
Result: -1 which is true for the test case.