the id in MySQL table is uuid, and no id is 1.
A. My query is:
select id, name from xxx_table where id=1
and I get the result:
+--------------------------------------+----------------------+
| id | name |
+--------------------------------------+----------------------+
| 1bdf0336-c5bf-4245-8897-dbda3bf9e202 | xxxxxxxxxxxxxxxxxxxx |
+--------------------------------------+----------------------+
that's not what I want. this id is not 1.
B. my new query:
select id, name from xxx_table where id='1'
and I get Empty set, that is what I want.
What I'm confused is, if it's the format issue of id, why int(1) can match uuid(1bdf0336-c5bf-4245-8897-dbda3bf9e202)?
================================================================
OK. Follow the suggestion of Luuk, when I check
show create table xxx_table;
and I get:
| Table | Create Table |
| xxx_table | CREATE TABLE `xxx_table` (`id` varchar(64) NOT NULL,
...,
PRIMARY KEY (`id`)
)ENGINE=InnoDB DEFAULT CHARSET=utf8
MySQL plays loose with type conversion. When implicitly converting a char to a number.
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.
For example, there is a query like below, you will get result 2 instead of an error from the query.
because Mysql will convert the query like 1 + 1 implicitly.
Query 1:
select '1bdf0336-c5bf-4245-8897-dbda3bf9e202' + 1 res
Results:
| res |
|-----|
| 2 |
this query will compare full string which equals to 1 string type
select id, name from xxx_table where id='1'
The following rules describe how conversion occurs for comparison operations:
1 - If one or both arguments are NULL, the result of the comparison is NULL, except for the NULL-safe <=> equality comparison operator. For NULL <=> NULL, the result is true. No conversion is needed.
2 - If both arguments in a comparison operation are strings, they are compared as strings.
3 - If both arguments are integers, they are compared as integers.
4 - Hexadecimal values are treated as binary strings if not compared to a number.
5 - If one of the arguments is a TIMESTAMP or DATETIME column and the other argument is a constant, the constant is converted to a timestamp before the comparison is performed. This is done to be more ODBC-friendly. Note that this is not done for the arguments to IN()! To be safe, always use complete datetime, date, or time strings when doing comparisons. For example, to achieve best results when using BETWEEN with date or time values, use CAST() to explicitly convert the values to the desired data type.
6 - A single-row subquery from a table or tables is not considered a constant. For example, if a subquery returns an integer to be compared to a DATETIME value, the comparison is done as two integers. The integer is not converted to a temporal value. To compare the operands as DATETIME values, use CAST() to explicitly convert the subquery value to DATETIME.
7 - If one of the arguments is a decimal value, comparison depends on the other argument. The arguments are compared as decimal values if the other argument is a decimal or integer value, or as floating-point values if the other argument is a floating-point value.
8 - In all other cases, the arguments are compared as floating-point (real) numbers.
type-conversion
After the SELECT ... you will see x warnings. Use SHOW WARNINGS to find out about those warnings:
mysql> select * from xxx_table where id=12;
+--------------------------------------+
| id |
+--------------------------------------+
| 12a1c7d5-6dfa-11ec-9124-309c23b7280c |
+--------------------------------------+
1 row in set, 5 warnings (0.00 sec)
mysql> show warnings;
+---------+------+--------------------------------------------------------------------------+
| Level | Code | Message |
+---------+------+--------------------------------------------------------------------------+
| Warning | 1292 | Truncated incorrect DOUBLE value: '12a1c7d5-6dfa-11ec-9124-309c23b7280c' |
This can also be seen when doing:
mysql> select id, cast(id as unsigned) from xxx_table;
+--------------------------------------+----------------------+
| id | cast(id as unsigned) |
+--------------------------------------+----------------------+
| 12a1c7d5-6dfa-11ec-9124-309c23b7280c | 12 |
| 13392fc5-6dfa-11ec-9124-309c23b7280c | 13392 |
| 13ad01fd-6dfa-11ec-9124-309c23b7280c | 13 |
| 1425df26-6dfa-11ec-9124-309c23b7280c | 1425 |
| 14a139e8-6dfa-11ec-9124-309c23b7280c | 14 |
+--------------------------------------+----------------------+
5 rows in set, 5 warnings (0.00 sec)
P.S. The long story is about type conversion, see other answer.
The other answers have dealt with the type-conversion in plenty of detail so I thought I would suggest that you look at the other issue. Why are you storing UUID in a varchar? It may be easy but it is very inefficient as you significantly increase the size of all your indices. Storing them in a BINARY(16) would make more sense.
CREATE TABLE `uuid_tests` (
`uuid` BINARY(16) NOT NULL PRIMARY KEY,
`string` CHAR(36) NOT NULL
);
INSERT INTO uuid_tests VALUES
(UUID_TO_BIN('1bdf0336-c5bf-4245-8897-dbda3bf9e202'), '1bdf0336-c5bf-4245-8897-dbda3bf9e202');
SELECT * FROM uuid_tests WHERE uuid = UUID_TO_BIN('1bdf0336-c5bf-4245-8897-dbda3bf9e202');
SELECT * FROM uuid_tests WHERE uuid = 1;
SELECT * FROM uuid_tests WHERE string = '1bdf0336-c5bf-4245-8897-dbda3bf9e202';
SELECT * FROM uuid_tests WHERE string = 1;
As you are using UUIDs as PK it is worth reading about the second argument to both BIN_TO_UUID and UUID_TO_BIN
Related
Using MySQL 5.5.60.
I'm running into some peculiar behavior when running select queries in Mysql. I have a table list whose schema looks like this:
+-----------------------+--------------+------+-----+-----------------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------------------+--------------+------+-----+-----------------+----------------+
| list_id | int(11) | NO | PRI | NULL | auto_increment |
| vendor_id | int(11) | NO | MUL | NULL | |
| referrer_id | int(11) | NO | | 0 | |
...
If I run this query
mysql> select * from list where list_id = "1946"\G
Everything works as it should and the list with id 1946 is returned. Here is where it gets weird. If I change my query to look like this:
mysql> select * from list where list_id = "1946dhkdf"\G
It still returns list 1946! Clearly MySQL somehow cast off the dhkdf part and uses the 1946 portion only. So does it try to cast that value to an Integer that way? Why then does this query return and empty set?
mysql> select * from list where list_id = "xq1946dhkdf"\G
I can't seem to find any documentation explaining this behavior. Can someone shed some light on it?
You are seeing MySQL's somewhat complex casting rules at work here. When trying to compare an integer column against a string literal, either one has to be cast to integer, or the other to string. In this case, MySQL will try to cast the string literal to an integer, to match the type of the column. But, in this case, it can't cast the entire string literal to an integer, since it contains characters. Therefore, the casting rules kick in, which state that if the first N characters of the string be numeric, then use only that leading number. So, as a result the following query:
select * from list where list_id = "1946dhkdf";
will return the same result set as:
select * from list where list_id = "1946";
I have a field for comments used to store the title of the item sold on the site as well as the bid number (bid_id). Unfortunately, the bid_id is not stored on its own in that table.
I want to query items that have a number (the bid_id) greater than 4,000 for example.
So, what I have is:
select * from mysql_table_name where comment like '< 4000'
I know this won't work, but I need something similar that works.
Thanks a lot!
Just get your bid_id column cleaned up. Then index is.
create table `prior`
( id int auto_increment primary key,
comments text not null
);
insert `prior` (comments) values ('asdfasdf adfas d d 93827363'),('mouse cat 12345678');
alter table `prior` add column bid_id int; -- add a nullable int column
select * from `prior`; -- bid_id is null atm btw
update `prior` set bid_id=right(comments,8); -- this will auto-cast to an int
select * from `prior`;
+----+-----------------------------+----------+
| id | comments | bid_id |
+----+-----------------------------+----------+
| 1 | asdfasdf adfas d d 93827363 | 93827363 |
| 2 | mouse cat 12345678 | 12345678 |
+----+-----------------------------+----------+
Create the index:
CREATE INDEX `idxBidId` ON `prior` (bid_id); -- or unique index
select * from mysql_table_name where substring(comment,start,length, signed integer) < 4000
This will work, but I suggest create new column and put the bid value in it then compare.
To update value in new column you can use
update table set newcol = substring(comment,start,length)
Hope this will help
There is nothing ready that works like that.
You could write a custom function or loadable UDF, but it would be a significant work, with significant impact on the database. Then you could run WHERE GET_BID_ID(comment) < 4000.
What you can do more easily is devise some way of extracting the bid_id using available string functions.
For example if the bid_id is always in the last ten characters, you can extract those, and replace all characters that are not digits with nil. What is left is the bid_id, and that you can compare.
Of course you need a complex expression with LENGTH(), SUBSTRING(), and REPLACE(). If the bid_id is between easily recognizable delimiters, then perhaps SUBSTRING_INDEX() is more your friend.
But better still... add an INTEGER column, initialize it to null, then store there the extracted bid_id. Or zero if you're positive there's no bid_id. Having data stored in mixed contexts is evil (and a known SQL antipattern to boot). Once you have the column available, you can select every few seconds a small number of items with new_bid_id still NULL and subject those to extraction, thereby gradually amending the database without overloading the system.
In practice
This is the same approach one would use with more complicated cases. We start by checking what we have (this is a test table)
SELECT commento FROM arti LIMIT 3;
+-----------------------------------------+
| commento |
+-----------------------------------------+
| This is the first comment 100 200 42500 |
| Another 7 Q 32768 |
| And yet another 200 15 55332 |
+-----------------------------------------+
So we need the last characters:
SELECT SUBSTRING(commento, LENGTH(commento)-5) FROM arti LIMIT 3;
+-----------------------------------------+
| SUBSTRING(commento, LENGTH(commento)-5) |
+-----------------------------------------+
| 42500 |
| 32768 |
| 55332 |
+-----------------------------------------+
This looks good but it is not; there's an extra space left before the ID. So 5 doesn't work, SUBSTRING is 1-based. No matter; we just use 4.
...and we're done.
mysql> SELECT commento FROM arti WHERE SUBSTRING(commento, LENGTH(commento)-4) < 40000;
+-------------------+
| commento |
+-------------------+
| Another 7 Q 32768 |
+-------------------+
mysql> SELECT commento FROM arti WHERE SUBSTRING(commento, LENGTH(commento)-4) BETWEEN 35000 AND 55000;
+-----------------------------------------+
| commento |
+-----------------------------------------+
| This is the first comment 100 200 42500 |
+-----------------------------------------+
The problem is if you have a number not of the same length (e.g. 300 and 131072). Then you need to take a slice large enough for the larger number, and if the number is short, you will get maybe "1 5 300" in your slice. That's where SUBSTRING_INDEX comes to the rescue: by capturing seven characters, from " 131072" to "1 5 300", the ID will always be in the last space separated token of the slice.
IN THIS LAST CASE, when numbers are not of the same length, you will find a problem. The extracted IDs are not numbers at all - to MySQL, they are strings. Which means that they are compared in lexicographic, not numerical, order; and "17534" is considered smaller than "202", just like "Alice" comes before "Bob". To overcome this you need to cast the string as unsigned integer, which further slows down the operations.
WHERE CAST( SUBSTRING(...) AS UNSIGNED) < 4000
searched around awhile for a solution to this problem, but no answer yet.
Have a column of alphanumeric model ID numbers to populate an index in a certain order. Two different attempts with the order they produced:
ORDER BY Model_ID ASC
1278-8
211E
350-50
996
3800P
AP23
AP263
AP26M
JA042
ORDER BY CAST(Model_ID AS UNSIGNED), Model_ID
AP23
AP263
AP26M
JA042
211E
350-50
996
1278-8
3800P
However, I need to have it sorted like so, with all of the integer-starting numbers exhausted first:
211E
350-50
996
1278-8
3800P
AP23
AP263
AP26M
JA042
Help? Thanks
For the sample data, this will get the desired order:
ORDER BY Model_ID+0=0, Model_ID+0, Model_ID ASC
Let's unpack that a bit.
The expression Model_ID+0 evaluates Model_ID in a numeric context, by adding zero to it. Basically, MySQL gets whatever leading characters that can be converted to a number, and disregards the rest. For values that can't be interpreted as a number, MySQL returns 0.
The first expression checks if the numeric value is zero, so those will be sorted last. (MySQL returns numeric value of 1 for boolean TRUE, 0 for FALSE.)
The second expression gets the non-zero values sorted in numeric order.
NOTE: these expressions "work" for the sample data; the edge case is values that have leading zeros as the numeric value, e.g. '000ABC' will be sorted after all the other "numeric" values. And MySQL doesn't stop at just the integer portion, if there's a dot character, that can be interpreted as a decimal point.
You can see the values of the expressions (for testing), by including them in the SELECT list, e.g.
SELECT Model_ID+0=0, Model_ID+0, ...
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table (model_id VARCHAR(20) NOT NULL PRIMARY KEY);
INSERT INTO my_table VALUES
('1278-8'),
('211E'),
('350-50'),
('996'),
('3800P'),
('AP23'),
('AP263'),
('AP26M'),
('JA042');
SELECT model_id FROM my_table ORDER BY model_id + 0 = 0,model_id + 0;
+----------+
| model_id |
+----------+
| 211E |
| 350-50 |
| 996 |
| 1278-8 |
| 3800P |
| AP23 |
| AP263 |
| AP26M |
| JA042 |
+----------+
I want to create a temporal table from a SELECT statement in MySQL. It involves several JOINs, and it can produce NULL values that I want MySQL to take as zeroes. It sounds like an easy problem (simply default to zero), but MySQL (5.6.12) fails to elicit the default value.
For example, take the following two tables:
mysql> select * from TEST1;
+------+------+
| a | b |
+------+------+
| 1 | 2 |
| 4 | 25 |
+------+------+
2 rows in set (0.00 sec)
mysql> select * from TEST2;
+------+------+
| b | c |
+------+------+
| 2 | 100 |
| 3 | 100 |
+------+------+
2 rows in set (0.00 sec)
A left join gives:
mysql> select TEST1.*,c from TEST1 left join TEST2 on TEST1.b=TEST2.b;
+------+------+------+
| a | b | c |
+------+------+------+
| 1 | 2 | 100 |
| 4 | 25 | NULL |
+------+------+------+
2 rows in set (0.00 sec)
Now, if I want to save these values in a temporal table (changing NULL for zero), this is the code I would use:
mysql> create temporary table TEST_JOIN (a int, b int, c int default 0 not null)
select TEST1.*,c from TEST1 left join TEST2 on TEST1.b=TEST2.b;
ERROR 1048 (23000): Column 'c' cannot be null
What am I doing wrong? The worst part is that this code used to work before I did a system-wide upgrade (I don't remember which version of MySQL I had, but surely it was lower than my current 5.6). It used to produce the behavior I would expect: if it's NULL, use the default, not the frustrating error I'm getting now.
From the documentation of 5.6 (unchanged since 4.1):
Inserting NULL into a column that has been declared NOT NULL. For
multiple-row INSERT statements or INSERT INTO ... SELECT statements,
the column is set to the implicit default value for the column data
type. This is 0 for numeric types, the empty string ('') for string
types, and the “zero” value for date and time types. INSERT INTO ...
SELECT statements are handled the same way as multiple-row inserts
because the server does not examine the result set from the SELECT to
see whether it returns a single row. (For a single-row INSERT, no
warning occurs when NULL is inserted into a NOT NULL column. Instead,
the statement fails with an error.)
My current workaround is to store the NULL values in the temporal table, and then replace them by zeroes, but it seems rather cumbersome with many columns (and terribly inefficient). Is there a better way to do it?
BTW, I cannot simply ignore some columns in the query (as suggested for another question), because it's a multirow query.
IFNULL(`my_column`,0);
That would set NULLs to 0. Other values stay as is.
Just wrap your values/column names with IFNULL and it will convert them to whatever default value you put into the function. E.g. 0. Or "european swallow", or whatever you want.
Then you can keep strict mode on and still handle NULLs gracefully.
For example - I create database and a table from cli and insert some data:
CREATE DATABASE testdb CHARACTER SET 'utf8' COLLATE 'utf8_general_ci';
USE testdb;
CREATE TABLE test (id INT, str VARCHAR(100)) TYPE=innodb CHARACTER SET 'utf8' COLLATE 'utf8_general_ci';
INSERT INTO test VALUES (9, 'some string');
Now I can do this and these examples do work (so - quotes don't affect anything it seems):
SELECT * FROM test WHERE id = '9';
INSERT INTO test VALUES ('11', 'some string');
So - in these examples I've selected a row by a string that actually stored as INT in mysql and then I inserted a string in a column that is INT.
I don't quite get why this works the way it works here. Why is string allowed to be inserted in an INT column?
Can I insert all MySQL data types as strings?
Is this behavior standard across different RDBMS?
MySQL is a lot like PHP, and will auto-convert data types as best it can. Since you're working with an int field (left-hand side), it'll try to transparently convert the right-hand-side of the argument into an int as well, so '9' just becomes 9.
Strictly speaking, the quotes are unnecessary, and force MySQL to do a typecasting/conversion, so it wastes a bit of CPU time. In practice, unless you're running a Google-sized operation, such conversion overhead is going to be microscopically small.
You should never put quotes around numbers. There is a valid reason for this.
The real issue comes down to type casting. When you put numbers inside quotes, it is treated as a string and MySQL must convert it to a number before it can execute the query. While this may take a small amount of time, the real problems start to occur when MySQL doesn't do a good job of converting your string. For example, MySQL will convert basic strings like '123' to the integer 123, but will convert some larger numbers, like '18015376320243459', to floating point. Since floating point can be rounded, your queries may return inconsistent results. Learn more about type casting here. Depending on your server hardware and software, these results will vary. MySQL explains this.
If you are worried about SQL injections, always check the value first and use PHP to strip out any non numbers. You can use preg_replace for this: preg_replace("/[^0-9]/", "", $string)
In addition, if you write your SQL queries with quotes they will not work on databases like PostgreSQL or Oracle.
Check this, you can understand better ...
mysql> EXPLAIN SELECT COUNT(1) FROM test_no WHERE varchar_num=0000194701461220130201115347;
+----+-------------+------------------------+-------+-------------------+-------------------+---------+------+---------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+------------------------+-------+-------------------+-------------------+---------+------+---------+--------------------------+
| 1 | SIMPLE | test_no | index | Uniq_idx_varchar_num | Uniq_idx_varchar_num | 63 | NULL | 3126240 | Using where; Using index |
+----+-------------+------------------------+-------+-------------------+-------------------+---------+------+---------+--------------------------+
1 row in set (0.00 sec)
mysql> EXPLAIN SELECT COUNT(1) FROM test_no WHERE varchar_num='0000194701461220130201115347';
+----+-------------+------------------------+-------+-------------------+-------------------+---------+-------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+------------------------+-------+-------------------+-------------------+---------+-------+------+-------------+
| 1 | SIMPLE | test_no | const | Uniq_idx_varchar_num | Uniq_idx_varchar_num | 63 | const | 1 | Using index |
+----+-------------+------------------------+-------+-------------------+-------------------+---------+-------+------+-------------+
1 row in set (0.00 sec)
mysql>
mysql>
mysql> SELECT COUNT(1) FROM test_no WHERE varchar_num=0000194701461220130201115347;
+----------+
| COUNT(1) |
+----------+
| 1 |
+----------+
1 row in set, 1 warning (7.94 sec)
mysql> SELECT COUNT(1) FROM test_no WHERE varchar_num='0000194701461220130201115347';
+----------+
| COUNT(1) |
+----------+
| 1 |
+----------+
1 row in set (0.00 sec)
AFAIK it is standard, but it is considered bad practice because
- using it in a WHERE clause will prevent the optimizer from using indices (explain plan should show that)
- the database has to do additional work to convert the string to a number
- if you're using this for floating-point numbers ('9.4'), you'll run into trouble if client and server use different language settings (9.4 vs 9,4)
In short: don't do it (but YMMV)
This is not standard behavior.
For MySQL 5.5. this is the default SQL Mode
mysql> select ##sql_mode;
+------------+
| ##sql_mode |
+------------+
| |
+------------+
1 row in set (0.00 sec)
ANSI and TRADITIONAL are used more rigorously by Oracle and PostgreSQL. The SQL Modes MySQL permits must be set IF AND ONLY IF you want to make the SQL more ANSI-compliant. Otherwise, you don't have to touch a thing. I've never done so.
It depends on the column type!
if you run
SELECT * FROM `users` WHERE `username` = 0;
in mysql/maria-db you will get all the records where username IS NOT NULL.
Always quote values if the column is of type string (char, varchar,...) otherwise you'll get unexpected results!
You don't need to quote the numbers but it is always a good habit if you do as it is consistent.
The issue is, let's say that we have a table called users, which has a column called current_balance of type FLOAT, if you run this query:
UPDATE `users` SET `current_balance`='231608.09' WHERE `user_id`=9;
The current_balance field will be updated to 231608, because MySQL made a rounding, similarly if you try this query:
UPDATE `users` SET `current_balance`='231608.55' WHERE `user_id`=9;
The current_balance field will be updated to 231609