Coalesce导致QuerySet

时间:2017-04-18 08:58:54

标签: python django postgresql

我有以下型号:

class Property(models.Model):
    name = models.CharField(max_length=100)

    def is_available(self, avail_date_from, avail_date_to):
        # Check against the owner's specified availability
        available_periods = self.propertyavailability_set \
                                .filter(date_from__lte=avail_date_from, \
                                        date_to__gte=avail_date_to) \
                                .count()
        if available_periods == 0:
            return False
        return True

class PropertyAvailability(models.Model):
    de_property = models.ForeignKey(Property, verbose_name='Property')
    date_from = models.DateField(verbose_name='From')
    date_to = models.DateField(verbose_name='To')
    rate_sun_to_thurs = models.IntegerField(verbose_name='Nightly rate: Sun to Thurs')
    rate_fri_to_sat = models.IntegerField(verbose_name='Nightly rate: Fri to Sat')
    rate_7_night_stay = models.IntegerField(blank=True, null=True, verbose_name='Weekly rate')
    minimum_stay_length = models.IntegerField(default=1, verbose_name='Min. length of stay')

    class Meta:
        unique_together = ('date_from', 'date_to')

基本上,每个Property都可以使用PropertyAvailability的实例指定其可用性。由此,Property.is_available()方法通过查询Property来检查PropertyAvailability在给定时间段内是否可用。

除以下情况外,此代码工作正常:

示例数据

enter image description here

使用当前的Property.is_available()方法,如果我要在2017年1月2日的 2017 和2017年1月的 5之间搜索可用性它' d工作,因为它匹配#1

但如果我要在2017年1月4日的 4 和2017年1月8日之间进行搜索,它将不会返回任何内容,因为日期范围重叠在多个结果之间 - 它既不匹配#1 也不匹配#2

I read this earlier(通过合并结果引入了类似的问题和解决方案)但是在使用Django的ORM编写它或者使用原始SQL时无法编写它。

那么,我怎样才能编写一个查询(最好是使用ORM)呢?或许还有一个我不知道的更好的解决方案?

其他说明

avail_date_fromavail_date_to必须与PropertyAvailability的{​​{1}}和date_from字段匹配:

  • date_to必须为> = avail_date_from
  • PropertyAvailability.date_from必须为< = avail_date_to

这是因为我需要在给定时间段内查询中的PropertyAvailability.date_to

软件规格

  • Django 1.11
  • PostgreSQL 9.3.16

2 个答案:

答案 0 :(得分:1)

我的解决方案是检查date_from的{​​{1}}或date_to字段是否包含在我们感兴趣的时间段内。我使用Q执行此操作对象。正如上面的评论中所提到的,我们还需要包含涵盖我们感兴趣的整个时期的PropertyAvailability个对象。如果我们找到多个实例,我们必须检查可用性对象是否是连续的。

PropertyAvailability

我觉得有必要提一下,数据库模型并不能保证数据库中没有重叠的from datetime import timedelta from django.db.models import Q class Property(models.Model): name = models.CharField(max_length=100) def is_available(self, avail_date_from, avail_date_to): date_range = (avail_date_from, avail_date_to) # Check against the owner's specified availability query_filter = ( # One of the records' date fields falls within date_range Q(date_from__range=date_range) | Q(date_to__range=date_range) | # OR date_range falls between one record's date_from and date_to Q(date_from__lte=avail_date_from, date_to__gte=avail_date_to) ) available_periods = self.propertyavailability_set \ .filter(query_filter) \ .order_by('date_from') # BEWARE! This might suck up a lot of memory if the number of returned rows is large! # I do this because negative indexing of a `QuerySet` is not supported. available_periods = list(available_periods) if len(available_periods) == 1: # must check if availability matches the range return ( available_periods[0].date_from <= avail_date_from and available_periods[0].date_to >= avail_date_to ) elif len(available_periods) > 1: # must check if the periods are continuous and match the range if ( available_periods[0].date_from > avail_date_from or available_periods[-1].date_to < avail_date_to ): return False period_end = available_periods[0].date_to for available_period in available_periods[1:]: if available_period.date_from - period_end > timedelta(days=1): return False else: period_end = available_period.date_to return True else: return False 对象。此外,唯一约束最有可能包含PropertyAvailability字段。

答案 1 :(得分:1)

Postgres没有办法做到这一点:它有联合运算符和相邻范围的组合,但没有任何东西可以聚合重叠/相邻范围的集合。

但是,您可以编写一个将它们组合在一起的查询,但是如何使用ORM这一点并不明显(尚未)。

这是一个解决方案(留作http://schinckel.net/2014/11/18/aggregating-ranges-in-postgres/#comment-2834554302的评论,并调整以组合相邻的范围,这似乎是你想要的):

SELECT int4range(MIN(LOWER(value)), MAX(UPPER(value))) AS value
  FROM (SELECT value, 
               MAX(new_start) OVER (ORDER BY value) AS left_edge
          FROM (SELECT value,  
                       CASE WHEN LOWER(value) <= MAX(le) OVER (ORDER BY value) 
                            THEN NULL 
                            ELSE LOWER(value) END AS new_start
                  FROM (SELECT value, 
                               lag(UPPER(value)) OVER (ORDER BY value) AS le
                          FROM range_test
                       ) s1
               ) s2
       ) s3
 GROUP BY left_edge;

从ORM中获取此查询的一种方法是将其放在Postgres VIEW中,并拥有一个引用它的模型。

但是,值得注意的是,这会查询整个源表,因此您可能希望应用过滤;可能是de_property

类似的东西:

CREATE OR REPLACE VIEW property_aggregatedavailability AS (
  SELECT de_property
         MIN(date_from) AS date_from,
         MAX(date_to) AS date_to
    FROM (SELECT date_from,
                 date_to,
                 MAX(new_from) OVER (PARTITION BY de_property
                                     ORDER BY date_from) AS left_edge
            FROM (SELECT de_property,
                         date_from,
                         date_to,
                         CASE WHEN date_from <= MAX(le) OVER (PARTITION BY de_property
                                                              ORDER BY date_from)
                              THEN NULL
                              ELSE date_from
                         END AS new_from
                    FROM (SELECT de_property,
                                 date_from,
                                 date_to,
                                 LAG(date_to) OVER (PARTITION BY de_property 
                                                    ORDER BY date_from) AS le
                            FROM property_propertyavailability
                         ) s1
                 ) s2
         ) s3
   GROUP BY de_property, left_edge
)

另外,您可能需要考虑使用Postgres的日期范围对象,因为这样您可以使用排除约束来阻止start > finish(自动),但也可以防止给定属性的重叠时段。

最后,另一种解决方案可能是拥有一个派生表,该表基于获取可用时段并反转它们来存储 un 可用性。这使得编写查询更简单,因为您可以编写直接重叠,但是否定(即,如果没有重叠的不可用时段,则属性可用于给定时段)。我在生产系统中为员工可用性/不可用性这样做,需要进行许多检查。请注意,这是一个非规范化的解决方案,并依赖于触发器功能(或其他更新)来确保它保持同步。