I'm attempting to write a MySQL stored function to generate v4 UUIDs as described in RFC 4122's section 4.4 ( http://www.ietf.org/rfc/rfc4122.txt ). My initial naive effort after a few tweaks is the following:
CREATE FUNCTION UUID_V4()
RETURNS BINARY(16)
READS SQL DATA
BEGIN
SET #uuid = CONCAT(
LPAD( HEX( FLOOR( RAND() * 4294967296 ) ), 8, '0' ),
LPAD( HEX( FLOOR( RAND() * 4294967296 ) ), 8, '0' ),
LPAD( HEX( FLOOR( RAND() * 4294967296 ) ), 8, '0' ),
LPAD( HEX( FLOOR( RAND() * 4294967296 ) ), 8, '0' )
);
SET #uuid = CONCAT(
SUBSTR( #uuid FROM 1 FOR 12 ),
'4',
SUBSTR( #uuid FROM 14 FOR 3 ),
SUBSTR( 'ab89' FROM FLOOR( 1 + RAND() * 4 ) FOR 1 ),
SUBSTR( #uuid FROM 18 )
);
RETURN UNHEX(#uuid);
END
The above function is quite slow: almost 100 times slower than the built-in UUID(), according to MySQL's BENCHMARK() feature. Short of writing a UDF using MySQL's C API, are there any improvements I can make here to, say, shave off an order of magnitude from its runtime?
If there is an already existing, well-regarded UUID UDF or stored procedure, I'd be happy to hear about that, too.
I didn't test this for correctness or for performance. It is just the idea of doing one only concatenation in instead of two.
create function uuid_v4()
returns binary(16)
begin
set #h1 = lpad(hex(floor(rand() * 4294967296)), 8, '0');
set #h2 = lpad(hex(floor(rand() * 4294967296)), 8, '0');
set #h3 = lpad(hex(floor(rand() * 4294967296)), 8, '0');
set #h4 = lpad(hex(floor(rand() * 4294967296)), 8, '0');
set #uuid = concat(
#h1,
substr(#h2 from 1 for 4),
'4',
substr(#h2 from 6),
substr('ab89' from floor(1 + rand() * 4) for 1 ),
substr(#h3 from 2),
#h4
);
return unhex(#uuid);
end
;
Also why do you use READS SQL DATA in your function?
Related
I am stuck at a point where i have to increment a string, and my strings are of type C001,SC001,B001
in my data base they are defined like
what i am trying to do do is write a query which check the previous highest code present into my db and the incriment it to +1
for example C001 -> C002,C009->C010,C099`->C100 and so on
Similarly for SC001->SC002,SC009->SC010,SC099->SC100 and so on
Similarly fro B001 -> B002,B009->B010,B099`->B100 and so on
I have a query which my friend has suggested me to use but that query only incriminating AAAA->AAAA01 , AAAA09->AAAA10
query is
SELECT id AS PrevID, CONCAT(
SUBSTRING(id, 1, 4),
IF(CAST(SUBSTRING(id, 5) AS UNSIGNED) <= 9, '0', ''),
CAST(SUBSTRING(id, 5) AS UNSIGNED) + 1
) AS NextID
FROM (
-- since you allow strings such as AAAA20 and AAAA100 you can no longer use MAX
SELECT id
FROM t
ORDER BY SUBSTRING(id, 1, 4) DESC, CAST(SUBSTRING(id, 5) AS UNSIGNED) DESC
LIMIT 1
) x
when i am replacing ID with CategoryCode it is giving me PrevID-C004 NextID-C00401 which is not my requirement i want PrevID-C004 and NextID->C005
NOTE i am using my sqlServer 5.1
Just try this one ,
SELECT
CategoryCode,CAST(CONCAT(LPAD(CategoryCode,1,0),LPAD(MAX(RIGHT(CategoryCode,
3)) + 1, 3, 0) ) AS CHAR),
FROM test
SELECT
SubCategoryCode,CAST(CONCAT(LPAD(SubCategoryCode,2,0),
LPAD(MAX(RIGHT(CategoryCode, 3)) + 1, 3, 0) ) AS CHAR),
FROM test
SELECT
BrandCode,CAST(CONCAT(LPAD(BrandCode,1,0), LPAD(MAX(RIGHT(BrandCode, 3)) +
1, 3, 0)) AS CHAR) FROM test
MySQL's UUID function returns a UUIDv1 GUID. I'm looking for an easy way to generate random GUIDs (i.e. UUIDv4) in SQL.
I've spent quite some time looking for a solution and came up with the following
mysql function that generates a random UUID (i.e. UUIDv4) using standard MySQL
functions. I'm answering my own question to share that in the hope that it'll be
useful.
-- Change delimiter so that the function body doesn't end the function declaration
DELIMITER //
CREATE FUNCTION uuid_v4()
RETURNS CHAR(36) NO SQL
BEGIN
-- Generate 8 2-byte strings that we will combine into a UUIDv4
SET #h1 = LPAD(HEX(FLOOR(RAND() * 0xffff)), 4, '0');
SET #h2 = LPAD(HEX(FLOOR(RAND() * 0xffff)), 4, '0');
SET #h3 = LPAD(HEX(FLOOR(RAND() * 0xffff)), 4, '0');
SET #h6 = LPAD(HEX(FLOOR(RAND() * 0xffff)), 4, '0');
SET #h7 = LPAD(HEX(FLOOR(RAND() * 0xffff)), 4, '0');
SET #h8 = LPAD(HEX(FLOOR(RAND() * 0xffff)), 4, '0');
-- 4th section will start with a 4 indicating the version
SET #h4 = CONCAT('4', LPAD(HEX(FLOOR(RAND() * 0x0fff)), 3, '0'));
-- 5th section first half-byte can only be 8, 9 A or B
SET #h5 = CONCAT(HEX(FLOOR(RAND() * 4 + 8)),
LPAD(HEX(FLOOR(RAND() * 0x0fff)), 3, '0'));
-- Build the complete UUID
RETURN LOWER(CONCAT(
#h1, #h2, '-', #h3, '-', #h4, '-', #h5, '-', #h6, #h7, #h8
));
END
//
-- Switch back the delimiter
DELIMITER ;
Note: The pseudo-random number generation used (MySQL's RAND) is not
cryptographically secure and thus has some bias which can increase the collision
risk.
Both existing answers relies on MySQL RAND() function:
RAND() is not meant to be a perfect random generator. It is a fast way to generate random numbers on demand that is portable between platforms for the same MySQL version.
In the practice, this mean that the generated UUID using this function might (and will) be biased, and collisions can occur more frequently then expected.
Solution
It's possible to generate safe UUID V4 on MySQL side using random_bytes() function:
This function returns a binary string of len random bytes generated using the random number generator of the SSL library.
So we can update the function to:
CREATE FUNCTION uuid_v4s()
RETURNS CHAR(36)
BEGIN
-- 1th and 2nd block are made of 6 random bytes
SET #h1 = HEX(RANDOM_BYTES(4));
SET #h2 = HEX(RANDOM_BYTES(2));
-- 3th block will start with a 4 indicating the version, remaining is random
SET #h3 = SUBSTR(HEX(RANDOM_BYTES(2)), 2, 3);
-- 4th block first nibble can only be 8, 9 A or B, remaining is random
SET #h4 = CONCAT(HEX(FLOOR(ASCII(RANDOM_BYTES(1)) / 64)+8),
SUBSTR(HEX(RANDOM_BYTES(2)), 2, 3));
-- 5th block is made of 6 random bytes
SET #h5 = HEX(RANDOM_BYTES(6));
-- Build the complete UUID
RETURN LOWER(CONCAT(
#h1, '-', #h2, '-4', #h3, '-', #h4, '-', #h5
));
END
This should generate UUID V4 random enough to don't care about collisions.
NOTE: Unfortunately MariaDB doesn't support RANDOM_BYTES() (See https://mariadb.com/kb/en/function-differences-between-mariadb-105-and-mysql-80/#miscellaneous)
Test
I've created following test scenario: Insert random UUID v4 as primary key for a table until 40.000.000 rows are created. When a collision is found, the row is updated incrementing collisions column:
INSERT INTO test (uuid) VALUES (uuid_v4()) ON DUPLICATE KEY UPDATE collisions=collisions+1;
The sum of collisions after 40 million rows with each function is:
+----------+----------------+
| RAND() | RANDOM_BYTES() |
+----------+----------------+
| 55 | 0 |
+----------+----------------+
The number collisions in both scenarios tends to increase as number of rows grows.
In the off chance you're working with a DB and don't have perms to create functions, here's the same version as above that works just as a SQL expression:
SELECT LOWER(CONCAT(
LPAD(HEX(FLOOR(RAND() * 0xffff)), 4, '0'),
LPAD(HEX(FLOOR(RAND() * 0xffff)), 4, '0'), '-',
LPAD(HEX(FLOOR(RAND() * 0xffff)), 4, '0'), '-',
'4',
LPAD(HEX(FLOOR(RAND() * 0x0fff)), 3, '0'), '-',
HEX(FLOOR(RAND() * 4 + 8)),
LPAD(HEX(FLOOR(RAND() * 0x0fff)), 3, '0'), '-',
LPAD(HEX(FLOOR(RAND() * 0xffff)), 4, '0'),
LPAD(HEX(FLOOR(RAND() * 0xffff)), 4, '0'),
LPAD(HEX(FLOOR(RAND() * 0xffff)), 4, '0')));
Adaptation of Elias Soares's answer using RANDOM_BYTES without creating a DB function:
SELECT LOWER(CONCAT(
HEX(RANDOM_BYTES(4)), '-',
HEX(RANDOM_BYTES(2)), '-4',
SUBSTR(HEX(RANDOM_BYTES(2)), 2, 3), '-',
CONCAT(HEX(FLOOR(ASCII(RANDOM_BYTES(1)) / 64)+8),SUBSTR(HEX(RANDOM_BYTES(2)), 2, 3)), '-',
HEX(RANDOM_BYTES(6))
))
select CONCAT(
Lpad(
FLOOR(HOUR(TIMEDIFF('2015-06-25 12:10', '2011-06-21 10:10')) / 24),
4,0),':',
Lpad(
MOD(HOUR(TIMEDIFF('2014-06-25 12:10', '2015-06-21 10:10')), 24),
4,0),':',
Lpad(
MINUTE(TIMEDIFF('2014-06-25 12:10', '2015-06-21 10:15')),
4,0), '')
The above MySQL select statement gives
incorrect report.Required format of date = days:hours:minutes
The functions you are using are limited by the TIME type that may range from '-838:59:59' to '838:59:59'. you should use TIMESTAMPDIFF which return integers.
select
TIMESTAMPDIFF(DAY,'2011-06-21 10:10','2015-06-25 12:10') as c_days,
TIMESTAMPDIFF(HOUR,'2014-06-25 12:10', '2015-06-21 10:10') as c_hrs,
TIMESTAMPDIFF(MINUTE,'2014-06-25 12:10', '2015-06-21 10:15') as c_mnt;
but this doesn't make much sense you've probably have some typos in your formula, the code bellow is probably what you're looking for.
set #a_mnt := TIMESTAMPDIFF(MINUTE,'2014-06-25 12:10', '2015-06-21 10:10');
select
concat(
lpad(floor(#a_mnt := #a_mnt / 1440 ), 4, 0), ':',
lpad(floor(#a_mnt := ((#a_mnt - floor(#a_mnt)) * 24)), 4, 0), ':',
lpad(floor((#a_mnt - floor(#a_mnt)) * 60 ), 4, 0)) as result;
I have a logs table that stores various file sizes in bytes. I want to be able to query the database and return the smallest possible float which has been converted to MB GB etc. At present I can return the value in MB but how do I continue to divide further to smallest value and append the unit?
SELECT CONCAT( ROUND( SUM( data_transferred ) /1048576, 2 ) , ' MB' )
FROM `logs`
Any help would be appreciated.
UPDATE:
Based on the link voodoo417 provided I updated my query to the following, which will output the most relevant file size to two decimal places and append the unit (1000 Bytes, 1 KB, 500 MB, 2 GB, etc):
SET #bytes := (SELECT SUM(data_transferred) FROM wp_ddownload_statistics);
SELECT
CASE
WHEN ABS(#bytes) < 1024 THEN CONCAT( ROUND( #bytes, 2 ), ' Bytes')
WHEN ABS(#bytes) < 1048576 THEN CONCAT( ROUND( (#bytes/1024), 2 ), ' KB')
WHEN ABS(#bytes) < 1073741824 THEN CONCAT( ROUND( (#bytes/1048576), 2 ), ' MB')
WHEN ABS(#bytes) < 1099511627776 THEN CONCAT( ROUND( (#bytes/1073741824), 2 ), ' GB' )
WHEN ABS(#bytes) < 1125899906842624 THEN CONCAT( ROUND( (#bytes/1099511627776), 2 ), ' TB')
WHEN ABS(#bytes) < 1152921504606846976 THEN CONCAT( ROUND( (#bytes/1125899906842624), 2 ), ' PB' )
WHEN ABS(#bytes) < 1180591620717411303424 THEN CONCAT( ROUND( (#bytes/1152921504606846976) ,2), ' EB' )
WHEN ABS(#bytes) < 1208925819614629174706176 THEN CONCAT( ROUND( (#bytes/1180591620717411303424), 2), ' ZB' )
WHEN ABS(#bytes) < 1237940039285380274899124224 THEN CONCAT( ROUND( (#bytes/1208925819614629174706176), 2), ' YB' )
WHEN ABS(#bytes) < 1267650600228229401496703205376 THEN CONCAT( ROUND( (#bytes/1237940039285380274899124224), 2), ' BB' )
END
I know this is an old question, but I was looking for the same thing recently, and found out that MySQL 5.7 added format_bytes function exactly for this purpose:
mysql> SELECT format_bytes(512), format_bytes(18446644073709551615);
+-------------------+------------------------------------+
| format_bytes(512) | format_bytes(18446644073709551615) |
+-------------------+------------------------------------+
| 512 bytes | 16383.91 PiB |
+-------------------+------------------------------------+
I have a more elegant solution (also using a user defined function):
CREATE FUNCTION `format_filesize`(filesize FLOAT) RETURNS varchar(20) CHARSET utf8
BEGIN
DECLARE n INT DEFAULT 1;
LOOP
IF filesize < 1024 THEN
RETURN concat(round(filesize, 2), ' ', elt(n, 'Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB', 'BB'));
END IF;
SET filesize = filesize / 1024;
SET n = n + 1;
END LOOP;
END
UPDATE:
Even better, and can be used outside procedures:
SET #filesize = 536870912;
SET #log = IFNULL(TRUNCATE(LOG(1024, #filesize), 0),0);
SELECT CONCAT(ROUND(#filesize / POW(1024, #log), 2), ' ',
ELT(#log + 1, 'Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB', 'BB'));
Renaat's code is failing when filesize is 0 (obviously you can't do LOG from zero). Therefore #log is filled with null and CONCAT produce null as well.
Correct fix is:
SET #filesize = 536870912;
SET #log = IFNULL(TRUNCATE(LOG(1024, #filesize), 0),0);
SELECT CONCAT(ROUND(#filesize / POW(1024, #log), 2), ' ',
ELT(#log + 1, 'Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB', 'BB'));
select concat(round(data*1048576/1073741824,2),' GB')
for example: 1024 = 1gb
select concat(round(1024*1048576/1073741824,2),' GB')
1gb
The top answer about using format_bytes, despite being answered in 2016, is sadly still not found in MariaDB.
I adapted the query from here to make a function I could use.
FUNCTION `format_bytes`(val float) RETURNS varchar(20) CHARSET latin1
BEGIN
DECLARE pw smallint;
IF val < 1024 THEN
return CONCAT(val, ' B');
END IF;
SET pw = LEAST(7, FLOOR(LOG(val) / LOG(1024)));
RETURN CONCAT(ROUND(val / POW(1024, pw), 2), ' ', SUBSTR('KMGTPEZY', pw, 1), 'B');
END
>>> SELECT format_bytes(512), format_bytes(18446644073709551615);
+-------------------+------------------------------------+
| format_bytes(512) | format_bytes(18446644073709551615) |
+-------------------+------------------------------------+
| 512 B | 16.00 EB |
+-------------------+------------------------------------+
A few things could be tweaked to more closely mimic the MySQL format_bytes, but it's not what I was aiming for.
How can I pass from seconds to time on mysql?
the date format of out is hh:mm:ss.ms
Sorry. I need for example 0.98 sec -> 00:00:00.980; With sec_to_tiem return 00:00:01.
thanks.
I have implemeted of this way:
select concat(if(floor(seconds/(60 * 60)) = 0,'00',lpad(floor(seconds/(60 * 60)),2,'0')),
':',
if(floor(seconds/60) = 0,'00',lpad(floor(seconds / 60),2,'0')),
':',
seconds % 60)
but it have to exist other way more efficient
other way:
CONCAT(lpad(floor(seconds / 3600), 2, '0'), ':',
lpad(floor(seconds / 60), 2, '0'), ':',
lpad(floor(seconds % 60), 2, '0'), '.',
lpad(SUBSTRING_INDEX((seconds * 1000) % 1000, '.', 1), 3, '0'))
SELECT MAKETIME (<yourseconds>/(60*24), <yourseconds>/60, <yourseconds>%60)
or with format
SELECT TIME_FORMAT( MAKETIME( <yourseconds>/ ( 60 *24 ) , <yourseconds>/60, <yourseconds>%60 ) , '%T.%f' )