MySQL concat() and lower() weirdness - mysql

Any idea why this works sensibly*:
mysql> select lower('AB100c');
+-----------------+
| lower('AB100c') |
+-----------------+
| ab100c |
+-----------------+
1 row in set (0.00 sec)
But this doesn't?
mysql> select lower(concat('A', 'B', 100,'C'));
+----------------------------------+
| lower(concat('A', 'B', 100,'C')) |
+----------------------------------+
| AB100C |
+----------------------------------+
1 row in set (0.00 sec)
*sensibly = 'the way I think it should work.'

As stated on MySql String functions:
LOWER(str)
LOWER() is ineffective when applied to
binary strings (BINARY, VARBINARY,
BLOB).
CONCAT(str1,str2,...)
Returns the string that results from
concatenating the arguments. May have
one or more arguments. If all
arguments are nonbinary strings, the
result is a nonbinary string. 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.
In your code you are passing 100 as a numeric so concat will return a binary string and lower is ineffective when applied to binary strings that's why it's not get converted. If you want to convert you can try this:
select lower(concat('A', 'B', '100','C'));

lower is used to convert STRINGS to lowercase. But your value 100 is considered numeric. If you want to still achieve the result of lower case conversion, you should enclose the number in quotes like this:
select lower(concat('A', 'B', '100','C'));
I've tested this and it works fine.

And here is an other example with CONCAT and LIKE
LOWER(CONCAT(firstname, ' ', lastname)) LIKE LOWER('%my name%')

Related

Can we use Sql BETWEEN operator with Hexa as Strings?

If I use Sql BETWEEN operator with Hexa numbers as Strings, will i get the same result as converting the hexa in to numeric and then performing between operation.
Will the below two sql's get same results.
Product ID is stored in db as hexa string
SELECT product_name FROM Products WHERE product_id BETWEEN "24ab" AND "82df" ORDER BY product_id;
Product ID is converted from hexa to decimal (int) in DB.
SELECT product_name FROM Products WHERE product_id BETWEEN 9387 AND 33503 ORDER BY product_id;
My expectation is above two sql's will output same products.
Example of sql BETWEEN operation on strings can be found here.
hexa(24ab) = decimal(9837) and
hexa(82df) = decimal(33503)
Just curious - does string comparison use ascii value of each char to compare. I hope so.
You're on the right track, but it's more accurate to say that string comparison compares character-by-character, according to the collation defined for the expression. This allows for characters to compare as the same if it's appropriate, according to national rules for string comparison. Not all the world is ASCII, in other words.
For purposes of hexadecimal strings, these include only characters that are ASCII, so we can simplify and say yes, the strings are compared by their ASCII values.
Where some people find trouble is that the hex strings are of different lengths. For example, is FF great than 24AB?
mysql> select 'ff' > '24ab';
+---------------+
| 'ff' > '24ab' |
+---------------+
| 1 |
+---------------+
To use hex strings in inequality comparisons (including BETWEEN), you should make sure the strings have equal length, and if not, then zero-pad the shorter strings.
mysql> select '00ff' > '24ab';
+-----------------+
| '00ff' > '24ab' |
+-----------------+
| 0 |
+-----------------+

select hex(md5("anything"))%4; Why is this always zero?

Why is this always zero in mysql???
select hex(md5("anything"))%4;
I have tried replacing "anything" with anything but it's always zero.
Expect to have 0,1,2 or 3 as value but it's not showing as shown.
I would like to have (md5sum("anything") % 4). How is this possible?
Is this the best answer / solution to what I need? Does not feel right
SELECT CONV(SUBSTRING(md5('anything0-10'), 1, 15), 16, 10)%4096;
HEX() returns a string of hexadecimal digits, not a number.
mysql> select hex(md5("anything"));
+------------------------------------------------------------------+
| hex(md5("anything")) |
+------------------------------------------------------------------+
| 6630653136366463333464313464366332323866666163353736633961343363 |
+------------------------------------------------------------------+
HEX(md5('anything')) returns all decimal digits. This is NOT a coincidence. MySQL's MD5() function already returns a hex-encoded string (your calling HEX() on it is redundant). Therefore it only returns characters 0-9 and a-f:
mysql> select md5('anything');
+----------------------------------+
| md5('anything') |
+----------------------------------+
| f0e166dc34d14d6c228ffac576c9a43c |
+----------------------------------+
When you call HEX() on this string, it returns only hex values 30-39 and 61-66, encoding the ascii values of 0-9 and a-f.
This is all to explain why the long string contains only decimal digits.
When you use that string in an expression with % 4, MySQL interprets the string in a numeric context. MySQL will try to evaluate the numeric value of the string, which means reading the leading decimal digits from the string.
We can also coerce the string to a numeric context with +0:
mysql> select hex(md5('anything'))+0;
+------------------------------------------------------------------+
| hex(md5('anything'))+0 |
+------------------------------------------------------------------+
| 6630653136366463000000000000000000000000000000000000000000000000 |
+------------------------------------------------------------------+
The 64-digit number is larger than MySQL integer types can represent, so the numeric coercion must convert it to a DOUBLE value. This data type is 8 bytes, which is still not enough to represent a 64-digit number. So it rounds the value and ignores the lower digits, converting them to zeroes.
Naturally, a number ending with a bunch of zeroes is evenly divisible by 4, so modulus expression is bound to return 0.
I would like to have (md5sum("anything") % 4). How is this possible?
mysql> select conv(right(md5('anything'), 1), 16, 10) % 4 as mod4;
+------+
| mod4 |
+------+
| 0 |
+------+
My guess would be as follows:
hex(md5("anything")) returns a STRING,
% performs implicit cast to DOUBLE,
casting drops low bits
after droping two low bits, every number is a multiply of 4
For the %4 operation only the last hex digit is relevant, so you could:
get the last hex digit from the MD5 sum
convert it to base 10
perform the %4 operation
SELECT CONV(SUBSTRING(md5('anything'), -1), 16, 10)%4;

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)