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

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

Related

MySQL returning multiple rows that does not match search criteria

I have MySQL installed locally, running SELECT VERSION() returns this value: 5.6.43-84.3
When I run a query it is returning multiple rows when it should only return 1 row. Let me set it up, it's easier to explain that way.
Create a test table:
CREATE TABLE test_table
(
test_val VARCHAR(255)
)
;
Load 3 values into the table:
INSERT INTO test_table (test_val)
VALUES
('9671986020630615'),
('9671986020630616'),
('9671986020630617')
;
Run this query (This query returns 1 row which is expected):
SELECT *
FROM test_table
WHERE test_val = '9671986020630615'
;
Run this query (This query returns 3 rows, which it shouldn't):
SELECT *
FROM test_table
WHERE test_val = 9671986020630615
;
Here's what I have observed about this situation:
The first query surrounds the value in the WHERE clause with single quotes.
The second query does not surround the value in the WHERE clause with single tics.
The column in the test table is defined as VARCHAR(255)
It makes sense that the first query returns just one row because it's comparing a string from the WHERE clause to a string value in the test table (VARCHAR(255))
Something is happening when MySQL compares the numerical value in the WHERE clause of the second query to the string value in the test table (VARCHAR(255)) which is causing MySQL to return 3 rows instead of just 1.
It makes sense that the first query returns the correct result because it is comparing a string to a string.
It also makes a degree of sense that the second query is returning a bad dataset (3 rows as opposed to the 1 row it should return).
But my question is why is MySQL doing this? Why when it compares a number to 3 different VARCHAR(255) values does it return all 3 rows when the true value of the numerical value in the WHERE clause only matches 1 row?
So, in essence for the first query MySQL is saying:
'9671986020630615' = '9671986020630615',
'9671986020630615' <> '9671986020630616',
'9671986020630615' <> '9671986020630617'
but for the second query it is saying:
9671986020630615 = '9671986020630615',
9671986020630615 = '9671986020630616',
9671986020630615 = '9671986020630617'
Any help will be much appreciated.
MySQL handles all numbers internally the same way Javascript does, with IEEE double-precision floating point representation.
When you omit the quotation marks from your long numeric strings, that is you write 9671986020630615 in place of '9671986020630615 ', MySQL uses the number. Then, when it runs the WHERE part of your query, it silently coerces each column value to a double precision number.
But due to the machine epsilon -- the limit of precision -- of double precision, 9671986020630615, 9671986020630616, and 9671986020630617 all have the same value. So the WHERE finds all three.
CAST(9671986020630615 AS DOUBLE) CAST(9671986020630616 AS DOUBLE) CAST(9671986020630617 AS DOUBLE)
9.671986020630616e15 9.671986020630616e15 9.671986020630616e15 |
See how all three integers have the same representation as DOUBLE?

Unexpected result using SELECT ... WHERE id = 0 on VARCHAR id in 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.

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

How to select value number of ENUM types in MySql?

I need to select a row from table below, but the problem is the value in $row['city'] is the textual represent of the value, and i need its number(Toronto = 2). (Same as when we INSERT INTO, and we use value number instead of text)
Requests Table Structure:
req_id INT
uname VARCHAR(30)
city ENUM('New York', 'Toronto', 'Las Vegas')
You just need to force your city into a numeric context, from the fine manual:
If you retrieve an ENUM value in a numeric context, the column value's index is returned. For example, you can retrieve numeric values from an ENUM column like this:
mysql> SELECT enum_col+0 FROM tbl_name;
So you want this sort of thing:
select req_id, city+0
from your_table
where city = 'Toronto'
BTW, you can insert an enum using either the string or integer representation.
You can use the CAST function. The documentation doesn't mention this specific use case, but it works as expected. I prefer it because it looks elegant and clear:
SELECT CAST(city AS UNSIGNED) FROM your_table;