一尘不染

在PostgreSQL中将bytea转换为双精度

sql

我有一个数据库,其中的一个表存储bytea从另一个系统收集的各种通用数据的Blob()。该bytea字段可以包含任何内容。为了知道如何解释数据,该表还具有一个格式字段。我写了一个Java应用程序读取bytea从数据库的字段byte[],然后我可以很容易地将其转换为double[]int[]或任何格式字段表示,通过ByteBuffer与各种视图(DoubleBufferIntBuffer,等)。

现在,我需要在触发器函数中对数据库本身上的数据进行一些操作,以保持与另一个表的完整性。我可以找到几乎可以想象到的任何数据类型的转换,但是我找不到从bytea(甚至bit到)往返的任何数据double precisionbytea可以将A分解,转换为位,然后转换为intor bigint,但不能转换为a double precision。例如,x'deadbeefdeadbeef'::bit(64)::bigint将毫无问题地转换为-2401053088876216593,而是x'deadbeefdeadbeef'::bit(64)::double precision失败,并显示“错误:无法将类型位转换为双精度”,而不给出IEEE 754的答案-1.1885959257070704E148

我找到了这个答案它基本上实现了IEEE标准,可以将位转换为两倍,但是PostgreSQL中确实没有基本的转换功能吗?另外,当我处理完数据并需要更新表时,我也需要倒退double precisionbytea直到这个答案没有提供。

最好的解决方案是使用SQL命令使用IEEE754-1985标准将其转换为字节。

首先,需要检查IEEE754-1985标准定义的特殊情况。然后,如果不是特殊情况,只需遵循标准算法进行转换。示例代码如下。

bytea_value bytea, is_little_endian boolean然后将输入分为4个字节,如下所示:

  byte_array[0]:= get_byte(bytea_value, 0);
  byte_array[1]:= get_byte(bytea_value, 1);
  byte_array[2]:= get_byte(bytea_value, 2);
  byte_array[3]:= get_byte(bytea_value, 3);

然后通过考虑小端或大端获得二进制值

IF is_little_endian THEN
binary_value:= byte_array[0]::bit(8) || byte_array[1]::bit(8) || byte_array[2]::bit(8) || byte_array[3]::bit(8);
ELSE
binary_value:= byte_array[3]::bit(8) || byte_array[2]::bit(8) || byte_array[1]::bit(8) || byte_array[0]::bit(8);
END IF;
现在检查特殊情况:

IF binary_value = '00000000000000000000000000000000' OR binary_value = '10000000000000000000000000000000' THEN -- IEEE754-1985 Zero
        return 0.0;
    END IF;

sign := substring(binary_value from 1 for 1);
    exponent := substring(binary_value from 2 for 8);
    mantissa := substring(binary_value from 10 for 23); 

    IF exponent = '11111111' THEN
        IF mantissa = '00000000000000000000000' THEN   -- IEEE754-1985 negative and positive infinity
            IF sign = '1' THEN                    
                return '-Infinity';                    
            ELSE                    
                return 'Infinity';  
            END IF;                  
        ELSE
          return 'NaN'; -- IEEE754-1985 Not a number
        END IF; 
    END IF;

如果它不属于任何特殊情况,则将其转换如下:

exp := exponent::int;

    IF exp > 126 THEN
     exp := exp - 127;
    ELSE
     exp:= -exp;
    END IF;

    WHILE mantissa_index < 24 LOOP
        IF substring(mantissa from mantissa_index for 1) = '1' THEN
            result := result + power(2, -(mantissa_index));
        END IF;
        mantissa_index = mantissa_index + 1;
    END LOOP;

    result := result * power(2, exp);

    IF(sign = '1') THEN
        result = -result;
    END IF;

    return result;

有任何想法吗?


阅读 161

收藏
2021-05-16

共1个答案

一尘不染

好的,我找到了答案。在PostgreSQL中,您可以使用Python编写函数。为了启用Python,您必须安装PostgreSQL安装所需的特定版本的Python,并将其在PATH环境变量中可用。您可以通过查看安装说明找到PostgreSQL安装所需的Python版本。我目前在Windows上使用PostgreSQL
9.6.5,它要求使用Python 3.3。我最初尝试了最新的Python 3.6,但无法正常工作。我选择了适用于Windows的最新Python
3.3,即3.3.5。

安装Python之后,您可以CREATE EXTENSION plpython3u;按照https://www.postgresql.org/docs/current/static/plpython.html所述在数据库上执行,从而在PostgreSQL中启用它。从那里,您可以使用Python主体编写任何函数。

对于我的具体情况,从转换byteadouble precision[]背,我写了下面的功能:

CREATE FUNCTION bytea_to_double_array(b bytea)
    RETURNS double precision[]
    LANGUAGE 'plpython3u'
AS $BODY$
  if 'struct' in GD:
    struct = GD['struct']
  else:
    import struct
    GD['struct'] = struct

  return struct.unpack('<' + str(int(len(b) / 8)) + 'd', b)
$BODY$;

CREATE FUNCTION double_array_to_bytea(dblarray double precision[])
    RETURNS bytea
    LANGUAGE 'plpython3u'
AS $BODY$
  if 'struct' in GD:
    struct = GD['struct']
  else:
    import struct
    GD['struct'] = struct

  # dblarray here is really a list.
  # PostgreSQL passes SQL arrays as Python lists
  return struct.pack('<' + str(int(len(dblarray))) + 'd', *dblarray)
$BODY$;

在我的情况下,所有双打都存储在little
endian中,因此我使用<。我还struct按照的描述,将模块的导入缓存在全局字典中。我使用GD而不是SD,因为我希望导入可以在我可能编写的其他函数中使用。有关GD和SD的信息,请参见https://www.postgresql.org/docs/current/static/plpython-
sharing.html。

要了解它的作用,请知道我数据库中的blob以小字节序存储,

SELECT bytea_to_double_array(decode('efbeaddeefbeadde', 'hex')), encode(double_array_to_bytea(array[-1.1885959257070704E148]), 'hex');

我得到的答案是

bytea_to_double_array    | encode
double precision[]       | text
-------------------------+------------------
{-1.18859592570707e+148} | efbeaddeefbeadde

这里'efbeaddeefbeadde''deadbeefdeadbeef'在小尾数。

2021-05-16