Django for PostgreSQL中动态日期的推荐架构设计是什么?

时间:2015-10-16 09:35:42

标签: python django database postgresql database-design

我们有一个专注于时间线演变可视化的Django应用程序。在概念上我们有以下关系:

1个具有1个或更多生命周期的项目(更多用于版本控制)

1生命周期有0..n里程碑

1里程碑是以YYYY-MM-DD形式存储为字符串的日期或特殊标记"今天",表示每日更改日期(动态 - 日期未说明,但直到今天为止某些州有效 - 如果今天小于下一个里程碑。)

数据的特点是对它们之间的里程碑和阶段有非常不同的解释。里程碑的数量也是多种多样的。然而,最多可使用7个里程碑。可以对生命周期记录的特征进行分组(具有相同含义的相同数量的里程碑)。

我们在PostgreSQL上使用Django,模型架构如下:

class Item(models.Model):
    ... other attributes
    lifecycle_actual     = models.IntegerField(null=True, default=-1, help_text="Selectable actual roadmap. Can be used to override the imported data. Use the ID of particular roadmap or -1 for the latest import.")

class Lifecycle(models.Model):
    ... other attributes
    lifecycle_group = models.ForeignKey(LifecycleGroup, help_text="Vizualization group.")
    date0 = models.CharField(max_length=10, blank=True)
    date1 = models.CharField(max_length=10, blank=True)
    date2 = models.CharField(max_length=10, blank=True)
    date3 = models.CharField(max_length=10, blank=True)
    date4 = models.CharField(max_length=10, blank=True)
    date5 = models.CharField(max_length=10, blank=True)
    date6 = models.CharField(max_length=10, blank=True)
    item = models.ForeignKey(Item, null=True, blank=True)

    def __unicode__(self):
        return self.item.fullname

class LifecycleGroup(models.Model):
    name = models.CharField(max_length=220, help_text="Name of the group") 
    era0_name = models.CharField(max_length=100, blank=True)
    era1_name = models.CharField(max_length=100, blank=True)
    era2_name = models.CharField(max_length=100, blank=True)
    era3_name = models.CharField(max_length=100, blank=True)
    era4_name = models.CharField(max_length=100, blank=True)
    era5_name = models.CharField(max_length=100, blank=True)
    era6_name = models.CharField(max_length=100, blank=True)

    era0_start_name = models.CharField(max_length=100, blank=True)
    era1_start_name = models.CharField(max_length=100, blank=True)
    era2_start_name = models.CharField(max_length=100, blank=True)
    era3_start_name = models.CharField(max_length=100, blank=True)
    era4_start_name = models.CharField(max_length=100, blank=True)
    era5_start_name = models.CharField(max_length=100, blank=True)
    era6_start_name = models.CharField(max_length=100, blank=True)

    era0_css_classes = models.CharField(max_length=150, blank=True)
    era1_css_classes = models.CharField(max_length=151, blank=True)
    era2_css_classes = models.CharField(max_length=152, blank=True)
    era3_css_classes = models.CharField(max_length=153, blank=True)
    era4_css_classes = models.CharField(max_length=154, blank=True)
    era5_css_classes = models.CharField(max_length=155, blank=True)
    era6_css_classes = models.CharField(max_length=156, blank=True)

    def __unicode__(self):
        return self.name

总体而言,它运作良好,但我们在报告问题时存在问题,例如:

  

哪些项目将在2015年12月达到某些特征的里程碑?

即使我们将模型代码更改为:

class Item(models.Model):
    ... other attributes
    lifecycle_actual     = models.IntegerField(null=True, default=-1, help_text="Selectable actual roadmap. Can be used to override the imported data. Use the ID of particular roadmap or -1 for the latest import.")

class Lifecycle(models.Model):
    ... other attributes
    # lifecycle group - not used anymore - have to duplicate info somehow in milestones
    # lifecycle_group = models.ForeignKey(LifecycleGroup, help_text="Vizualization group.")
    item = models.ForeignKey(Item, null=True, blank=True)

    def __unicode__(self):
        return self.item.fullname

class Milestone(models.Model):

    lifecycle = models.ForeignKey(Lifecycle, null=True, blank=True) 
    date = models.CharField(max_length=10, blank=True)
    name = models.CharField(max_length=100, blank=True)
    next_era = models.ForeignKey(Era, null=True, blank=True)

    impact = ... cca 4 choices
    order = models.PositiveIntegerField()

class Era(models.Model):
    name = models.CharField(max_length=100, blank=True)
    css_classes = models.CharField(max_length=150, blank=True) 

我们还有几个问题:

  1. 我们必须始终为生命周期中的每个vizualization查询加入里程碑(接缝与此规范化相矛盾)
  2.   

    针对此类需求推荐的架构设计是什么?

    1. 动态约会"今天"在里程碑日期字段
    2.   

      如何在数据库中存储动态(更改)日期,以便它对SELECTS有效并与存储的静态日期相比?

      所以我们可以这样做:

      SELECT * FROM item, lifecycle, milestone 
      WHERE item.id = lifecycle.item AND milestone.lifecycle = lifecycle.id 
      AND milestone.impact = 'huge'
      AND milestone.date between '2015-12-01' AND '2015-12-31'
      
      1. 我们希望提升今天"控制字符串
      2. 所以我们可以像这样存储里程碑定义:

        "today +365d" or "today -20d",  resp. “YYYY-MM-DD<today<YYYY-MM-DD”.
        

        提前感谢任何意见和建议!

        修改

        想象一下这样的数据:

        (item lifecycle => milestone name: date, ...)
        
        item1 => born: 2011-12-02, 
                 decline: 2015-06-01, 
                 end of life:2017-06-01 
        
        item2 => lifecycle check: 2015-08-01, 
                 some significant milestone: 2017-09-01,
                 depreciation ends: 2019-04-15, 
                 to be decommissioned: 2022-04-01
        
        item3 => initiated: 2012-05-08, 
                 life until at least: *today*, 
                 end of life: not declared 
        
        item4 => initiated: 2012-05-08, 
                 productive life until at least: *today +2 years*, 
                 end of life: 2032-08-01 
        
        item5 => born: unknown but latest *today*, 
                 end of life:2017-06-01 
        

        今天是正在进行的日期,即用户使用数据时的每个当前日期。

        我们假设我们应该选择所有项目,这些项目在2015-10-01和2015-12-01之间有任何里程碑。如果今天运行SELECT(2015-10-29),则item3和item5应该在输出中。如果我们在2015-12-15运行SELECT,则item3和item5不能在输出中。

2 个答案:

答案 0 :(得分:0)

您应该在日期中使用models.DateTimeField(default = timezone.now)并使用models.BooleanField来定义TODAY行为里程碑。

我想这更好:

class Milestone(models.Model):
    lifecycle = models.ForeignKey(Lifecycle, null=True, blank=True) 
    date = models.DateTimeField(max_length=10, blank=True)
    today = models.BooleanField(default=False)
    name = models.CharField(max_length=100, blank=True)
    next_era = models.ForeignKey(Era, null=True, blank=True)

答案 1 :(得分:0)

DateTimeField上反对arkadyzalko的建议,但会另外注意一些事项。

首先,我建议您阅读此documentation并重点关注范围类型。如果每个时代都达到一个范围(你在时代结束时提前知道)那么就可以很容易地添加索引来确定一个时代的内容 - 即问题是日期是否在一个范围内,你可以加入好。

所以从数据库设计的角度来看,我会看看

  1. 使用范围类型作为时代边界
  2. 使用排除约束来确保它们不重叠
  3. 加入活动日期与时代的重叠。
  4. Django应该支持所有这些(尽管您可能必须自己做排除约束)。

    作为一些日期范围查询的示例:

    test=# select '[2011-01-01,2011-02-01)'::daterange @> '2011-01-15'::date;
     ?column? 
    ----------
     t
    (1 row)
    
    test=# select '[2011-01-01,2011-02-01)'::daterange @> '2011-01-1'::date;
     ?column? 
    ----------
      t
    (1 row)
    
    test=# select '[2011-01-01,2011-02-01)'::daterange @> '2011-02-1'::date;
     ?column? 
     ----------
     f
    (1 row)
    

    但这意味着您可以加入范围内的值,s  FROM dates JOIN epoch ON epoch.range @> dates.date

    GiST索引还允许您使用索引查找执行此操作。