[英]Opinions on sensor / reading / alert database design
我最近问了几个关于数据库设计的问题,可能太多了;-)但是我相信我正在慢慢地用我的设计解决问题的核心并且慢慢地将它煮沸了。 我仍在努力解决关于“警报”如何存储在数据库中的几个决定。
在该系统中,警报是必须被确认,采取行动等的实体。
最初我将读数与这样的警报相关联(非常简化): -
[Location]
LocationId
[Sensor]
SensorId
LocationId
UpperLimitValue
LowerLimitValue
[SensorReading]
SensorReadingId
Value
Status
Timestamp
[SensorAlert]
SensorAlertId
[SensorAlertReading]
SensorAlertId
SensorReadingId
最后一个表是将读数与警报相关联,因为它是指示传感器处于警报状态的读数。
这种设计的问题在于它允许来自许多传感器的读数与单个警报相关联 - 而每个警报仅针对单个传感器,并且应该仅具有与其相关联的传感器的读数(我应该担心数据库允许这虽然?)。
我想简化一些事情,为什么还要烦扰SensorAlertReading表呢? 相反,我可以这样做:
[Location]
LocationId
[Sensor]
SensorId
LocationId
[SensorReading]
SensorReadingId
SensorId
Value
Status
Timestamp
[SensorAlert]
SensorAlertId
SensorId
Timestamp
[SensorAlertEnd]
SensorAlertId
Timestamp
基本上我现在没有将读数与警报相关联 - 相反,我只知道特定传感器的开始和结束时间之间的警报是活动的,如果我想查找该警报的读数,我可以做。
显然缺点是我不再有任何约束阻止我删除警报期间发生的读数,但我不确定约束是否必要。
现在从外面看作为开发人员/ DBA,这会让你想生病还是看起来合情合理?
有没有其他方法可以让我失踪?
谢谢。
编辑:这是另一个想法 - 它以不同的方式工作。 它存储每个传感器状态变化,从表格中的正常变为警报,然后读数简单地与特定状态相关联。 这似乎解决了所有问题 - 你怎么想? (我唯一不确定的是调用表“SensorState”,我不禁认为有一个更好的名字(也许是SensorReadingGroup?): -
[Location]
LocationId
[Sensor]
SensorId
LocationId
[SensorState]
SensorStateId
SensorId
Timestamp
Status
IsInAlert
[SensorReading]
SensorReadingId
SensorStateId
Value
Timestamp
必须有一个优雅的解决方案!
数据模型
我认为您的数据模型应如下所示: ▶传感器数据模型◀ 。 (第2页与您的其他问题有关的历史)。
不熟悉关系建模标准的读者可能会发现▶IDEF1X Notation◀很有用。
业务(评注中制定的规则)
我确实找到了一些现在已经过时的早期业务规则,所以我删除了它们
这些可以在关系中“读取”(与数据模型相邻)。 业务规则和所有隐含的参照和数据完整性可以在任何ISO SQL数据库中实现,因此由RULES,CHECK约束条件保证。 这是IDEF1X的演示,用于开发Relational键和实体和关系。 请注意,动词短语不仅仅是蓬勃发展。
除了三个参考表之外,唯一的静态识别实体是Location,NetworkSlave和User。 传感器是系统的核心,所以我给它自己的标题。
位置
Location
包含一对多Sensors
Location
可能有一个记录器 NetworkSlave
用户
User
可以维护零到多的Locations
User
可以维护零对多Sensors
User
可以维护零到多的NetworkSlaves
User
可以执行零对多Downloads
User
可以在一个Alert
上进行零对多的Acknowledgements
User
可以采取零到许多Actions
,每一个的ActionType
传感器
SensorType
安装为零对多Sensors
Logger
(房屋和)收集一个LoggerSensor
Readings
甲Sensor
是任一个NetworkSensor
或一个LoggerSensor
NetworkSensor
记录由一个NetworkSlave
收集的Readings
Logger
定期Downloaded
一次到多次
LoggerSensor
记录一个Logger
收集的Readings
Reading
可以被视为Alert
之一, AlertType
AlertType
可能发生在零对多Readings
Alert
可能是一个用户的一个Acknowledgement
。 Acknowledgement
可以由一个关闭Action
,一个的ActionType
,由一个User
Actions
采用ActionType
回应评论
在所有移动的东西上粘贴Id
列会干扰标识符的确定,标识符是为数据库提供关系“权力”的自然关系密钥。 它们是代理键,这意味着额外的键和索引,它阻碍了关系的力量; 这会产生比其他必要更多的连接。 因此,只有当Relational键变得太麻烦而无法迁移到子表(并接受强加的额外连接)时,我才使用它们。
可空键是Unnormalised数据库的典型症状。 数据库中的空白对性能来说是个坏消息; 但FK中的Nulls意味着每个表都做了太多的事情,含义太多,结果是代码非常糟糕。 适合喜欢“重构”数据库的人; 对于Relational数据库完全没有必要。
已解决: Alert
可能已被Acknowledged
; Acknowledgement
可能会被Actioned
。
该行上方的列是主键(请参阅符号文档)。 SensorNo
是LocationId
序号; 参考业务规则,在Location
之外没有意义; 两列一起形成PK。 当您准备好插入传感器时(在检查了尝试是否有效之后等),它的推导如下。 这不包括LoggerSensors,它们为零:
INSERT Sensor VALUES ( @LocationId, SensorNo = ( SELECT ISNULL(MAX(SensorNo), 0) + 1 FROM Sensor WHERE LocationId = @LocationId ) @SensorCode )
为了准确或改进意义,我已将NetworkSlave monitors NetworkSensor
更改为NetworkSlave collects Readings from NetworkSensor
。
检查约束。 NetworkSensor
和LoggerSensor
是Sensor
独有子类型,它们的完整性可以通过CHECK约束来设置。 Alerts, Acknowledgements
和Actions
不是子类型,但它们的完整性由相同的方法设置,因此我将它们列在一起。
数据模型中的每个关系都作为FOREIGN KEY(child_FK_columns)中的子(或子类型)中的CONSTRAINT实现REFERENCES Parent(PK_columns)
需要使用鉴别Sensor
来识别Sensor
所属的子类型。 对于LoggerSensors
,这是SensorNo = 0
; NetworkSensors
非零。
NetworkSensors
和LoggerSensors
的存在分别受到FK CONSTRAINTS到NetworkSlave
和Logger
约束; 以及传感器。 NetworkSensor
,包含CHECK约束以确保SensorNo
不为零 在LoggerSensor
,包含CHECK约束以确保SensorNo
为零
的存在Acknowledgements
和Actions
是通过所识别的FK约束所约束(一个Acknowledgement
不能存在没有Alert
;一个Action
不能没有一个存在Acknowledgement
)。 相反,没有Acknowledgement
的Alert
处于未确认状态; Alert
和Acknowledgement
但没有Action
处于已确认但未Action
状态。 。
警报。 这种(实时监控和警报)应用程序设计中的概念是许多小程序,独立运行; 所有使用数据库作为事实的单一版本。 一些程序插入行( Readings, Alerts
); 其他程序轮询数据库是否存在此类行(并发送SMS消息等;或者手持单元仅接收与该单元相关的警报)。 从这个意义上说,db可以被描述为一个消息框(一个程序放入行,另一个程序读取和操作)。
假设是, Sensors
Readings
由NetworkSlave
“实时”记录,每隔一分钟左右就会插入一组新的Readings
。 后台进程定期执行(每分钟或其他),这是主要的“监视器”程序,它在循环中将有许多功能。 一个这样的功能是监视Readings
并产生自上次迭代(程序循环)以来发生的Alerts
。
以下代码段将在循环内执行,每个AlertType一个。 这是一个经典的投影:
-- Assume @LoopDateTime contains the DateTime of the last iteration INSERT Alert SELECT LocationId, SensorNo, ReadingDtm, "L" -- AlertType "Low" FROM Sensor s, Reading r WHERE s.LocationId = r.LocationId AND s.SensorNo = r.SensorNo AND r.ReadingDtm > @LoopDtm AND r.Value < s.LowerLimit INSERT Alert SELECT LocationId, SensorNo, ReadingDtm, "H" -- AlertType "High" FROM Sensor s, Reading r WHERE s.LocationId = r.LocationId AND s.SensorNo = r.SensorNo AND r.ReadingDtm > @LoopDtm AND r.Value > s.UpperLimit
因此, Alert
绝对是一个事实,它作为数据库中的一行存在。 随后可被Acknowledged
由User
(另一行/事实),并且Actioned
与ActionType
由User
。 其他的(通过投影法创作),即。 一般的和不变的情况下,我要提到Alert
只是作为连续Alert
; 创建后的静态对象。
关注改变Users
。 这已经得到了解决,如下所述。 在我的(昨日修订)答案的顶部,我声明主要的识别元素是静态的 。 我重新排列了业务规则以提高清晰度。
由于您提到的原因, User.Name
不是User
的好PK,尽管它仍然是备用键(唯一)和用于人工交互的键。
User.Name
不能重复,不能有多个Fred
; 可以有FirstName-LastName
; 两个Fred Bloggs
,但不是User.Name
。 我们的第二个Fred需要选择另一个User.Name
。 请注意已识别的指数。
UserId
是永久记录,它已经是PK了。 永远不要删除User
,它具有历史意义。 事实上,FK约束将阻止你(永远不要在真正的数据库中使用CASCADE,这是纯粹的精神错乱)。 不需要代码或触发器等。
或者(删除从未执行任何操作的Users
,从而释放User.Name
以供使用),只要没有FK违规(即, Download, Acknowledgement, Action
未引用UserId
),则允许删除。
要确保只有Current当前执行Actions
Users
,在User(DM Updated)中添加一个IsObsolete
布尔值,并在查询该表时检查该列是否有任何功能(报告除外)您可以实现仅返回那些Users
的View UserCurrent
。
Location
和NetworkSlave
。 如果您需要区分当前与历史,请告诉我,我也会向他们添加IsObsolete
。
我不知道:您可以定期清除古代历史数据库,删除(例如)超过10年的行。 这必须首先从底部(表格)完成,处理关系。
随意问的问题。
请注意,IDEF1表示法文档已展开。
这是我对这个问题的两分钱。
AlertType表包含所有可能的警报类型。 AlertName
可能是高温,低压,低水位等。
AlertSetup表允许从传感器为特定警报类型设置警报阈值。 例如, TresholdLevel
= 100和TresholdType
='HI'应触发超过100的读数警报。
读取表在传输到服务器(应用程序)时保存传感器读数。
警报表包含所有警报。 它保持链接到触发警报的第一个读数和完成它的最后一个读取( FirstReadingId
, LastReadingId
)。 如果( SensorId
, AlertTypeId
)组合存在活动警报,则IsActive
为true。 只有通过读取低于警报阈值才能将IsActive
设置为false。 IsAcknowledged
表示操作员已确认警报。
应用程序层将新读数插入Reading表中,捕获ReadingId
。
然后,应用程序会针对每个( SensorId
, AlertTypeId
)组合的警报设置检查读数。 此时{SensorId, AlertTypeId, ReadingId, IsAlert}
将创建一个对象集合{SensorId, AlertTypeId, ReadingId, IsAlert}
,并为每个对象设置IsAlert
标志。
然后{SensorId, AlertTypeId, ReadingId, IsAlert}
从集合中检查Alert表以查找每个对象{SensorId, AlertTypeId, ReadingId, IsAlert}
活动警报。
如果IsAlert
为TRUE并且( SensorId
, AlertTypeId
)组合没有活动警报, AlertTypeId
向Alert表添加一个新行, FirstReadingID
指向当前的ReadingId
。 IsActive
设置为TRUE, IsAcknowledged
为FALSE。
如果IsAlert
为TRUE并且( SensorId
, AlertTypeId
)组合存在活动警报,则通过设置指向当前ReadingId
的LastReadingID
来更新该行。
如果IsAlert
为FALSE并且( SensorId
, AlertTypeId
)组合存在活动警报,则通过设置IsActive
FALSE来更新该行。
如果IsAlert
为FALSE并且( SensorId
, AlertTypeId
)组合没有活动警报,则不会修改警报表。
您必须处理的主要“三角形”是传感器,[传感器]读数和警报。 假设你必须跟踪活动的发生(而不是“一次加载所有”设计),你的第三个解决方案与我们最近做的事情类似。 一些调整,它看起来像:
[Location]
LocationId
[Sensor]
SensorId
LocationId
CurrentSensorState -- Denormalized data!
[SensorReading]
SensorReadingId
SensorState
Value
Timestamp
[SensorStateLog]
SensorId
Timestamp
SensorState
Status -- Does what?
IsInAlert
(Primary key is {SensorId, Timestamp})
“SensorState”可以是SensorStateId,其中关联的查找表列出(并约束)所有可能的状态。
我们的想法是,传感器每个传感器包含一行并显示其当前状态。 SensorReading通过传感器读数连续更新。 如果和当给定的传感器当前状态发生变化(即新的读数状态与传感器的当前状态不同),则更改当前状态并向SensorStateLog添加一行,显示状态的变化。 (可选地,您可以使用“状态结束”时间戳更新该传感器的“先前”条目,但这是繁琐的代码。)
传感器表中的CurrentSensorState是非规范化数据,但如果维护得当(如果你有数百万行),它将使查询当前状态的效率大大提高,因此值得付出努力。
所有这一切的明显缺点是警报不再是一个实体,它们变得更难以跟踪和识别。 如果这些必须立即可识别和可用,那么您的第三个方案将无法满足您的需求。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.