LINUX.ORG.RU

values и select_related в django

 , ,


0

1

Я к вам опять со своими баранами )

Имеются модели:

class Player(models.Model):
    steamid = models.BigIntegerField();

class Player_server_session(models.Model):
    player = models.ForeignKey(Player)
    server = models.ForeignKey(Server)
    connect_timestamp = models.DateTimeField()
    disconnect_timestamp = models.DateTimeField(null=True)

class Player_display_name(models.Model):
    player = models.ForeignKey(Player)
    display_name = models.CharField(max_length=128)
    last_use_timestamp = models.DateTimeField()

Я хочу для заданного сервера отобразить игроков с числом их сессий на сервере, с сортировкой по числу сессий:

Player_server_session.objects.filter(server=server).values('player').annotate(Count('player')).order_by('-player__count')

А теперь я хочу отобразить steamid и display_names.

Player_server_session.objects.filter(server=server).select_related('player').prefetch_related('player__player_display_name_set').values('player').annotate(Count('player')).order_by('-player__count')

Однако из-за values каждый элемент так и остается словарем, содержащим 'player' в виде id и 'player__count'. А без values я не знаю как посчитать число сессий.

Подскажите как выразить одним запросом.


Возникает вопрос: а нахера тебе на две таблицы разносить плеера? Чем его display_name не умещается в Player?

А без values я не знаю как посчитать число сессий.

Считай с values, какие проблемы?

Подскажите как выразить одним запросом.

Выражай большим количеством запросов.

Goury ★★★★★
()
Ответ на: комментарий от Goury

Возникает вопрос: а нахера тебе на две таблицы разносить плеера? Чем его display_name не умещается в Player?

У пользователя может быть много display_name.

Считай с values, какие проблемы?

values вернет словарь вида с raW значениями, а не query.

Выражай большим количеством запросов.

На SQL я одним запросом это могу реализовать парой left join. Печально, что django orm так не может :(

totik
() автор топика
Ответ на: комментарий от totik

У пользователя может быть много display_name.

Прям так много что в таблицу не влезет?
Какой-нибудь jsonfield не решает эту проблему?

На SQL я одним запросом это могу реализовать парой left join.

А зачем ты взял джангу вообще и использовал её ORM в частности?
На ассемблере я ещё и не такое могу, но джанга это не про могуние, а про быстро, аккуратно и надёжно.

Ridiculously fast

Значит тебе не надо заниматься простынеписательством чтобы сделать простую вешь.

Reassuringly secure

Значит что если не совать куда попало пару left join, то когда придёт вася-брось-таблицу — таблица останется в порядке.

Exceedingly scalable

Значит что в случае чего ты сможешь докупить серверов и оно не будет тормозить.


Короче, если ты хочешь использовать джангу как положено — не надо перекладывать логику на базу данных. Оно не для того.
Сделай минимальную выборку необходимых данных и обрабатывай их дальше джангой.

А если тебе очень хочется чтобы база данных думала за джангу, а джангу использовать только для рендера результата — пиши свои модели без ORM и в процедурах используй свой любимый SQL.

Goury ★★★★★
()
Ответ на: комментарий от Goury

Если не использовать .select_related('player').prefetch_related('player__player_display_name_set') выходит слишком много запросов к базе данных.

Если предварительно собирать данные по каждой сущности требуется несколько проходов по всем элементам, что тоже плохо, когда элементов несколько тысяч.

    server_players = Player_server_session.objects.filter(server=server).values('player').annotate(Count('player')).order_by('-player__count')

    players_ids = set()
    for server_player in server_players:
        players_ids.add(server_player['player'])

    players = {}
    for player in Player.objects.filter(id__in=players_ids).prefetch_related('player_display_name_set'):
        players[player.id] = player

Такой вариант выполняет один компактный запрос к player_server_session и один монструозный запрос к player с конструкцией id IN (несколько тысяч идентификаторов)

Все-таки orm-ом надо пользоваться с оглядкой на эффективность работы с базой данных, имхо.

totik
() автор топика
Ответ на: комментарий от totik

слишком много
несколько тысяч

научись в кэширование что ли

orm-ом надо пользоваться с оглядкой на эффективность

Может я тебе тайну великую открою, но любую абстракцию надо применять только тогда, когда ты понимаешь как работают все слои под ней. Абстракция это не волшебная кнопка «сделать всё как надо», а просто инструмент для упрощения некоторых процедур. В руках специалиста абстракция экономит ресурсы, в руках дилетанта это просто очередная граната для обезьяны.

Если не использовать .select_related('player').prefetch_related('player__player_display_name_set') выходит слишком много запросов к базе данных.

Используй, никто не запрещает.

Если предварительно собирать данные по каждой сущности

Зачем собирать по каждой? Собирай только по тем, по которым надо собирать.

хочу для заданного сервера отобразить игроков с числом их сессий на сервере, с сортировкой по числу сессий

В исходных данных есть сервер, задача: получить плееров.
Плееры получаются из сессий, сессии получаются из сервера.
Делай выборку сессий по серверу, результат сортируй по плеерам и готовь из этого список кортежей с плеерами и количеством сессий.
Потом по этому списку выбирай для плееров недостающие детали.
Потом клади результат в кэш и больше не лазь в базу в ближайшее время.

Да, траффика между базой данных и питоном будет больше чем при сложном SQL запросе. Но джанга нужна не для экономии на спичках, а для организации спичек в аккуратные спичечные домики.

Ну и никто не запрещает https://docs.djangoproject.com/en/1.9/topics/db/sql/
Просто это не лучшая практика.

Goury ★★★★★
()
Последнее исправление: Goury (всего исправлений: 1)
Ответ на: комментарий от Goury

Не лучшая практика - это делать ORM'ом странные вещи, которые в него плохо лезут, вместо простого и понятного прямого запроса. Хотя, новые фичи django-orm позволяют лезть в сырой sql все реже и реже:

class SomeQuerySet(models.QuerySet):
    def allowed_to(self, user):
        def perm_lookup_expr(*perms):
            return Case(When(
                pk__in=SomePermission.objects.filter(
                    user=user,
                    permission__in=SomePermission.perms(*perms))
                                                   .values('some__id'),
                then=Value(True)),
                    default=Value(False),
                    output_field=BooleanField())
        return (self
            .filter(pk__in=ScriptPermission.objects
                .filter(user=user)
                .values("some_id"))
            .annotate(
                has_perm_own=perm_lookup_expr('some_perm_own'),
                has_perm_execute=perm_lookup_expr('some_perm_own', 'some_perm_execute'),
                has_perm_change=perm_lookup_expr('some_perm_own', 'some_perm_change'),
                has_perm_view=perm_lookup_expr('some_perm_own', 'some_perm_change',
                                               'some_perm_execute', 'some_perm_view'))
            .filter(has_perm_view=True))
— пример ACL через аннотации объекта, причем, все идет одним запросом, не специфично к БД, но с кучей вложенных запросов.

anon1984
()
Ответ на: комментарий от anon1984

Есть много разных не лучших практик и довольно мало лучших.

Goury ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.