这不是关于存储过程(或者至少我认为不是)。
假设我有一个数据库。数据库中包含一些城市,以及在这些城市中发生的事件。有些人会使用此网站,他们希望在进入网站时收到有关某些事件的通知。
用于指定他们想要通知的事件的规则应该是通用的。
例如,我希望用户能够说“我希望收到有关所有活动的通知,这些活动将在星期日,在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”将是一个有两个孩子的根 - 内部和内部或。 第一个孩子将有两个简单的条件,这将反映实际情况为生。
然后我会根据用户的请求调用一个方法,该方法可以:
评估为所有即将发生的事件设置的条件的值(真/假)
或
使用过滤器形成一个查询集,该过滤器将获取满足给定条件的所有即将发生的事件(更加困难且效率更高)。
现在,这是一个好方法,还是我应该尝试别的东西? 它似乎相当复杂(我已经看到测试它会有多痛苦),我可以想象很多人过去可能需要这样的东西,但我找不到任何建议,因为任何寻找“数据库中的逻辑”自动指向我关于存储过程的文章/问题。
如果这有任何区别,我正在使用django和mysql。
答案 0 :(得分:3)
如果是我的话,我会将规则存储在数据库中 使用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)
现在,假设您的Event
和City
模型如下:
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 :(得分:1)
它不是关于数据库中的逻辑,它更好地称为存储过滤器模式或存储过滤器首选项。
通常,您希望让您的用户能够在配置文件设置中创建和存储过滤器,这些过滤器将从数据库中提取与之匹配的所有事件,并向用户发送有关它们的通知。
首先,您应该考虑过滤器必须有多深。它可以是这样的:
FilterSet
- 将具有一些全局设置(例如通知类型)并将分配给特定用户Filter
- 将有一个过滤规则(或一组规则,例如日期范围),并将分配给FilterSet
每个用户都应该能够定义多个filterset。创建查询时,所有过滤器将与AND连接在一起(过滤器中的某些规则除外。该特定过滤器的类型将设置它)。
创建某些类型的过滤器(甚至开始的日期范围,星期几等)后,您将过滤器类型存储在一列中,并使用json序列化过滤其他列或一列中的参数。
当发送通知时,处理器会检查每个FilterSet
是否返回一些数据,如果是,它会将返回的数据发送给FilterSet
的所有者。
在json中存储整个WHERE条件并不复杂,但它会提供类似的灵活性。您只需为用户创建多个FilterSet
即可覆盖一些复杂的案例。