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.
Related
I have a table of users similar to the statement below, (relevant fields included)
CREATE TABLE User (
ID INT NOT NULL AUTO_INCREMENT,
Username VARCHAR(30) NOT NULL,
IsActive BIT NOT NULL DEFAULT 1,
PRIMARY KEY (ID)
);
When I query the table with a simple SELECT statements, I get the fields back exactly as expected.
+----------------------------+
| ID | Username | IsActive |
+----+------------+----------+
| 42 | CoolGuy92 | 1 |
+----+------------+----------+
That's all well and good. If, though, I try to run the following query, and send its output to the browser for use, I get this error on the client side when trying to parse the return: Uncaught SyntaxError: Unexpected token in JSON at position X.
SELECT JSON_OBJECT(
"ID", Users.ID,
"Username", Users.Username,
"IsActive", Users.IsActive
)
FROM Users
What is causing this parsing error?
The parsing error occurs due to your IsActive column being the BIT data type. BIT columns have a charset and collation of binary, and are thusly stored as binary. An apparent value of 1 is actually stored as 0x01. During a normal query, MySQL will output the value of a BIT column using the schema's default character set, often treating it as an integer*. So for most cases, an apparent value of 1, becomes 0x31 and 0 becomes 0x30. This allows the returned values to be readable, and easily used as numbers or strings.
In the case of JSON_OBJECT though,
Strings produced by converting JSON values have a character set of utf8mb4 and a collation of utf8mb4_bin*
By making this conversion, the field's literal value is used and is output as \u0001. This is not escaped in any way, so when your browser attempts to parse the output, it fails when it reaches such a value.
To get around this, you can cast your BIT columns to another numeric datatype in your JSON_OBJECT call, or add 0 to it*.
SELECT JSON_OBJECT(
"ID", Users.ID,
"Username", Users.Username,
"IsActive", Users.IsActive + 0
)
FROM Users
I have a table which primary key is numeric and auto-incremented.
When I run a query such as:
SELECT * FROM my_table where id = '1a';
The query returns the row with the primary key set to "1".
I was not aware of this behavior, is it possible to prevent it?
I was expecting this WHERE clause to retrieve nothing since the id is "1" and not "1a". It is behaving like it was a LIKE clause.
MySQL implicitly converts a String literal to int while comparing with an int column.
You should really fix your application code (eg: PHP), and properly typecast to (int) before using them in a query. Ideally, your application should not have been inputting string values to compare against an integer field.
Now still, if you don't have control over input value, an approach can be to check if the value is numeric or not, and use it accordingly for comparison. Adapting a sargable approach from https://dba.stackexchange.com/q/89760/160363
SELECT * FROM my_table
WHERE id = CASE WHEN CONCAT('','1a'*1) = '1a' THEN '1a' ELSE NULL END;
mysql automatically converts strings to numbers, and just takes the leading characters that are digits. You could instead explicitly cast the ID to a string:
SELECT * FROM my_table where CAST(id AS CHAR) = '1a';
I'm using MySQL 8 with InnoDB with a node server with mysql2 driver.
My table looks like:
CREATE TABLE IF NOT EXISTS users(
id VARCHAR(36) NOT NULL,
name VARCHAR(32) NOT NULL,
email VARCHAR(255) NOT NULL,
...
PRIMARY KEY (id)
)
I use no auto increment and as VARCHAR ids, I use time based UUIDs.
If I now do my SELECT query:
SELECT * FROM users where id = 'some valid id';
I get my expected result.
If I do:
SELECT * FROM users where id = '0';
I get nothing, because no id in my table has the value '0'.
BUT, if i do:
SELECT * FROM users where id = 0;
I get the last inserted row, which has, of course, a valid VARCHAR id different from 0.
This behavior occured on my node server by accident, because JS sometimes interpretes undefined as 0 in http querys.
In consequence I can easyly avoid inserting 0 in my querys (what I do now), but I would like to understand why this happens.
Your id is varchar(), so this comparison:
WHERE id = 0
requires type conversion.
According to the conversion rules in SQL, the id is turned into a string. Now, in many databases, you would get an error if any values of id could not be converted into numbers.
However, MySQL supports implicit conversion with no errors. (You can read about such conversion in the documentation.) This converts all leading digits to a number -- ignoring the rest. If there are no leading digits, then the value is zero. So, all these are true in MySQL:
'a' = 0
'0a' = 0'
'anything but 0!' = 0
There are two morals to this story.
If you really want id to be a number, then use a number data type (int, bigint, decimal).
Don't mix types in comparisons.
I have a table:
CREATE TABLE `ids` (
id int(11) not null auto_increment,
PRIMARY KEY (id)
);
It contains some IDs: 111, 112, 113, 114 etc.
I made a query:
SELECT * FROM `ids` WHERE id = '112abcdefg'
I expected nothing but I've got a result, a row with ID of 112. Seems that MySQL quietly converted my string to integer and then compared it against column values.
How can I change the query so that querying the same string from id column will give no results as I expect? Is there a strict comparison modifier in MySQL?
One option is to CAST the 112 to CHAR to get a proper match:
WHERE CAST(id AS CHAR(12)) = '112abcdefg'
The 12 in CHAR is a guess; it should be large enough for your biggest id.
That will probably kill any chance of optimization, so another option (though one I'm not 100% sure of) is to use a BINARY comparison. I've tried this with a few different values and it works:
WHERE BINARY id = '112abcdefg'
You are comparing a string, just put the number with no quotes:
SELECT * FROM `ids` WHERE id = 112
If you dont, it will convert the string '112abcdefg' to a number and say its 112
The response you are seeing is because you are trying to compare an integer column to a string value. In that case, MySQL will type-cast the string literal value to an integer, and when it does that it starts from the left of the string and as soon as it reaches a character that cannot be considered part of a number, it strips out everything from that point on. So trying to compare "256abcd" to an integer column will result in actually comparing the number 256.
So your options (or at least a few of them) would be:
Validate the input string in your application code and reject it if it's not an integer (see the ctype_digit function in PHP).
Change the column type for the filename if you want to treat it as a string (e.g. a VARCHAR type).
Cast the column value to a string:
. . . WHERE CAST(Id AS CHAR) = '256aei'
Source
you can use this :
SET sql_mode = STRICT_TRANS_TABLES;
this sets you sql mode to strict checking, and then try firing the query you mentioned.
lame + kills optimization but serves it purpose
SELECT * FROM `ids` WHERE concat(id) = '112abcdefg';
that way you enforce casting to string
http://dev.mysql.com/doc/refman/5.1/en/type-conversion.html
I am writing a flexible search mechanism for a customer's website. I am utilizing union clauses to query a number of different fields in the database in search of a string value entered by the user. This works fine except for one issue.
When comparing a string of a text to an integer that is currently set to zero, the match always returns true. In other words, according to MySQL, "email#example.com" is equal to 0.
I have tried utilizing the CAST and CONVERT function to turn this into a standard string to string comparison, but I can't seem to get the syntax right. My attempts either repeat the above issue or return no rows at all when some should match. I am also concerned that doing this would have an effect on performance since I am combining lots of unions.
What I really need is a strict comparison between an entered string and the value in the database, be it an integer or string.
EDIT:
Here is an example.
CREATE TABLE `test_table` (
`id` INT NOT NULL AUTO_INCREMENT ,
`email` VARCHAR(255) NOT NULL ,
`phone` BIGINT(19) NOT NULL DEFAULT '0' ,
PRIMARY KEY (`id`) )
ENGINE = MyISAM;
INSERT INTO `test_table` (`id`, `email`, `phone`) VALUES (1, 'email#example.com', 0);
SELECT * FROM test_table WHERE phone = 'email#example.com';
Execute this and the one row that has been inserted will return. My issue is that it shouldn't!
This query should fail:
SELECT * FROM test_table WHERE cast(phone as char) = 'email#example.com';
The cause of the original problem is that when comparing strings and numbers, it converts the string to a number (so you can write where phone = '123'). You need to use an explicit cast of the field to make it a string-to-string comparison, to prevent this default conversion.
Unfortunately, casting like this is likely to prevent it from using indexes. Even if the field is already char, the cast apparently prevents it from indexing.
You could also solve it during input validation: if phone is an integer, don't allow the user to provide a non-integer value in the search field.
How about replacing:
SELECT * FROM test_table WHERE phone = 'email#example.com'
with:
SELECT * FROM test_table WHERE phone = 'email#example.com' and phone <> 0
<> means different from.
This will work for you because you are using 0 in the phone column to mean there isn't a phone number (although it would be better style to use NULL for no phone number).