Strict matching of strings and integers - mysql

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

Related

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

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.

MySQL WHERE Condition on integer field returning incorrect values

I'm having a problem with MySQL returning the incorrect result when applying a WHERE condition to an integer field with a string value.
CREATE TABLE `people` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=latin1;
INSERT INTO `people` (`id`, `name`)
VALUES
(1, 'Bob'),
(2, 'Sally'),
(3, 'Jim');
Now when I run the query:
SELECT *
FROM people
WHERE id = '1-abcd';
My result set is:
id name
1 Bob
MySQL appears to be truncating the string value '1-abcd' to '1' behind the scenes as soon as it hits a non-integral character (in the conversion from a string to INT).
You're probably wondering why this matters. I'm trying to fix a site for a PCI compliance scan. The scan thinks the URI '/some/page?id=102-1' is allowing some form of sequel injection, but in reality it's showing the same content at '/some/page?id=102'.
This is not an issue in one place. It is an issue all over the place, and it's a fairly large system. Is there some way to rectify this on the MySQL end of things, so it no longer mistakenly judges the two values to be equivalent? I looked at the documentation for SQL modes, but didn't see anything regarding this circumstance.
UPDATE: I filed a dispute with the company that produced the scan, which they accepted, so I'm no longer in the woods. But it is disappointing that there's apparently no way to configure the casting behavior of MySQL from a string to INT in this case. (You can, but only for INSERTs and UPDATEs.)
What happens that MySQL type-casts 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 1-0 gives output matching to 1. To do this you can use cast. I am not 100% sure about the syntax but it is like this:
select * from people
where id =
(
case when ISNUMERIC( '1-0' )
then cast ('1-0' as int)
else null
end )
What this will do is that if it is an numeric value then it will return the correct matching row or else not.
Edit:
The above query seems to be of MSSQL/Oracle and would not work with MySQL. For MySQL you can use RegExp. I have never use one but you can find more details here:
http://mysqlhints.blogspot.in/2012/01/how-to-find-out-if-entire-string-is.html
http://www.ash.burton.fm/blogs/2010/12/quick-tip-mysql-equivalent-of-isnumeric
http://www.justskins.com/forums/how-to-use-isnumeric-137604.html

Mysql restrict value of a field to be one of the defined ones

I am using mysql database.
I have a field user_type in USER table. I would like to restrict the values in this field to be one of ('ADMIN','AGENT','CUSTOMER').
The insert statements should fail if they tried to insert anything else than the above possible values. Also, I need defaulting to 'CUSTOMER' is none is specified in the insert statements.
The possible solution I could think of is use of triggers, but I would like to know How this could be handled more efficiently (possibly in the create table ddl itself?).
Any ideas, How to do this?
This is what the column type "enum" is for. You treat it like a string, and behind the scenes it is stored as an int, and must be one of the values defined in the DDL:
CREATE TABLE users (
id int unsigned NOT NULL auto_increment primary key,
user_type enum('ADMIN', 'AGENT', 'CUSTOMER') NOT NULL default 'CUSTOMER'
)
Then insert like so:
INSERT INTO users (user_type) VALUES ('ADMIN'); // success
INSERT INTO users (user_type) VALUES ('ANONYMOUS'); // failure (or '' if not "strict" mode)
INSERT INTO users (user_type) VALUES (default(user_type)); // uses default
INSERT INTO users () VALUES (); // uses default
INSERT INTO users (user_type) VALUES (NULL); // failure
note
Note that for the query to actually fail, you must use "SQL strict mode". Otherwise, an "empty string" value (which is slightly special in that it has the numeric value of 0) is inserted.
Quoting this docs page:
When this manual refers to “strict mode,” it means a mode where at least one of STRICT_TRANS_TABLES or STRICT_ALL_TABLES is enabled.
I came across this post, and as it dates somewhat back, I was thinking of others coming across it these days too and miss the (in my opinion) simpler approach of simply adding a CHECKconstraint (e.g. this for MySQL, or this for MariaDB).
In my opinion, using a CHECK constraint is much easier than using things like ENUM and / or SET as you don't need to worry about the relations to integer indexes etc. when relying on them. They for example can become weird when you try to preset allowed integer values for a column.
Example, where you want to have a column which has values ranging from 1 to 5:
CREATE TABLE myTable (
myCol INT NOT NULL
CONSTRAINT CHECK (0 < `myCol` < 5)
);

Strange behavior when query for varchar filed

I came across this strange behavior when I was hunting for a bug in a system. Consider following.
We have a mysql table which have varchar(100) column. See the following sql script.
create table user(`id` bigint(20) NOT NULL AUTO_INCREMENT,`user_id` varchar(100) NOT NULL,`username` varchar(255) DEFAULT NULL,PRIMARY KEY (`id`),UNIQUE KEY `user_id` (`user_id`)) ENGINE=InnoDB AUTO_INCREMENT=129 DEFAULT CHARSET=latin1;
insert into user(user_id, username) values('20120723145614834', 'user1');
insert into user(user_id, username) values('20120723151128642', 'user1');
When I execute following query I received 0 results.
select * from user where user_id=20120723145614834;
But When I execute following I get the result(note the single quote).
select * from user where user_id='20120723145614834';
This is expected since user_id field is varchar. Strange thing is that both following queries yield result.
select * from user where user_id=20120723151128642;
select * from user where user_id='20120723151128642';
Can anybody explain me the reason for this strange behavior. My MySql version is 5.1.63-0ubuntu0.11.10.1
Check mysql document 12.2. Type Conversion in Expression Evaluation
Comparisons that use floating-point numbers (or values that are
converted to floating-point numbers) are approximate because such
numbers are inexact. This might lead to results that appear
inconsistent:
mysql> SELECT '18015376320243458' = 18015376320243458;
-> 1
mysql> SELECT '18015376320243459' = 18015376320243459;
-> 0
So we better use always right data type for SQL.