【问题标题】:null values in optional columns可选列中的空值
【发布时间】:2012-06-15 18:44:48
【问题描述】:

假设我有一个 Table1,包含三列:ID(主键、身份)、A 和 B

现在,假设我有 3 个方法,假设它们在列方面没有任何共同之处:

方法一:C、D、E

方法2:F、G、H、I

方法三:J

我可以做一张桌子: ID、A、B、C、D、E、F、G、H、I、J、M

其中 M 是方法的名称(或方法 ID)。

但是,如果 90% 的时间使用方法 3,则会有很多空值。
这是一个问题吗?如果是这样,有没有更好的方法来设置它?

如果我将每个方法都设为自己的表实体,我如何确保每个 ID 都有一个与之匹配的方法?

如果我将其保留为一张表,如何确保仅填写 C、D、E,如果 M 为 1,则 F 到 J 为 NULL?


好吧,似乎有些人难以抽象思考,所以我将创建一个随机的具体示例来应用上述内容:

假设我有人们进行锻炼的记录。
每条记录总是有一个 ID(用于唯一标识事件)、TIME_STARTED 和 TIME_ENDED。

但是,根据他们进行的练习,需要不同的属性。假设只有三个练习:

椭圆机:INCLINE、LEVEL、SPEED

仰卧起坐:User_Weight、Reps、Delay、Extra_Weight

硬拉:Weight_Lifted

每个 ID 只能有一个“方法”。应用它,请参阅上面的问题。

【问题讨论】:

  • 方法是什么意思?如果您的表只有 ID、A、B,那么 c,d,e,f,g,h,i,j,m 来自哪里?
  • 您可能在这里问错了问题。最好从“我需要存储 X 数据,最佳架构是什么?”开始。就目前而言,由于我们没有上下文,因此很难提供帮助,不知道 ABCDEFGHIJM 是什么,但听起来您的方向非常错误。
  • 假设,我记录了一次测试运行,ID、A 和 B 始终是每次运行的一部分。但是,如果运行是使用方法 1 完成的,我需要将值存储在列 C、D 和 E 中。这可能是与运行方法 2 需要列 F、G、H、I 等不同的数据类型。
  • @M_M 它是故意通用的,我认为这是一个基本的东西。对不起,如果您觉得它太抽象了。
  • 抽象和模糊不是一回事。无论哪种方式,最大的问题是,您希望在存储数据后如何处理这些数据?

标签: sql-server database-design


【解决方案1】:

听起来您在这里遇到了supertype/subtype 的情况。在这种情况下,Table1 保存您的超类型,您可能希望创建一个不同的表来保存您的每个子类型。这些子类型表上的 PK 也将是超类型表的 FK。所以你会有这样的东西:

Supertype_table
|    ID(PK)   |  A  |  B  |

Subtype1_table
|  ID(PK&FK)  |  C  |  D  |  E  |

Subtype2_table
|  ID(PK&FK)  |  F  |  G  |  H  |  I  |

Subtype3_table
|  ID(PK&FK)  |  J  |

此架构的重点是确保您没有一堆大部分为空的行。对于每种方法,您都必须编写一个单独的查询来插入/更新相应的表。使用 SQL Server,您可以创建一个组合所有这些表并抽象出任何连接的视图:

CREATE VIEW MyView
SELECT Super.ID, Super.A, Super.B, 
Sub1.C, Sub1.D, Sub1.E, 
Sub2.F, Sub2.G, Sub2.H, Sub2.I, 
Sub3.J
FROM Supertype_table as Super
LEFT OUTER JOIN Subtype1_table as Sub1 on Super.ID = Sub1.ID
LEFT OUTER JOIN Subtype2_table as Sub2 on Super.ID = Sub2.ID
LEFT OUTER JOIN Subtype3_table as Sub3 on Super.ID = Sub3.ID

那么你就可以这样写:

SELECT ID, A, B, J
FROM MyView
WHERE J is not null

编辑:对于 OP 的评论

为了确保每个 ID 都在一个且仅一个表中,您需要在超类型表上使用某种标识符。因此,如果您有一个名为 TypeID 的列,您将创建该函数:

CREATE FUNCTION [dbo].[SubtypeofSuperID](@ID int)
RETURNS int
AS
BEGIN
  DECLARE @TypeID int;

    SELECT @TypeID = TypeID 
    FROM Supertype_table
    WHERE ID = @ID

  RETURN @TypeID 
END

然后,使用它,您可以对每个子类型表创建一个检查:

ALTER TABLE Subtype1_table CONSTRAINT [CK_CorrectSubtype1]
CHECK ( [dbo].[SubtypeofSuperID]([ID]) = 1 )
ALTER TABLE Subtype2_table CONSTRAINT [CK_CorrectSubtype2]
CHECK ( [dbo].[SubtypeofSuperID]([ID]) = 2 )
ALTER TABLE Subtype3_table CONSTRAINT [CK_CorrectSubtype3]
CHECK ( [dbo].[SubtypeofSuperID]([ID]) = 3 )

【讨论】:

  • 一开始我也在想这样的事情,但是我遇到了一个问题。可以有多个子类型表都指向同一个超类型行。如何确保只有一个超类型行适用于一个且仅一个子类型表(以及该表中的行)。
  • 例如如果我想要不相交的子类型,我想使用超类型中的一列作为子类型鉴别器。是否基于我的逻辑检查执行插入?还是可以将数据库设置为自动禁止此类插入?
  • 您必须以一种或另一种方式进行明确的检查。我使用函数+检查方法,它对我有用。我还会确保检查您的客户端代码,以便用户在尝试做他们不应该做的事情时收到一个很好的错误消息。对我来说,数据库检查是最后一道防线。
  • 为什么需要这个功能?你可以用 DRI 做到这一点
【解决方案2】:

几种方法...

首先,Jason 的回答是其中之一——将你的设计分成多个表格——这对于大多数事情来说都是一个很好的方法。 (又名更“标准化”的方法)

替代品...

1) 您可以在最后一个字段中包含一串名称-值对。例如:

ID   Method   DateTimeStart   DateTimeEnd   ValueString
1    1        2012-01-01...   2012-01-01... C:value,D:value,E:value
2    2        2012-01-01...   2012-01-01... F:value,G:value,H:value,I:value

因此,在您无法提前计划值类型的情况下,这可能会很方便。例如,也许您需要能够即时决定开始记录方法 1 的“W”值,而像在更规范化的设计中那样进行结构模型是不可行的。

这是我们的电子签名表单开发人员使用的常用方法。您可以想象一个网络表单,例如“部门休假申请”,有人将填写并以电子方式发送以供批准。然后出现了一个客户,他希望他们构建一个“采购订单”网络表单,该表单将具有必须记录的不同字段和值。他们没有为此创建一个全新的表(或向现有表添加列等),而是使用名称-值-对模型将所有表单数据存储在同一个表中,无论它是哪种形式。

2) 如果您不信任在单个大表上运行插入的人员/进程,您可以使用表触发器来强制执行完整性。例如,更新前触发器可以检查方法号,然后通过取消任何不适当的数据值来修改人员或进程试图插入的数据。

FWIW

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-06-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多