MySQL ORDER BY Alphanumeric values, first all integers, then all alphas - mysql

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 |
+----------+

Related

MySQL select query when id is uuid format

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

Mysql - How do I avoid group by but still with concat and group concat I would need to combine multiple columns and rows results

I have something like in table
mysql> select uuid , short-uuid FROM sampleUUID WHERE identifier ="test123";
+--------------------------------------+-------------+
| uuid | short-uuid |
+--------------------------------------+-------------+
| 11d52ebd-1404-115d-903e-8033863ee848 | 8033863ee848 |
| 22b6f783-aeaf-1195-97ef-a6d8c47261b1 | 8033863ee848 |
| 33c51085-ccd8-1119-ac37-332510a16e1b | 332510a16e1b |
+--------------------------------------+-------------+
I would be needing a result like (grouped all in single row, single value w.r.t uuid and short-uuid being same)
| uuidDetails
+----------------------------------------------------------------------------------------------------------------+-------------+
| 11d52ebd-1404-115d-903e-8033863ee848,22b6f783-aeaf-1195-97ef-a6d8c47261b1|8033863ee848&&33c51085-ccd8-1119-ac37-332510a16e1b| 332510a16e1b |
+----------------------------------------------------------------------------------------------------------------+-------------+
(basically grouping uuid and short uuid in a single row from multiple rows and columns)
I know this can be achieved by select GROUP_CONCAT(uuid)FROM sampleUUID WHERE identifier ="test123" group by short-uuid;
but i don't wanna use group by here because that give multiple rows, i would need all in one row .
I have tried with below stuffs but failed to get the the results in single row
select ANY_VALUE(CONCAT_WS( '||',CONCAT_WS('|',GROUP_CONCAT(uuid) SEPARATOR ','),short-uuid)) )as uuidDetails from sampleUUID
where identifier ="test123";
this resulted like below with not appending short-uuid properly (there is only 1 short uuid appended here,Actually it needs to be grouped first 2 uuids with 1 short(because same short-uuid) uuid and 3rd uuid with other short uuid)
| uuidDetails
+----------------------------------------------------------------------------------------------------------------+-------------+
| 11d52ebd-1404-115d-903e-8033863ee848,22b6f783-aeaf-1195-97ef-a6d8c47261b1,33c51085-ccd8-1119-ac37-332510a16e1b| 332510a16e1b |
+----------------------------------------------------------------------------------------------------------------+-------------+
which is not i expected
Any help here will be appreciated . Thank you
Use nested queries.
SELECT GROUP_CONCAT(result ORDER BY result SEPARATOR '&&') AS uuidDetails
FROM (
SELECT CONCAT(GROUP_CONCAT(uuid ORDER BY uuid SEPARATOR ','), '|', short_uid) AS result
FROM sampleUUID
WHERE identifier = 'test123'
GROUP BY short_uid
) AS x
NOTE: If there is no requirement for ordering of the UUID values, we can use ORDER BY inside the GROUP_CONCAT aggregates to make the result more deterministic, so the query will return just one of a number of possible results given the same data e.g. return aa,bb|1&&cc|3 rather than bb,aa|1&&cc|3 or cc|3&&aa,bb|1 or cc|3&&bb,aa|1.

How could "None" be equal to 0 in mysql?

I find that in one table the value "None" is equal to 0
mysql> select distinct id from sometable where id="None";
+-------+
| id |
+-------+
| 0 |
+-------+
Note that the type of id is int(7)!
Although all the value is shown as 0 in mysql client, when I use PyMySQL to query records in django, some of them are 0 while others are unicode "None", how could this happen?
MySQL loose with type conversions. When implicitly converting a string to a number, it will take characters from the start of the string as long as they are digits, and ignore the others.
In your example, "None" isn't digits, so MySQL will return 0.
From Mysql type-conversion
For comparisons of a string column with a number, MySQL cannot use an index on the column to look up the value quickly.
Implicitly converting a string is horrible for performance, since you lose the usage on the index you may have on your column
Here is a sample to compare "2arse" string and 2 number, we can see when ID = "2arse" will return ID = 2 row data because it will take digits 2 and ignore arse string to implicitly converting.
Schema (MySQL v5.7)
CREATE TABLE sometable(
ID INT
);
INSERT INTO sometable VALUES (0);
INSERT INTO sometable VALUES (2);
Query #1
select distinct id
from sometable
where ID = "2arse";
| id |
| --- |
| 2 |
View on DB Fiddle
use cast to int then compare
select distinct id from sometable where cast(id AS SIGNED)=0

Mysql query like number greater than x

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

MySql REGEXP to match two comma separated strings

I have a table containing following values :
id | value |
-----------------------
1 | 1,2,5,8,12,20 |
2 | 11,25,26,28 |
-----------------------
now I want to search some comma separated IDs e.g. '1,3,6,7,11' from above value column e.g.
SELECT id FROM tbl_name
WHERE value REGEXP '*some reg exp goes here containing 1,3,6,7,11*'
LIMIT 1,0;
SELECT id FROM tbl_name
WHERE value REGEXP '*some reg exp goes here containing 3,6,27,15*'
LIMIT 1,0;
above 1st query should return 1 while the 2nd should return NULL
I am new with regular expressions can anyone help. Thanks
REGEXP '(^|,)(1|3|6|7|11)(,|$)'
Will match all values containing one number of the sequence 1,3,6,7,11.
You should not use one column to save several values. Normalize data!
Edited answer