从oracle plsql中的ulid提取时间戳

从oracle plsql中的ulid提取时间戳,oracle,plsql,Oracle,Plsql,有没有办法从Oracle PL/SQL中的数据中提取时间戳?我们最近不得不与一家供应商打交道,该供应商为唯一的变更序列标识符提供ulid变更数据,并且需要提取时间戳。正如creator的github中提到的,解决方案存在于其他语言中,但我们希望能够在Oracle中运行转换。这花了太长时间来实现,我想节省其他人自己尝试的时间 DECLARE p_Ulid VARCHAR2(200) := '01EG664DVCY5NTH7WFN57PA7TM'; --Example ULID FU

有没有办法从Oracle PL/SQL中的数据中提取时间戳?我们最近不得不与一家供应商打交道,该供应商为唯一的变更序列标识符提供ulid变更数据,并且需要提取时间戳。正如creator的github中提到的,解决方案存在于其他语言中,但我们希望能够在Oracle中运行转换。

这花了太长时间来实现,我想节省其他人自己尝试的时间

DECLARE
    p_Ulid VARCHAR2(200) := '01EG664DVCY5NTH7WFN57PA7TM'; --Example ULID
    FUNCTION Get_Ulid_Ts(p_In VARCHAR2) RETURN TIMESTAMP
        WITH TIME ZONE IS
        Dec_Value   NUMBER := 0;
        t_Time_Part VARCHAR2(10) := Substr(p_In, 0, 10); --First 10 characters are the timestamp
        Ret         TIMESTAMP WITH TIME ZONE := To_Timestamp_Tz('19700101 +00:00', 'yyyymmdd TZH:TZM'); --Unix timestamp sentinal
        c_Base      NUMBER := 32;
        c_Base32    VARCHAR2(32) := '0123456789ABCDEFGHJKMNPQRSTVWXYZ'; --Crockford's base32
        TYPE B32_Map_Typ IS TABLE OF NUMBER INDEX BY VARCHAR2(1);
        B32map B32_Map_Typ;
    BEGIN
        --initialize base32 map
        FOR i IN 0 .. Length(c_Base32) - 1
        LOOP
            B32map(Substr(c_Base32, i + 1, 1)) := i;
        END LOOP;
        --convert base 32 to base 10
        FOR i IN 1 .. Length(t_Time_Part)
        LOOP
            Dec_Value := Dec_Value +
                         Power(c_Base, i - 1) *
                         B32map(Substr(t_Time_Part, -i, 1));
        END LOOP;
        --add to unix timestamp sentinal
        Ret := Ret +numtodsinterval(((Dec_Value/ 1000) ),'SECOND') ;
        RETURN Ret;
    END;
BEGIN
    --ISO8601 timestamp formats
    EXECUTE IMMEDIATE q'!alter session set nls_timestamp_format = 'YYYY-MM-DD"T"HH24:MI:SS.ff3"Z"' !';
    EXECUTE IMMEDIATE q'!alter session set nls_timestamp_tz_format = 'YYYY-MM-DD"T"HH24:MI:SS.ff3 TZR' !';
    --test function
    Dbms_Output.Put_Line(Get_Ulid_Ts(p_Ulid));
    Dbms_Output.Put_Line(Get_Ulid_Ts(p_Ulid) At TIME ZONE
                         'America/New_York');
END;
/
如果有人有更有效的方法,请评论。(我也没有足够的声誉在StackOverflow中创建“ulid”标记)

以下是一个稍微提高性能的软件包变体:

CREATE OR REPLACE PACKAGE LUTOOLS.Ulid IS

    Nls_Timestamp_Format    VARCHAR2(64) := 'YYYY-MM-DD"T"HH24:MI:SS.ff3"Z"';
    Nls_Timestamp_Tz_Format VARCHAR2(64) := 'YYYY-MM-DD"T"HH24:MI:SS.ff3"Z"';

    TYPE B32_Map_Typ IS TABLE OF NUMBER INDEX BY VARCHAR2(1);
    B32map B32_Map_Typ;

    c_Base   NUMBER := 32;
    c_Base32 VARCHAR2(32) := '0123456789ABCDEFGHJKMNPQRSTVWXYZ'; --Crockford's base32

    FUNCTION Get_Ulid_Ts(p_In VARCHAR2) RETURN TIMESTAMP
        WITH TIME ZONE;

END Ulid;
/

CREATE OR REPLACE PACKAGE BODY LUTOOLS.Ulid IS

    FUNCTION Get_Ulid_Ts(p_In VARCHAR2) RETURN TIMESTAMP
        WITH TIME ZONE IS
        Dec_Value   NUMBER := 0;
        t_Time_Part VARCHAR2(10) := Substr(p_In, 0, 10); --First 10 characters are the timestamp
        Ret         TIMESTAMP WITH TIME ZONE := To_Timestamp_Tz('19700101 +00:00', 'yyyymmdd TZH:TZM');
    
    BEGIN
        --convert base 32 to base 10
        FOR i IN 1 .. Length(t_Time_Part)
        LOOP
            Dec_Value := Dec_Value +
                         Power(c_Base, i - 1) *
                         B32map(Substr(t_Time_Part, -i, 1));
        END LOOP;
        --add to unix timestamp sentinal
        Ret := Ret + Numtodsinterval(((Dec_Value / 1000)), 'SECOND');
        RETURN Ret;
    END;

BEGIN
    --initialize base32 map
    FOR i IN 0 .. Length(c_Base32) - 1
    LOOP
        B32map(Substr(c_Base32, i + 1, 1)) := i;
    END LOOP;

END Ulid;
/
已在添加程序包