数据库设计:用时间戳列替换布尔列?

时间:2010-10-12 16:22:20

标签: sql database oracle database-design relational-database

早些时候我用这种方式创建了表格:

create table workflow (
    id number primary key,
    name varchar2(100 char) not null,
    is_finished number(1) default 0 not null,
    date_finished date
);

列is_finished指示工作流程是否已完成。列date_finished是工作流完成的时间。

然后我有了一个想法“我不需要is_finished,因为我可以说:data_finished不是null”,而我设计的没有is_finished列:

create table workflow (
    id number primary key,
    name varchar2(100 char) not null,
    date_finished date
);

(我们使用Oracle 10)

是好还是坏?我听说你没有一个带有NULL值的列的索引,所以where data_finished is not null在大表上会非常慢。

10 个答案:

答案 0 :(得分:15)

  

是好还是坏?

好主意。

您已经消除了冗余列占用的空间; DATE列具有双重功能 - 您知道工作已完成,

  

我听说你不能在一个带有NULL值的列上有索引,所以“data_finished不为null”在大表上会非常慢。

这是不正确的。 Oracle索引忽略NULL值。

You can create a function based index in order to get around the NULL values not being indexed,但我遇到的大多数DBA 真的不喜欢它们,所以要为战斗做好准备。

答案 1 :(得分:11)

有一种正确的方法来索引空值,它不使用FBI。 Oracle 索引空值,但它不会索引树中的空LEAF值。所以,你可以删除列is_finished并创建这样的索引。

CREATE INDEX ON workflow (date_finished, 1);

然后,如果您查看有关此查询的解释计划:

SELECT count(*) FROM workflow WHERE date_finished is null;

您可能会看到正在使用的索引(如果优化程序很满意)。

回到原来的问题:在这里查看各种答案,我认为没有正确的答案。如果没有必要,我可能有个人偏好消除列,但我也不喜欢重载列的含义。这里有两个概念:

  1. 记录已经完成。 is_finished
  2. 记录在特定日期结束。 date_finished
  3. 也许你需要将这些分开,也许你不需要。当我考虑删除is_finished列时,它让我感到困扰。在路上,记录完成的情况可能会出现,但你不确切知道何时。也许您必须从​​其他来源导入数据并且日期未知。当然,这不符合现在的业务需求,但事情会发生变化。那你怎么办呢?好吧,你必须在date_finished列中添加一些虚拟值,现在你已经对数据进行了一些妥协。不是很可怕,但那里有一个磨擦。当我做这样的事情的时候,我脑子里的那个小小的声音喊着你做错了

    我的建议,保持分开。你在谈论一个很小的列和一个非常瘦的索引。存储不应成为问题。

      

    代表规则:折叠知识   进入数据,所以程序逻辑可以   愚蠢而健壮。

         

    -Eric S. Raymond

答案 2 :(得分:5)

  

是好还是坏?我听说你不能在一个有NULL值的列上有索引,所以“data_finished不为null”在大表上会非常慢。

Oracle会对可空字段进行索引,但不会将 NULL值编入索引

这意味着您可以在标记为NULL的字段上创建索引,但此字段中包含NULL的记录不会将其添加到索引中。

这反过来意味着,如果您设置date_finished NULL,则索引将更小,因为NULL值不会存储在索引。

因此,date_finished上涉及范围搜索相等的查询实际上会表现得更好。

此解决方案的缺点当然是涉及NULL date_finished值的查询必须恢复为全表扫描。

您可以通过创建两个索引来解决此问题:

CREATE INDEX ON mytable (date_finished)
CREATE INDEX ON mytable (DECODE(date_finished, NULL, 1))

并使用此查询查找未完成的工作:

SELECT  *
FROM    mytable
WHERE   DECODE(date_finished, NULL, 1) = 1

这将表现得像分区索引:完整的作品将被第一个索引索引;不完整的将被第二个索引。

如果您不需要搜索完整或不完整的作品,您可以随时删除相应的索引。

答案 3 :(得分:4)

对于所有那些说这个专栏是浪费空间的人:

Double Duty在数据库中不是一件好事。你的主要目标应该是清晰。许多系统,工具,人们都会使用您的数据。如果你通过在其他列中隐藏意义来伪装值,那么你就会让另一个系统或用户感到错误。

任何认为节省空间的人都是完全错误的。

你需要在该日期列上有两个索引......一个将是OMG建议的基于功能的。它看起来像这样:

NVL(Date_finished,TO_DATE('01 -JAN-9999'))

因此,要找到未完成的工作,您必须确保正确编写where子句

看起来像这样:

WHERE NVL(Date_finished,TO_DATE('01 -JAN-9999'))= TO_DATE('01 -JAN-9999')

是的。那太清楚了。它完全比

WHERE IS_Unfinished ='YES'

您希望在同一列上拥有第二个索引的原因是该日期的每个其他查询...您不希望使用该索引按日期查找作业。

让我们看看你在OMG的建议等人所取得的成就。

你使用了更多的空间,你已经混淆了数据的含义,你更容易犯错误......赢家!

有时似乎程序员仍然生活在70年代,当时MB的硬盘空间是房子的首付款。

你可以节省空间,而不会放弃很多清晰度。使Is_unfinished为Y或NULL ... IF 您将只使用该列来查找“要做的工作”。这将使该指数保持紧凑。它只会与未完成的行一样大(以这种方式你利用未编入索引的空值而不是被它搞砸)。你在桌子上放了一点空间,但总比它少于FBI。你需要1个字节的列,你只需要索引未完成的行,这样'工作的一小部分可能保持相当不变。无论你是否想要找到它们,FBI都需要每个行7个字节。该指数将与表格的大小保持同步,而不仅仅是未完成工作的规模。

回复OMG的评论

在他/她的评论中,他/她声明要找到未完成的工作,你只需要使用

WHERE date_finished IS NULL

但在他的回答中他说

  

您可以创建基于函数的索引,以便绕过未编入索引的NULL值

如果您按照他指向的链接,使用NVL将空值替换为其他任意值,那么我不确定还有什么可以解释。

答案 4 :(得分:3)

就表格设计而言,我认为您删除is_finished列是很好的,因为您说没有必要(这是多余的)。如果没有必要,则无需存储额外数据,只会浪费空间。在性能方面,我不认为这是NULL值的问题。应该忽略它们。

答案 5 :(得分:2)

我会使用null作为索引工作,正如其他答案中已经提到的那样,除了“WHERE date_finished IS NULL”之外的所有查询(所以它取决于你是否需要使用该查询)。我肯定不会像答案所建议的那样使用像9999这样的异常值:

  

您还可以使用“虚拟”值(例如9999年12月31日)作为未完成工作流的date_finished值

9999年的异常值会影响效​​果,因为(来自http://richardfoote.wordpress.com/2007/12/13/outlier-values-an-enemy-of-the-index/):

  

范围扫描的选择性基本上由CBO计算为感兴趣范围内的值除以可能值的全部范围(IE 。最大值减去最小值)

如果使用类似9999的值,则DB会认为存储在字段中的值的范围是例如2008-9999而不是实际的2008-2010;所以任何范围查询(例如“2008年和2009年之间”)似乎都会覆盖可能值范围的极小百分比,而实际覆盖范围的一半左右。它使用此统计信息来表示,如果所涵盖的可能值的百分比很高,则可能会有很多行匹配,然后全表扫描将比索引扫描更快。如果数据中存在异常值,它将无法正确执行此操作。

答案 6 :(得分:1)

好主意删除其他人所说的可导出值列。

还有一个想法是,通过删除列,您将避免需要编写代码的矛盾条件,例如当is_finished = No和finished_date =昨天等等时会发生什么。

答案 7 :(得分:1)

要解析索引/非索引列,简单地加入两个表就不容易了,如下所示:

-- PostgreSQL
CREATE TABLE workflow(
    id SERIAL PRIMARY KEY
  , name VARCHAR(100) NOT NULL
);

CREATE TABLE workflow_finished(
    id INT NOT NULL PRIMARY KEY REFERENCES workflow
  , date_finished date NOT NULL
);

因此,如果workflow_finished中存在记录,则此工作流程已完成,否则不会。在我看来,这很简单。

查询未完成的工作流程时,查询变为:

-- Only unfinished workflow items
SELECT workflow.id
FROM workflow
WHERE NOT EXISTS(
  SELECT 1
  FROM workflow_finished
  WHERE workflow_finished.id = workflow.id);

也许你想要原始查询?有国旗和日期?这样查询:

-- All items, with the flag and date
SELECT
    workflow.id
  , CASE
    WHEN workflow_finished.id IS NULL THEN 'f'
    ELSE                                   't'
    END AS is_finished
  , workflow_finished.date_finished
FROM
            workflow
  LEFT JOIN workflow_finished USING(id);

对于数据的消费者,可以而且应该根据他们的需要创建视图。

答案 8 :(得分:0)

作为基于函数的索引的替代方法,您还可以使用“虚拟”值(例如9999年12月31日,或者在最早的预期date_finished值之前一天)作为未完成工作流的date_finished值。

编辑:备注虚拟日期值,注释后。

答案 9 :(得分:0)

我更喜欢单列解决方案。

但是,在我最常使用的数据库中,NULL会包含在索引中,因此搜索开放工作流的常见情况会很快,而在您的情况下会慢一些。由于搜索开放工作流程的情况可能是您最常见的事情之一,因此您可能需要冗余列来支持该搜索。

测试性能以确定您是否可以在性能方面使用更好的解决方案,然后在必要时再回到不太好的解决方案。