【问题标题】:What is the recommended schema design for dynamic dates in Django for PostgreSQL?Django for PostgreSQL 中动态日期的推荐架构设计是什么?
【发布时间】:2016-01-15 00:08:31
【问题描述】:

我们有一个专注于时间线演化可视化的 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. 对于我们拥有的每个可视化查询,我们必须始终在生命周期下加入里程碑(接缝与此规范化相矛盾)

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

  1. 里程碑日期字段中的动态日期“今天”

如何在数据库中存储动态(更改)日期,使其对 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. 我们希望增强“今天”控制字符串

所以我们可以像这样存储里程碑定义:

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

提前感谢任何cmets,建议!

编辑

想象这样的数据:

(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 之间具有任何里程碑的所有项目。如果我们今天 (2015-10-29) 运行 SELECT,则 item3 和 item5 应该在输出中。如果我们在 2015 年 12 月 15 日运行该 SELECT,则 item3 和 item5 不能在输出中。

【问题讨论】:

  • “如何在数据库中存储动态(更改)日期,使其对 SELECTS 有效并与存储的静态日期相媲美?” 通常,我希望看到这样的东西表示为数据库视图。视图可以将诸如“TODAY”之类的文本解析为 current_date(或其他)的值,并将结果转换为 DATE。客户端代码使用视图,而不是基表。
  • 视图的有趣想法 - 但是我不确定如何通过 Django ORM 处理。我还假设必须每天重新创建视图。 (为了正确呈现 TODAY 数据。
  • 这是一篇不错的文章,涵盖了 Django 中的视图:blog.rescale.com/using-database-views-in-django-orm
  • 我添加了数据以便更好地了解问题。

标签: python django database postgresql database-design


【解决方案1】:

您应该在日期中使用 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)

【讨论】:

  • 有趣的方法,但是我不明白这将如何帮助制作像所描述的那样的 SELECT 语句。您能否在您的建议中给出调整 SELECT 的示例?
  • 或者是用 2 个 SELECT 代替吗?选择所有 toady 的第一个 = 真正的里程碑,如果现在对标准日期范围有效,第二个 SELECT 使用传统的日期属性?
  • 我现在明白了。接缝是正确的方向。我已经在考虑优化具体日期(1001-01-01)可以用作今天的行为,所以我会保存属性:) 我会检查你的答案作为解决方案如果你在那里添加示例 SELECT.
【解决方案2】:

赞同 arkadyzalko 关于 DateTimeField 的建议,但会注意一些额外的事情。

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

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

  1. 为时代边界使用范围类型
  2. 使用排除约束确保它们不重叠
  3. 加入事件日期和时代之间的重叠。

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 @&gt; dates.date

GiST 索引还允许您通过索引查找来执行此操作。

【讨论】:

  • 谢谢,我不知道范围类型!我是否理解正确,建议是将模型的概念更改为:1 Lifecycle has 0 .. n ranged eras? (而不是当前的 1 生命周期有 0 .. n 里程碑)
  • 您如何将今天视为范围日期的边界?使用无?
  • 嗯,PostgreSQL 理解“今天”的意思是now()::date,所以一旦你保存它,它就固定在它保存的日期。如果你想要一个无限期的未来,那么使用时间戳而不是日期并使用“无限”
  • 就您的第一个问题而言,在某种程度上是的。但关键是你最终会进入一个具有定义范围的时代,例如“[2011-01-01,2011-02-01)”(即从一月一日开始,直到但不包括二月一日)。将发布第二条评论以显示查询。
  • 用示例查询更新了答案
猜你喜欢
  • 2013-10-13
  • 2014-09-04
  • 2010-10-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-04-03
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多