Strange casting behavior in mysql - mysql

Here is the code
mysql> SELECT id FROM tbl WHERE id = '1h';
+----+
| id |
+----+
| 1 |
+----+
1 row in set
There is indeed a field with id 1 (but not '1h').
Here is an extraction from MySQL docs: http://dev.mysql.com/doc/refman/5.1/en/type-conversion.html
mysql> SELECT 1 > '6x';
-> 0
mysql> SELECT 7 > '6x';
-> 1
So this bug is documented, so to say. The question is what's the reason for such behavior and how to correct it to make this not cast strings with char symbols? I can cast all field values like
mysql> SELECT id FROM tbl WHERE cast(`id`, BINARY) = '1h';
but i don't like this variant too much

This is not a bug.
The solution is not to query on numeric columns using a string value for your condition.
Never rely on implicit type casting.

None of your observations are bugs. They are the result of relying in implicit type casting.
In all of your examples, you're requiring MySQL to convert a string to an int. If you read the very page that you linked to, you will see that MySQL follows some rules in achieving this. As a result
'1h' -> 1
'6x' -> 6
'x6' -> 0
So, if you follow these rules, you'll be OK.
Better still, just don't put MySQL in a position where it needs to be doing these conversion. Such situations usually point to some kind of logic bug elsewhere in the system.

Related

whats wrong in below query Select CONVERT(xml,'<x>' + Replace(A.name,':','</x><x>')+'</x>' ) as xDim from Erecharge;

I want to convert string to xml column ..
I used below query for that :
Select CONVERT(xml,'<x>' + Replace(A.name,':','</x><x>')+'</x>' ) as xDim from Erecharge;
but it shows error of incorrect sql syntax..
I want to know whats wrong in above query
I also tried this:
Select Cast('<x>' + Replace(A.name,':','</x><x>')+'</x>' as XML) as xDim from Erecharge;
check the manual that corresponds to your MySQL server version for the right syntax to use near 'XML) as xDim from Erecharge'
This means that XML is incorrect in a expression like this:
CAST('foo' AS XML)
As per the docs, the values allowed for CAST type do not include XML.
Additionally, using the + operator on strings is just a convoluted way to render zero:
mysql> SELECT 'a' + 'b';
+-----------+
| 'a' + 'b' |
+-----------+
| 0 |
+-----------+
1 row in set, 2 warnings (0.00 sec)
It's not entirely clear what you're trying to do. MySQL has XML Functions but it doesn't have XML data types. If you just want to produce a string that happens to contain XML code then you need to CONCAT():
mysql> SELECT CONCAT('<date>', CURRENT_TIMESTAMP, '</date>') AS foo;
+----------------------------------+
| foo |
+----------------------------------+
| <date>2018-10-12 11:44:29</date> |
+----------------------------------+
1 row in set (0.00 sec)
... but of course you still need to ensure that angle brackets and similar stuff don't break the XML. CDATA may help. (No idea about XML functions, I'm not familiar with them.)

How do I convert from a date to string in mySQL?

I need to treat a date field in mySQL as if it is a string. For the purposes of using the date in a LIKE statement:
select * from table where dob like some_string;
Doing this currently produces the following warning:
mysql> show warnings;
+---------+------+--------------------------------------------------------+
| Level | Code | Message |
+---------+------+--------------------------------------------------------+
| Warning | 1292 | Incorrect date value: '1492' for column 'dob' at row 1 |
+---------+------+--------------------------------------------------------+
1 row in set (0.00 sec)
I would use DATE_FORMAT to get a string representation of your DATE column:
SELECT
*
FROM
yourtable
WHERE
DATE_FORMAT(dob, '%Y-%m-%d') LIKE '1492%';
and use the pattern that suits your need best. You find the specifiers right in the linked manual page.
Note:
MySQL can't use an index for this kind of query, so it will be slow.
Use a cast
Select *
From Table
Where Cast(dob as nvarchar(20)) Like some_string;
using the date in a LIKE statement
No. Things that this approach does wrong:
Wastes CPU time converting dates to strings.
Wastes time doing string comparisons when integer comparisons could be done.
Throws away any index on dob that might otherwise make the query more efficient.
A better approach would be something like:
SELECT *
FROM table
WHERE dob BETWEEN '1492-01-01' AND '1492-12-31'
You will always want to do as few type conversions as possible and keep the table data as-is so that indexes are properly utilized.

Broken unicode after simple concat + left mysql commands

I found some very strange mysql behavior.
If I run the following command:
mysql> select left(concat("A", "B®"), 3);
Then the output is as expected:
+-----------------------------+
| left(concat("A", "B®"), 3) |
+-----------------------------+
| AB® |
+-----------------------------+
1 row in set (0.00 sec)
However, if I change "A" with some number (1 in this case):
mysql> select left(concat(1, "B®"), 3);
The unicode character "®" becomes corrupted:
+---------------------------+
| left(concat(1, "B®"), 3) |
+---------------------------+
| 1B? |
+---------------------------+
1 row in set (0.00 sec)
Anybody knows how to explain this strange behavior and how to avoid it?
The example above is only a reproduction, in the real life it's a concat of numbers together with strings unknown ahead (not hard-coded strings).
Thanks a lot!
Mysql doesn't convert integer to strings literally. It converts number into the binary representation of it, which is not the same. "if the arguments include any binary strings, the result is a binary string. A numeric argument is converted to its equivalent binary string form; if you want to avoid that, you can use an explicit type cast, as in this example:
SELECT CONCAT(CAST(int_col AS CHAR), char_col);
Refer this for details.
I would also like to read from others if someone has different opinion.

Mysql Like + Wild Card vs Equals Operator

I recently just fixed a bug in some of my code and was hoping someone could explain to me why the bug occurred.
I had a query like this:
SELECT * FROM my_table WHERE my_field=13
Unexpectedly, this was returning rows where my_field was equal to either 13 or 13a. The fix was simple, I changed the query to:
SELECT * FROM my_table WHERE my_field='13'
My question is, is this supposed to be the case? I've always thought that to return a similar field, you would use something like:
SELECT * FROM my_table WHERE my_field LIKE '13%'
What is the difference between LIKE + a Wild Card vs an equals operator with no quotes?
This statement returns rows for my_field = '13a':
SELECT * FROM my_table WHERE my_field=13
Because MySQL performs type conversion from string to number during the comparison, turning '13a' to 13. More on that in this documentation page.
Adding quotes turns the integer to a string, so MySQL only performs string comparison. Obviously, '13' cannot be equal to '13a'.
The LIKE clause always performs string comparison (unless either one of the operands is NULL, in which case the result is NULL).
My guess would be that since you didn't enclose it in quotes, and the column was a char/varchar column, MySQL tried to do an implicit conversion of the varchar column to an int.
If one of the rows in that table contained a value that couldn't be converted to an int, you would probably get an error. Also, because of the conversion, any indexes you might have had on that column would not be used either.
This has to do with types and type conversion. With my_field=13 , 13 is an integer, while my_field is in your case likely some form of text/string. In such a case, mysql will try to convert both to a floating point number and compare those.
So mysql tries to convert e,g, "13a" to a float, which will which be 13, and 13 = 13
In my_field = '13' , both operands are text and will be compared as text using =
In my_field like '13%' both operands are also text and will be compared as such using LIKE, where the special % means a wildcard.
You can read about the type conversion mysql uses here.
This is because the MySQL type conversion works this way. See here: http://dev.mysql.com/doc/refman/5.0/en/type-conversion.html
It releases a warning as well. see the code below
mysql> select 12 = '12bibo';
+---------------+
| 12 = '12bibo' |
+---------------+
| 1 |
+---------------+
1 row in set, 1 warning (0.00 sec)
mysql> show warnings;
+---------+------+--------------------------------------------+
| Level | Code | Message |
+---------+------+--------------------------------------------+
| Warning | 1292 | Truncated incorrect DOUBLE value: '12bibo' |
+---------+------+--------------------------------------------+
1 row in set (0.00 sec)
Looks like someone raised a bug as well: http://bugs.mysql.com/bug.php?id=42241

ActiveRecord / MySQL Select Condition Comparing String Components

I have a string that is defined as one or more dot-separated integers like 12345, 543.21, 109.87.654, etc. I'm storing values in a MySQL database and then need to find the rows that compare with a provided value. What I want is to select rows by comparing each component of the string against the corresponding component of the input string. With standard string comparison in MySQL, here's where this breaks down:
mysql> SELECT '543.21' >= '500.21'
-> 1
mysql> SELECT '543.21' >= '5000.21'
-> 1
This is natural because the string comparison is a "dictionary" comparison that doesn't account for string length, but I want a 0 result on the second query.
Is there a way to provide some hint to MySQL on how to compare these? Otherwise, is there a way to hint to ActiveRecord how to do this for me? Right now, the best solution I have come up with is to select all the rows and then filter the results using Ruby's split and reject methods. (The entire data set is quite small and not likely to grow terribly much for the foreseeable future, so it is a reasonable option, but if there's a simpler way I'm not considering I'd be glad to know it.)
You can use REPLACE to remove dots and CAST to convert string to integer:
SELECT CAST(REPLACE("543.21", ".", "") AS SIGNED) >= CAST(REPLACE("5000.21", ".", "") AS SIGNED)
mysql> SELECT '543.21' >= '5000.21';
+-----------------------+
| '543.21' >= '5000.21' |
+-----------------------+
| 1 |
+-----------------------+
1 row in set (0.00 sec)
mysql> SELECT '543.21'+0 >= '5000.21'+0;
+---------------------------+
| '543.21'+0 >= '5000.21'+0 |
+---------------------------+
| 0 |
+---------------------------+
1 row in set (0.00 sec)
This indeed only works for valid floats. Doing it for more then 1 dot would require a LOT of comparing of SUBSTRING_INDEX(SUBSTRING_INDEX(field, '.', <positionnumber you're comparing>), '.', -1) (with a manual repeat for the maximum number of position's you are comparing)