Situation:

想象一下这样一个脚本,它在完成时将当前时间戳和其他一些数据插入到MySQL表中.它每30分钟执行一次,有时根本不执行,这会在数据中留下空白.

Goal:

有一个查询,当没有数据时,获取时间戳四舍五入为最接近的半小时的所有数据和空行(除时间戳之外的所有字段都应为空).

Restrictions:

表 struct 、数据本身和脚本都不能更改.

Problem:

我能想出的唯一能产生预期结果的解决方案是不能扩展的.目前,实际的表约有50‘000行,完成查询已经花费了15分钟以上.

Example:

CREATE TABLE IF NOT EXISTS `statuses` (
    `timestamp` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    `status` INT NOT NULL DEFAULT '0',
    PRIMARY KEY (`timestamp`)
);
INSERT
    IGNORE INTO `statuses` (`timestamp`, `status`)
VALUES
    ('2023-01-01 00:03:34', '164850'),
    ('2023-01-01 00:31:23', '794088'),
    ('2023-01-01 03:31:28', '686754'),
    ('2023-01-01 04:01:15', '684711'),
    ('2023-01-01 05:31:35', '116777'),
    ('2023-01-01 06:01:52', '469332'),
    ('2023-01-01 06:31:55', '816300'),
    ('2023-01-01 08:33:53', '309583'),
    ('2023-01-01 09:03:54', '847976'),
    ('2023-01-01 09:31:33', '812517');
WITH RECURSIVE `timestamps`(`timestamp`) AS (
    SELECT
        (
            SELECT
                FROM_UNIXTIME(
                    UNIX_TIMESTAMP(MIN(`timestamp`)) - MOD(UNIX_TIMESTAMP(MIN(`timestamp`)), 1800)
                )
            FROM
                `statuses`
        )
    UNION
    ALL
    SELECT
        DATE_ADD(`timestamp`, INTERVAL 30 MINUTE)
    FROM
        `timestamps`
    WHERE
        `timestamp` < (
            SELECT
                FROM_UNIXTIME(
                    UNIX_TIMESTAMP(MAX(`timestamp`)) - MOD(UNIX_TIMESTAMP(MAX(`timestamp`)), 1800)
                )
            FROM
                `statuses`
        )
)
SELECT
    `t`.`timestamp`,
    `s`.`status`
FROM
    `timestamps` AS `t`
    LEFT OUTER JOIN `statuses` AS `s` ON `t`.`timestamp` = FROM_UNIXTIME(
        UNIX_TIMESTAMP(`s`.`timestamp`) - MOD(UNIX_TIMESTAMP(`s`.`timestamp`), 1800)
    )
ORDER BY
    `t`.`timestamp` ASC;

推荐答案

我们可以稍微简化一下递归部分.正如Thorsten Kettner提到的,没有必要在每次迭代中重新 Select 最大状态日期,我们可以在锚点中这样做--我还想说,我们甚至不需要对最大日期进行舍入(尽管这是微优化).

对于外部查询,我不建议对状态时间戳应用函数;这是表的主键,我们确实希望对其运行SARGEable谓词:让我们改用半开放间隔(并且我们不需要在这里再次执行unixtime转换):

with recursive timestamps (ts, max_ts) as (
    select from_unixtime(floor(unix_timestamp(min(ts)) / 1800) * 1800) ts, max(ts) max_ts
    from statuses
    union all
    select ts + interval 30 minute, max_ts from timestamps where ts + interval 30 minute <= max_ts
)
select t.ts, s.status
from timestamps t
left join statuses s on s.ts >= t.ts and s.ts < t.ts + interval 30 minute
order by t.ts

请注意,我将第timestamp列重命名为ts,以避免与相应的SQL关键字冲突.


如果这还不够好,那么一种替代方法是将递归查询的结果materialize放在日程表中.

您通常会在很长一段时间内使用该表.您可以使用递归查询来创建它:

create table timestamp_calendar as 
with recursive timestamps as (
    select '2022-01-01 00:00:00' ts, '2022-01-02 12:00:00' max_ts -- short period for testing
    union all
    select ts + interval 30 minute, max_ts 
    from timestamps
    where ts < max_ts
)
select * from timestamps;

我们可以声明一个主键以使基础索引受益:

alter table timestamp_calendar add primary key (ts);

然后,我们可以在查询中使用该表.理想情况下,您应该提前知道要查找的日期范围.但如果您没有,我们可以带来最小/最大状态日期,并使用它们来预筛选日历表.

select t.ts, s.status
from timestamp_calendar t
inner join (select min(ts) min_ts, max(ts) max_ts from statuses) x 
    on  t.ts >  x.min_ts - interval 30 minute 
    and t.ts <= x.max_ts
left join statuses s 
    on  s.ts >= t.ts 
    and s.ts <  t.ts + interval 30 minute
order by t.ts

这是一张100

Mysql相关问答推荐

是否有一种方法可以 Select 表中具有特定列值的行,该行还显示phpmyadmin中原始表中的行号?

如何让Docker MySQL使用Unicode(utf-8)和初始化脚本?

LaravelEloquent ToSql()缺少连接表

用于将具有多个状态更改日期列的单行转换为具有状态和时间戳的多行的SQL查询

仅获取我不是最后一个 comments 者的博客帖子

MySQL 搜索列字段字符串

使用Workbench时如何添加mysqldump配置标志

如何将数据导入MySQL中的master/replica struct

Next-key lock explication - 范围的主键

过滤查询结果

SQL从字典中的键获取值

有没有更好的方法在无限滚动的网页上呈现获取的提要数据?

非常规字符的不正确字符串值错误

无法在两个 mysql 表上执行内部联接

在 PHP 中获取 MySQL 列的总和

MySQL INSERT IF(自定义 if 语句)

如何在sequelize中定义多列的唯一索引

如何在特定数据库中创建表?

如何从 MySQL Workbench 中的图表生成 SQL 脚本?

哪个更好 - 许多小桌子或一张大桌子?