PostgreSQL 9.1を使用して16進数を10進数に変換しようとしています
このクエリでは:
SELECT to_number('DEADBEEF', 'FMXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX');
次のエラーが表示されます。
ERROR: invalid input syntax for type numeric: " "
私は何を間違えていますか?
2つの差し迫った問題があります。
to_number
は16進数を理解しません。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"
は2つの入力文字をスキップします。
ただし、フォーマット文字ではない引用符で囲まれていない文字の動作は指定されていないようです。
いずれにせよ、to_number
は16進数を数値に変換するための適切なツールではありません。次のように言います。
select x'deadbeef'::int;
したがって、おそらく この関数 はより適切に機能します。
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)
**整数オーバーフローエラーによるこのような負の数を回避するには、intではなくbigintを使用して、より大きな16進数(IPアドレスなど)に対応します。
動的SQLなしの方法があります。
text
表現の16進数から数値型へのキャストはありませんが、ウェイポイントとして bit(n)
を使用できます。 4ビットビット文字列で1桁の16進数をエンコードします。 bit(32)
(最大8桁の16進数)までのビット文字列から 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
_
all 8桁までの16進数をエンコードするには4バイトで十分ですが、Postgresのinteger
は符号付きタイプなので、16進数上記の_'7fffffff'
_負の整数数値へのオーバーフロー。これはまだ一意の表現ですが、の意味は異なります。それがbigint
への切り替えが重要な場合は、以下を参照してください。
未知数の16進数可変長 leading zeros _0
_をbit(32)
にキャストするように埋め込む必要があります。既知の長さの数については、長さ指定子を調整するだけです。 7桁の16進数とint
または8桁とbigint
の例:
_SELECT ('x'|| 'deafbee')::bit(28)::int
, ('x'|| 'deadbeef')::bit(32)::bigint;
int4 | int8
-----------+------------
233503726 | 3735928559
_
bigint
(_int8
_、8バイト整数)を16桁までの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桁を超える16進数の場合、最下位文字(右端を超える)は truncated になります。
このキャストは文書化されていない動作に依存しています。引用する Tom Lane here :
これは、ビット型入力コンバーターのいくつかの文書化されていない動作に依存していますが、それが壊れると予想する理由はありません。おそらくより大きい問題は、PG> = 8.3を必要とすることです。その前にビットキャストするテキストがなかったためです。
Postgres uuid
データ型は数値型ではないであるため、これは質問とは異なります。ただし、標準のPostgresで最大32桁の16進数を格納するのが最も効率的なタイプで、16バイトのストレージしか占有しません。 直接キャストがありますが、 exactly 32桁の16進数が必要です。
_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
_
ご覧のように、標準出力は16進数の文字列で、UUIDの一般的な区切り文字が付いています。
これは、md5ハッシュを保存するのに特に便利です。
_SELECT md5('Store hash for long string, maybe for index?')::uuid AS md5_hash
_
結果:
_ md5_hash
--------------------------------------
02e10e94-e895-616e-8e23-bb7f8025da42
_
他の誰かが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バージョン:
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
_内部的には、_pg-bignum
_は大きな数に対してSSLライブラリを使用します。この方法には、数値に関する他の回答で言及されている欠点はありません。また、plpgsqlによって速度が低下することもありません。それは高速であり、あらゆるサイズの数で動作します。比較のためのアーウィンの回答から取られたテストケース、
_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
を使用して、型を数値に取得できます。
以下はnumeric
を使用するバージョンです。したがって、任意の大きな16進文字列を処理できます。
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