I have a very large table in MySQL. I'm using a CHAR(32) field which contains an MD5 as a string of course. I'm running into an issue where I need to convert this to a decimal value using MySQL. A third party tool runs the query so writing code to do this isn't really an option.
MySQL does support storing hex values natively and converting them to integers. But it gets hung up converting it from a string. Here's what I've tried so far (md5_key is the name of my column)
First I just tried the UNHEX function but that returns a string so it gave me gooblygoop. I won't put that here. Next I tried the CAST function
SELECT CAST( CONCAT('0x',md5_key) AS UNSIGNED ) FROM bigtable limit 1
Result = 0
Show warnings gives me: "Truncated incorrect INTEGER value: '0x000002dcc38af6f209e91518db3e79d3'"
BUT if I do:
SELECT CAST( 0x000002dcc38af6f209e91518db3e79d3 AS UNSIGNED );
I get the correct decimal value.
So I guess what I need to know, is there a way to get MySQL to see that string as a hex value? (I also tried converting it to BINARY and then to the UNSIGNED but that didn't work either).
Thanks in advance!
conv() is limited to 64 bit integers. You can convert the high and low part to decimal and then add them together:
> select cast(conv(substr("000002dcc38af6f209e91518db3e79d3", 1, 16), 16, 10) as
decimal(65))*18446744073709551616 +
cast(conv(substr("000002dcc38af6f209e91518db3e79d3", 17, 16), 16, 10) as
decimal(65));
58055532535286745202684464101843
Where 18446744073709551616 = 2^64. So in your case:
> select cast(conv(substr(md5_key, 1, 16), 16, 10) as
decimal(65))*18446744073709551616 +
cast(conv(substr(md5_key, 17, 16), 16, 10) as
decimal(65))
from bigtable limit 1;
Beware MD5 are 16 Byte long, and BIGINT UNSIGNED is 8 Byte long, so even in your second case you don't get the right answer, the number can't fit you are receiving the value of the lowest 8 Byte=> 09e91518db3e79d3.
I wrote a function to convert big hex numbers to decimal(65).
CREATE FUNCTION `hexnum_to_decimal`(hex varchar(66)) RETURNS decimal(65,0)
DETERMINISTIC
BEGIN
declare group1 decimal(65);
declare group2 decimal(65);
declare group3 decimal(65);
declare group4 decimal(65);
declare multiplier decimal(65);
if (substr(hex, 1, 2) = "0x") then
set hex = substr(hex, 3); -- trim 0x if exists
end if;
set hex = trim(LEADING '0' from hex);
if (length(hex) > 54) then
return null; -- too big number
end if;
set hex = lpad(hex, 64, 0);
set group1 = cast(conv(substr(hex, 49, 16), 16, 10) as decimal(65));
set group2 = cast(conv(substr(hex, 33, 16), 16, 10) as decimal(65));
set group3 = cast(conv(substr(hex, 17, 16), 16, 10) as decimal(65));
set group4 = cast(conv(substr(hex, 1, 16), 16, 10) as decimal(65));
set multiplier = 18446744073709551616; -- 2 ^ 16
-- check for overflow
if (
(group4 > 15930919) or
(group4 = 15930919 and group3 > 2053574980671369030) or
(group4 = 15930919 and group3 = 2053574980671369030 and group2 > 5636613303479645705) or
(group4 = 15930919 and group3 = 2053574980671369030 and group2 = 5636613303479645705 and group1 > 18446744073709551615)
) then
return null;
end if;
return cast(
group1 +
group2 * multiplier +
group3 * multiplier * multiplier +
group4 * multiplier * multiplier * multiplier
as decimal(65));
END
In your case for 000002dcc38af6f209e91518db3e79d3
select hexnum_to_decimal("000002dcc38af6f209e91518db3e79d3");
58055532535286745202684464101843
select hexnum_to_decimal('F316271C7FC3908A8BEF464E3945EF7A253609FFFFFFFFFFFFFFFF');
99999999999999999999999999999999999999999999999999999999999999999
if bigger hex number is passed, the function will return null.
Related
I have a very large table in MySQL. I'm using a CHAR(32) field which contains an MD5 as a string of course. I'm running into an issue where I need to convert this to a decimal value using MySQL. A third party tool runs the query so writing code to do this isn't really an option.
MySQL does support storing hex values natively and converting them to integers. But it gets hung up converting it from a string. Here's what I've tried so far (md5_key is the name of my column)
First I just tried the UNHEX function but that returns a string so it gave me gooblygoop. I won't put that here. Next I tried the CAST function
SELECT CAST( CONCAT('0x',md5_key) AS UNSIGNED ) FROM bigtable limit 1
Result = 0
Show warnings gives me: "Truncated incorrect INTEGER value: '0x000002dcc38af6f209e91518db3e79d3'"
BUT if I do:
SELECT CAST( 0x000002dcc38af6f209e91518db3e79d3 AS UNSIGNED );
I get the correct decimal value.
So I guess what I need to know, is there a way to get MySQL to see that string as a hex value? (I also tried converting it to BINARY and then to the UNSIGNED but that didn't work either).
Thanks in advance!
conv() is limited to 64 bit integers. You can convert the high and low part to decimal and then add them together:
> select cast(conv(substr("000002dcc38af6f209e91518db3e79d3", 1, 16), 16, 10) as
decimal(65))*18446744073709551616 +
cast(conv(substr("000002dcc38af6f209e91518db3e79d3", 17, 16), 16, 10) as
decimal(65));
58055532535286745202684464101843
Where 18446744073709551616 = 2^64. So in your case:
> select cast(conv(substr(md5_key, 1, 16), 16, 10) as
decimal(65))*18446744073709551616 +
cast(conv(substr(md5_key, 17, 16), 16, 10) as
decimal(65))
from bigtable limit 1;
Beware MD5 are 16 Byte long, and BIGINT UNSIGNED is 8 Byte long, so even in your second case you don't get the right answer, the number can't fit you are receiving the value of the lowest 8 Byte=> 09e91518db3e79d3.
I wrote a function to convert big hex numbers to decimal(65).
CREATE FUNCTION `hexnum_to_decimal`(hex varchar(66)) RETURNS decimal(65,0)
DETERMINISTIC
BEGIN
declare group1 decimal(65);
declare group2 decimal(65);
declare group3 decimal(65);
declare group4 decimal(65);
declare multiplier decimal(65);
if (substr(hex, 1, 2) = "0x") then
set hex = substr(hex, 3); -- trim 0x if exists
end if;
set hex = trim(LEADING '0' from hex);
if (length(hex) > 54) then
return null; -- too big number
end if;
set hex = lpad(hex, 64, 0);
set group1 = cast(conv(substr(hex, 49, 16), 16, 10) as decimal(65));
set group2 = cast(conv(substr(hex, 33, 16), 16, 10) as decimal(65));
set group3 = cast(conv(substr(hex, 17, 16), 16, 10) as decimal(65));
set group4 = cast(conv(substr(hex, 1, 16), 16, 10) as decimal(65));
set multiplier = 18446744073709551616; -- 2 ^ 16
-- check for overflow
if (
(group4 > 15930919) or
(group4 = 15930919 and group3 > 2053574980671369030) or
(group4 = 15930919 and group3 = 2053574980671369030 and group2 > 5636613303479645705) or
(group4 = 15930919 and group3 = 2053574980671369030 and group2 = 5636613303479645705 and group1 > 18446744073709551615)
) then
return null;
end if;
return cast(
group1 +
group2 * multiplier +
group3 * multiplier * multiplier +
group4 * multiplier * multiplier * multiplier
as decimal(65));
END
In your case for 000002dcc38af6f209e91518db3e79d3
select hexnum_to_decimal("000002dcc38af6f209e91518db3e79d3");
58055532535286745202684464101843
select hexnum_to_decimal('F316271C7FC3908A8BEF464E3945EF7A253609FFFFFFFFFFFFFFFF');
99999999999999999999999999999999999999999999999999999999999999999
if bigger hex number is passed, the function will return null.
I have a table that contains color options for a product. The color options include a hex color code, which is used to generate the UI (HTML).
I would like to sort the rows so that the colors in the UI look like a rainbow, instead of the current order that sorts based off of the Name of the color (not very useful).
Here is what my query looks like. I get the R G B decimal values from the hex code. I just don't know how to order it.
I've looked into color difference algorithms. They seem more useful to compare 2 colors' similarity, not sort.
I'm using MySQL:
select a.*, (a.c_r + a.c_g + a.c_b) color_sum
from (
select co.customization_option_id,
co.designer_image_url,
concat(co.name, " (",cog.name, ")") name,
co.customization_option_group_id gr,
designer_hex_color,
conv(substr(designer_hex_color, 1, 2), 16, 10) c_r,
conv(substr(designer_hex_color, 3, 2), 16, 10) c_g,
conv(substr(designer_hex_color, 5, 2), 16, 10) c_b
from customization_options co
left join customization_option_groups cog
on cog.id = co.customization_option_group_id
where co.customization_id = 155
and co.customization_option_group_id
in (1,2,3,4)) a
order by ????
You want to sort hex codes by wavelength, this roughly maps onto the hue-value. Given a hexcode as a six character string: RRGGBB.
You just need to make a function that takes in a hexcode string and outputs the hue value, here's the formula from this Math.SO answer:
R' = R/255
G' = G/255
B' = B/255
Cmax = max(R', G', B')
Cmin = min(R', G', B')
Δ = Cmax - Cmin
I wanted to see if this would work, so I whipped up a sample program in Ruby, it samples 200 random colors uniformly from RGB-space, and sorts them, the output looks like a rainbow!
Here's the Ruby source:
require 'paint'
def hex_to_rgb(hex)
/(?<r>..)(?<g>..)(?<b>..)/ =~ hex
[r,g,b].map {|cs| cs.to_i(16) }
end
def rgb_to_hue(r,g,b)
# normalize r, g and b
r_ = r / 255.0
g_ = g / 255.0
b_ = b / 255.0
c_min = [r_,g_,b_].min
c_max = [r_,g_,b_].max
delta = (c_max - c_min).to_f
# compute hue
hue = 60 * ((g_ - b_)/delta % 6) if c_max == r_
hue = 60 * ((b_ - r_)/delta + 2) if c_max == g_
hue = 60 * ((r_ - g_)/delta + 4) if c_max == b_
return hue
end
# sample uniformly at random from RGB space
colors = 200.times.map { (0..255).to_a.sample(3).map { |i| i.to_s(16).rjust(2, '0')}.join }
# sort by hue
colors.sort_by { |color| rgb_to_hue(*hex_to_rgb(color)) }.each do |color|
puts Paint[color, color]
end
Note, make sure to gem install paint to get the colored text output.
Here's the output:
It should be relatively straight-forward to write this as a SQL user-defined function and ORDER BY RGB_to_HUE(hex_color_code), however, my SQL knowledge is pretty basic.
EDIT: I posted this question on dba.SE about converting the Ruby to a SQL user defined function.
This is based on the answer by #dliff. I initially edited it, but it turns out my edit was rejected saying "it should have been written as a comment or an answer". Seeing this would be too large to post as a comment, here goes.
The reason for editing (and now posting) is this: there seems to be a problem with colors like 808080 because their R, G and B channels are equal. If one needs this to sort or group colors and keep the passed grayscale/non-colors separate, that answer won't work, so I edited it.
DELIMITER $$
DROP FUNCTION IF EXISTS `hex_to_hue`$$
CREATE FUNCTION `hex_to_hue`(HEX VARCHAR(6)) RETURNS FLOAT
BEGIN
DECLARE r FLOAT;
DECLARE b FLOAT;
DECLARE g FLOAT;
DECLARE MIN FLOAT;
DECLARE MAX FLOAT;
DECLARE delta FLOAT;
DECLARE hue FLOAT;
IF(HEX = '') THEN
RETURN NULL;
END IF;
SET r = CONV(SUBSTR(HEX, 1, 2), 16, 10)/255.0;
SET g = CONV(SUBSTR(HEX, 3, 2), 16, 10)/255.0;
SET b = CONV(SUBSTR(HEX, 5, 2), 16, 10)/255.0;
SET MAX = GREATEST(r,g,b);
SET MIN = LEAST(r,g,b);
SET delta = MAX - MIN;
SET hue=
(CASE
WHEN MAX=r THEN (60 * ((g - b)/delta % 6))
WHEN MAX=g THEN (60 * ((b - r)/delta + 2))
WHEN MAX=b THEN (60 * ((r - g)/delta + 4))
ELSE NULL
END);
IF(ISNULL(hue)) THEN
SET hue=999;
END IF;
RETURN hue;
END$$
DELIMITER ;
Again, I initially wanted to edit the original answer, not post as a separate one.
If your products can have lots of color probably a good UI will require a color picker, normally those are rectangular, so not really something possible with the order by.
If the products have a manageable number of colors you have different choice, the easiest to implement is an order table, where for every possible color is defined an order position, this table can then be joined to your query, something like
SELECT ...
FROM (SELECT ...
...
...
, ci.color_order
FROM customization_options co
LEFT JOIN customization_option_groups cog
ON cog.id = co.customization_option_group_id
LEFT JOIN color_ ci
ON designer_hex_color = ci.color
WHERE ...) a
ORDER BY color_order
Another way to go is to transform the RGB color to hue and use this as the order.
There are different formula for this conversion, depending on wich order you want the primary color to have, all of them can be found on the wikipedia page for hue, I can update the answer to help you convert one of those to T-SQL, if needed.
MySQL function Hex to Hue. Based on Tobi's answer. :)
CREATE FUNCTION `hex_to_hue`(hex varchar(6)) RETURNS float
BEGIN
declare r float;
declare b float;
declare g float;
declare min float;
declare max float;
declare delta float;
declare hue float;
set r = conv(substr(hex, 1, 2), 16, 10)/255.0;
set g = conv(substr(hex, 3, 2), 16, 10)/255.0;
set b = conv(substr(hex, 5, 2), 16, 10)/255.0;
set max = greatest(r,g,b);
set min = least(r,g,b);
set delta = max - min;
set hue=
(case
when max=r then (60 * ((g - b)/delta % 6))
when max=g then (60 * ((b - r)/delta + 2))
when max=b then (60 * ((r - g)/delta + 4))
else null
end);
RETURN hue;
END
I thought I ran into a bug with MySQL 5.1, but the bug was in the perl code that's creating the timestamps. perl's localtime uses 0-11 for months, but MySQL's datetime uses 1-12. So, I've got all these malformed timestamps that I need to update.
2012-00-19 09:03:30
This should be:
2012-01-19 09:03:30
The problem is that the date functions for MySQL return NULL on a 00 month. Is there a way to do this in MySQL?
EDIT: Solution =
UPDATE test_stats
SET start_time = CAST(CONCAT(SUBSTRING(start_time, 1, 5),
CAST((CAST(SUBSTRING(start_time, 6, 2) AS UNSIGNED) + 1) AS CHAR(2)),
SUBSTRING(start_time, 8, 12)) AS DATETIME);
By the way, I was using MySQL 5.1
This should work:
UPDATE MyTable
SET DateTimeField =
CAST (
SUBSTRING(DateTimeString, 1, 5) -- '2012-'
+ CAST((CAST(SUBSTRING(DateTimeString, 6, 2) AS INT) + 1) AS VARCHAR) -- '00' => '1'
+ SUBSTRING(DateTimeString, 8, 12) -- '-19 09:03:30'
AS DATETIME)
Test with this select
DECLARE #x VARCHAR(50) = '2012-00-19 09:03:30'
SELECT CAST(SUBSTRING(#x, 1, 5)
+ CAST((CAST(SUBSTRING(#x, 6, 2) AS INT) + 1) AS VARCHAR)
+ SUBSTRING(#x, 8, 12) AS DATETIME)
In SQL, I would like to query a list, in order by pageNumber
SELECT * FROM `comics`
WHERE 1
ORDER BY pageNumber ASC
Then, I would like to set their pageNumbers based on their index in the query (starting with 1 instead of 0).
Here is my pseudo code for the functionality as desired; where list is the return value of the Select Query above.
for(var n:int = 0; n<list.length; n++){
if(list[n].pageNumber != n+1){
list[n].pageNumber = n+1
}
}
For example I might have pageNumbers 5, 17, 23, 24, 18, 7
The ORDER BY pageNumber ASC will sort this to 5, 7, 17, 18, 23, 24
I would then like to alter the pageNumbers in order to be 1, 2, 3, 4, 5, 6
edit:
#fortheworld MySQL
#cyberkiwi UPDATE
sorry for being unclear. guess i need to learn more for my questions to be clear :)
thanks for all your help
SET #I := 0;
SELECT *,
#I := #I + 1 AS newPageNumber
FROM comics
ORDER BY pageNumber ASC
I don't understand why peops insist on writing an SQL batch when a single statement will do.
SELECT comics.*, #n := #n + 1 AS PageNumber2
FROM (SELECT #n := 0) X CROSS JOIN comics
ORDER BY pageNumber ASC
Like the title says, I'm trying to implement the programmatic parts of RFC4226 "HOTP: An HMAC-Based One-Time Password Algorithm" in SQL. I think I've got a version that works (in that for a small test sample, it produces the same result as the Java version in the code), but it contains a nested pair of hex(unhex()) calls, which I feel can be done better. I am constrained by a) needing to do this algorithm, and b) needing to do it in mysql, otherwise I'm happy to look at other ways of doing this.
What I've got so far:
-- From the inside out...
-- Concatinate the users secret, and the number of time its been used
-- find the SHA1 hash of that string
-- Turn a 40 byte hex encoding into a 20 byte binary string
-- keep the first 4 bytes
-- turn those back into a hex represnetation
-- convert that into an integer
-- Throw away the most-significant bit (solves signed/unsigned problems)
-- Truncate to 6 digits
-- store into otp
-- from the otpsecrets table
select (conv(hex(substr(unhex(sha1(concat(secret, uses))), 1, 4)), 16, 10) & 0x7fffffff) % 1000000
into otp
from otpsecrets;
Is there a better (more efficient) way of doing this?
I haven't read the spec, but I think you don't need to convert back and forth between hex and binary, so this might be a little more efficient:
SELECT (conv(substr(sha1(concat(secret, uses)), 1, 8), 16, 10) & 0x7fffffff) % 1000000
INTO otp
FROM otpsecrets;
This seems to give the same result as your query for a few examples I tested.
This is absolutely horrific, but it works with my 6-digit OTP tokens. Call as:
select HOTP( floor( unix_timestamp()/60), secret ) 'OTP' from SecretKeyTable;
drop function HOTP;
delimiter //
CREATE FUNCTION HOTP(C integer, K BINARY(64)) RETURNS char(6)
BEGIN
declare i INTEGER;
declare ipad BINARY(64);
declare opad BINARY(64);
declare hmac BINARY(20);
declare cbin BINARY(8);
set i = 1;
set ipad = repeat( 0x36, 64 );
set opad = repeat( 0x5c, 64 );
repeat
set ipad = insert( ipad, i, 1, char( ascii( substr( K, i, 1 ) ) ^ 0x36 ) );
set opad = insert( opad, i, 1, char( ascii( substr( K, i, 1 ) ) ^ 0x5C ) );
set i = i + 1;
until (i > 64) end repeat;
set cbin = unhex( lpad( hex( C ), 16, '0' ) );
set hmac = unhex( sha1( concat( opad, unhex( sha1( concat( ipad, cbin ) ) ) ) ) );
return lpad( (conv(hex(substr( hmac, (ascii( right( hmac, 1 ) ) & 0x0f) + 1, 4 )),16,10) & 0x7fffffff) % 1000000, 6, '0' );
END
//
delimiter ;