【问题标题】:Database: when to split into separate tables?数据库:何时拆分为单独的表?
【发布时间】:2010-10-05 13:09:48
【问题描述】:

假设我有两种不同类型的传感器:一种监测模拟电压(例如在温度传感器上),另一种测量某物是打开还是关闭(开关传感器)。

我无法决定是否要一张桌子:

[Sensor]
Id : PK
UpperLimit : FLOAT
UpperLimitAlertDelay : INT
LowerLimit : FLOAT
LowerLimitAlertDelay : INT
IsAnalog : BOOL

[SensorReading]
Id : PK
SensorId : FK
AnalogValue : FLOAT
IsOn : BOOL

或将其全部分成单独的表:

[AnalogSensor]
Id : PK
UpperLimit : FLOAT
UpperLimitAlertDelay : INT
LowerLimit : FLOAT
LowerLimitAlertDelay : INT

[AnalogSensorReadings]
Id : PK
AnalogSensorId : FK
Value : FLOAT

[SwitchSensor]
Id : PK
OnTooLongAlertDelay : INT

[SwitchSensorReadings]
Id : PK
SwitchSensorId : FK
IsOn : BOOL

目前我将它作为一张表使用,当不将其用作模拟传感器时,我使用“UpperLimitAlertDelay”作为“OnTooLongAlertDelay”。

在代码中,我通过传感器表上的布尔标志进行区分并创建适当的对象(即 AnalogSensor 或 SwitchSensor),但我想知道在数据库级别将其分离出来是否更整洁/更合适。

对于这种决定,您会使用什么经验法则?它们在一个层面上是不同的实体,但在另一个层面上你可以说它们都只是传感器。

这通常是我在创建数据库时永远无法决定采取什么方向的地方。也许每当我使用 bool 来区分哪些字段的含义/应该使用时,它真的应该是一个单独的表?

对这个主题或这个特定问题的一般想法表示赞赏。

谢谢!

编辑:一些进一步的信息。

开关传感器监控诸如门是否打开、冰箱压缩机是否运行、电器是否打开等信息。

图表和报告可以在任何传感器上生成,因此它们的使用方式相同;只是数据将根据类型打开/关闭或模拟值。

所以基本上它们通常被同等对待。

在读数表中,总是一行用于 ONE 传感器的 ONE 读数。

到目前为止,这些意见似乎都是很主观的——我想这两种方式都各有利弊。

以上信息是否会改变任何人的看法?

谢谢! 标记。

【问题讨论】:

    标签: database database-design


    【解决方案1】:

    这和your other question 是同一个应用程序/数据库吗?

    在这种情况下,答案已在 Data Model 中提供。

    如果不是同一个应用程序/数据库,或者这个问题没有得到充分回答,请发表或评论。例如。根据之前的信息,我对其进行建模,以便 SensorType 表区分 Sensor(模拟或布尔值)......但我们可以:

    • 在传感器级别区分它,

    • 或将Reading 设置为子类型:ReadingAnalogReadingSwitch。这可以让生成图表等的程序更容易一些。

    【讨论】:

    • 是的 - 我很快就会结束/评论以前的问题。现在病了:-(
    【解决方案2】:

    表通常被拆分为逻辑上不同的“things”,因此您不会有两次相同的“things”。例如,您不希望:

    [SensorReadings]
    Id : PK
    UpperLimit : FLOAT
    UpperLimitAlertDelay : INT
    LowerLimit : FLOAT
    LowerLimitAlertDelay : INT
    IsAnalog : BOOL
    AnalogValue : FLOAT
    IsOn : BOOL
    

    因为您将传感器和它的读数混合在一行中。传感器是不同于其读数的事物

    [Sensors]                      [SensorReadings]
    Id                             Id
    UpperLimit                     SensorID
    UpperLimitAlertDelay           Reading
    LowerLimit
    LowerLimitAlertDelay
    IsAnalog
    Manufacturer
    SerialNumber
    LastInspectionDate
    ...
    

    我不会这样做的一件事是将“传感器”分成两个表。传感器就是传感器,它就是这样。就像一个客户就是一个客户,或者一首歌就是一首歌。您将有一张歌曲表,而不是一张古典歌曲表,而另一张表则用于其他所有歌曲。如果您确实将传感器分成两个表,您可以想象有两个传感器具有相同的ID。传感器是唯一的实体,它们应该在同一个表中,并且都具有唯一的 ID。传感器是模拟还是数字的事实是传感器的属性。


    您的问题是独一无二的 - 您的传感器可以有不同逻辑格式的读数;有些是模拟浮点值,有些是数字布尔值。当并非所有传感器读数都适合相同的逻辑列数据类型(即浮点数与布尔值)时,您正在为如何存储传感器的“读数”而苦苦挣扎。它归结为实用性,以及什么对系统最有利。

    您可以将所有读数存储在浮点数列中:

    [SensorReadings]
    Id  SensorID  Reading
    ==  ========  =======
     1   3728     120.2
     2   3728     120.3
     3     89         1
     4     89         0
     5   3728     120.2
     6     89         0
    

    但是现在您必须知道将浮点值 0,1 解释为逻辑 on,off。这很难做到吗?我个人不这么认为。诚然,它没有充分利用数据库引擎中可用的数据类型,但我并不在乎。您将加入SensorReadingsSensors,因此您将拥有IsAnalog 列来帮助您解释。换句话说:

    SELECT Id, SensorID, Reading, Sensors.IsAnalog
    FROM SensorReadings sr
       INNER JOIN Sensors s ON sr.SensorID = s.SensorID
    

    会给你一个非常容易解析的结果集:

    Id  SensorID  Reading  IsAnalog
    ==  ========  =======  ========
     1   3728     120.2     false
     2   3728     120.3     false
     3     89         1      true
     4     89         0      true
     5   3728     120.2     false
     6     89         0      true
    

    您甚至可以创建一个帮助视图(或只是一个查询),将读数解码为AnalogReadingDigitalReading

    CREATE VIEW SimpleSensorReadings AS
    
    SELECT Id, SensorID, Reading AS RawReading,
        CASE Sensors.IsAnalog
        WHEN 0 THEN Reading
        ELSE NULL
        END AS AnalogReading,
    
        CASE Sensors.IsAnalog
        WHEN 1 THEN CAST(Reading AS BOOL)
        ELSE NULL
        END AS DigitalReading,
        Sensors.IsAnalog
    FROM SensorReadings sr
       INNER JOIN Sensors s ON sr.SensorID = s.SensorID
    

    这会给你:

    [SimpleSensorReadings]
    Id  SensorID  RawReading  AnalogReading  DigitalReading  IsAnalog
    ==  ========  ==========  =============  ==============  ========
     1   3728        120.2         120.2                       true
     2   3728        120.3         120.3                       true
     3     89            1                       true         false
     4     89            0                      false         false
     5   3728        120.2         120.2                       true
     6     89            0                      false         false
    

    这取决于谁必须处理结果。我可以很容易地想象代码首先检查“IsAnalog”列,然后根据需要读出AnalogReadingDigitalReading


    你可以按照你最初的建议去做;将它们分成多个表。但现在问题变成了:你如何访问数据?在我看来,如果我有这个传感器读数系统,在某些时候我将不得不对它们做点什么——把它们展示给用户。现在我必须跳过障碍重新加入数据:

    SELECT ID, AnalogSensorID AS SensorID, 
       Value AS RawReading, Value AS AnalogReading, 
       true AS IsAnalog
    FROM AnalogSensorReadings
    
    UNION ALL 
    
    SELECT ID, SwitchSensorID AS SensorID, 
       CAST(IsOn AS float) AS RawReading, null AS AnalogReading, IsOn AS DigitalReading,
       false AS IsAnalog
    

    给你

    Id  SensorID  RawReading  AnalogReading  DigitalReading  IsAnalog
    ==  ========  ==========  =============  ==============  ========
     1   3728        120.2         120.2                       true
     2   3728        120.3         120.3                       true
     1     89            1                       true         false
     2     89            0                      false         false
     3   3728        120.2         120.2                       true
     3     89            0                      false         false
    

    除了现在“Id”也很难解码,因为两个不同的读数可以有相同的“ID”。读书就是读书,应该是独一无二的。

    您可能正在寻找的折衷方案是您最初拥有的。

    [SensorReadings]
    Id  SensorID  AnalogReading  DigitalReading
    ==  ========  =============  ==============
     1   3728        120.2         
     2   3728        120.3         
     3     89                       true
     4     89                      false
     5   3728        120.2         
     6     89                      false
    

    是的,这会给您留下很多 (null) 值 - 但是将表重新连接在一起的费用是一个实际问题,必须考虑到您的设计决策。


    我认为它就像 Windows 中的注册表。 key 包含 value。您并不真正关心该值的存储方式,只要您可以读取它,因为类型在逻辑上是这样的。为了在数据库中实现这一点,我将使用多个数据类型列,并根据需要读取它们。

    【讨论】:

      【解决方案3】:

      问题是:从您的系统的角度来看,它们是一回事吗?如果是这样,它们属于一个表。如果不是,它们属于两个表。

      通常这是不费吹灰之力的。 “员工”和“保险计划”是两个不同的东西。 “Employee named Bob”和“Employee named Sally”是同一事物的两个实例。

      有时它更棘手。 “卡车”和“船”是两个不同的东西,还是它们都只是“车辆”的子类型?这取决于您的系统的观点。如果您要出售它们,它们可能是同一回事。您可能不在乎一个浮动而另一个不浮动,您只关心它们的成本以及您有多少库存等。也就是说,您保留有关它们的相同数据并在相同的查询中使用它们。但是,如果您的系统管理着一个捕鱼船队,而对于小船,您关心的是船员是谁、他们的工资是多少以及他们今天捕获了多少鱼,而对于卡车,您关心的是它什么时候会出现在码头接今天的渔获,以及你必须向货运公司支付每磅多少钱,它们可能是两件截然不同的事情。

      确定它们是同一事物的迹象是:

      • 它们具有相同的数据(当然不是相同的值,而是相同的字段)
      • 查询通常会针对两者应用,几乎没有区别

      如果这些不是真的,它们可能不是一回事。

      也就是说,如果你发现对于类型 1,字段 A 将有一个值,而字段 B 将始终为空,而对于类型 2,字段 A 将为空,而字段 B 将有一个值,那么它们将失败数据测试。

      如果您发现如果将它们放在一个表中,那么您通常必须为所有查询添加类型检查,以便只得到正确的查询,然后它们无法通过数据测试。如果您得出结论,如果您将它们放在两个单独的表中,您将不得不不断编写查询来对两个表执行连接或联合以获取两者,那么它们通过了数据测试。

      在您的示例中,您没有告诉我们您打算如何处理数据,因此我无法讨论查询测试。显然,对于两种类型的传感器,您有不同的数据——数字与开/关——所以马上就可以对两种不同的事物进行投票。但是然后我们回到这对您的系统实际上是如何重要的。如果对于温度探头,您将生成随时间变化的温度图或监视它们是否在特定范围内,对于开/关开关,您将在它们打开时触发一个过程并在它们关闭时停止它,它们可能是两个不同的事物。如果在这两种情况下,您都会在任何给定时间生成值报告(无论是数字还是开/关),那么它们可能是同一件事。

      我倾向于认为它们可能不同,但不知道更多,我真的不能说。

      【讨论】:

        【解决方案4】:

        通常,您希望数据库设计中的冗余尽可能少。去查找Normal Forms,任何低于 BCNF 的东西通常都很难维护。一些应用程序使用更多的冗余来实现更高的读取性能,但会为此牺牲清晰度和写入性能,例如数据仓库。 连接可能会很慢,但是当相同的信息被存储两次时,它们比不一致的数据要好。

        因此,我建议使用较低的。假设您的传感器不再与完美的时间戳关联:突然,第一个布局建议严重失败。

        【讨论】:

        • 谢谢。不确定与完美时间戳相关联是什么意思?
        • 好吧,如果你有一个传感器,它总是写下一个温度值和一个湿度值,你可以用一个表来解决这个问题:(ID、时间、湿度、温度)。但是,一旦两者没有同时进行测量,您就必须将其拆分。而且由于您通常无法保证您的下一次硬件购买行为会相同,因此从一开始就拆分似乎更实用。
        • 这在任何一种设计中都不会发生。它总是一个传感器测量一件事。单个读数只是单个传感器的单个读数。
        【解决方案5】:

        我建议按照normalisation rules行事。

        根据需要,可以选择no-sql数据库。

        【讨论】:

          【解决方案6】:

          这是在执行对象-关系映射时需要做出的相当标准的设计决策。

          您提供的第一个选项称为 table-per-hierarchy,第二个选项是 table-per-concrete-class。还有其他变体,例如将抽象类映射到它们自己的表。一些 O-R 框架(例如 hibernate)提供现成的代码来实现其中一些模式。

          在某些搜索中使用其中一些关键字可以为您提供更多信息。有很多权衡需要考虑。我想首先要考虑的是您可能拥有多少种不同类型的传感器。

          要考虑的另一件事是报告。如果您要编写大量查询所有类型传感器的报告,那么每个类的表将需要联合,这可能不是我们所希望的。

          【讨论】:

            【解决方案7】:

            我怀疑这将取决于与未显示的其他实体的关系。如果有很多实体与一种类型的传感器相关但与另一种无关,那么将它们分开可能是有意义的 - 否则,我会倾向于使用更简单的设计(即两表方法,而不是比四表法)。

            我建议的一些更改:

            1. 将“UpperLimitAlertDelay”和“OnTooLongAlertDelay”拆分为单独的字段 - 据我了解,它们是不同的值,因此(在 1NF 下)应该是单独的字段。
            2. 在阅读表中添加一个日期时间戳字段。

            【讨论】:

              猜你喜欢
              • 2013-12-13
              • 2014-04-13
              • 1970-01-01
              • 2020-03-28
              • 2011-06-20
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多