如何在 PostgreSQL 中将长 NUMERIC 整数转换为位字符串?

Posted

技术标签:

【中文标题】如何在 PostgreSQL 中将长 NUMERIC 整数转换为位字符串?【英文标题】:How can I cast an long NUMERIC integer into a bit string in PostgreSQL? 【发布时间】:2014-12-04 20:51:53 【问题描述】:

我正在尝试使用 Postgres 的 pg_similarity 扩展来计算 Django 应用程序中长整数对(每个 20 位)的汉明距离,并且很难弄清楚如何做到这一点。 Django 似乎没有当前的 BitString 字段(这将是理想的,但 django_postgres 似乎已失效),所以我试图在 SQL 查询本身中将整数转换为位串。我当前的查询:

    sql = ''' SELECT id, hamming(
        "HashString"::BIT(255),
        %s::BIT(255)
    ) as hamming_distance
    FROM images
    HAVING hamming_distance < %s
    ORDER BY hamming_distance;'''

正在引发数据库错误:cannot cast type numeric to bit。我究竟做错了什么?我还能尝试什么?

【问题讨论】:

尝试使用此包将整数转换为 BitString:pypi.python.org/pypi/bitstring/3.1.3 我试过了,但 Python BitString 似乎在 66 位时被截断。问题似乎是 Postgres 位字段不能超过某个最大值(因为上面的代码适用于较小的整数)。有什么好办法吗? 在这种情况下,我会用 python 来做。获取图像,遍历它们并检查汉明距离。 因为您的 20 位整数超出了 64 位整数(int8 或 bigint)值的范围,所以您使用的是 numeric,但没有从 numeric 转换为 @987654326 @。这将是“有趣的”,因为数字也没有位移支持,因为它们是任意精度 decimal 浮点数。 【参考方案1】:

根据the manual,如果您的“长整数”实际上是“长整数”,即 bigint / int8,则强制转换是正确的方法:

regress=> SELECT ('1324'::bigint)::bit(64);
                               bit                                
------------------------------------------------------------------
 0000000000000000000000000000000000000000000000000000010100101100
(1 row)

但是(编辑)您实际上是在询问如何将仅整数 numeric 转换为 bit。没那么简单,等一下。

您也无法对数字进行位移,因此无法轻松地将其位移为 64 位块、转换和重新组装。

您必须改用除法和模数。

给定:

SELECT '1792913810350008736973055638379610855835'::numeric(40,0);

您可以在“bigint”块中得到它,当乘以 max-long (9223372036854775807) 乘以它们的位置值时,会产生原始值。

例如这将获得最低的 64 位:

SELECT ('1792913810350008736973055638379610855835'::numeric(40,0) / '9223372036854775807'::numeric(256,0)) % '9223372036854775807'::numeric(40,0);

这将获取最多 256 位的给定值及其指数的所有块

WITH numval(v) AS (VALUES ('1792913810350008736973055638379610855835'::numeric(40,0)))
SELECT exponent, floor(v / ('9223372036854775807'::numeric(256,0) ^ exponent) % '9223372036854775807'::numeric(40,0)) from numval, generate_series(1,3) exponent;

你可以把它重新组装成原来的值:

WITH
  numval(v) AS (
    VALUES ('1792913810350008736973055638379610855835'::numeric(40,0))
  ),
  chunks (exponent, chunk) AS (
     SELECT exponent, floor(v / ('9223372036854775807'::numeric(40,0) ^ exponent) % '9223372036854775807'::numeric(40,0))::bigint from numval, generate_series(1,3) exponent
  )
SELECT floor(sum(chunk::numeric(40,0) * ('9223372036854775807'::numeric(40,0) ^ exponent))) FROM chunks;

所以我们知道它被正确分解了。

现在我们正在处理一系列 64 位整数,我们可以将每个整数转换为位域。因为我们使用的是有符号整数,每个只有 63 个有效位,所以:

WITH
  numval(v) AS (
    VALUES ('1792913810350008736973055638379610855835'::numeric(40,0))
  ),
  chunks (exponent, chunk) AS (
     SELECT exponent, floor(v / ('9223372036854775807'::numeric(40,0) ^ exponent) % '9223372036854775807'::numeric(40,0))::bigint from numval, generate_series(1,3) exponent
  )
SELECT
  exponent,
  chunk::bit(63)
FROM chunks;

为我们提供每个 63 位块的位值。然后我们可以重新组装它们。没有位域连接运算符,但我们可以移位和bit_or,然后将其包装到 SQL 函数中,产生怪物:

CREATE OR REPLACE FUNCTION numericint40_to_bit189(numeric(40,0)) RETURNS bit(189)
LANGUAGE sql
AS
$$
    WITH
      chunks (exponent, chunk) AS (
         SELECT exponent, floor($1 / ('9223372036854775807'::numeric(40,0) ^ exponent) % '9223372036854775807'::numeric(40,0))::bigint 
         FROM generate_series(1,3) exponent
      )
    SELECT
      bit_or(chunk::bit(189) << (63*(exponent-1)))
    FROM chunks;
$$;

可以在这里看到使用中:

regress=> SELECT numericint40_to_bit189('1792913810350008736973055638379610855835');
                                                                                    numericint40_to_bit189                                                                                     
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010101000100110101101010001110110110101001111100011100011110000010110
(1 row)

【讨论】:

【参考方案2】:

感谢克雷格·林格的初步回答!这是该函数的正确版本。它最多支持 300 位,并且可以根据需要进行扩展。

CREATE OR REPLACE FUNCTION numeric_to_bit(NUMERIC)
  RETURNS BIT VARYING AS $$
DECLARE
  num ALIAS FOR $1;
  -- 1 + largest positive BIGINT --
  max_bigint NUMERIC := '9223372036854775808' :: NUMERIC(19, 0);
  result BIT VARYING;
BEGIN
  WITH
      chunks (exponent, chunk) AS (
        SELECT
          exponent,
          floor((num / (max_bigint ^ exponent) :: NUMERIC(300, 20)) % max_bigint) :: BIGINT
        FROM generate_series(0, 5) exponent
    )
  SELECT bit_or(chunk :: BIT(300) :: BIT VARYING << (63 * (exponent))) :: BIT VARYING
  FROM chunks INTO result;
  RETURN result;
END;
$$ LANGUAGE plpgsql;

【讨论】:

【参考方案3】:

用 python 试试:

sql = sorted(([hamming("HashString", %s,image.id) for image in Image.objects.all() if hamming("HashString",%s) < %s])

【讨论】:

那不是很贵吗? 肯定是 O(n) 复杂度。但是,为什么要为每个请求重新计算汉明距离呢?在这种情况下,我会使用数据库存储,或者只是一个已经计算出汉明距离的表。添加新图像可以触发计算汉明距离并将结果存储到数据库中。 从数据库中提取所有数据只是为了计算汉明距离可能会很昂贵。汉明距离是两个值之间的距离,因此预先计算和存储所有可能的距离是不现实的。此外,您的语法似乎以一种奇怪的方式混合了 Python 和 SQL;我不是 Python 人,但对我来说它看起来不对。 这是 Django 吗?

以上是关于如何在 PostgreSQL 中将长 NUMERIC 整数转换为位字符串?的主要内容,如果未能解决你的问题,请参考以下文章

如何在windows的“omnidb”中将csv文件数据导入postgresql

如何在 PostgreSQL 11.1 中将现有列更改为身份

如何在 PostgreSQL 8.0.2 中将列表转换为数组

如何在PostgreSQL中将状态日志数据聚合成具有相同状态的时间间隔?

如何在python flask app中将数据从postgresql渲染到csv?

如何在PostgreSQL中将空转换为null?