【问题标题】:Convert alphanumeric string to numeric - Oracle将字母数字字符串转换为数字 - Oracle
【发布时间】:2021-01-06 20:38:01
【问题描述】:

我有这个字母数字字符串

000193CA020008AA190038AA

我希望它是一个唯一的数字,例如

123456791011121314151617.

有没有办法在 PLSQL 上做到这一点? 并非所有字母数字的长度都相同,不只包含十六进制数字。

我试过了

create or replace FUNCTION FN_BOM_STEEL_CHAR_TO_NUMBER(
  value IN VARCHAR2
) RETURN NUMBER DETERMINISTIC
IS
  c        CHAR(1);
  up_value VARCHAR2(5000) := LTRIM(UPPER(value),'0');
  total    NUMBER  := 0;
  len      PLS_INTEGER    := COALESCE(LENGTH(up_value),0);
BEGIN
  FOR i IN 1 .. len LOOP
    c := SUBSTR(up_value,i,1);
    total := total * 36
                   + CASE
                     WHEN '0' <= c AND c <= '9'
                     THEN TO_NUMBER(c)
                     ELSE ASCII(c) - 55
                     END;
  END LOOP;
  RETURN total;
END FN_BOM_STEEL_CHAR_TO_NUMBER;

而且效果非常好,但我刚刚发现有一些具有不同字母数字字符串的重复项,例如

  1. 010000AA000001AA020019AA020047AA020138AA020139AA060018AA190016AA

  2. 010000AA000001AA020019AA020047AA020138AA020139AA060019AA190016AA

您可以看到 18 和 19 对它们产生了影响,但是当我选择函数时,两者的 ID 相同。这样的案例还有很多。

这是我为这些示例尝试的选择

SELECT FN_BOM_STEEL_CHAR_TO_NUMBER('010000AA000001AA020019AA020047AA020138AA020139AA060018AA190016AA
') AS ID
FROM   DUAL;

这是两者的 ID: 30956732997597051152261028430459303606100000000000000000000000000000000000000000000000000000000000

我需要它们与众不同。 提前致谢!

【问题讨论】:

  • 有可能吗?可能,如果您告诉我们将“源”转换为“目标”的算法。
  • 你能给我们展示一些带有输出的示例数据,以及上述转换背后的逻辑吗?
  • 唯一性取决于许多条件。它应该在某个时间范围内还是在数据库的整个生命周期内都是唯一的?是否应该将相同的源字符串(或其中的一部分)转换为相同的结果字符串(称为函数)?源和目标之间是否需要有任何关系或结果编号可以是任意编号?

标签: sql oracle alphanumeric


【解决方案1】:

如果你只有字符 0123456789ABCDEF 那么你可以使用:

SELECT TO_NUMBER(
         '000193CA020008AA190038AA',
         'XXXXXXXXXXXXXXXXXXXXXXXX'
       ) AS from_hex_value
FROM   DUAL;

哪些输出:

| FROM_HEX_VALUE | | ------------------------------------: | | 1906840079024071083309226 |

如果您有字符 0-9A-Z 并希望将其视为 36 进制字符串,那么您可以创建函数:

CREATE FUNCTION base36_to_number(
  value  IN VARCHAR2,
  -- The value to parse from base 36 to base 10.
  width  IN INT DEFAULT 0,
  -- The width of the substring to consider; 0 is the entire string.
  offset IN INT DEFAULT 0
  -- The number of widths to offset from the least-significant digit.
) RETURN NUMBER DETERMINISTIC
IS
  c         CHAR(1);
  total     NUMBER := 0;
  len       PLS_INTEGER    := COALESCE(LENGTH(value),0);
  start_pos PLS_INTEGER;
  end_pos   PLS_INTEGER;
BEGIN
  IF width < 0 THEN
    RAISE_APPLICATION_ERROR( -20000, 'Invalid width.' );
  ELSIF offset < 0 THEN
    RAISE_APPLICATION_ERROR( -20000, 'Invalid offset.' );
  ELSIF width = 0 THEN
    start_pos := 1;
    end_pos   := len;
  ELSIF width * offset > len THEN
    RETURN 0;
  ELSIF width * (offset + 1) > len THEN
    start_pos := 1;
    end_pos   := len - width  * offset;
  ELSE
    start_pos := len - width * ( offset + 1 ) + 1;
    end_pos   := start_pos + width - 1;
  END IF;

  FOR i IN start_pos .. end_pos LOOP
    c := SUBSTR(value,i,1);
    total := total * 36
                   + CASE
                     WHEN '0' <= c AND c <= '9'
                     THEN TO_NUMBER(c)
                     WHEN 'A' <= c AND c <= 'Z'
                     THEN ASCII(c) - 55
                     WHEN 'a' <= c AND c <= 'z'
                     THEN ASCII(c) - 87
                     ELSE NULL
                     END;
  END LOOP;
  
  total := total * POWER( 36, width * offset );
  RETURN total;
END;
/

然后:

SELECT BASE36_TO_NUMBER('000193CA020008AA190038AA') AS from_base36_value
FROM   DUAL;

输出:

| FROM_BASE36_VALUE | | -------------------------------------------: | | 16743829188442650222733747166386 |

NUMBER 的最大大小为 38 位十进制数字,并且不能容纳超过 24 个字符的 base-36 数字。如果您的值较大,则需要将其转换为多个数字:

即:

CREATE TABLE table_name ( value ) AS
SELECT '010000AA000001AA020019AA020047AA020138AA020139AA060018AA190016AA' FROM DUAL UNION ALL
SELECT '010000AA000001AA020019AA020047AA020138AA020139AA060019AA190016AA' FROM DUAL UNION ALL
SELECT 'ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZzzzzzzzzzzzzzzzzzzzzzzzz' FROM DUAL;

然后:

SELECT BASE36_TO_NUMBER( value, 24, 0 ) AS low_value,
       BASE36_TO_NUMBER( value, 24, 1 ) AS mid_value,
       BASE36_TO_NUMBER( value, 24, 2 ) AS high_value
FROM   table_name;

输出:

低值 |中值 |高价值 --------------------------------------: | -------------------------------------------------- ------------------------------------: | -------------------------------------------------- --------------------------------------------------: 34663123071664682325935025837090322 | 777948567801018784511679412336101916888000000000000000000000000000000000 | 30956732997597051152261020650973625595700000000000000000000000000000000000000000000000000000000000 34663123071664682329591184277153298 | 777948567801018784511679412336101916888000000000000000000000000000000000 | 30956732997597051152261020650973625595700000000000000000000000000000000000000000000000000000000000 22452257707354557240087211123792674815 | 504103876157462118901767181449118688664000000000000000000000000000000000000 | 40119919145476304800650528835985676552280000000000000000000000000000000000000000000000000000000000000

db小提琴here

【讨论】:

  • 如果不是所有的字母数字的长度都一样,因为尝试过但显然没有成功 SELECT TO_NUMBER(NUM_SEPARADOR, 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' ) AS ID_SEPARADOR FROM tmp_bom_steel7;我也试过 SELECT TO_NUMBER(NUM_SEPARADOR) AS ID_SEPARADOR FROM tmp_bom_steel7;但一直告诉我错误的无效号码。我是初学者,对于简单的问题抱歉
  • @RenataEsquivel 如果您的列是 VARCHAR2(50),则使用 50 个 X 字符,以便格式模型尽可能长的字符串值。
  • 另外,如果您的列不包含仅十六进制数字(这仅是数字和 A-F 字母),您可以使用 dbms_crypto 并首先生成 sha1。 Sha1 的长度为 40 个字符。
  • @KrzysztofKaszkowiak 理论上可能存在哈希冲突(并且它们已经生成),因此 SHA1 可能无法满足问题对唯一性的要求(实际上,您不太可能遇到哈希冲突,因此可能就足够了)。
  • 不包含十六进制数字,这就是错误的原因。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-02-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多