J'essaye de convertir hex en décimal à l'aide de PostgreSQL 9.1
avec cette requête:
SELECT to_number('DEADBEEF', 'FMXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX');
Je reçois l'erreur suivante:
ERROR: invalid input syntax for type numeric: " "
Qu'est-ce que je fais mal?
Vous avez deux problèmes immédiats:
to_number
ne comprend pas l'hexadécimal.X
n'a aucune signification dans une chaîne de formatage to_number
et toute chose sans signification signifie apparemment "ignorer un caractère".Je n'ai pas de justification faisant autorité pour (2), juste des preuves empiriques:
=> SELECT to_number('123', 'X999');
to_number
-----------
23
(1 row)
=> SELECT to_number('123', 'XX999');
to_number
-----------
3
La documentation mentionne le comportement supposé des motifs cités:
Dans
to_date
,to_number
etto_timestamp
, les chaînes entre guillemets doubles ignorent le nombre de caractères saisis contenus dans la chaîne, par exemple."XX"
ignore deux caractères saisis.
mais le comportement des caractères non cités qui ne sont pas des caractères de formatage semble être non spécifié.
Dans tous les cas, to_number
n'est pas le bon outil pour convertir un hex en nombres, vous voulez dire quelque chose comme ceci:
select x'deadbeef'::int;
alors peut-être cette fonction fonctionnera mieux pour vous:
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;
Ensuite:
=> select hex_to_int('DEADBEEF');
hex_to_int
------------
-559038737 **
(1 row)
** Pour éviter des nombres négatifs comme ceux-ci dus à une erreur de dépassement d'entier, utilisez bigint au lieu de int pour gérer des nombres hexadécimaux plus grands (comme les adresses IP).
Il existe des moyens sans SQL dynamique.
Il n'y a pas d'incantation à partir de nombres hexadécimaux dans text
représentation en un type numérique, mais nous pouvons utiliser bit(n)
comme point de cheminement. 4 bits dans une chaîne de bits encoder 1 chiffre hexadécimal. Il existe une conversion non documentée de chaînes binaires jusqu'à bit(32)
(max. 8 chiffres hexadécimaux) vers integer
(entier standard à 4 octets) - la représentation interne est compatible binaire.
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);
Résultat:
int_val
------------
1
15
256
2147483647
-2147483648
-559038737
-1
4 octets suffisent pour encoder tous nombres hexadécimaux jusqu'à 8 chiffres mais integer
dans Postgres est un type signé, ainsi les nombres hexadécimaux supérieurs '7fffffff'
débordent dans un nombre négatif int. Ceci est toujours une représentation unique, mais le signification est différent. Si cela est important, passez à bigint
, voir ci-dessous.
Pour les nombres hexadécimaux inconnus de longueur variable, nous devons ajouter interligne zéros 0
, comme indiqué pour le transtypage en bit(32)
. Pour les nombres de longueur connue, nous pouvons simplement adapter le spécificateur de longueur. Exemple avec 7 chiffres hexadécimaux et int
ou 8 chiffres et bigint
:
SELECT ('x'|| 'deafbee')::bit(28)::int
, ('x'|| 'deadbeef')::bit(32)::bigint;
int4 | int8
-----------+------------
233503726 | 3735928559
Utilisez bigint
(int8
, entier sur 8 octets) pour un maximum de 16 chiffres hexadécimaux - dépassement des nombres négatifs dans la moitié supérieure:
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);
Résultat:
int8_val
---------------------
255
2147483647
2147483648
3735928559
9223372036854775807
-9223372036854775808
-1
-1
Pour plus de 16 chiffres hexadécimaux, les caractères les moins significatifs (dépassement à droite) obtiennent tronqué.
Cette distribution s'appuie sur un comportement non documenté, je cite Tom Lane ici :
Cela repose sur un comportement non documenté de l'entrée de type bit convertisseur, mais je ne vois aucune raison de s’attendre à ce que cela casse. Un possible Le plus gros problème est qu'il nécessite un PG> = 8.3 puisqu'il n'y avait pas de texte mordre avant cela.
Le type de données Postgres uuid
est n'est pas un type numérique, ce qui diffère de la question posée. Mais c'est le type le plus efficace dans Postgres standard pour stocker jusqu'à 32 chiffres hexadécimaux, n'occupant que 16 octets de stockage. Il y a un diffusion directe, mais exactement 32 chiffres hexadécimaux sont requis.
SELECT lpad(hex, 32, '0')::uuid AS uuid_val
FROM (
VALUES ('ff'::text)
, ('deadbeef')
, ('ffffffffffffffff')
, ('ffffffffffffffffffffffffffffffff')
, ('ffffffffffffffffffffffffffffffff123') -- too long
) t(hex);
Résultat:
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
Comme vous pouvez le constater, la sortie standard est une chaîne de chiffres hexadécimaux avec des séparateurs typiques pour l’UUID.
Ceci est particulièrement utile pour stocker md5 hashes:
SELECT md5('Store hash for long string, maybe for index?')::uuid AS md5_hash
Résultat:
md5_hash
--------------------------------------
02e10e94-e895-616e-8e23-bb7f8025da42
Si quelqu'un d'autre est coincé avec PG8.2, voici une autre façon de le faire.
version 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;
version int:
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;
pg-bignum
En interne, pg-bignum
utilise la bibliothèque SSL pour les grands nombres. Cette méthode ne présente aucun des inconvénients mentionnés dans les autres réponses avec numérique. Il n'est pas non plus ralenti par plpgsql. C'est rapide et cela fonctionne avec un nombre de n'importe quelle taille. Cas de test tiré de la réponse d'Erwin pour comparaison,
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)
Vous pouvez obtenir le type numérique en utilisant bn_in_hex('deadbeef')::text::numeric
.
Voici une version qui utilise numeric
, afin de pouvoir gérer des chaînes hexagonales de taille arbitraire:
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$;
Par exemple:
=# select hex_to_decimal('ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff');
32592575621351777380295131014550050576823494298654980010178247189670100796213387298934358015