Oracle 函数 return VALUES 之间的随机日期和时间戳

Oracle function to return random date and timestamp between VALUES

我在创建一个 returns 随机日期(下一个是 returns 随机时间戳的函数)的函数时遇到了一些困难,当在开始和结束日期范围内传递时。

我希望该值还包括与日期关联的随机时间。对于时间戳函数,一个随机小数值。

以下是我目前拥有的 DATE 函数,但我似乎无法编译它。任何帮助将不胜感激。感谢所有回答的人。


CREATE OR REPLACE FUNCTION random_date(
  p_from IN DATE,
  p_to   IN DATE
)
  RETURN date DETERMINISTIC
IS
  v_start DATE := TRUNC(LEAST(p_from, p_to));
  v_end   DATE := TRUNC(GREATEST(p_from, p_to));
RETURN  p_from
       + DBMS_RANDOM.VALUE(0, p_to -  p_from + 1);
END random_date;
/

它:

  • 缺少 BEGIN 关键字;
  • 不希望它成为 DETERMINISTIC(因为它不会成为):
CREATE FUNCTION random_date(
  p_from IN DATE,
  p_to   IN DATE
) RETURN DATE
IS
  c_from CONSTANT DATE := LEAST(p_from, p_to);
  c_to   CONSTANT DATE := GREATEST(p_from, p_to);
BEGIN
  RETURN c_from + TRUNC(DBMS_RANDOM.VALUE() * ((c_to - c_from) * 86400 + 1))
                  / 86400;
END random_date;
/

并且,对于 TIMESTAMP 秒:

CREATE FUNCTION random_timestamp(
  p_from IN TIMESTAMP,
  p_to   IN TIMESTAMP
) RETURN TIMESTAMP
IS
  c_from CONSTANT TIMESTAMP(9) := LEAST(p_from, p_to);
  c_to   CONSTANT TIMESTAMP(9) := GREATEST(p_from, p_to);
BEGIN
  RETURN c_from + DBMS_RANDOM.VALUE()
                  * (c_to + INTERVAL '0.000000001' SECOND - c_from);
END random_timestamp;
/

注意:DBMS_RANDOM.VALUE() 会 return 一个大于或等于 0 且小于 1 的值,所以如果你想在range 那么您需要以尽可能小的增量增加范围;因此为日期增加 1 秒,为时间戳增加 1e-9 秒。


上面的 RANDOM_TIMESTAMP 函数有效但不是真正随机的,因为由于舍入问题,范围任一端的瞬间发生的概率是两个极端之间所有其他瞬间的一半。对于大多数情况,这不是一个特定的问题,但如果它是(特别是对于几微秒的小范围),那么您需要在应用随机性之前将间隔的持续时间转换为整数:

CREATE OR REPLACE FUNCTION random_timestamp(
  p_from IN TIMESTAMP,
  p_to   IN TIMESTAMP
) RETURN TIMESTAMP
IS
  c_from TIMESTAMP(9) := LEAST(p_from, p_to);
  c_to   TIMESTAMP(9) := GREATEST(p_from, p_to);
  c_diff INTERVAL DAY(9) TO SECOND(9) := c_to - c_from;
  c_sdiff NUMBER(38,0) := EXTRACT(DAY FROM c_diff) * 86400e6
                       + EXTRACT(HOUR FROM c_diff) * 3600e6
                       + EXTRACT(MINUTE FROM c_diff) * 60e6
                       + EXTRACT(SECOND FROM c_diff) *  1e6
                       + 1;
BEGIN
  RETURN c_from + NUMTODSINTERVAL(
                    TRUNC(DBMS_RANDOM.VALUE() * c_sdiff) / 86400e6,
                    'DAY'
                  );
END random_timestamp;
/

db<>fiddle here

我无法为该范围的最后一天生成日期或时间戳,无论它在月份中的哪个位置。例如,random_date (DATE '2022-04-27', DATE '2022-05-03') 在该月的最后一天(4 月 30 日)而不是 5 月 3 日生成日期。

如果p_from是2022年4月1日午夜,p_to是2022年4月30日午夜,那么我需要生成一个0到30之间的随机数,但是(p_to - p_from) = 29,不是 30为 0,而不是 1。)如果我想要一个小于(从不等于)5 月 1 日午夜的随机日期,则将 5 月 1 日作为第二个参数传递给 random_date。为了自动执行此操作,我对函数进行了以下更改。


ALTER SESSION SET NLS_TIMESTAMP_FORMAT = 'DD-MON-YYYY  HH24:MI:SS.FF';

ALTER SESSION SET NLS_DATE_FORMAT = 'DD-MON-YYYY HH24:MI:SS';


 CREATE OR REPLACE FUNCTION random_date(
      p_from IN DATE,
      p_to   IN DATE
    ) RETURN DATE
   IS
   BEGIN
      RETURN p_from + DBMS_RANDOM.VALUE() * (p_to - p_from + 1);
   END random_date;
   /

 CREATE OR REPLACE FUNCTION random_timestamp(
      p_from IN TIMESTAMP,
      p_to   IN TIMESTAMP
    ) RETURN TIMESTAMP
   IS
 BEGIN
      RETURN p_from + DBMS_RANDOM.VALUE() * (p_to - p_from + interval '1' day);
   END random_timestamp;
   /


CREATE TABLE t1 (     
 seq_num NUMBER GENERATED BY DEFAULT AS IDENTITY (START WITH 1) NOT NULL,
   dt   DATE,
   ts TIMESTAMP 
);

 INSERT INTO t1 (dt, ts )
        SELECT
            random_date(DATE '2022-04-01', DATE '2022-04-30'),
            random_timestamp(DATE '2022-04-01', DATE '2022-04-30')
        FROM
            dual CONNECT BY level <= 10000;


select trunc(dt), count(*)
from t1
group by trunc(dt)
Order by trunc(dt);

select trunc(ts), count(*)
from t1
group by trunc(ts)
Order by trunc(ts);