新手问题 求教,不知道这个语句有没有更简便的写法呢,看着太丑了。

tianlitao · 2016年10月15日 · 最后由 flingfox63 回复于 2016年10月19日 · 3482 次阅读
plan = Plan.where('((start_time >= ? and start_time <= ?) or (end_time >= ? and end_time <= ?) or (start_time  >=  ? and end_time <= ?) or (start_time <= ? and end_time >= ?) )',start_at, end_at, start_at, end_at, start_at, end_at, start_at, end_at)

给出一个时间段,判断是否和数据库已存在的时间区间有重叠的部分 补充:

#  a:       |------|
#  b:   |------|
#  a.start_time >= start_at and a.start_time <= end_at
#  a: |------| 
#  b:    |------|
#  a.end_time >= start_at and a.end_time <= end_at
#  a:   |------|
#  b: |----------|
#  a.start_time >= start_at and a.end_time <= end_at
#  a: |----------|
#  b:   |------|
#  a.start_time <= start_at and a.end_time >= end_at

使用 #1 楼 @mimosa 简化参数后方法为

plan = Plan.where('((start_time >= :start_at and start_time <= :end_at) or (end_time >= :start_at and end_time <=  :end_at) or (start_time  >= :start_at and end_time <=  :end_at) or (start_time <= :start_at and end_time >=  :end_at) )',start_at: start_at, end_at: end_at)

优化后的结果:

#  a:       |------|
#  b:   |------|
#  a.start_time >= start_at and a.start_time <= end_at
#  a: |------| 
#  b:    |------|
#  a.start_time <= start_at and a.end_time >= start_at
plan = Plan.where('((start_time >= :start_at and start_time <= :end_at) or (start_time <= :start_at and end_time >=  :start_at) )',start_at: start_at, end_at: end_at)

#12 楼 最终的结果:

plan = Plan.where('start_time <= ? and end_time >= ?', end_at, start_at

不优化你的 SQL 逻辑,简单可以这样

plan = Plan.where('((start_time >= :start_at and start_time <= :end_at) or (end_time >= :end_at and end_time <= :end_at) or (start_time <= ? and start_time >= ?) or (start_time >= ? and start_time <= ?) or (start_time <= ? and end_time >= ?))',start_at: start_at, end_at : end_at)

PS: 问号我就不帮你一一替换了,手机不方便输入.

end_time >= end_at and end_time <= end_at 不就是 end_time = end_at 么。。

另外应该有 where.or 这种东西的(不确定)

#1 楼 @mimosa 谢谢,这么写应该可以

#2 楼 @mizuhashi 看错了吧,end_time >= start and end_time <= end,sql 的话一共是四种情况

没看懂你的 SQL ,但显然复杂了(可能有错)。

反过来思考下,时间段不重合 情况就两种:

# a: |-------|
# b:              |-------|
# a.end_at < b.start_at

# a:              |-------|
# b: |-------|
# a.start_at > b.end_at

综合一下,取个反就是你想要的结果了。这个可以简化 SQL ,1 楼的写法简化参数,应该够了。

Rails 5 以上可以

Plan
  .where(start_time: start_at.. end_at).or
  .where(end_time: start_at..end_at).or
# blablabla

#5 楼 @darkbaby123 之前的是有点问题,我又重新更新了下帖子,重合的应该就是这四种情况。但是还是没有想到如何简化 sql

考虑 start_at,

  1. start_at < start_time,则 end_at >= start_time 时重合
  2. start_at >= start_time && start_at < end_time,则 一定重合
  3. start_at > end_time 不重合

总共就两种情况,开始时间小于时间段开始 和开始时间在时间段之间

什么?start_at 可能 大于 end_at ?当我没说。。。

#8 楼 @lithium4010 的确是,我写的后两种情况根本没必要,前面两种已经包括了,谢谢。

plan = Plan.where('((start_time >= :start_at and start_time <= :end_at) or (start_time <= :start_at and end_time >=  :start_at) )',start_at: start_at, end_at: end_at)
tianlitao 关闭了讨论。 10月16日 11:03
tianlitao 重新开启了讨论。 10月16日 11:03

判断时间区段重叠可以通过这样的方式来简化:

先是判断时间区段不重叠,有 2 种情况

                        |start_a ----- end_a| 
|start_b ----- end_b|                       

---------------------------------------------

|start_a ----- end_a|                        
                        |start_b ----- end_b|

得出不重叠的表达式是:

start_a > end_b || end_a < start_b

那么不重叠就是取反

!(start_a > end_b || end_a < start_b)

布尔运算等价于

start_a <= end_b and end_a >= start_b

代码可以简化成:

plan = Plan.where('start_time <= ? and end_time >= ?', end_at, start_at)

首先想到的是用『线段树』来解决这个问题。直观上感觉应该比 where 要快。躺在床上手机码字,回来我研究一下

建议问题可以把需求写下, 有可能有以下两个情况:

  1. 看最终结果的需求是在 plan 的 start_time 或 end_time 落在 start_at 和 end_at 之间.
  2. 还有一个可能的需求是 start_time 与 end_time 都落在 start_at 和 end_at 之间.

PS: 最终结果应该写错了, or 之后的 start_time 应该为 end_time.

#12 楼 @quakewang 这种情况才是没有不重叠

                                     |start_a ----- end_a| 
|start_b ----- end_b|
                                                                          |start_c ----- end_c|  

start_a > end_b 并不能说明,在已有的数据中不重叠

#14 楼 @flingfox63 我又看了下最终结果,应该是没有问题的。 需求是:给出一个区间,和表中已有的数据进行比对,包含,被包含,交叉,都算是重叠

#15 楼 @tianlitao end_a < start_c 就是 || 表达式右边的第二种情况呀

这样不就行了么?

Plan.where('end_time >= ? and start_time =< ?', start_at, end_at)

原来 quake 已经写过答案了,这个答案怎么没有归纳到主贴里呢?

20 楼 已删除

@quakewang @fsword @davidqhr 谢谢,现在理清楚思路了

能把你的需求说一下吗?

#22 楼 @easonlovewan 一个时间段内只允许有一个计划

24 楼 已删除

看完才发现原来需求不是我上面的说的需求,真正的业务需求应该是:在这个时间内处过未完成状态的 plan.

这个重叠不重叠来描述业务需求, 你们产品经理好强, 你们也好强!

需要 登录 后方可回复, 如果你还没有账号请 注册新账号