合并将导致查询

2024-10-02 00:38:15 发布

您现在位置:Python中文网/ 问答频道 /正文

我有以下型号:

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()方法通过查询PropertyAvailability来检查{}是否在给定的时间段内可用。在

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

示例数据

enter image description here

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

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

I read this earlier(它通过合并结果引入了一个类似的问题和解决方案),但是在编写使用Django的ORM或使其与原始SQL一起工作时遇到了困难。在

那么,我如何编写一个查询(最好使用ORM)来实现这一点呢?或者也许有更好的解决方案我不知道?在

其他注意事项

avail_date_fromavail_date_to必须与PropertyAvailabilitydate_from和{}字段匹配:

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

这是因为我需要查询在给定的时间段内Property是否可用。在

软件规格

  • 迪亚戈1.11
  • PostgreSQL 9.3.16版

Tags: tonamefromverbosedaterateismodels
2条回答

我的解决方案是检查date_from或{}字段是否包含在我们感兴趣的时段中。我使用^{}对象来执行此操作。正如上面的注释中所提到的,我们还需要包括包含我们感兴趣的整个时期的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对象。此外,unique约束很可能包含de_property字段。在

应该能够做的是聚合要查询的数据,并合并任何重叠(或相邻)范围。在

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视图中,并有一个引用它的模型。在

但是,值得注意的是,它查询整个源表,因此您可能希望应用筛选;可能是通过de_property。在

比如:

^{pr2}$

另外,您可能需要考虑使用Postgres的date range对象,因为这样可以防止start > finish(自动),也可以使用排除约束来防止给定属性的句点重叠。在

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

相关问题 更多 >

    热门问题