Unexpected result using SELECT ... WHERE id = 0 on VARCHAR id in MySQL - mysql

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.

Related

MySQL JSON_OBJECT returning unparsable JSON

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

Why MySQL "WHERE" clause approximation: retrieves values even the condition is not met

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';

Null entering as 0 in the below query

In the below query I am saving into data base an array from multiple select2 options.
Here i have a database table participants with event_id(int, not null), user_id(int, null), p_mail(varchar, null).
When I pass proper id which is in users the data gets saved properly and null will be displayed in p_email column.
But when i pass some varchar values through array, the user_id is saved as 0 which should be NULL and Null is stored in p_email which should store the values.
foreach($resources as $r) {
global $response;
$sql = "INSERT INTO participants(event_id, user_id, p_email)
SELECT $response,
CASE
WHEN EXISTS (SELECT * from users where id = '$r') THEN
'$r'
ELSE NULL
END,
CASE
WHEN EXISTS (SELECT * from users where id = '$r') THEN
NULL
ELSE '$r'
END
FROM users WHERE id = 1;";
$query_result = $conn->query($sql) or die("Failed".$sql);
this is expected where it int is passed it should be stored in user_id column if id exists otherwise if its varchar it should be stored in p_email.
Present if I store a varchar which is not in users after checking instead of storing it in p_email. it stores null in p_email and 0 in user_id.
I think the issue here is that the id column is numeric. In the first test, you are presumably passing some legitimate numerical string id, such as '123'. MySQL has no issue internally converting between '123', the string, and 123, the number. However, in your second test, you are passing something like abc. In this case, MySQL appears to be converting that to a numeric value of 0. Also, assuming the id zero does exist, you would see NULL in your email column as well.
So, I think the correction here is to just bind numbers to your insert query, assuming the id column is really numeric.
And, you should read about using prepared statements in MySQL.
I cleared this issue by using LIKE instead of '=' in the code.

Querying a string from int column?

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

Strict matching of strings and integers

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).