我正在尝试使用PostgreSQL 9.1
将十六进制转换为十进制使用此查询:
SELECT to_number('DEADBEEF', 'FMXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX');
我收到以下错误:
ERROR: invalid input syntax for type numeric: " "
我做错了什么?
答案 0 :(得分:66)
有些方法没有动态SQL 。
text
表示中没有从十六进制数转换为数字类型,但我们可以使用bit(n)
作为航点。位串中的 4位编码1个十六进制数字。从位字符串到bit(32)
(最多8个十六进制数字)到integer
(标准的4字节整数)有一个未记录的强制转换 - 内部表示是二进制兼容的。
SELECT ('x' || lpad(hex, 8, '0'))::bit(32)::int AS int_val
FROM (
VALUES ('1'::text)
,('f')
,('100')
,('7fffffff')
,('80000000')
,('deadbeef')
,('ffffffff')
) AS t(hex);
结果:
int_val
------------
1
15
256
2147483647
-2147483648
-559038737
-1
4个字节足以编码所有十六进制数字,最多8位数,但Postgres中的integer
是有符号类型,因此十六进制数高于'7fffffff'
溢出到负数数字。这仍然是一种独特的表示,但含义是不同的。如果重要,请转到bigint
,请参阅下文。
对于未知不同长度的十六进制数字,我们需要填充前导零0
,如演示要强制转换为bit(32)
。对于已知长度的数字,我们可以调整长度说明符。示例包含7个十六进制数字和int
或8位数字bigint
:
SELECT ('x'|| 'deafbee')::bit(28)::int
, ('x'|| 'deadbeef')::bit(32)::bigint;
int4 | int8
-----------+------------
233503726 | 3735928559
使用bigint
(int8
,8字节整数)最多16个十六进制数字 - 溢出到上半部分的负数:
SELECT ('x' || lpad(hex, 16, '0'))::bit(64)::bigint AS int8_val
FROM (
VALUES ('ff'::text)
, ('7fffffff')
, ('80000000')
, ('deadbeef')
, ('7fffffffffffffff')
, ('8000000000000000')
, ('ffffffffffffffff')
, ('ffffffffffffffff123') -- too long
) t(hex);
结果:
int8_val
---------------------
255
2147483647
2147483648
3735928559
9223372036854775807
-9223372036854775808
-1
-1
对于超过16个十六进制数字,最不重要的字符(超出右侧)得到截断。
此演员依赖无证件行为,我引用Tom Lane here:
这依赖于位类型输入的一些未记录的行为 转换器,但我认为没有理由期望会破坏。可能 更大的问题是它需要PG> = 8.3,因为没有文本 在那之前投点。
Postgres uuid
数据类型不是数字类型,因此这与提出的问题不同。但它是标准Postgres中最有效的类型,可存储多达32个十六进制数字,仅占用16个字节的存储空间。有一个直接投射,但正好 32个十六进制数字是必需的。
SELECT lpad(hex, 32, '0')::uuid AS uuid_val
FROM (
VALUES ('ff'::text)
, ('deadbeef')
, ('ffffffffffffffff')
, ('ffffffffffffffffffffffffffffffff')
, ('ffffffffffffffffffffffffffffffff123') -- too long
) t(hex);
结果:
uuid_val
--------------------------------------
00000000-0000-0000-0000-0000000000ff
00000000-0000-0000-0000-0000deadbeef
00000000-0000-0000-ffff-ffffffffffff
ffffffff-ffff-ffff-ffff-ffffffffffff
ffffffff-ffff-ffff-ffff-ffffffffffff
如您所见,标准输出是一个十六进制数字字符串,其中包含UUID的典型分隔符。
这对于存储 md5哈希:
特别有用SELECT md5('Store hash for long string, maybe for index?')::uuid AS md5_hash
结果:
md5_hash
--------------------------------------
02e10e94-e895-616e-8e23-bb7f8025da42
答案 1 :(得分:19)
你有两个直接的问题:
to_number
无法理解十六进制。X
在to_number
格式字符串中没有任何意义,任何没有含义的内容显然都意味着“跳过一个字符”。我没有(2)的权威理由,只是经验证据:
=> SELECT to_number('123', 'X999');
to_number
-----------
23
(1 row)
=> SELECT to_number('123', 'XX999');
to_number
-----------
3
文档提到双引号模式应该如何表现:
在
to_date
,to_number
和to_timestamp
中,双引号字符串会跳过字符串中包含的输入字符数,例如"XX"
跳过两个输入字符。
但是非格式化字符的非引用字符的行为似乎未指定。
在任何情况下,to_number
都不是将十六进制转换为数字的正确工具,您想要这样说:
select x'deadbeef'::int;
所以也许this function会更适合你:
CREATE OR REPLACE FUNCTION hex_to_int(hexval varchar) RETURNS integer AS $$
DECLARE
result int;
BEGIN
EXECUTE 'SELECT x' || quote_literal(hexval) || '::int' INTO result;
RETURN result;
END;
$$ LANGUAGE plpgsql IMMUTABLE STRICT;
然后:
=> select hex_to_int('DEADBEEF');
hex_to_int
------------
-559038737 **
(1 row)
**为了避免整数溢出错误这样的负数,请使用bigint而不是int来容纳更大的十六进制数(如IP地址)。
答案 2 :(得分:3)
如果有其他人坚持PG8.2,这是另一种方法。
bigint版本:
create or replace function hex_to_bigint(hexval text) returns bigint as $$
select
(get_byte(x,0)::int8<<(7*8)) |
(get_byte(x,1)::int8<<(6*8)) |
(get_byte(x,2)::int8<<(5*8)) |
(get_byte(x,3)::int8<<(4*8)) |
(get_byte(x,4)::int8<<(3*8)) |
(get_byte(x,5)::int8<<(2*8)) |
(get_byte(x,6)::int8<<(1*8)) |
(get_byte(x,7)::int8)
from (
select decode(lpad($1, 16, '0'), 'hex') as x
) as a;
$$
language sql strict immutable;
int version:
create or replace function hex_to_int(hexval text) returns int as $$
select
(get_byte(x,0)::int<<(3*8)) |
(get_byte(x,1)::int<<(2*8)) |
(get_byte(x,2)::int<<(1*8)) |
(get_byte(x,3)::int)
from (
select decode(lpad($1, 8, '0'), 'hex') as x
) as a;
$$
language sql strict immutable;
答案 3 :(得分:3)
pg-bignum
在内部,pg-bignum
将SSL库用于大数字。这种方法没有在数字的其他答案中提到的缺点。它也不会被plpgsql放慢速度。速度很快,适用于任何规模。测试案例取自Erwin的答案进行比较,
CREATE EXTENSION bignum;
SELECT hex, bn_in_hex(hex::cstring)
FROM (
VALUES ('ff'::text)
, ('7fffffff')
, ('80000000')
, ('deadbeef')
, ('7fffffffffffffff')
, ('8000000000000000')
, ('ffffffffffffffff')
, ('ffffffffffffffff123')
) t(hex);
hex | bn_in_hex
---------------------+-------------------------
ff | 255
7fffffff | 2147483647
80000000 | 2147483648
deadbeef | 3735928559
7fffffffffffffff | 9223372036854775807
8000000000000000 | 9223372036854775808
ffffffffffffffff | 18446744073709551615
ffffffffffffffff123 | 75557863725914323415331
(8 rows)
您可以使用bn_in_hex('deadbeef')::text::numeric
将数据类型化为数字。
答案 4 :(得分:1)
这里是使用numeric
的版本,因此它可以处理任意大的十六进制字符串:
create function hex_to_decimal(hex_string text)
returns text
language plpgsql immutable as $pgsql$
declare
bits bit varying;
result numeric := 0;
exponent numeric := 0;
chunk_size integer := 31;
start integer;
begin
execute 'SELECT x' || quote_literal(hex_string) INTO bits;
while length(bits) > 0 loop
start := greatest(1, length(bits) - chunk_size);
result := result + (substring(bits from start for chunk_size)::bigint)::numeric * pow(2::numeric, exponent);
exponent := exponent + chunk_size;
bits := substring(bits from 1 for greatest(0, length(bits) - chunk_size));
end loop;
return trunc(result, 0);
end
$pgsql$;
例如:
=# select hex_to_decimal('ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff');
32592575621351777380295131014550050576823494298654980010178247189670100796213387298934358015