Ответы
SQLalchemy решает ввести его, несмотря на то, что ему явно не дано указание
Нет.Вы говорите ему использовать подзапрос в тот самый момент, когда вы звоните db.sesion.query(query)
(хотя вы можете не знать об этом).Вместо этого используйте db.session.execute(query)
.
, почему добавление псевдонима препятствует созданию подвыбора и почему псевдоним не используется!Псевдоним "foo", по-видимому, не учитывался, но оказал существенное влияние на сгенерированный запрос.
Он сделал не , а используется .
Объяснение - введение
SQLAlchemy только что обманул вас.Я предполагаю, что вы использовали print(query)
, чтобы заглянуть под капот и понять, что не так - на этот раз не повезло, он не сказал вам всей правды.
Чтобы просмотреть реальный SQL, который был сгенерирован, включить функцию эха на в двигателе.Сделав это, вы обнаружите, что в действительности sqlalchemy сгенерировал следующий запрос:
WITH battle_appearence AS
(
SELECT
waifu_battles.date AS date,
waifu_battles.winner_name AS name,
1 AS was_winner,
0 AS was_loser
FROM waifu_battles
UNION ALL
SELECT
waifu_battles.date AS date,
waifu_battles.loser_name AS name,
0 AS was_winner,
1 AS was_loser
FROM waifu_battles
)
SELECT foo.name AS foo_name, foo.wins AS foo_wins, foo.losses AS foo_losses
FROM (
SELECT
battle_appearence.name AS name,
sum(battle_appearence.was_winner) AS wins,
sum(battle_appearence.was_loser) AS losses
FROM battle_appearence
GROUP BY battle_appearence.name
ORDER BY count(*) DESC
LIMIT ?
)
AS foo
Оба запроса работают правильно (тот, который, как я утверждаю, действительно использовался - выше), и запрос, который вы дали наконец вашего ответа).Давайте сначала углубимся в это - почему они отличаются?
Как отлаживать запросы и почему то, что вы видели, было другим
Запрос, который вы видели (давайте назовем его S as select over alias ) - строковое представление запроса или результат str(query.compile())
.Вы можете настроить его для использования диалекта postgres:
dialect = postgresql.dialect()
str(query.compile(dialect=dialect))
и получить немного другой результат, но без подзапроса.Интригующе, не правда ли?Просто для дальнейшего использования query.compile
- это (в упрощении) то же самое, что вызов dialect.statement_compiler(dialect, query, bind=None)
второго запроса (назовем его A как с псевдонимом )генерируется при вызове db.session.query(query).all()
.Если вы просто наберете str(db.session.query(query))
, вы увидите, что мы получаем другой запрос (по сравнению с N query.compile()
) - с подзапросом и псевдонимом.
Делает ли этоиметь какое-либо отношение к сессии?Нет - вы можете проверить, преобразовав запрос в Query
объект, игнорируя информацию о сеансе:
from sqlalchemy.orm.query import Query
str(Query(query))
Вглядываясь в детали реализации (Query.__str__
), мы можем видеть, что происходит для A - это:
context = Query(query)._compile_context()
str(context.statement.compile(bind=None))
context.statement.compile
попытается выбрать диалект (в нашем случае правильно идентифицируя Postgres) и затем выполнит оператор так же, как это было сделано для S вариант:
dialect.statement_compiler(dialect, context.statement, bind=None)
Напомним, что S происходит от:
dialect = postgresql.dialect()
str(dialect.statement_compiler(dialect, query, bind=None))
Это указывает на то, что в контекстеэто то, что меняет поведение компилятора операторов.Что делает dialect.statement_compiler
?Это конструктор подкласса SQLCompiler
, специализирующийся на процессе наследования, чтобы соответствовать вашим потребностям диалекта;для Postgres это должно быть PGCompiler
.
Примечание: мы можем использовать ярлык для A :
dialect.statement_compiler(dialect, Query(query).statement, bind=None)
Давайте сравним состояние скомпилированных объектов,Это легко сделать, обратившись к атрибуту __dict__
компиляторов:
with_subquery = dialect.statement_compiler(dialect, context.statement, bind=None)
no_subquery = dialect.statement_compiler(dialect, query, bind=None)
from deepdiff import DeepDiff
DeepDiff(sub.__dict__, nosub.__dict__, ignore_order=True)
Важно, что типы операторов изменились.Это не является неожиданным, так как в первом случае context.statement
является объектом sqlalchemy.sql.selectable.Select
, а во втором query
является sqlalchemy.sql.selectable.Alias
объектом.
Это подчеркивает тот факт, что преобразование запроса вQuery
объект с db.session.query()
, заставляет компилятор выбрать другой маршрут в зависимости от измененного типа оператора.Мы можем видеть, что S , на самом деле, является псевдонимом, заключенным в select, используя:
>>> context.statement._froms
[<sqlalchemy.sql.selectable.Alias at 0x7f7e2f4f7160; foo>]
Тот факт, что псевдоним отображается при переносе в операторе select (S ), создание подзапроса каким-то образом согласуется с документацией , которая описывает псевдоним как использование в операторе SELECT (но не как корень запроса):
Когда псевдоним создается из объекта Table, это приводит к тому, что таблица отображается как псевдоним AS таблицы в операторе SELECT.
Почему сначала был суб-выбор?
Давайте назовем запрос без .alias('foo')
как N (без псевдонима) и представим его в псевдокоде ниже как n_query
. Поскольку он имеет тип sqlalchemy.sql.selectable.Select
, когда вы вызываете db.session.query(n_query)
, он создает подзапрос почти так же, как и в случае с псевдонимом. Вы можете проверить, что мы получили выбор внутри другого выбора с помощью:
>>> Query(nquery).statement._froms
[<sqlalchemy.sql.selectable.Select at 0x7f7e1e26e668; Select object>]
Теперь вы должны легко увидеть, что наличие выбора внутри выбора означает, что суб-выбор всегда создавался при запросе базы данных с помощью db.session.query(n_query)
.
Я не уверен, почему в первом показанном вами запросе отображается подзапрос - возможно ли, что вы использовали echo (или str(db.session(n_query))
тогда?
)
Могу ли я изменить это поведение?
Конечно! Просто выполните ваш запрос с:
db.session.execute(n_query)
и затем (если вы включили echo, как указано выше) вы увидите, что отправляется тот же запрос (как вы разместили в самом конце).
Это точно так же, как выполнение запроса с псевдонимом:
db.session.execute(n_query.alias('foo'))
потому что псевдоним не имеет смысла, если нет последовательного выбора!