您将如何存储和查询营业时间?
-
02-07-2019 - |
题
我们正在构建一个应用程序来存储各种企业的“营业时间”。表示此数据以便您可以轻松检查项目是否打开的最简单方法是什么?
一些选项:
- 分割出可以标记为“打开/关闭”的块(每 15 分钟)。检查涉及查看“开放”位是否设置为所需时间(有点像火车时刻表)。
- 存储时间范围列表(上午 11 点至下午 2 点、下午 5 点至下午 7 点等)并检查当前时间是否落在任何指定范围内(这就是我们的大脑在解析上面的字符串时所做的事情)。
有谁有存储和查询时间表信息的经验以及可以提供的建议吗?
(有各种疯狂的极端情况,例如“该月的第一个星期二关闭”,但我们将其留到另一天)。
解决方案
将每个连续的时间块存储为开始时间和持续时间;这使得检查时间何时跨越日期边界变得更容易
如果您确定营业时间永远不会跨越日期界限(即永远不会有通宵促销或 72 小时马拉松活动等),那么开始/结束时间就足够了
其他提示
最灵活的解决方案可能是使用位集方法。一周有 168 个小时,因此有 672 个 15 分钟的时段。这只是 84 字节的空间,应该是可以忍受的。
我会使用这样的表:
BusinessID | weekDay | OpenTime | CloseTime
---------------------------------------------
1 1 9 13
1 2 5 18
1 3 5 18
1 4 5 18
1 5 5 18
1 6 5 18
1 7 5 18
在这里,我们有一家企业,正常营业时间为 5 至 6 点,但周日营业时间较短。
if open 的查询是 (psuedo-sql)
SELECT @isOpen = CAST
(SELECT 1 FROM tblHours
WHERE BusinessId = @id AND weekDay = @Day
AND CONVERT(Currentime to 24 hour) IS BETWEEN(OpenTime,CloseTime)) AS BIT;
如果您需要存储边缘情况,那么只需 365 个条目,每天一个……从总体上看,这实际上并不算多,在 day 列和businessId 列上放置一个索引。
不要忘记将企业时区存储在单独的表中(标准化!),并在进行这些比较之前在您的时间和它之间执行转换。
我想我个人会选择开始+结束时间,因为这会让一切变得更加灵活。一个好问题是:块大小在某个点发生变化的可能性有多大?然后选择最适合您情况的解决方案(如果它可能会改变,我肯定会选择时间跨度)。
您可以将它们存储为时间跨度,并在应用程序中使用分段。这样您就可以使用块轻松输入,同时保持数据存储中更改的灵活性。
添加到乔纳森·霍兰德的内容 说, ,我允许同一天有多个条目。
我还允许使用小数时间,或另一列分钟。
为什么?许多餐馆和一些企业以及世界各地的许多企业都有午餐和/或下午休息时间。此外,许多餐馆(据我所知,我家附近有 2 家)在奇数非 15 增量时间关门。周日晚上 9:40 关闭,凌晨 1:40 关闭。
还有假期时间的问题,例如商店在感恩节提前关门,因此您需要基于日历的覆盖。
也许可以做的是日期/时间打开、日期时间关闭,如下所示:
businessID | datetime | type
==========================================
1 10/1/2008 10:30:00 AM 1
1 10/1/2008 02:45:00 PM 0
1 10/1/2008 05:15:00 PM 1
1 10/2/2008 02:00:00 AM 0
1 10/2/2008 10:30:00 AM 1
ETC。(类型:1 表示打开,0 表示关闭)
并提前1-2年预先计算好未来1-2年的所有日子。请注意,您只有 3 列:int、日期/时间/位,因此数据消耗应该是最小的。
这还允许您修改已知的特殊日子的奇数时间的特定日期。
它还负责跨越午夜以及 12/24 小时转换。
它也是时区不可知的。如果您存储开始时间和持续时间,那么当您计算结束时间时,您的机器是否会为您提供 TZ 调整时间?那是你要的吗?更多代码。
至于查询打开关闭状态:查询相关的日期时间,
select top 1 type from thehours where datetimefield<=somedatetime and businessID = somebusinessid order by datetime desc
然后看“类型”。如果为 1,则打开;如果为 0,则关闭。
附:我在零售业工作了 10 年。所以我很熟悉小企业的疯狂工作时间问题。
好吧,我会根据它的价值投入其中。
我需要处理很多事情。
- 快速/高性能查询
- 任何时间增量,晚上 9:01、12:14 等。
- 国际(?) - 不确定即使是时区这是否也是一个问题,至少在我的情况下,但更熟悉这里的人可以随意插话
- 开盘 - 持续到第二天收盘(中午开盘,凌晨 2:00 收盘)
- 多个时间跨度/天
- 能够覆盖特定日期(节假日等)
- 能够重复覆盖
- 能够查询任何时间点并让企业开业(现在、未来时间、过去时间)
- 能够轻松排除即将关门的企业结果(过滤 30 分钟内关门的企业,您不想让您的用户成为“食品/饮料行业中在关门前 5 分钟出现的那个人”)
我喜欢其中提出的很多方法,并且我借鉴了其中的一些方法。在我的网站、项目中,无论我需要考虑什么,我可能拥有数百万家企业,而这里的一些方法似乎对我个人来说不太适用。
这是我提出的算法和结构。
我们必须在全球范围内、任何地点、任何时间做出一些具体的假设:一周有 7 天。一天有1440分钟。可能存在有限数量的打开/关闭分钟排列。
不具体但合理的假设:许多开放/关闭分钟的排列将在企业之间共享,从而减少实际存储的总排列。在我的生活中曾经有一段时间,我可以轻松地计算出这种方法的实际可能组合,但如果有人可以提供帮助/认为它会很有用,那就太好了。
我建议3张表:在停止阅读之前,请考虑在现实世界中,其中 2 个表将足够小,并且可以整齐地进行缓存。这种方法并不适合所有人,因为将 UI 解释为数据模型并在需要时再次返回所需的代码非常复杂。您的里程和需求可能会有所不同。无论这意味着什么,这是对合理“企业”级解决方案的尝试。
营业时间表
id |开放(一天中的分钟)|关闭(一天的分钟)
1 | 360 | 1020(示例:上午 9 点至下午 5 点)
2 | 365 | 1021(示例:边缘情况上午 9:05 - 下午 5:01(怪人))
ETC。
HoursOfOperations 不关心是哪几天,只关心开放和关闭以及唯一性。每个开/关组合只能有一个条目。现在,根据您的环境,可以缓存整个表,也可以缓存当天的当前时间等。无论如何,您不需要为每个操作查询该表。根据您的存储解决方案,我设想此表中的每一列都已建立索引以提高性能。随着时间的推移,该表的 INSERT 可能性可能呈指数倒数。事实上,处理这个表应该主要是一个进程内操作(RAM)。
商务2小时地图
笔记:在我的示例中,我将“Day”存储为位标志字段/列。这很大程度上是由于我的需求以及 C# 中 LINQ / Flags Enums 的进步。没有什么可以阻止您将其扩展到 7 位字段。两种方法在存储逻辑和查询方法上应该相对相似。
另一个注意事项:我不会就“每个表都需要一个 PK ID 列”进行语义论证,请为此寻找另一个论坛。
BusinessID |小时|一天(或者,如果您更喜欢分裂为:周一 BIT,周二 BIT,...)
1 | 1 | 1111111(这项业务每周每天开放9-5)
2 | 2 | 1111110(这项业务开放9:05-5:01 M -Sat(星期一=第1天)
这很容易查询的原因是我们总是可以很容易地确定我们所追求的 MOTD(一天中的分钟)。如果我想知道明天下午 5 点营业时间,我会获取所有 HoursOfOperations IDS,其中 Close >= 1020。除非我正在寻找一个时间范围,否则 Open 就变得微不足道了。如果您不想显示企业在接下来的半小时内关门,只需相应地调整您的到达时间(搜索下午 5:30 (1050),而不是下午 5:00 (1020)。第二个查询自然是“给我所有有关 HoursID IN (1, 2, 3, 4, 5) 等的业务”。这可能会引起危险信号,因为这种方法存在局限性。然而,如果有人能够回答上面的实际排列问题,我们也许能够消除危险信号。考虑一下我们一次只需要方程任一侧的可能排列,无论是开排列还是闭排列。
考虑到我们已经缓存了第一个表,这是一个快速的操作。第二个操作是查询这个潜在的大行表,但我们正在搜索非常小的(SMALLINT)希望有索引的列。
现在,您可能会看到代码方面的复杂性。在我的特定项目中,我的目标主要是酒吧,因此可以非常安全地假设我将拥有大量营业时间为“11:00 AM - 2:00 AM(第二天)”的企业。这确实是 HoursOfOperations 表和 Business2HoursMap 表中的 2 个条目。例如。营业时间为 11:00 AM - 2:00 AM 的酒吧将有 2 个对 HoursOfOperations 表 660 - 1440(11:00 AM - 午夜)和 0 - 120(午夜 - 2:00 AM)的引用。这些引用将反映到 Business2HoursMap 表中的实际天数中,在我们的简单情况下为 2 个条目,1 个条目 = 全天小时参考 #1,另一个全天参考 #2。希望这是有道理的,这是漫长的一天。
在特殊日子/假期/任何其他情况下优先。覆盖本质上是基于日期的,而不是基于星期几的。我认为这就是一些方法试图将众所周知的圆钉塞进方孔的地方。我们需要另一张桌子。
小时| BusinessID |天|月|年
1 | 2 | 1 | 1 |无效的
如果您需要“每个第二个星期二,这家公司去钓鱼 4 个小时”之类的内容,这肯定会变得更加复杂。然而,这将使我们能够很容易地做的是允许 1 - 覆盖,2 - 合理的重复覆盖。例如。如果年份为 NULL,则每年元旦这家怪异酒吧的营业时间为上午 9:00 至下午 5:00,与我们上面的数据示例保持一致。IE。- 如果设定了年份,则仅适用于 2013 年。如果月份为空,则为该月的每个第一天。同样,这不会单独通过 NULL 列来处理每个调度场景,但理论上,如果需要,您可以通过依赖一长串绝对日期来处理几乎任何事情。
同样,我会在滚动日的基础上缓存该表。我只是无法在单日快照中实际看到该表的行非常大,至少对于我的需要来说是这样。我会首先检查这个表,因为它很好,是一个覆盖,并且会针对存储端更大的 Business2HoursMap 表保存查询。
有趣的问题。我真的很惊讶这是我第一次真正需要仔细考虑这个问题。一如既往,我非常热衷于不同的见解、方法或方法中的缺陷。
分段块更好,只需确保为用户提供一种简单的方法来设置它们即可。单击并拖动效果很好。
当您跨越午夜边界时,任何其他系统(例如范围)都会非常烦人。
至于如何存储它们,C++ 位域可能是最好的。在大多数其他语言中,数组可能更好(浪费大量空间,但运行速度更快并且更容易理解)。
我现在会考虑一下这些边缘情况,因为它们将告知您是否有基本配置加上覆盖或完整的静态存储打开时间或其他什么。
有很多例外情况 - 并且定期(例如下雪天、复活节、耶稣受难日等不规则假期),如果这预计是现实的可靠表示(而不是一个好的猜测),那么您需要很快就会在架构中解决这个问题。
像这样的事情怎么样:
营业时间表
Business_id (int)
Start_Time (time)
End_Time (time)
Condition varchar/string
Open bit
“Condition”是一个 lambda 表达式(“where”子句的文本)。动态构建查询。因此,对于特定业务,您可以选择所有开放/关闭时间
Let Query1 = select count(open) from store_hours where @t between start_time and end_time and open = true and business_id = @id and (.. dynamically built expression)
Let Query2 = select count(closed) from store_hours where @t between start_time and end_time and open = false and business_id = @id and (.. dynamically built expression)
所以结束你想要的东西是这样的:
select cast(Query1 as bit) & ~cast(Query2 as bit)
如果最后一次查询的结果为 1,则商店在时间 t 开门,否则商店关门。
现在您只需要一个友好的界面来为您生成 where 子句(lambda 表达式)。
我能想到的唯一的另一种特殊情况是,如果一家商店在某个日期从早上 7 点到凌晨 2 点营业,但在下一个日期晚上 11 点关门,会发生什么情况。您的系统应该能够通过巧妙地分配两天之间的时间来处理这个问题。
这里肯定不需要节省内存,但可能需要干净且易于理解的代码。恕我直言,“有点摆弄”不是正确的方法。
这里我们需要一个集合容器,它可以容纳任意数量的唯一项,并且可以快速轻松地确定某个项是否是成员。设置需要小心,但在日常使用中,一行简单易懂的代码决定了您是打开还是关闭
概念:为每 15 分钟的块分配索引号,例如从周日午夜开始。
初始化:当您打开时,将每 15 分钟块的索引号插入到集合中。(假设您的营业时间少于休息时间。)
使用:减去上周日午夜的有趣时间(以分钟为单位),然后除以 15。如果该号码出现在集合中,则您处于开放状态。