使用MySQL查询模拟移动时间表

时间:2019-09-10 16:24:32

标签: mysql sql datetime mariadb

我正尝试使用基于以下表结构的MySQL查询来模拟时间轴

drop table if exists testing;
create table testing (
   idno integer not null auto_increment,
   date datetime not null,
   primary key (idno)
);

insert into testing values
(default, '2019-08-25'),
(default, '2019-09-01'),
(default, '2019-09-05'),
(default, '2019-09-10'),
(default, '2019-09-15'),
(default, '2019-09-20');

我要完成的工作是检索从给定日期介于两个日期之间的行开始

例如,我可以执行以下操作

select * from testing
where date >= (
   select date from testing
   where date <= '2019-09-09 00:00:00' 
   order by date desc limit 1 
)
order by date;

返回所有从2019-09-05起的日期,但如果我使用

select * from testing
where date >= (
   select date from testing
   where date <= '2019-09-10 00:00:00' 
   order by date desc limit 1 
)
order by date;

然后它将返回2019年9月10日及以后的所有日期,该日期为新的截止日期(这是我想要的)

问题在于,它仅在至少达到一个截止日期时才有效,因此如果我以此查询它

select * from testing
where date >= (
   select date from testing
   where date <= '2019-08-24 00:00:00' 
   order by date desc limit 1 
)
order by date;

它不返回任何内容,因为子查询试图在date> = none的地方执行操作,我通过更改查询来解决此问题,以解决此问题,但是现在这只是一个大麻烦而已

select * from testing
where (
   date >= (
      select date from testing
      where date <= '2019-08-24 00:00:00' 
      order by date desc limit 1
   ) or not exists (
      select date from testing
      where date <= '2019-08-24 00:00:00' 
      order by date desc limit 1
   )
)
order by date;

是否有一种更清洁的方法来实现这一目标?这样感觉很笨拙

2 个答案:

答案 0 :(得分:2)

在这种情况下,> all表现得很好:

select *
from testing
where date >= all (
      select t2.date
      from testing t2
      where t2.date <= '2019-08-24 00:00:00' 
     )
order by date;

编辑:

如果没有all,则可以使用聚合:

select t.*
from testing t
where t.date >= (
        select coalesce(max(t2.date), t.date)
        from testing t2
        where t2.date <= '2019-08-24 00:00:00' 
       )
order by t.date;

关键是coalesce()处理NULL的值(即,所有被过滤掉的行)。

答案 1 :(得分:1)

我认为Gordon的>= ALL解决方案是最优雅的-但是这里有一些替代方案。

您可以通过添加一行代码来解决您的第一个查询:

select * from testing
where date >= (
   select date from testing
   where date <= @date
   union all select '1000-01-01 00:00:00' -- <--
   order by date desc limit 1 
)
order by date;

如果没有行与子查询的WHERE条件匹配,则将返回'1000-01-01 00:00:00',这是最小的DATETIME值。在这种情况下,所有行都将匹配外部查询的条件。该查询的一个缺点是对UNION集进行排序将导致“文件排序”,而不是仅从索引中收集第一行。但是,由于子查询不相关(不依赖于外部查询的值),因此只需要评估一次。尽管可以通过在第一个UNION部分中添加ORDER BYLIMIT 1来改善这一点:

select * from testing
where date >= (
   (
     select date from testing
     where date <= @date
     order by date desc limit 1 
   )
   union all select '1000-01-01 00:00:00'
   order by date desc limit 1 
)
order by date;

不幸的是,这仅适用于MySQL 8.0。 (可能也可以在MariaDB上工作-但我没有测试。)

您还可以使用当前行的日期值:

select * from testing t1
where date >= (
   select date from testing t2
   where date <= @date
   union all select t1.date
   order by date desc limit 1 
)
order by date;

但是在这种情况下,将为外部查询的每一行评估子查询。

如果您以“获取日期为<= X的最后一行且日期为> X的所有行”的形式“读取”您的要求,则可以将其“转换”为SQL:

(
  select *
  from testing
  where date <= @date
  order by date desc limit 1
) union all (
  select *
  from testing
  where date > @date
)
order by date;

请参见db-fiddle demo

这似乎也可行:

select *
from testing t1
where not exists (
  select *
  from testing t2
  where t2.date > t1.date
    and t2.date <= @date
);

但是我无法解释原因。我什至不明白:-)。