MySQL, equal or not equal? - mysql

I am a bit puzzled to see this on MySQL
SELECT 1='1x';
gives me 1
Whereas
SELECT 1='2x';
gives me 0 (as expected)
When doing a binary compare
SELECT CAST(1 AS BINARY)=CAST('1x' AS BINARY);
it gives me 0 (also as expected)
Question: How can I stop MySQL from behaving like this? I want '=' to act as 100% binary equal. I don't want MySQL to assume or guess what I might wanted to compare.

When comparing strings and numbers then MySQL automatically makes a conversion.
So the string gets converted to a number and it starts from left to right and tries to make a number from it.
So
'1x' => 1
'x1' => 0 //because it does not start with a number on the left
You can't change that behaviour. And you should not compare different data types anyway. So it is your fault :)

Related

SQL Query giving wrong results

The query executed should match the story_id with the provided string but when I execute the query it's giving me a wrong result. Please refer to the screenshot.
story_id column in your case is of INT (or numeric) datatype.
MySQL does automatic typecasting in this case. So, 5bff82... gets typecasted to 5 and thus you get the row corresponding to story_id = 5
Type Conversion in Expression Evaluation
When an operator is used with operands of different types, type
conversion occurs to make the operands compatible. Some conversions
occur implicitly. For example, MySQL automatically converts strings to
numbers as necessary, and vice versa.
Now, ideally your application code should be robust enough to handle this input. If you expect the input to be numeric only, then your application code can use validation operations on the data (to ensure that it is only a number, without typecasting) before sending it to MySQL server.
Another way would be to explicitly typecast story_id as string datatype and then perform the comparison. However this is not recommended approach as this would not be able to utilize Indexing.
SELECT * FROM story
WHERE (CAST story_id AS CHAR(12)) = '5bff82...'
If you run the above query, you would get no results.
you can also use smth like this:
SELECT * FROM story
WHERE regexp_like(story_id,'^[1-5]{1}(.*)$');
for any story_ids starting with any number and matching any no of charatcers after that it wont match with story_id=5;
AND if you explicitly want to match it with a string;

Why does the following SQL query work, despite the syntax looking wrong?

I have written a lengthy SQL UPDATE query, joined across multiple tables, for the purpose of redacting/anonymizing old customer data.
As part of this process i want to keep the first segment of any UK postcodes found, so I have this query ( in the SET clause of an UPDATE query):
oh.postcode = IF(oh.country = 'United Kingdom',
IF(#cPos:=LOCATE(' ', TRIM(oh.postcode) > 0),
SUBSTRING(UPPER(TRIM(oh.postcode)),
0,
#cPos - 1),
TRIM(REVERSE(SUBSTRING(REVERSE(UPPER(TRIM(oh.postcode))),
4)))),
LEFT(LTRIM(oh.postcode), 2)),
('oh' is just a table alias)
the question is, why does this work? I draw your attention to the second line, which I would expect to be;
IF(#cPos:=LOCATE(' ', TRIM(oh.postcode)) > 0,
since the second argument to LOCATE() ought not to be a boolean expression...
or even
IF( ( #cPos:=LOCATE(' ', TRIM(oh.postcode)) ) > 0,
extra brackets around assignment to ensure the string offset is assigned, and not the result of the boolean expression???
It seems that something odd is going on here, or i don't fully understand the syntax and associativity rules here... can anyone explain what is going on?
I am not looking for a rewrite here, though if anyone know a better way to do this i would be happy to learn something, what i'm after is a deeper understanding of why the original query works, when it doesn't look like it should.
EDIT: Correctly Answered by Damien_The_Unbeliever, it appears that the split on space part of this query never runs, and it alway just chops the 3 chars off the end, of course i did not notice this to begin with as the end result looks the same...
I realise it's not part of the original question, but i would welcome any suggestions on the proper way to achieve this...
EDIT2
This works:
SET #pc = ' W1s 3NW';
SELECT
IF(#cPos:=LOCATE(' ', TRIM(#pc)),
SUBSTRING(UPPER(TRIM(#pc)),
1,
#cPos - 1),
TRIM(REVERSE(SUBSTRING(REVERSE(UPPER(TRIM(#pc))),
4))));
i think i forgot about mysql strings having a 1-based index...
So far as I can see, we first work with this expression:
TRIM(oh.postcode) > 0
On the left, we have a string. On the right, we have a number. By MySQL's logic2, that means we should convert both to floats and do that comparison.
Since most1 UK postcodes don't start with any numeric digits, the conversion to float will produce a 02. And 0 is not greater than 0. This expression will always (absent a really screwed up attempt at a UK postcode) be false. But even if that was true, we now come to:
LOCATE(' ', <boolean>)
Well, that's no good. LOCATE wants a string, but we have a boolean. That's okay though. By MySQL logic2, we'll convert false (not sure if it goes via a numeric conversion or direct) to the string 0, and we'll convert true to 1.
Since neither 0 nor 1 ever contain a space, LOCATE(' ',<'0' or '1'>) is always going to return 0, so that's what's always assigned to #cPos, and since that assignment is then used as the truth value for the inner IF, we never use the expression in the second parameter and always use the third, which is just using the simple "chop" heuristic rather than trying to work with a space.
I think that you would get the correct-ish behaviour just by removing the > 0 comparison entirely and just leave the TRIM result as the second parameter to LOCATE.
1I am also based in the UK :-)
2You may be able to infer from my tone. I am not a fan of MySQL's logic. I'd far rather have a honking error stuck in my face that I can fix in seconds by adding an explicit type conversion if that's what I really intended rather than all of the conversions that MySQL will assume.

Select statement returns data although given value in the where clause is false

I have a table on my MySQL db named membertable. The table consists of two fields which are memberid and membername. The memberid field has the type of integer and uses auto_increment function starting from 2001. The membername table has the type of varchar.
The membertable has two records with the same order as described above. The records look like this :
memberid : 2001
membername : john smith
memberid : 2002
membername : will smith
I found something weird when I ran a SELECT statement against the memberid field. Running the following statement :
SELECT * FROM `membertable` WHERE `memberid` = '2001somecharacter'
It returned the first data.
Why did that happen? There's no record with memberid = 2001somecharacter. It looks like MySQL only search the first 4 character (2001) and when It's found related data, which is the returned data above, it denies the remaining characters.
How could this happen? And is there any way to turn off this behavior?
--
membertable uses innodb engine
This happens because mysql tries to convert "2001somecharacter" into a number which returns 2001.
Since you're comparing a number to a string, you should use
SELECT * FROM `membertable` WHERE CONVERT(`memberid`,CHAR) = '2001somecharacter';
to avoid this behavior.
OR to do it properly, is NOT put your search variable in quotes so that it has to be a number otherwise it'll blow up because of syntax error and then in front end making sure it's a number before passing in the query.
sqlfiddle
Your finding is an expexted MySQL behaviour.
MySQL converts a varchar to an integer starting from the beginning. As long as there are numeric characters wich can easily be converted, they are icluded in the conversion process. If there's a letter, the conversion stops returning the integer value of the numeric string read so far...
Here's some description of this behavior on the MySQL documentation Site. Unfortunately, it's not mentioned directly in the text, but there's an example which exactly shows this behaviour.
MySQL is very liberal in converting string values to numeric values when evaluated in numeric context.
As a demonstration, adding 0 causes the string to evaluated in a numeric context:
SELECT '2001foo' + 0 --> 2001
, '01.2-3E' + 0 --> 1.2
, 'abc567g' + 0 --> 0
When a string is evaluated in a numeric context, MySQL reads the string character by character, until it encounters a character where the string can no longer be interpreted as a numeric value, or until it reaches the end of the string.
I don't know of a way to "turn off" or disable this behavior. (There may be a setting of sql_mode that changes this behavior, but likely that change will impact other SQL statements that are working, which may stop working if that change is made.
Typically, this kind of check of the arguments is done in the application.
But if you need to do this in the SELECT statement, one option would be cast/convert the column as a character string, and then do the comparison.
But that can have some significant performance consequences. If we do a cast or convert (or any function) on a column that's in a condition in the WHERE clause, MySQL will not be able to use a range scan operation on a suitable index. We're forcing MySQL to perform the cast/convert operation on every row in the table, and compare the result to the literal.
So, that's not the best pattern.
If I needed to perform a check like that within the SQL statement, I would do something like this:
WHERE t.memberid = '2001foo' + 0
AND CAST('2001foo' + 0 AS CHAR) = '2001foo'
The first line is doing the same thing as the current query. And that can take advantage of a suitable index.
The second condition is converting the same value to a numeric, then casting that back to character, and then comparing the result to the original. With the values shown here, it will evaluate to FALSE, and the query will not return any rows.
This will also not return a row if the string value has a leading space, ' 2001'. The second condition is going to evaluate as FALSE.
When comparing an INT to a 'string', the string is converted to a number.
Converting a string to a number takes as many of the leading characters as it can and still be a number. So '2001character' is treated as the number 2001.
If you want non-numeric characters in member_id, make it VARCHAR.
If you want only numeric ids, then reject '200.1character'

Why does MySQL equal sign matches wrong entries?

I'm running a select query on two tables and searching the matching entries with an equal sign. In my understanding, MySQL should only return entries exactly matching the WHERE condition, however it's returns entries like when I use the LIKE statement:
Any explanations why would the first row be returned as a result of the query?
EDIT:
Here's the query:
SELECT `ts`.`ticker_symbol`, `sm`.`id` AS `matchescount`, `sm`.`ticker_symbol_ids`
FROM `mk_ticker_symbols` `ts`, `mk_submissions` `sm`
WHERE `sm`.`ticker_symbol_ids` = `ts`.`id` AND `ts`.`id` = "1506"
EDIT 2:
Here's the SQL Fiddle:
http://sqlfiddle.com/#!9/5550b/1/0
EDIT 3:
Here's the SQL Fiddle with JOINs:
http://sqlfiddle.com/#!9/5550b/2/0
Piero,
The one with JOINs can be corrected. CAST() within JOIN will fix the issue.
INNER JOIN `mk_submissions` `sm`
ON `sm`.`ticker_symbol_ids` = CAST(`ts`.`id` AS CHAR(10))
I know you are not looking for solution, but I still post it.
The problem is VERY interesting.
I searched online, and did some trial-error on my DB. I have no explanations....
I tried to put 1506, in the second, or third place in comma separated list - the query works fine.
So, I have a feeling, that in case of JOIN with comma-separated list, comma gets treated as wildcard 'end of string'...
If you ever find an explanation, please post it here.
When evaluating expressions, MySQL converts both arguments (in this case) to floating point numbers to compare them. This is because one is a string, and one is an integer, which results in the final condition in the link above being applied.
In all other cases, the arguments are compared as floating-point
(real) numbers.
So what is the floating point equivalent of the string "1506,..."?
Running the following on my test server:
SELECT "1506,3101,26673,26745,2277,1216,26847,26865,20711,1468,26947,233,20539,26985"+0.0
Results in:
1506
Which of course equals the floating point version of the integer 1506.
So, everything is behaving as expected. At least, assuming you expect this floating point comparison to be happening.
I can't give a full explanation for the problem, but I have a solution.
ts.id is likely and INTEGER so your where clause should be
`ts`.`id` = 1506
(remove quotes from the number).
Also you should use a join instead of a where clause to match the tables:
FROM `mk_ticker_symbols` `ts`
JOIN `mk_submissions` `sm` on sm.ticker_symbol_ids = ts.id
I found the answer to this issue. I was comparing string to integer here:
`sm`.`ticker_symbol_ids` = `ts`.`id` AND `ts`.`id` = "1506"
The problem is that, this is converted to integer internally for comparison:
1506,3101,26673,26745,2277,1216,26847,26865,20711,1468,26947,233,20539,26985
Because of the comma, MySQL thinks it's a decimal or float with the floating point, and everything after the comma is omitted for comparison. So it becomes 1506 instead of 1506,3101,26673,26745,2277,1216,26847,26865,20711,1468,26947,233,20539,26985, and that matches the WHERE condition.
#cyadvert and #Willem_Renzema were absolutely correct.
To resolve the issue
I only needed to:
CAST(`ts`.`id` AS CHAR)

MySQL CHAR_LENGTH returns wrong number

I'm making a serial system for my site, and it using the MySQL CHAR_LENGTH function to get all the serials with the matched length. Thing is, it's returning strings that are more than I'm looking for. I've also tried with CHARACTER_LENGTH and LENGTH, having no impact on the result :/.
This is my I/O:
SELECT serials.code
FROM serials
WHERE CHARACTER_LENGTH(code) = 12
These are the results:
niels-er-sej
KUVX-21-40
KUVX-21-40
As you can see, the last two, are only 10 characters, yet they show up when I search for 12?
I've added a picture of the Character lengths as request: