【问题标题】:Hashing a String to a Numeric Value in PostgreSQL在 PostgreSQL 中将字符串散列为数值
【发布时间】:2012-04-06 06:04:11
【问题描述】:

我需要将存储在我的数据库中的字符串转换为数值。结果可以是整数(首选)或 Bigint。这种转换将在数据库端的 PL/pgSQL 函数中完成。

有人可以指点我一些算法或任何可用于实现此目的的 API 吗?

我已经在谷歌上搜索了好几个小时了,到目前为止找不到任何有用的东西:(

【问题讨论】:

  • 您的意思是将“42”转换为 42(char 到 int)吗?
  • 没有。我的意思是带有字符和符号的字符串。我正在寻找一种良好的散列算法,它具有非常低的冲突并且可以生成 Int 或 Bigint 数据类型的散列。

标签: postgresql plpgsql postgresql-8.4


【解决方案1】:

PostgreSQL 有许多列类型的散列函数。如果你想要整数哈希值,你可以使用hashtext,如果你喜欢bigint哈希值,你可以使用hashtextextended

注意hashXXXextended 函数需要一个额外的种子参数,0 表示不使用种子。

作者 Robert Haas 在提交消息中说:

以防万一有人想要一个兼容的 64 位哈希值 使用现有的 32 位哈希值,使低 32 位 当种子为 0 时,64 位哈希值匹配 32 位哈希值。

示例

postgres=# SELECT hashtextextended('test string of type text', 0);
   hashtextextended
----------------------
 -6578719834206879717
(1 row)

postgres=# SELECT hashtext('test string of type text');
  hashtext
-------------
 -1790427109
(1 row)

其他数据类型呢?

您可以通过检查\df hash* 的输出来检查后端所有可用的哈希函数。您可以在下面看到 PG 14.0 中可用的功能。

hanefi=# \df hash*
                                     List of functions
   Schema   |           Name           | Result data type |   Argument data types    | Type
------------+--------------------------+------------------+--------------------------+------
 pg_catalog | hash_aclitem             | integer          | aclitem                  | func
 pg_catalog | hash_aclitem_extended    | bigint           | aclitem, bigint          | func
 pg_catalog | hash_array               | integer          | anyarray                 | func
 pg_catalog | hash_array_extended      | bigint           | anyarray, bigint         | func
 pg_catalog | hash_multirange          | integer          | anymultirange            | func
 pg_catalog | hash_multirange_extended | bigint           | anymultirange, bigint    | func
 pg_catalog | hash_numeric             | integer          | numeric                  | func
 pg_catalog | hash_numeric_extended    | bigint           | numeric, bigint          | func
 pg_catalog | hash_range               | integer          | anyrange                 | func
 pg_catalog | hash_range_extended      | bigint           | anyrange, bigint         | func
 pg_catalog | hash_record              | integer          | record                   | func
 pg_catalog | hash_record_extended     | bigint           | record, bigint           | func
 pg_catalog | hashbpchar               | integer          | character                | func
 pg_catalog | hashbpcharextended       | bigint           | character, bigint        | func
 pg_catalog | hashchar                 | integer          | "char"                   | func
 pg_catalog | hashcharextended         | bigint           | "char", bigint           | func
 pg_catalog | hashenum                 | integer          | anyenum                  | func
 pg_catalog | hashenumextended         | bigint           | anyenum, bigint          | func
 pg_catalog | hashfloat4               | integer          | real                     | func
 pg_catalog | hashfloat4extended       | bigint           | real, bigint             | func
 pg_catalog | hashfloat8               | integer          | double precision         | func
 pg_catalog | hashfloat8extended       | bigint           | double precision, bigint | func
 pg_catalog | hashhandler              | index_am_handler | internal                 | func
 pg_catalog | hashinet                 | integer          | inet                     | func
 pg_catalog | hashinetextended         | bigint           | inet, bigint             | func
 pg_catalog | hashint2                 | integer          | smallint                 | func
 pg_catalog | hashint2extended         | bigint           | smallint, bigint         | func
 pg_catalog | hashint4                 | integer          | integer                  | func
 pg_catalog | hashint4extended         | bigint           | integer, bigint          | func
 pg_catalog | hashint8                 | integer          | bigint                   | func
 pg_catalog | hashint8extended         | bigint           | bigint, bigint           | func
 pg_catalog | hashmacaddr              | integer          | macaddr                  | func
 pg_catalog | hashmacaddr8             | integer          | macaddr8                 | func
 pg_catalog | hashmacaddr8extended     | bigint           | macaddr8, bigint         | func
 pg_catalog | hashmacaddrextended      | bigint           | macaddr, bigint          | func
 pg_catalog | hashname                 | integer          | name                     | func
 pg_catalog | hashnameextended         | bigint           | name, bigint             | func
 pg_catalog | hashoid                  | integer          | oid                      | func
 pg_catalog | hashoidextended          | bigint           | oid, bigint              | func
 pg_catalog | hashoidvector            | integer          | oidvector                | func
 pg_catalog | hashoidvectorextended    | bigint           | oidvector, bigint        | func
 pg_catalog | hashtext                 | integer          | text                     | func
 pg_catalog | hashtextextended         | bigint           | text, bigint             | func
 pg_catalog | hashtid                  | integer          | tid                      | func
 pg_catalog | hashtidextended          | bigint           | tid, bigint              | func
 pg_catalog | hashvarlena              | integer          | internal                 | func
 pg_catalog | hashvarlenaextended      | bigint           | internal, bigint         | func
(47 rows)

注意事项

如果您希望在不同系统之间拥有一致的哈希值,请确保您具有相同的排序规则。

内置的可整理数据类型为textvarcharchar。如果您有不同的排序规则选项,您可以看到不同的哈希值。例如,与早期版本相比,glibc 2.28(Debian 10、RHEL 8)中字符串“a-a”和“a+a”的排序顺序发生了翻转。

如果您希望在同一台机器上使用哈希,您不必担心,只要您不更新 glibc 或使用不同的排序规则即可。

查看更多详情:https://www.citusdata.com/blog/2020/12/12/dont-let-collation-versions-corrupt-your-postgresql-indexes/

【讨论】:

    【解决方案2】:

    这是 Java 的 String.hashCode() 的实现:

    CREATE OR REPLACE FUNCTION hashCode(_string text) RETURNS INTEGER AS $$
    DECLARE
      val_ CHAR[];
      h_ INTEGER := 0;
      ascii_ INTEGER;
      c_ char;
    BEGIN
      val_ = regexp_split_to_array(_string, '');
    
      FOR i in 1 .. array_length(val_, 1)
      LOOP
        c_ := (val_)[i];
        ascii_ := ascii(c_);
        h_ = 31 * h_ + ascii_;
        raise info '%: % = %', i, c_, h_;
      END LOOP;
    RETURN h_;
    END;
    $$ LANGUAGE plpgsql;
    

    【讨论】:

      【解决方案3】:

      只保留 MD5 哈希的前 32 位或 64 位。当然,它使 md5 的主要属性无效(=碰撞概率是无穷小),但您仍然会得到广泛分散的值,这可能足以解决您的问题。

      从其他答案派生的 SQL 函数:

      对于大整数:

      create function h_bigint(text) returns bigint as $$
       select ('x'||substr(md5($1),1,16))::bit(64)::bigint;
      $$ language sql;
      

      对于整数:

      create function h_int(text) returns int as $$
       select ('x'||substr(md5($1),1,8))::bit(32)::int;
      $$ language sql;
      

      【讨论】:

      • ('x'|| 条件句有什么作用?
      • @MichaelYoo:|| 运算符用于字符串连接(只有 MySQL 和受 C 启发的编程语言将 || 解释为逻辑 OR,它不是 SQL 中的标准解释)。跨度>
      • 我明白了。我认为这只是将十六进制表示“取消十六进制”转换为二进制,然后将其转换为位,对吗? (因为 Postgres 没有 unhex?)我看到的示例总是将 'x' 放在 x'1b1c2f' 之类的引号之外,在这种情况下如何预先添加字符串文字 'x'?即 CONCAT() 也可以吗?
      【解决方案4】:

      必须是整数吗? pg_crypto 模块提供了许多标准散列函数(md5、sha1 等)。他们都返回 bytea。我想您可以丢弃一些位并将字节茶转换为整数。

      bigint 太小,无法存储加密哈希。 Pg 支持的最大的非字节二进制类型是 uuid。您可以像这样将摘要转换为 uuid:

      select ('{'||encode( substring(digest('foobar','sha256') from 1 for 16), 'hex')||'}')::uuid;
                       uuid                 
      --------------------------------------
       c3ab8ff1-3720-e8ad-9047-dd39466b3c89
      

      【讨论】:

        【解决方案5】:

        您可以毫无问题地创建 md5 哈希值:

        select md5('hello, world');
        

        这将返回一个带有十六进制数字的字符串。

        不幸的是,没有将十六进制转换为整数的内置函数,但正如您在 PL/pgSQL 中所做的那样,这可能会有所帮助:

        https://stackoverflow.com/a/8316731/330315

        【讨论】:

        • md5 的输出将有子串以避免 Integer Out of range 错误。
        猜你喜欢
        • 2013-04-11
        • 2013-01-24
        • 1970-01-01
        • 2010-12-21
        • 2013-04-21
        • 2022-11-18
        • 2021-09-05
        • 1970-01-01
        • 2022-01-25
        相关资源
        最近更新 更多