Сложный запрос к базе данных в Django Graph-Structure - PullRequest
0 голосов
/ 11 апреля 2020

У меня есть django -проект со следующей структурой модели. Сначала у меня есть графовая структура с узлами и (направленными и взвешенными) ребрами следующим образом:

class Node(models.Model):
    name = models.TextField()

class Edge(models.Model):
    from_node = models.ForeignKey(Node, on_delete=models.DO_NOTHING, related_name='edge_from_node')
    to_node = models.ForeignKey(Node, on_delete=models.DO_NOTHING, related_name='edge_to_node')
    costs = models.IntegerField()

Также существует токен, посредством которого узел может быть связан с несколькими токенами, но токен может относиться только к одному узлу:

class Token(models.Model):
    node = models.ForeignKey(Node, on_delete=models.DO_NOTHING)

Дополнительно У меня есть объект User, который может иметь несколько токенов:

class MyUser(models.Model):
    tokens = models.ManyToManyField(Token)

Теперь я хочу сделать запрос к базе данных, который для данного TargetNode и MyUser дает преимущество с минимальными затратами на подключение любого узла, для которого MyUser владеет токеном, с TargetNode. Позвольте мне привести простой пример:

# we create 4 nodes ...
n1 = Node(name="node 1")
n2 = Node(name="node 2")
n3 = Node(name="node 3")
n4 = Node(name="node 4")
n1.save()
n2.save()
n3.save()
n4.save()

# -.. and create 3 edges
e1 = Edge(from_node=n2, to_node=n1, costs=3)
e2 = Edge(from_node=n3, to_node=n1, costs=2)
e3 = Edge(from_node=n4, to_node=n1, costs=1)
e1.save()
e2.save()
e3.save()

# create a user
user = MyUser()
user.save()

# create 2 tokens for the given user to node2 and node3
t1 = Token(node=n2)
t2 = Token(node=n3)
t1.save()
t2.save()
user.tokens.add(t1, t2)

это дает следующую простую структуру

enter image description here

Определение node1 в качестве цели узел. В этом простом примере есть только два узла (node2 и node3), которые подключены к node1, и пользователю принадлежит токен. Таким образом, есть только два ребра, о которых идет речь: edge1 и edge2. Поскольку edge1 имеет costs=3 и edge2 имеет costs=2, я хотел бы иметь edge2.

Я уже написал функцию для этой цели, которая работает (мой код дает только затраты на ребро, а не само ребро, что тоже нормально).

# get list of all nodes from where we can got to the node1
incoming_nodes = {x.from_node for x in Edge.objects.filter(to_node=n1)}

# get all nodes for which the user owns a token
nodes_with_token = {x.node for x in user.tokens.all()}

# create a empty list for saving the minimal costs
minimal_costs = []
for n in incoming_nodes.intersection(nodes_with_token):
    # add all edge costs of edges that are connecting then nodes with the target node
    minimal_costs += [y.costs for y in Edge.objects.filter(from_node=n)]

    if len(minimal_costs) == 0:
        print("there is no connecting edge")
        pass

result = min(minimal_costs)

, но, как вы можете видеть, моя функция выполняет много (на первый взгляд) избыточных запросов к базе данных, что нормально для простых приложений, но не в том случае, если моя база данных масштабирование. Итак, мой вопрос: есть ли способ сделать этот запрос сразу или, может быть, как-то иначе, чем я сделал, но более эффективно?

ps Я не знаю, является ли это полезной информацией, но я использую PostgreSQL -Database

1 Ответ

0 голосов
/ 12 апреля 2020

Я нашел очень хорошее улучшение (все еще открытое для лучших / более быстрых решений).

eligible_nodes = Node.objects.filter(from_node__to_node=target)  & Node.objects.filter(token__myuser=user)
minimal_cost_edge = Edge.objects.filter(from_node__in=eligible_nodes, to_node=target).order_by('costs')[0]
print("result new", minimal_cost_edge.costs)

Я снова рассчитал старую версию новой версии с 10000 узлами и пользователя с 5000. Старая Версия заняла 10.45 se c. и новый 0,03 se c. Таким образом, улучшение 351%

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...