繁体   English   中英

在数据库中存储逻辑

[英]Storing logic inside database

这与存储过程无关(或者至少我不认为如此)。

可以说我有一个数据库。 数据库中包含一些城市以及在这些城市中发生的事件。 某些人将要使用该网站,他们希望在进入该网站时获得有关某些事件的通知。
用于指定要通知其事件的事件的规则应该是通用的。

例如,我希望用户能够说“我希望收到有关所有活动的通知,该活动将在星期日发生,该城市成立于1200年至1400年之间,该国家的名称以字母开头” F”或“在南美”,将其转换为以下伪逻辑代码:

  (
  event.date.day == Sunday 
  and 
  event.city.founded_date.year.between(1200, 1400)
  ) 
AND 
  (
  event.city.country.starts_with("F")
  or
  event.city.country.continent == "South Africa"
  )

诸如“大陆是”,“天是”,“成立日期在”等规则将被预先定义,用户可以选择它,但我希望将来能够添加新规则。

存储这种逻辑的最佳方法是什么? 我唯一能想到的解决方案是“ NotificationGatherer”模型。 它包含一个用户的ID,以及一个带有json的字符串。 我将创建一个json二进制树,对于这种特殊情况,大写的“ AND”将是带有两个子级的根-内部和内部或。 第一个孩子将有两个简单的条件,可以反映实际情况。

然后,我将根据用户的请求调用一个方法,该方法可以:

  1. 评估为所有即将发生的事件设置的该条件的值(是/否)

    要么

  2. 用过滤器构成一个查询集,该过滤器将获取所有满足给定条件的事件(难度更大,效率更高)。

现在,这是否是个好方法,还是我应该尝试其他方法? 似乎相当复杂(我已经知道测试它会很痛苦),而且我可以想象过去很多人可能需要这样的东西,但是我找不到任何建议,因为要寻找任何东西“数据库中的逻辑”自动将我引向有关存储过程的文章/问题。

如果有什么不同,我正在使用django和mysql。

如果是我的话,我会将规则存储在数据库中,然后不时使用Celery处理它们。

对于模型部分,我认为应该采用多表继承,因为不同的规则需要存储不同的数据。 我认为django-polymorphic是您的朋友:

我建议诸如:

from django.db import models
from polymorphic import PolymorphicModel


class AbtractRuleObject(models.Model):
    class Meta:
        abstract = True

    def filter_queryset(self, queryset):
        """Will handle actual filtering of the event queryset"""
        raise NotImplementedError

    def match_instance(self, instance):
        raise NotImplementedError

class RuleSet(AbtractRuleObject):
    """Will manage the painful part o handling the OR / AND logic inside the database"""
    NATURE_CHOICES = (
        ('or', 'OR'),
        ('and', 'AND'),
    )
    nature = models.CharField(max_length=5, choices=NATURE_CHOICES, default='and')

    # since a set can belong to another set, etc.
    parent_set = models.ForeignKey('self', null=True, blank=True, related_name='children')

    def filter_queryset(self, queryset):
        """This is rather naive and could be optimized"""
        if not self.parent_set:
            # this is a root rule set so we just filter according to registered rules
            for rule in self.rules:
                if self.nature == 'and':
                    queryset = rule.filter_queryset(queryset)
                elif self.nature == 'or':
                    queryset = queryset | rule.filter_queryset(queryset)
        else:
            # it has children rules set
            for rule_set in self.children:
                if self.nature == 'and':
                    queryset = rule_set.filter_queryset(queryset)
                elif self.nature == 'or':
                    queryset = queryset | rule_set.filter_queryset(queryset)
        return queryset

    def match_instance(self, instance):
        if not self.parent_set:
            if self.nature == 'and':
                return all([rule_set.match_instance(instance) for rule_set in self.children])
            if self.nature == 'any':
                return any([rule_set.match_instance(instance) for rule_set in self.children])
        else:
            if self.nature == 'and':
                return all([rule_set.match_instance(instance) for rule_set in self.children])
            if self.nature == 'any':
                return any([rule_set.match_instance(instance) for rule_set in self.children])

class Rule(AbtractRuleObject, PolymorphicModel):
    """Base class for all rules"""
    attribute = models.CharField(help_text="Attribute of the model on which the rule will apply")
    rule_set = models.ForeignKey(RuleSet, related_name='rules')

class DateRangeRule(Rule):
    start = models.DateField(null=True, blank=True)
    end = models.DateField(null=True, blank=True)

    def filter_queryset(self, queryset):
        filters = {}
        if self.start:
            filters['{0}__gte'.format(self.attribute)] = self.start
        if self.end:
            filters['{0}__lte'.format(self.attribute)] = self.end
        return queryset.filter(**filters)

    def match_instance(self, instance):
        start_ok = True
        end_ok = True
        if self.start:
            start_ok = getattr(instance, self.attribute) >= self.start
        if self.end:
            end_ok = getattr(instance, self.attribute) <= self.end

        return start_ok and end_ok

class MatchStringRule(Rule):
    match = models.CharField()
    def filter_queryset(self, queryset):
        filters = {'{0}'.format(self.attribute): self.match}
        return queryset.filter(**filters)

    def match_instance(self, instance):
        return getattr(instance, self.attribute) == self.match

class StartsWithRule(Rule):
    start = models.CharField()

    def filter_queryset(self, queryset):
        filters = {'{0}__startswith'.format(self.attribute): self.start}
        return queryset.filter(**filters)

    def match_instance(self, instance):
        return getattr(instance, self.attribute).startswith(self.start)

现在,假设您的EventCity模型如下所示:

class Country(models.Model):
    continent = models.CharField()
    name = models.CharField(unique=True)

class City(models.Model):
    name = models.CharField(unique=True)
    country = models.ForeignKey(Country)
    founded_date = models.DateField()

class Event(models.Model):
    name = models.CharField(unique=True)
    city = models.ForeignKey(City)
    start = models.DateField()
    end = models.DateField()

然后您可以使用我的示例,如下所示:

global_set = RuleSet(nature='and')
global_set.save()

set1 = RuleSet(nature='and', parent_set=global_set)
set1.save()

year_range = DateRangeRule(start=datetime.date(1200, 1, 1),
                           end=datetime.date(1400, 1, 1),
                           attribute='city__founded_date',
                           rule_set=set1)
year_range.save()

set2 = RuleSet(nature='or', parent_set=global_set)
set2.save()

startswith_f = StartsWithRule(start='F',
                              attribute='city__country__name')
                              rule_set=set2)
startswith_f.save()

exact_match = MatchStringRule(match='South Africa',
                              attribute='city__country__continent')
                              rule_set=set2)
exact_match.save()

queryset = Event.objects.all()

# Magic happens here

# Get all instances corresponding to the rules
filtered_queryset = global_set.filter_queryset(queryset)

# Check if a specific instance match the rules
assert global_set.match_instance(filtered_queryset[0]) == True

该代码绝对未经测试,但我认为它最终可能会起作用,或者至少会为您提供实现想法。

希望对您有所帮助!

这与数据库中的逻辑无关,最好称为存储过滤器模式或存储过滤器首选项。

通常,您希望使用户能够创建和存储在配置文件设置中的过滤器,这些过滤器将从数据库中提取所有与它们匹配的事件并发送给用户有关它们的通知。

首先,您应该考虑过滤器的深度。 例如可以是这样:

  1. 模型FilterSet将具有一些全局设置(例如通知类型),并将分配给特定用户
  2. 模型Filter -具有一个过滤规则(或一组规则,例如日期范围),并将其分配给FilterSet

每个用户应能够定义多个过滤器集。 创建查询时,所有过滤器都将与AND结合在一起(过滤器中的某些规则除外。该特定过滤器的类型将对其进行设置)。

创建了某些类型的过滤器(偶数开始的日期范围,星期几等)后,您将使用json序列化将过滤器类型存储在一个列中,并将过滤器参数存储在其他列或一列中。

当应该发送通知时,处理器将检查每个FilterSet是否正在返回一些数据,如果是,它将把返回的数据发送给该FilterSet所有者。

它并不像将整个WHERE条件存储在json中那样复杂,但是它将提供类似的灵活性。 您只需要为用户创建多个FilterSet即可涵盖某些复杂的情况。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM