【问题标题】:Generate 50 random-duration time intervals in MySQL or PHP, between two dates在 MySQL 或 PHP 中,在两个日期之间生成 50 个随机持续时间间隔
【发布时间】:2014-10-23 16:21:57
【问题描述】:

我正在尝试将 50 个日期时间范围的随机列表插入 MySQL 数据库。这些日期范围必须介于特定的开始日期和结束日期之间,并且它们必须相邻并按时间排序。

这是我尝试在 2014-09-01 00:00:00 - 2014-10-12 23:59:59 之间生成的数据库表示例>:

start_time                     end_time
--------------------------------------------------------
2014-09-01 00:00:00            2014-09-01 02:45:12  
2014-09-01 02:45:13            2014-09-01 04:12:33
2014-09-01 04:12:34            2014-09-02 12:12:20
....
2014-10-12 23:30:13            2014-10-12 23:59:59

我不确定这是否可以仅在 MySQL 中完成,或者我是否需要 PHP 脚本。生成日期之间的随机日期列表很好,它将随机性平均分配到 50 个条目之间,这让我感到困惑。

这可以在 MySQL 中完成吗?如果可以,首选的方法是什么?

编辑: 为了更好地解释我的问题,我有一个日期范围,我需要生成一个包含 50 个开始和结束日期的列表。我应该提到,就像我的示例一样,开始日期必须紧邻上一个结束日期之后的下一秒。

【问题讨论】:

  • 因此,您确实需要一组 50 个单调递增、不重叠的两个给定日期时间之间的时间间隔,以便这些间隔触及前面的边界。图形化:[[...][.][......][..][.......][................][.]<etc>]'。如果是这样,我真的没有从您的原始问题中得到这一点,尤其是您的标题。我在这里吗?
  • @bishop 这正是我正在寻找的,但更雄辩的说法。
  • 好的,我已经发布了一个 MySQL 解决方案。

标签: php mysql date datetime date-range


【解决方案1】:

是的,您可以在 MySQL 中执行此操作。 N个区间的通用算法是:

  1. 在开始时间 + 1 秒和结束时间 - 2 秒之间生成 N-1 个不同的随机时间戳。
  2. 对它们进行排序,从大到小。这些是您的间隔开始点。
  3. 从每个结束点减去一秒,得到前一个区间的开始点。

示例
Install these generator views,然后:

CREATE TABLE times (id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,begin_time DOUBLE NOT NULL,end_time DOUBLE NOT NULL DEFAULT 0);
INSERT INTO times (begin_time) SELECT @low bp UNION SELECT s.bp FROM (SELECT t.bp FROM (SELECT @low + FLOOR(RAND() * (@high-@low)) bp FROM generator_256 JOIN (SELECT @low := UNIX_TIMESTAMP('2014-09-01 00:00:00'), @high := UNIX_TIMESTAMP('2014-10-12 23:59:59')) init LIMIT 49) t ORDER by bp) s;
UPDATE times JOIN (SELECT curr.id, curr.begin_time, (SELECT next.begin_time-1 FROM times next WHERE next.id=curr.id+1) end_time FROM times curr) g ON g.id = times.id SET times.end_time = COALESCE(g.end_time, UNIX_TIMESTAMP('2014-10-12 23:59:59'));
SELECT FROM_UNIXTIME(begin_time), FROM_UNIXTIME(end_time) FROM times;
+---------------------------+-------------------------+
| FROM_UNIXTIME(begin_time) | FROM_UNIXTIME(end_time) |
+---------------------------+-------------------------+
| 2014-09-01 00:00:00       | 2014-09-02 13:32:45     |
| 2014-09-02 13:32:46       | 2014-09-03 07:57:24     |
| 2014-09-03 07:57:25       | 2014-09-04 17:34:01     |
| 2014-09-04 17:34:02       | 2014-09-04 19:46:25     |
| 2014-09-04 19:46:26       | 2014-09-05 17:44:48     |
                           ...
| 2014-10-10 18:39:47       | 2014-10-11 05:11:13     |
| 2014-10-11 05:11:14       | 2014-10-11 11:27:29     |
| 2014-10-11 11:27:30       | 2014-10-12 13:03:02     |
| 2014-10-12 13:03:03       | 2014-10-12 17:55:54     |
| 2014-10-12 17:55:55       | 2014-10-12 19:11:11     |
| 2014-10-12 19:11:12       | 2014-10-12 23:59:59     |
+---------------------------+-------------------------+
50 rows in set (0.00 sec)

说明
让我们逐步分解这些。要在 MySQL 中生成行,您必须使用 generator view。每次您请求时,生成器视图只会为您提供 N 行。例如,要获得 49 行(N-1):

SELECT * FROM generator_256 LIMIT 49;

要在 MySQL 中的两个其他数字之间生成单个随机数,请使用随机数公式 low + (RAND() * (high-low))。这个公式与生成器视图相结合,为我们提供了第 1 步所需的 49 个开始点:

SELECT (@low + FLOOR(RAND() * (@high-@low))) AS bp FROM generator_256 LIMIT 49;

(我在这里使用会话变量来保持 SQL 的简单性。它们很快就会成为查询的一部分。如果要调试,请记住 bp 是时间戳,因此 FROM_UNIXTIME(bp) 将显示你是一种人性化的格式。)

现在,要对列表进行排序,请使用子查询:如果您对生成的查询进行排序,您将获得聚集在开始时间附近的随机值。因此,主要完成第 2 步:

SELECT t.bp FROM (SELECT @low + FLOOR(RAND() * (@high-@low)) bp FROM generator_256 LIMIT 49) t ORDER by t.bp;

现在,它开始变得棘手。对于任何给定的行,我们希望用比下一行的开始时间少一秒的时间来填充结束时间。虽然有几种方法可以解决它,但我认为最容易理解的是使用目标表(或它的副本)来存储我们生成的开始点的方法。 (注意我在这里初始化了@low 和@high 的值,并在列表中包含了起点):

CREATE TABLE times (id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,begin_time DOUBLE NOT NULL,end_time DOUBLE NOT NULL DEFAULT 0);
INSERT INTO times (begin_time) SELECT @low bp UNION SELECT s.bp FROM (SELECT t.bp FROM (SELECT @low + FLOOR(RAND() * (@high-@low)) bp FROM generator_256 JOIN (SELECT @low := UNIX_TIMESTAMP('2014-09-01 00:00:00'), @high := UNIX_TIMESTAMP('2014-10-12 23:59:59')) init LIMIT 49) t ORDER by bp) s;

最后,我们可以使用联合更新来添加结束时间。

UPDATE times JOIN (SELECT curr.id, curr.begin_time, (SELECT next.begin_time-1 FROM times next WHERE next.id=curr.id+1) end_time FROM times curr) g ON g.id = times.id SET times.end_time = COALESCE(g.end_time, UNIX_TIMESTAMP('2014-10-12 23:59:59'));

在我的示例中,我省略了两件事:

  1. 我忽略了在开始后一秒和结束前两秒开始的需要。因此,您的随机值之一可能等于开始点或结束点,这将违反您声明的需求。您可以使用 MySQL INTERVAL 运算符添加这些约束。
  2. 在一个真正随机的系统中,一长串相同的值与一个混乱的值一样可能。例如,在二进制中,000000000000000 的可能性与 111111111111111 的可能性与 0101010111101011 的可能性一样。这意味着我们可以从查询中得到 50 个相同的日期。您可以通过生成一大堆随机时间(可能是 1000 秒)并从中剔除来解决此问题。

【讨论】:

  • 哇,谢谢主教。我已经重新阅读了您发送的生成器视图链接,这很有意义。设法创建了我需要的 50 行。干得好!
【解决方案2】:

您可以generate random dates using php 并添加一些功能来检查其他日期范围:

您必须加载内部日期范围:(我使用数组,但您将从数据库加载)

$ranges = array(
    0 => array('start' => '2014-09-01 00:00:00', 'end' => '2014-09-01 02:45:12'),
    1 => array('start' => '2014-09-01 02:45:13', 'end' => '2014-09-01 04:12:33'),
    2 => array('start' => '2014-09-01 04:12:34', 'end' => '2014-09-02 12:12:20'),
    3 => array('start' => '2014-10-12 23:30:13', 'end' => '2014-10-12 23:59:59')
);

检查日期的函数在 $ranges 数组中:

function date_in_range($date_ranges,$rand_epoch){
    foreach($date_ranges as $date){
        $min_epoch = strtotime($date['start']);
        $max_epoch = strtotime($date['end']);
        if($rand_epoch >= $min_epoch && $rand_epoch <= $max_epoch){
            return true;
        }
    }
    return false;
}

修改函数生成随机日期:

function rand_date($min_date, $max_date, $internal_ranges) {
    /* Gets 2 dates as string, earlier and later date.
       Returns date in between them.
    */

    $min_epoch = strtotime($min_date);
    $max_epoch = strtotime($max_date);

    $rand_epoch = rand($min_epoch, $max_epoch);

   // If date is not in internal range, get another random date again:
    while(!date_in_range($internal_ranges,$rand_epoch)){
        $rand_epoch = rand($min_epoch, $max_epoch);
    }

    return date('Y-m-d H:i:s', $rand_epoch);
}

// Testing
print rand_date('2014-09-01 00:00:00','2014-10-12 23:59:59',$ranges);

【讨论】:

  • 谢谢伊万。棘手的一点是在主要日期范围之间生成随机日期范围
  • 让我改一下答案
  • 如果您需要 PHP 解决方案,我强烈建议您使用 Fazinotto's Faker, which has a datetimeBetween() method
  • 我刚刚修改了我的答案,也许不是最好的性能......但是是一个解决方案。
猜你喜欢
  • 2015-10-01
  • 2010-12-30
  • 2010-10-07
  • 1970-01-01
  • 2016-05-16
  • 1970-01-01
  • 2018-11-23
  • 2023-03-25
相关资源
最近更新 更多