Pra quem ja estudou django, o funcionamento do SqlAlchemy é muito similar ao dos models do django.
Desenvolvido para a linguagem de programação Python, o SQLAlchemy é um framework de mapeamento objeto-relacional SQL (ORM) de código aberto sobre a licença MIT.
Mas afinal, o que é o ORM?
Mapeamento objeto-relacional ou simplesmente ORM é uma técnica de programação que auxilia na conversão de dados entre banco de dados relacionais e linguagens de programação que são orientadas à objetos.
A utilização deste Framework faz com que o programador reduza a programação de acesso ao banco de dados, desta forma, obtendo uma produtividade significativa no desenvolvimento de suas aplicações.
Ou seja, ORM trata-se de um Framework que visa auxiliar na redução da impedância, realizando todas as conversões necessárias entre o modelo relacional e o modelo orientado a objetos de maneira automática, geralmente da seguinte forma:
- Cada classe é interpretada com uma tabela;
- Cada linha de uma determinada tabela, junto com seu relacionamento é tratada como instância do objeto relacionado à tabela em questão.
Sua utilização também visa retirar a necessidade do desenvolvedor de software em se preocupar com a linguagem SQL (geração dos comandos SQL) e com as conversões necessárias entre os diferentes tipos de dados, já que tudo isto fica a cargo do Framework ORM.
Conseguimos utilizar banco de dados relacionais por meio de objetos escritos em Python, não se preocupando em como este BD está funcionando (relações, campos, etc), apenas mapeando sua estrutura.
Checar a versão
Uma verificação rapida pode ser feita com os seguintes comandos no console python:>>> import sqlalchemy
>>> sqlalchemy.__version__
1.3.0
Conectando
Para esse tutorial nos iremos usar apenas uma base de dados SQLite na memoria ram (temporaria)
>>> from sqlalchemy import create_engine
>>> engine = create_engine('sqlite:///:memory:', echo=True)
O parametro echo
é um atalho para definir o log do SqlAlchemy, que é realizado atraves do modulo padrão do Python logging
.Com isso ativado, veremos todo o SQL gerado produzido. Se você estiver trabalhando neste tutorial e desejar gerar menos resultados, defina esse parametro echo = False
.
o valor de returno do comando create_engine()
é uma instancia da Engine
, e isso representa uma interface de uso da base de dados, adaptado através de um dialeto que lida com os detalhes do banco de dados e da DBAPI em uso. Nesse caso, o dialeto SQLite interpretará instruções para o módulo sqlite3
interno do Python.
A primeira vez que um método como Engine.execute()
ou Engine.connect()
é executado a Engine
estabelece uma conexão DBAPI real com a database, que é então usado para emitir os comandos SQL.
Ao usar o ORM, normalmente não usamos a Engine
que criamos direntamente; em vez disso, é usado no backend pelo ORM, como veremos em breve .
Veja tambem:
Database Urls - incluem exemplos de uso do comando create_engine()
conectando-se a vários tipos de bancos de dados (com links para mais informações).
Para esse tutorial nos iremos usar apenas uma base de dados SQLite na memoria ram (temporaria)
>>> from sqlalchemy import create_engine
>>> engine = create_engine('sqlite:///:memory:', echo=True)
O parametro
echo
é um atalho para definir o log do SqlAlchemy, que é realizado atraves do modulo padrão do Python logging
.Com isso ativado, veremos todo o SQL gerado produzido. Se você estiver trabalhando neste tutorial e desejar gerar menos resultados, defina esse parametro echo = False
.
o valor de returno do comando
create_engine()
é uma instancia da Engine
, e isso representa uma interface de uso da base de dados, adaptado através de um dialeto que lida com os detalhes do banco de dados e da DBAPI em uso. Nesse caso, o dialeto SQLite interpretará instruções para o módulo sqlite3
interno do Python.
A primeira vez que um método como
Ao usar o ORM, normalmente não usamos a
Engine.execute()
ou Engine.connect()
é executado a Engine
estabelece uma conexão DBAPI real com a database, que é então usado para emitir os comandos SQL.Ao usar o ORM, normalmente não usamos a
Engine
que criamos direntamente; em vez disso, é usado no backend pelo ORM, como veremos em breve .
Veja tambem:
Database Urls - incluem exemplos de uso do comando create_engine()
conectando-se a vários tipos de bancos de dados (com links para mais informações).Declarar um mapeamento
Ao usar o ORM, o processo de configuração começa descrevendo as tabelas de banco de dados com as quais lidaremos e depois definindo nossas próprias classes que serão mapeadas para essas tabelas. No SQLAlchemy moderno, essas duas tarefas geralmente são executadas juntas, usando um sistema conhecido como Declarativo, que permite criar classes que incluem diretivas para descrever a tabela de banco de dados real para a qual eles serão mapeados.Classes mapeadas usando o sistema Declarative são definidas em termos de uma classe base que mantém um catálogo de classes e tabelas relativas a essa base - isso é conhecido como classe base declarativa. Nosso aplicativo geralmente terá apenas uma instância dessa base em um módulo comumente importado. Criamos a classe base usando a função
declarative_base()
da seguinte maneira:>>> from sqlalchemy.ext.declarative import declarative_base
>>> Base = declarative_base()
>>> from sqlalchemy import Column, Integer, String
>>> class Usuario(Base):
... __tablename__ = 'users'
...
... id = Column(Integer, primary_key=True)
... nome = Column(String)
... sobrenome = Column(String)
... apelido = Column(String)
...
... def __repr__(self):
... return "<Usuario(nome='%s', sobrenome='%s', apelido='%s')>" % (
... self.nome, self.sobrenome, self.apelido)
Uma classe usando a forma declarativa precisa ter no minimo um atributo
Quando nossa classe é construída, Declarative substitui todos os objetos
__tablename__
, e pelo menos uma coluna Column
que faz parte de uma chave primária 1.O SQLAlchemy nunca faz nenhuma suposição por si só sobre a tabela à qual uma classe se refere, incluindo o fato de não possuir convenções internas para nomes, tipos de dados ou restrições. Mas isso não significa que o clichê seja necessário; em vez disso, incentive-o a criar suas próprias convenções automatizadas usando funções auxiliares e classes mixin, descritas detalhadamente em Mixin e Classes personalizadas.Quando nossa classe é construída, Declarative substitui todos os objetos
Column
por acessadores especiais do Python conhecidos como descritores; este é um processo conhecido como instrumentação. A classe mapeada "instrumentada" nos fornecerá os meios para nos referir à nossa tabela em um contexto SQL, bem como para persistir e carregar os valores das colunas do banco de dados.
Fora o que o processo de mapeamento faz com a nossa classe, a classe permanece, na maioria das vezes, uma classe Python normal, na qual podemos definir qualquer número de atributos e métodos comuns necessários ao nosso aplicativo.
- Para informações do por que uma chave primaria é necessaria veja: How do I map a table that has no primary key?.
Criar um esquema
Com nossa classe User
construída através do sistema Declarative, definimos informações sobre nossa tabela, conhecidas como metadados da tabela . O objeto usado pelo SQLAlchemy para representar essas informações para uma tabela específica é chamado de objeto Table
, e aqui o Declarative criou um para nós. Podemos ver esse objeto inspecionando o atributo __table__
:
>>> User . __table__
Table('users', MetaData(bind=None),
Column('id', Integer(), table=<users>, primary_key=True, nullable=False),
Column('name', String(), table=<users>),
Column('fullname', String(), table=<users>),
Column('nickname', String(), table=<users>), schema=None)
Quando declaramos nossa classe, o Declarative usou uma metaclasse do Python para executar atividades adicionais depois que a declaração da classe foi concluída; nessa fase, ele criou um objeto Table
acordo com nossas especificações e o associou à classe construindo um objeto Mapper
. Esse objeto é um objeto de bastidores com o qual normalmente não precisamos lidar diretamente (embora possa fornecer muitas informações sobre nosso mapeamento quando necessário).
O objeto Table
é um membro de uma coleção maior conhecida como MetaData
. Ao usar Declarative, esse objeto está disponível usando o atributo .metadata
da nossa classe base declarativa.
O MetaData
é um registro que inclui a capacidade de emitir um conjunto limitado de comandos de geração de esquema para o banco de dados. Como nosso banco de dados SQLite realmente não possui uma tabela de users
presente, podemos usar o MetaData
para emitir instruções CREATE TABLE no banco de dados para todas as tabelas que ainda não existem. Abaixo, chamamos o método MetaData.create_all()
, transmitindo nosso Engine
como fonte de conectividade do banco de dados. Veremos que comandos especiais são emitidos primeiro para verificar a presença da tabela de users
e, a seguir, a instrução CREATE TABLE
real:
>>> Base . metadata . create_all ( engine )
SELECT ...
PRAGMA main . table_info ( "users" )
()
PRAGMA temp . table_info ( "users" )
()
CREATE TABLE users (
id INTEGER NOT NULL , name VARCHAR ,
fullname VARCHAR ,
nickname VARCHAR ,
PRIMARY KEY ( id )
)
()
COMMIT
Descrições mínimas de tabela vs. descrições completas
Usuários familiarizados com a sintaxe de CREATE TABLE podem perceber que as colunas VARCHAR foram geradas sem um comprimento; no SQLite e PostgreSQL, este é um tipo de dados válido, mas em outros, não é permitido. Portanto, se você estiver executando este tutorial em um desses bancos de dados e desejar usar o SQLAlchemy para emitir CREATE TABLE, poderá ser fornecido um "comprimento" para o tipo String
como abaixo:
Column ( String ( 50 ))
O campo de comprimento em String
, bem como os campos de precisão / escala semelhantes disponíveis em Integer
, Numeric
, etc. não são referenciados por SQLAlchemy, exceto quando se cria tabelas.
Além disso, o Firebird e o Oracle exigem sequências para gerar novos identificadores de chave primária, e o SQLAlchemy não os gera ou assume sem ser instruído. Para isso, você usa a construção Sequence
:
from sqlalchemy import Sequence
Column ( Integer , Sequence ( 'user_id_seq' ), primary_key = True )
Uma Table
completa e infalível gerada por meio de nosso mapeamento declarativo é, portanto:
class User ( Base ):
__tablename__ = 'users'
id = Column ( Integer , Sequence ( 'user_id_seq' ), primary_key = True )
name = Column ( String ( 50 ))
fullname = Column ( String ( 50 ))
nickname = Column ( String ( 50 ))
def __repr__ ( self ):
return "<User(name=' %s ', fullname=' %s ', nickname=' %s ')>" % (
self . name , self . fullname , self . nickname )
Incluímos essa definição de tabela mais detalhada separadamente para destacar a diferença entre uma construção mínima voltada principalmente para o uso em Python, versus uma que será usada para emitir instruções CREATE TABLE em um conjunto específico de back-end com requisitos mais rigorosos.
User
construída através do sistema Declarative, definimos informações sobre nossa tabela, conhecidas como metadados da tabela . O objeto usado pelo SQLAlchemy para representar essas informações para uma tabela específica é chamado de objeto Table
, e aqui o Declarative criou um para nós. Podemos ver esse objeto inspecionando o atributo __table__
: >>> User . __table__
Table('users', MetaData(bind=None),
Column('id', Integer(), table=<users>, primary_key=True, nullable=False),
Column('name', String(), table=<users>),
Column('fullname', String(), table=<users>),
Column('nickname', String(), table=<users>), schema=None)
Table
acordo com nossas especificações e o associou à classe construindo um objeto Mapper
. Esse objeto é um objeto de bastidores com o qual normalmente não precisamos lidar diretamente (embora possa fornecer muitas informações sobre nosso mapeamento quando necessário).O objeto
Table
é um membro de uma coleção maior conhecida como MetaData
. Ao usar Declarative, esse objeto está disponível usando o atributo .metadata
da nossa classe base declarativa.O
MetaData
é um registro que inclui a capacidade de emitir um conjunto limitado de comandos de geração de esquema para o banco de dados. Como nosso banco de dados SQLite realmente não possui uma tabela de users
presente, podemos usar o MetaData
para emitir instruções CREATE TABLE no banco de dados para todas as tabelas que ainda não existem. Abaixo, chamamos o método MetaData.create_all()
, transmitindo nosso Engine
como fonte de conectividade do banco de dados. Veremos que comandos especiais são emitidos primeiro para verificar a presença da tabela de users
e, a seguir, a instrução CREATE TABLE
real: >>> Base . metadata . create_all ( engine )
SELECT ...
PRAGMA main . table_info ( "users" )
()
PRAGMA temp . table_info ( "users" )
()
CREATE TABLE users (
id INTEGER NOT NULL , name VARCHAR ,
fullname VARCHAR ,
nickname VARCHAR ,
PRIMARY KEY ( id )
)
()
COMMIT
Descrições mínimas de tabela vs. descrições completas
Usuários familiarizados com a sintaxe de CREATE TABLE podem perceber que as colunas VARCHAR foram geradas sem um comprimento; no SQLite e PostgreSQL, este é um tipo de dados válido, mas em outros, não é permitido. Portanto, se você estiver executando este tutorial em um desses bancos de dados e desejar usar o SQLAlchemy para emitir CREATE TABLE, poderá ser fornecido um "comprimento" para o tipo String
como abaixo: Column ( String ( 50 ))
String
, bem como os campos de precisão / escala semelhantes disponíveis em Integer
, Numeric
, etc. não são referenciados por SQLAlchemy, exceto quando se cria tabelas.Além disso, o Firebird e o Oracle exigem sequências para gerar novos identificadores de chave primária, e o SQLAlchemy não os gera ou assume sem ser instruído. Para isso, você usa a construção
Sequence
: from sqlalchemy import Sequence
Column ( Integer , Sequence ( 'user_id_seq' ), primary_key = True )
Table
completa e infalível gerada por meio de nosso mapeamento declarativo é, portanto: class User ( Base ):
__tablename__ = 'users'
id = Column ( Integer , Sequence ( 'user_id_seq' ), primary_key = True )
name = Column ( String ( 50 ))
fullname = Column ( String ( 50 ))
nickname = Column ( String ( 50 ))
def __repr__ ( self ):
return "<User(name=' %s ', fullname=' %s ', nickname=' %s ')>" % (
self . name , self . fullname , self . nickname )
Criar uma instância da classe mapeada
Com os mapeamentos concluídos, agora vamos criar e inspecionar um objeto User
:
>>> ed_user = User ( name = 'ed' , fullname = 'Ed Jones' , nickname = 'edsnickname' )
>>> ed_user . name
'ed'
>>> ed_user . nickname
'edsnickname'
>>> str ( ed_user . id )
'None'
Mesmo que não o tenhamos especificado no construtor, o atributo id
ainda produz um valor None
quando o acessamos (em oposição ao comportamento usual do Python de aumentar o AttributeError
para um atributo indefinido). A instrumentação do SQLAlchemy normalmente produz esse valor padrão para atributos mapeados em colunas quando acessados pela primeira vez. Para os atributos em que realmente atribuímos um valor, o sistema de instrumentação está rastreando essas atribuições para uso em uma eventual instrução INSERT a ser emitida no banco de dados.
User
: >>> ed_user = User ( name = 'ed' , fullname = 'Ed Jones' , nickname = 'edsnickname' )
>>> ed_user . name
'ed'
>>> ed_user . nickname
'edsnickname'
>>> str ( ed_user . id )
'None'
id
ainda produz um valor None
quando o acessamos (em oposição ao comportamento usual do Python de aumentar o AttributeError
para um atributo indefinido). A instrumentação do SQLAlchemy normalmente produz esse valor padrão para atributos mapeados em colunas quando acessados pela primeira vez. Para os atributos em que realmente atribuímos um valor, o sistema de instrumentação está rastreando essas atribuições para uso em uma eventual instrução INSERT a ser emitida no banco de dados.Criando uma sessão
Agora estamos prontos para começar a conversar com o banco de dados. O "identificador" do ORM para o banco de dados é a Session
. Quando configuramos o aplicativo pela primeira vez, no mesmo nível da instrução create_engine()
, definimos uma classe Session
que servirá como uma fábrica para novos objetos Session
:
>>> from sqlalchemy.orm import sessionmaker
>>> Session = sessionmaker ( bind = engine )
No caso em que seu aplicativo ainda não possui um Engine
ao definir seus objetos no nível do módulo, basta configurá-lo da seguinte maneira:
>>> Session = sessionmaker ()
Posteriormente, quando você criar seu mecanismo com create_engine()
, conecte-o à Session
usando configure()
:
>>> Session . configure ( bind = engine ) # once engine is available
Essa classe Session
customizada criará novos objetos Session
vinculados ao nosso banco de dados. Outras características transacionais também podem ser definidas ao chamar o sessionmaker
; estes são descritos em um capítulo posterior. Então, sempre que precisar conversar com o banco de dados, instancie uma Session
:
>>> session = Session ()
A Session
acima está associada ao nosso Engine
habilitado para SQLite, mas ainda não abriu nenhuma conexão. Quando é usado pela primeira vez, ele recupera uma conexão de um conjunto de conexões mantidas pelo Engine
e o mantém até que confirmamos todas as alterações e / ou fechemos o objeto da sessão.
Session
. Quando configuramos o aplicativo pela primeira vez, no mesmo nível da instrução create_engine()
, definimos uma classe Session
que servirá como uma fábrica para novos objetos Session
: >>> from sqlalchemy.orm import sessionmaker
>>> Session = sessionmaker ( bind = engine )
Engine
ao definir seus objetos no nível do módulo, basta configurá-lo da seguinte maneira: >>> Session = sessionmaker ()
create_engine()
, conecte-o à Session
usando configure()
: >>> Session . configure ( bind = engine ) # once engine is available
Session
customizada criará novos objetos Session
vinculados ao nosso banco de dados. Outras características transacionais também podem ser definidas ao chamar o sessionmaker
; estes são descritos em um capítulo posterior. Então, sempre que precisar conversar com o banco de dados, instancie uma Session
: >>> session = Session ()
Session
acima está associada ao nosso Engine
habilitado para SQLite, mas ainda não abriu nenhuma conexão. Quando é usado pela primeira vez, ele recupera uma conexão de um conjunto de conexões mantidas pelo Engine
e o mantém até que confirmamos todas as alterações e / ou fechemos o objeto da sessão.Adicionando e atualizando objetos
Para persistir nosso objeto User
, add()
à nossa Session
:
>>> ed_user = User ( name = 'ed' , fullname = 'Ed Jones' , nickname = 'edsnickname' )
>>> session . add ( ed_user )
Neste ponto, dizemos que a instância está pendente ; nenhum SQL ainda foi emitido e o objeto ainda não está representado por uma linha no banco de dados. A Session
emitirá o SQL para persistir Ed Jones
assim que necessário, usando um processo conhecido como flush . Se consultarmos o banco de dados em busca de Ed Jones
, todas as informações pendentes serão liberadas primeiro e a consulta será emitida imediatamente a seguir.
Por exemplo, abaixo, criamos um novo objeto Query
que carrega instâncias de User
. "Filtramos por" o atributo name
de ed
e indicamos que gostaríamos apenas do primeiro resultado na lista completa de linhas. Uma instância de User
é retornada, equivalente à que adicionamos:
SQL >>> our_user = session . query ( User ) . filter_by ( name = 'ed' ) . first ()
>>> our_user
< User ( name = 'ed' , fullname = 'Ed Jones' , nickname = 'edsnickname' ) >
De fato, a Session
identificou que a linha retornada é a mesma que a já representada em seu mapa interno de objetos; portanto, recuperamos a instância idêntica à que acabamos de adicionar:
>>> ed_user is our_user
True
O conceito ORM em ação aqui é conhecido como mapa de identidade e garante que todas as operações em uma linha específica em uma Session
operem no mesmo conjunto de dados. Quando um objeto com uma chave primária específica estiver presente na Session
, todas as consultas SQL nessa Session
sempre retornarão o mesmo objeto Python para essa chave primária específica; ele também gera um erro se for feita uma tentativa de colocar um segundo objeto já persistente com a mesma chave primária na sessão.
Podemos adicionar mais objetos User
ao mesmo tempo usando add_all()
:
>>> session . add_all ([
... User ( name = 'wendy' , fullname = 'Wendy Williams' , nickname = 'windy' ),
... User ( name = 'mary' , fullname = 'Mary Contrary' , nickname = 'mary' ),
... User ( name = 'fred' , fullname = 'Fred Flintstone' , nickname = 'freddy' )])
Além disso, decidimos que o apelido de Ed não é tão bom, então vamos alterá-lo:
>>> ed_user . nickname = 'eddie'
A Session
está prestando atenção. Sabe, por exemplo, que Ed Jones
foi modificado:
>>> session . dirty
IdentitySet ([ < User ( name = 'ed' , fullname = 'Ed Jones' , nickname = 'eddie' ) > ])
e que três novos objetos User
estão pendentes:
>>> session . new
IdentitySet ([ < User ( name = 'wendy' , fullname = 'Wendy Williams' , nickname = 'windy' ) > ,
< User ( name = 'mary' , fullname = 'Mary Contrary' , nickname = 'mary' ) > ,
< User ( name = 'fred' , fullname = 'Fred Flintstone' , nickname = 'freddy' ) > ])
Dizemos à Session
que gostaríamos de emitir todas as alterações restantes no banco de dados e confirmar a transação, que já estava em andamento. Fazemos isso via commit()
. A Session
emite a UPDATE
para a mudança de apelido em "ed", bem como as instruções INSERT
para os três novos objetos User
que adicionamos:
SQL >>> session . commit ()
commit()
libera as alterações restantes no banco de dados e confirma a transação. Os recursos de conexão mencionados pela sessão agora são retornados ao pool de conexões. As operações subsequentes com esta sessão ocorrerão em uma nova transação, que novamente readquirirá recursos de conexão quando necessário.
Se olharmos para o atributo id
de Ed, que anteriormente era None
, ele agora tem um valor:
SQL >>> ed_user . id
1
Após a Session
inserir novas linhas no banco de dados, todos os identificadores recém-gerados e padrões gerados pelo banco de dados ficam disponíveis na instância, imediatamente ou por meio do carregamento no primeiro acesso. Nesse caso, a linha inteira foi recarregada no acesso porque uma nova transação foi iniciada após a emissão do commit()
. Por padrão, o SQLAlchemy atualiza os dados de uma transação anterior na primeira vez em que é acessada em uma nova transação, para que o estado mais recente esteja disponível. O nível de recarregamento é configurável, conforme descrito em Usando a Sessão .
Estados do Objeto de Sessão
À medida que nosso objeto User
passou de fora da Session
, para dentro da Session
sem uma chave primária, para realmente ser inserido, ele se moveu entre três dos quatro "estados de objeto" disponíveis - transitório , pendente e persistente . Estar ciente desses estados e do que eles significam é sempre uma boa idéia - leia Introdução Rápida aos Estados Objetos para obter uma visão geral rápida.
User
, add()
à nossa Session
: >>> ed_user = User ( name = 'ed' , fullname = 'Ed Jones' , nickname = 'edsnickname' )
>>> session . add ( ed_user )
Session
emitirá o SQL para persistir Ed Jones
assim que necessário, usando um processo conhecido como flush . Se consultarmos o banco de dados em busca de Ed Jones
, todas as informações pendentes serão liberadas primeiro e a consulta será emitida imediatamente a seguir.Por exemplo, abaixo, criamos um novo objeto
Query
que carrega instâncias de User
. "Filtramos por" o atributo name
de ed
e indicamos que gostaríamos apenas do primeiro resultado na lista completa de linhas. Uma instância de User
é retornada, equivalente à que adicionamos: SQL >>> our_user = session . query ( User ) . filter_by ( name = 'ed' ) . first ()
>>> our_user
< User ( name = 'ed' , fullname = 'Ed Jones' , nickname = 'edsnickname' ) >
Session
identificou que a linha retornada é a mesma que a já representada em seu mapa interno de objetos; portanto, recuperamos a instância idêntica à que acabamos de adicionar: >>> ed_user is our_user
True
Session
operem no mesmo conjunto de dados. Quando um objeto com uma chave primária específica estiver presente na Session
, todas as consultas SQL nessa Session
sempre retornarão o mesmo objeto Python para essa chave primária específica; ele também gera um erro se for feita uma tentativa de colocar um segundo objeto já persistente com a mesma chave primária na sessão.Podemos adicionar mais objetos
User
ao mesmo tempo usando add_all()
: >>> session . add_all ([
... User ( name = 'wendy' , fullname = 'Wendy Williams' , nickname = 'windy' ),
... User ( name = 'mary' , fullname = 'Mary Contrary' , nickname = 'mary' ),
... User ( name = 'fred' , fullname = 'Fred Flintstone' , nickname = 'freddy' )])
>>> ed_user . nickname = 'eddie'
Session
está prestando atenção. Sabe, por exemplo, que Ed Jones
foi modificado: >>> session . dirty
IdentitySet ([ < User ( name = 'ed' , fullname = 'Ed Jones' , nickname = 'eddie' ) > ])
User
estão pendentes: >>> session . new
IdentitySet ([ < User ( name = 'wendy' , fullname = 'Wendy Williams' , nickname = 'windy' ) > ,
< User ( name = 'mary' , fullname = 'Mary Contrary' , nickname = 'mary' ) > ,
< User ( name = 'fred' , fullname = 'Fred Flintstone' , nickname = 'freddy' ) > ])
Session
que gostaríamos de emitir todas as alterações restantes no banco de dados e confirmar a transação, que já estava em andamento. Fazemos isso via commit()
. A Session
emite a UPDATE
para a mudança de apelido em "ed", bem como as instruções INSERT
para os três novos objetos User
que adicionamos: SQL >>> session . commit ()
commit()
libera as alterações restantes no banco de dados e confirma a transação. Os recursos de conexão mencionados pela sessão agora são retornados ao pool de conexões. As operações subsequentes com esta sessão ocorrerão em uma nova transação, que novamente readquirirá recursos de conexão quando necessário.Se olharmos para o atributo
id
de Ed, que anteriormente era None
, ele agora tem um valor: SQL >>> ed_user . id
1
Session
inserir novas linhas no banco de dados, todos os identificadores recém-gerados e padrões gerados pelo banco de dados ficam disponíveis na instância, imediatamente ou por meio do carregamento no primeiro acesso. Nesse caso, a linha inteira foi recarregada no acesso porque uma nova transação foi iniciada após a emissão do commit()
. Por padrão, o SQLAlchemy atualiza os dados de uma transação anterior na primeira vez em que é acessada em uma nova transação, para que o estado mais recente esteja disponível. O nível de recarregamento é configurável, conforme descrito em Usando a Sessão .
Estados do Objeto de Sessão
À medida que nosso objeto User
passou de fora da Session
, para dentro da Session
sem uma chave primária, para realmente ser inserido, ele se moveu entre três dos quatro "estados de objeto" disponíveis - transitório , pendente e persistente . Estar ciente desses estados e do que eles significam é sempre uma boa idéia - leia Introdução Rápida aos Estados Objetos para obter uma visão geral rápida.Revertendo
Como a Session
funciona em uma transação, também podemos reverter as alterações feitas. Vamos fazer duas alterações que reverteremos; O nome de usuário de ed_user
é definido como Edwardo
:
>>> ed_user . name = 'Edwardo'
e adicionaremos outro usuário incorreto, fake_user
:
>>> fake_user = User ( name = 'fakeuser' , fullname = 'Invalid' , nickname = '12345' )
>>> session . add ( fake_user )
Consultando a sessão, podemos ver que elas estão liberadas na transação atual:
SQL >>> session . query ( User ) . filter ( User . name . in_ ([ 'Edwardo' , 'fakeuser' ])) . all ()
[ < User ( name = 'Edwardo' , fullname = 'Ed Jones' , nickname = 'eddie' ) > , < User ( name = 'fakeuser' , fullname = 'Invalid' , nickname = '12345' ) > ]
ed_user
, podemos ver que o nome de ed_user
voltou a ser ed
, e fake_user
foi expulso da sessão:
emitir um SELECT ilustra as alterações feitas no banco de dados:
SQL >>> session . query ( User ) . filter ( User . name . in_ ([ 'ed' , 'fakeuser' ])) . all ()
[ < User ( name = 'ed' , fullname = 'Ed Jones' , nickname = 'eddie' ) > ]
Session
funciona em uma transação, também podemos reverter as alterações feitas. Vamos fazer duas alterações que reverteremos; O nome de usuário de ed_user
é definido como Edwardo
: >>> ed_user . name = 'Edwardo'
fake_user
: >>> fake_user = User ( name = 'fakeuser' , fullname = 'Invalid' , nickname = '12345' )
>>> session . add ( fake_user )
SQL >>> session . query ( User ) . filter ( User . name . in_ ([ 'Edwardo' , 'fakeuser' ])) . all ()
[ < User ( name = 'Edwardo' , fullname = 'Ed Jones' , nickname = 'eddie' ) > , < User ( name = 'fakeuser' , fullname = 'Invalid' , nickname = '12345' ) > ]
ed_user
, podemos ver que o nome de ed_user
voltou a ser ed
, e fake_user
foi expulso da sessão:emitir um SELECT ilustra as alterações feitas no banco de dados:
SQL >>> session . query ( User ) . filter ( User . name . in_ ([ 'ed' , 'fakeuser' ])) . all ()
[ < User ( name = 'ed' , fullname = 'Ed Jones' , nickname = 'eddie' ) > ]
Consulta
Um objeto Query
é criado usando o método query()
na Session
. Essa função recebe um número variável de argumentos, que pode ser qualquer combinação de classes e descritores instrumentados por classe. Abaixo, indicamos uma Query
que carrega instâncias de User
. Quando avaliada em um contexto iterativo, a lista de objetos User
presentes é retornada:
SQL >>> for instance in session . query ( User ) . order_by ( User . id ):
... print ( instance . name , instance . fullname )
ed Ed Jones
wendy Wendy Williams
mary Mary Contrary
fred Fred Flintstone
A Query
também aceita descritores instrumentados por ORM como argumentos. Sempre que várias entidades de classe ou entidades baseadas em coluna são expressas como argumentos para a função query()
, o resultado do retorno é expresso como tuplas:
SQL >>> for name , fullname in session . query ( User . name , User . fullname ):
... print ( name , fullname )
ed Ed Jones
wendy Wendy Williams
mary Mary Contrary
fred Fred Flintstone
As tuplas retornadas pelo Query
são denominadas tuplas, fornecidas pela classe KeyedTuple
, e podem ser tratadas como um objeto Python comum. Os nomes são os mesmos que o nome do atributo para um atributo e o nome da classe para uma classe:
SQL >>> for row in session . query ( User , User . name ) . all ():
... print ( row . User , row . name )
< User ( name = 'ed' , fullname = 'Ed Jones' , nickname = 'eddie' ) > ed
< User ( name = 'wendy' , fullname = 'Wendy Williams' , nickname = 'windy' ) > wendy
< User ( name = 'mary' , fullname = 'Mary Contrary' , nickname = 'mary' ) > mary
< User ( name = 'fred' , fullname = 'Fred Flintstone' , nickname = 'freddy' ) > fred
Você pode controlar os nomes das expressões de colunas individuais usando a construção label()
, disponível em qualquer objeto derivado de ColumnElement
, bem como em qualquer atributo de classe mapeado para um (como User.name
):
SQL >>> for row in session . query ( User . name . label ( 'name_label' )) . all ():
... print ( row . name_label )
ed
wendy
mary
fred
O nome dado a uma entidade completa, como User
, assumindo que várias entidades estejam presentes na chamada para query()
, pode ser controlado usando o aliased()
:
>>> from sqlalchemy.orm import aliased
>>> user_alias = aliased ( User , name = 'user_alias' )
SQL >>> for row in session . query ( user_alias , user_alias . name ) . all ():
... print ( row . user_alias )
< User ( name = 'ed' , fullname = 'Ed Jones' , nickname = 'eddie' ) >
< User ( name = 'wendy' , fullname = 'Wendy Williams' , nickname = 'windy' ) >
< User ( name = 'mary' , fullname = 'Mary Contrary' , nickname = 'mary' ) >
< User ( name = 'fred' , fullname = 'Fred Flintstone' , nickname = 'freddy' ) >
As operações básicas com o Query
incluem a emissão de LIMIT e OFFSET, mais convenientemente usando fatias de array Python e normalmente em conjunto com ORDER BY:
SQL >>> for u in session . query ( User ) . order_by ( User . id )[ 1 : 3 ]:
... print ( u )
< User ( name = 'wendy' , fullname = 'Wendy Williams' , nickname = 'windy' ) >
< User ( name = 'mary' , fullname = 'Mary Contrary' , nickname = 'mary' ) >
e resultados de filtragem, que são realizados com filter_by()
, que usa argumentos de palavra-chave:
SQL >>> for name , in session . query ( User . name ) . \
... filter_by ( fullname = 'Ed Jones' ):
... print ( name )
ed
… Ou filter()
, que usa construções de linguagem de expressão SQL mais flexíveis. Isso permite que você use operadores Python regulares com os atributos de nível de classe na sua classe mapeada:
SQL >>> for name , in session . query ( User . name ) . \
... filter ( User . fullname == 'Ed Jones' ):
... print ( name )
ed
O objeto Query
é totalmente generativo , o que significa que a maioria das chamadas de método retorna um novo objeto Query
no qual critérios adicionais podem ser adicionados. Por exemplo, para consultar usuários nomeados "ed" com um nome completo de "Ed Jones", você pode chamar filter()
duas vezes, que une os critérios usando AND
:
SQL >>> for user in session . query ( User ) . \
... filter ( User . name == 'ed' ) . \
... filter ( User . fullname == 'Ed Jones' ):
... print ( user )
< User ( name = 'ed' , fullname = 'Ed Jones' , nickname = 'eddie' ) >
Query
é criado usando o método query()
na Session
. Essa função recebe um número variável de argumentos, que pode ser qualquer combinação de classes e descritores instrumentados por classe. Abaixo, indicamos uma Query
que carrega instâncias de User
. Quando avaliada em um contexto iterativo, a lista de objetos User
presentes é retornada: SQL >>> for instance in session . query ( User ) . order_by ( User . id ):
... print ( instance . name , instance . fullname )
ed Ed Jones
wendy Wendy Williams
mary Mary Contrary
fred Fred Flintstone
Query
também aceita descritores instrumentados por ORM como argumentos. Sempre que várias entidades de classe ou entidades baseadas em coluna são expressas como argumentos para a função query()
, o resultado do retorno é expresso como tuplas: SQL >>> for name , fullname in session . query ( User . name , User . fullname ):
... print ( name , fullname )
ed Ed Jones
wendy Wendy Williams
mary Mary Contrary
fred Fred Flintstone
Query
são denominadas tuplas, fornecidas pela classe KeyedTuple
, e podem ser tratadas como um objeto Python comum. Os nomes são os mesmos que o nome do atributo para um atributo e o nome da classe para uma classe: SQL >>> for row in session . query ( User , User . name ) . all ():
... print ( row . User , row . name )
< User ( name = 'ed' , fullname = 'Ed Jones' , nickname = 'eddie' ) > ed
< User ( name = 'wendy' , fullname = 'Wendy Williams' , nickname = 'windy' ) > wendy
< User ( name = 'mary' , fullname = 'Mary Contrary' , nickname = 'mary' ) > mary
< User ( name = 'fred' , fullname = 'Fred Flintstone' , nickname = 'freddy' ) > fred
label()
, disponível em qualquer objeto derivado de ColumnElement
, bem como em qualquer atributo de classe mapeado para um (como User.name
): SQL >>> for row in session . query ( User . name . label ( 'name_label' )) . all ():
... print ( row . name_label )
ed
wendy
mary
fred
User
, assumindo que várias entidades estejam presentes na chamada para query()
, pode ser controlado usando o aliased()
: >>> from sqlalchemy.orm import aliased
>>> user_alias = aliased ( User , name = 'user_alias' )
SQL >>> for row in session . query ( user_alias , user_alias . name ) . all ():
... print ( row . user_alias )
< User ( name = 'ed' , fullname = 'Ed Jones' , nickname = 'eddie' ) >
< User ( name = 'wendy' , fullname = 'Wendy Williams' , nickname = 'windy' ) >
< User ( name = 'mary' , fullname = 'Mary Contrary' , nickname = 'mary' ) >
< User ( name = 'fred' , fullname = 'Fred Flintstone' , nickname = 'freddy' ) >
Query
incluem a emissão de LIMIT e OFFSET, mais convenientemente usando fatias de array Python e normalmente em conjunto com ORDER BY: SQL >>> for u in session . query ( User ) . order_by ( User . id )[ 1 : 3 ]:
... print ( u )
< User ( name = 'wendy' , fullname = 'Wendy Williams' , nickname = 'windy' ) >
< User ( name = 'mary' , fullname = 'Mary Contrary' , nickname = 'mary' ) >
filter_by()
, que usa argumentos de palavra-chave: SQL >>> for name , in session . query ( User . name ) . \
... filter_by ( fullname = 'Ed Jones' ):
... print ( name )
ed
filter()
, que usa construções de linguagem de expressão SQL mais flexíveis. Isso permite que você use operadores Python regulares com os atributos de nível de classe na sua classe mapeada: SQL >>> for name , in session . query ( User . name ) . \
... filter ( User . fullname == 'Ed Jones' ):
... print ( name )
ed
Query
é totalmente generativo , o que significa que a maioria das chamadas de método retorna um novo objeto Query
no qual critérios adicionais podem ser adicionados. Por exemplo, para consultar usuários nomeados "ed" com um nome completo de "Ed Jones", você pode chamar filter()
duas vezes, que une os critérios usando AND
: SQL >>> for user in session . query ( User ) . \
... filter ( User . name == 'ed' ) . \
... filter ( User . fullname == 'Ed Jones' ):
... print ( user )
< User ( name = 'ed' , fullname = 'Ed Jones' , nickname = 'eddie' ) >
Operadores de filtro comuns
Aqui está um resumo de alguns dos operadores mais comuns usados em filter()
:
equals
:
query . filter ( User . name == 'ed' )
not equals
:
query . filter ( User . name != 'ed' )
LIKE
:
query . filter ( User . name . like ( ' %e d%' ))
Nota
ColumnOperators.like()
renderiza o operador LIKE, que não faz ColumnOperators.like()
maiúsculas e minúsculas em alguns backends e ColumnOperators.like()
maiúsculas de minúsculas em outros. Para comparações garantidas sem ColumnOperators.ilike()
entre maiúsculas e minúsculas, use ColumnOperators.ilike()
.
ILIKE
(LIKE que não ILIKE
maiúsculas de minúsculas):
query . filter ( User . name . ilike ( ' %e d%' ))
Nota
a maioria dos back-end não oferece suporte direto ao ILIKE. Para esses, o operador ColumnOperators.ilike()
renderiza uma expressão combinando LIKE com a função SQL LOWER aplicada a cada operando.
IN
:
query . filter ( User . name . in_ ([ 'ed' , 'wendy' , 'jack' ]))
# works with query objects too:
query . filter ( User . name . in_ (
session . query ( User . name ) . filter ( User . name . like ( ' %e d%' ))
))
# use tuple_() for composite (multi-column) queries
from sqlalchemy import tuple_
query . filter (
tuple_ ( User . name , User . nickname ) . \
in_ ([( 'ed' , 'edsnickname' ), ( 'wendy' , 'windy' )])
)
NOT IN
:
query . filter ( ~ User . name . in_ ([ 'ed' , 'wendy' , 'jack' ]))
IS NULL
:
query . filter ( User . name == None )
# alternatively, if pep8/linters are a concern
query . filter ( User . name . is_ ( None ))
IS NOT NULL
:
query . filter ( User . name != None )
# alternatively, if pep8/linters are a concern
query . filter ( User . name . isnot ( None ))
AND
:
# use and_()
from sqlalchemy import and_
query . filter ( and_ ( User . name == 'ed' , User . fullname == 'Ed Jones' ))
# or send multiple expressions to .filter()
query . filter ( User . name == 'ed' , User . fullname == 'Ed Jones' )
# or chain multiple filter()/filter_by() calls
query . filter ( User . name == 'ed' ) . filter ( User . fullname == 'Ed Jones' )
Nota
Certifique-se de usar and_()
e não o Python and
operador!
OR
:
from sqlalchemy import or_
query . filter ( or_ ( User . name == 'ed' , User . name == 'wendy' ))
Nota
Certifique-se de usar or_()
e não o Python or
operador!
MATCH
:
query . filter ( User . name . match ( 'wendy' ))
Nota
match()
usa uma função MATCH
ou CONTAINS
específica do banco de dados; seu comportamento varia de acordo com o back-end e não está disponível em alguns back-end, como o SQLite.
Aqui está um resumo de alguns dos operadores mais comuns usados em
filter()
:equals
:
query . filter ( User . name == 'ed' )
not equals
:
query . filter ( User . name != 'ed' )
LIKE
:
query . filter ( User . name . like ( ' %e d%' ))
NotaColumnOperators.like()
renderiza o operador LIKE, que não fazColumnOperators.like()
maiúsculas e minúsculas em alguns backends eColumnOperators.like()
maiúsculas de minúsculas em outros. Para comparações garantidas semColumnOperators.ilike()
entre maiúsculas e minúsculas, useColumnOperators.ilike()
.
ILIKE
(LIKE que nãoILIKE
maiúsculas de minúsculas):
query . filter ( User . name . ilike ( ' %e d%' ))
Notaa maioria dos back-end não oferece suporte direto ao ILIKE. Para esses, o operadorColumnOperators.ilike()
renderiza uma expressão combinando LIKE com a função SQL LOWER aplicada a cada operando.
IN
:
query . filter ( User . name . in_ ([ 'ed' , 'wendy' , 'jack' ])) # works with query objects too: query . filter ( User . name . in_ ( session . query ( User . name ) . filter ( User . name . like ( ' %e d%' )) )) # use tuple_() for composite (multi-column) queries from sqlalchemy import tuple_ query . filter ( tuple_ ( User . name , User . nickname ) . \ in_ ([( 'ed' , 'edsnickname' ), ( 'wendy' , 'windy' )]) )
NOT IN
:
query . filter ( ~ User . name . in_ ([ 'ed' , 'wendy' , 'jack' ]))
IS NULL
:
query . filter ( User . name == None ) # alternatively, if pep8/linters are a concern query . filter ( User . name . is_ ( None ))
IS NOT NULL
:
query . filter ( User . name != None ) # alternatively, if pep8/linters are a concern query . filter ( User . name . isnot ( None ))
AND
:
# use and_() from sqlalchemy import and_ query . filter ( and_ ( User . name == 'ed' , User . fullname == 'Ed Jones' )) # or send multiple expressions to .filter() query . filter ( User . name == 'ed' , User . fullname == 'Ed Jones' ) # or chain multiple filter()/filter_by() calls query . filter ( User . name == 'ed' ) . filter ( User . fullname == 'Ed Jones' )
NotaCertifique-se de usarand_()
e não o Pythonand
operador!
OR
:
from sqlalchemy import or_ query . filter ( or_ ( User . name == 'ed' , User . name == 'wendy' ))
NotaCertifique-se de usaror_()
e não o Pythonor
operador!
MATCH
:
query . filter ( User . name . match ( 'wendy' ))
Notamatch()
usa uma funçãoMATCH
ouCONTAINS
específica do banco de dados; seu comportamento varia de acordo com o back-end e não está disponível em alguns back-end, como o SQLite.
Retornando listas e escalares
Vários métodos no Query
emitem imediatamente o SQL e retornam um valor que contém os resultados carregados do banco de dados. Aqui está um breve tour:
all()
retorna uma lista:
>>> query = session . query ( User ) . filter ( User . name . like ( ' %e d' )) . order_by ( User . id )
SQL >>> query . all ()
[ < User ( name = 'ed' , fullname = 'Ed Jones' , nickname = 'eddie' ) > ,
< User ( name = 'fred' , fullname = 'Fred Flintstone' , nickname = 'freddy' ) > ]
Aviso
Quando o objeto Query
retorna listas de objetos mapeados em ORM, como o objeto User
acima, as entradas são deduplicadas com base na chave primária, pois os resultados são interpretados no conjunto de resultados SQL. Ou seja, se a consulta SQL retornar uma linha com id=7
duas vezes, você só obteria um único objeto User(id=7)
na lista de resultados. Isso não se aplica ao caso em que colunas individuais são consultadas.
first()
aplica um limite de um e retorna o primeiro resultado como um escalar:
SQL >>> query . first ()
< User ( name = 'ed' , fullname = 'Ed Jones' , nickname = 'eddie' ) >
one()
busca totalmente todas as linhas e, se não houver exatamente uma identidade de objeto ou linha composta presente no resultado, gera um erro. Com várias linhas encontradas:
>>> user = query . one ()
Traceback ( most recent call last ):
...
MultipleResultsFound : Multiple rows were found for one ()
Sem linhas encontradas:
>>> user = query . filter ( User . id == 99 ) . one ()
Traceback ( most recent call last ):
...
NoResultFound : No row was found for one ()
O método one()
é ótimo para sistemas que esperam lidar com “nenhum item encontrado” versus “vários itens encontrados” de maneira diferente; como um serviço da web RESTful, que pode gerar um "404 não encontrado" quando nenhum resultado for encontrado, mas gerar um erro de aplicativo quando vários resultados forem encontrados.
one_or_none()
é como one()
, exceto que, se nenhum resultado for encontrado, não gera um erro; apenas retorna None
. Como one()
, no entanto, ele gera um erro se vários resultados forem encontrados.
scalar()
chama o método one()
e, com sucesso, retorna a primeira coluna da linha:
>>> query = session . query ( User . id ) . filter ( User . name == 'ed' ) . \
... order_by ( User . id )
SQL >>> query . scalar ()
1
Vários métodos no
Query
emitem imediatamente o SQL e retornam um valor que contém os resultados carregados do banco de dados. Aqui está um breve tour:all()
retorna uma lista:
>>> query = session . query ( User ) . filter ( User . name . like ( ' %e d' )) . order_by ( User . id ) SQL >>> query . all () [ < User ( name = 'ed' , fullname = 'Ed Jones' , nickname = 'eddie' ) > , < User ( name = 'fred' , fullname = 'Fred Flintstone' , nickname = 'freddy' ) > ]
AvisoQuando o objetoQuery
retorna listas de objetos mapeados em ORM, como o objetoUser
acima, as entradas são deduplicadas com base na chave primária, pois os resultados são interpretados no conjunto de resultados SQL. Ou seja, se a consulta SQL retornar uma linha comid=7
duas vezes, você só obteria um único objetoUser(id=7)
na lista de resultados. Isso não se aplica ao caso em que colunas individuais são consultadas.
first()
aplica um limite de um e retorna o primeiro resultado como um escalar:
SQL >>> query . first () < User ( name = 'ed' , fullname = 'Ed Jones' , nickname = 'eddie' ) >
one()
busca totalmente todas as linhas e, se não houver exatamente uma identidade de objeto ou linha composta presente no resultado, gera um erro. Com várias linhas encontradas:
>>> user = query . one () Traceback ( most recent call last ): ... MultipleResultsFound : Multiple rows were found for one ()
>>> user = query . filter ( User . id == 99 ) . one () Traceback ( most recent call last ): ... NoResultFound : No row was found for one ()
one()
é ótimo para sistemas que esperam lidar com “nenhum item encontrado” versus “vários itens encontrados” de maneira diferente; como um serviço da web RESTful, que pode gerar um "404 não encontrado" quando nenhum resultado for encontrado, mas gerar um erro de aplicativo quando vários resultados forem encontrados.one_or_none()
é comoone()
, exceto que, se nenhum resultado for encontrado, não gera um erro; apenas retornaNone
. Comoone()
, no entanto, ele gera um erro se vários resultados forem encontrados.scalar()
chama o métodoone()
e, com sucesso, retorna a primeira coluna da linha:
>>> query = session . query ( User . id ) . filter ( User . name == 'ed' ) . \ ... order_by ( User . id ) SQL >>> query . scalar () 1
Usando SQL textual
Seqüências de caracteres literais podem ser usadas de maneira flexível com o Query
, especificando seu uso com a construção text()
, que é aceita pela maioria dos métodos aplicáveis. Por exemplo, filter()
e order_by()
:
>>> from sqlalchemy import text
SQL >>> for user in session . query ( User ) . \
... filter ( text ( "id<224" )) . \
... order_by ( text ( "id" )) . all ():
... print ( user . name )
ed
wendy
mary
fred
Os parâmetros de ligação podem ser especificados com SQL baseado em cadeia, usando dois pontos. Para especificar os valores, use o método params()
:
SQL >>> session . query ( User ) . filter ( text ( "id<:value and name=:name" )) . \
... params ( value = 224 , name = 'fred' ) . order_by ( User . id ) . one ()
< User ( name = 'fred' , fullname = 'Fred Flintstone' , nickname = 'freddy' ) >
Para usar uma instrução inteiramente baseada em string, uma construção text()
representando uma instrução completa pode ser passada para from_statement()
. Sem especificadores adicionais, as colunas na string SQL são correspondidas às colunas do modelo com base no nome, como abaixo, onde usamos apenas um asterisco para representar o carregamento de todas as colunas:
SQL >>> session . query ( User ) . from_statement (
... text ( "SELECT * FROM users where name=:name" )) . \
... params ( name = 'ed' ) . all ()
[ < User ( name = 'ed' , fullname = 'Ed Jones' , nickname = 'eddie' ) > ]
As colunas correspondentes no nome funcionam em casos simples, mas podem se tornar difíceis de lidar ao lidar com instruções complexas que contêm nomes de colunas duplicados ou ao usar construções ORM anonimizadas que não correspondem facilmente a nomes específicos. Além disso, há um comportamento de digitação presente em nossas colunas mapeadas que podemos achar necessário ao lidar com linhas de resultados. Para esses casos, a construção text()
permite vincular seu SQL textual às expressões de coluna mapeadas no Core ou no ORM; podemos conseguir isso passando expressões de coluna como argumentos posicionais para o método TextClause.columns()
:
>>> stmt = text ( "SELECT name, id, fullname, nickname "
... "FROM users where name=:name" )
>>> stmt = stmt . columns ( User . name , User . id , User . fullname , User . nickname )
SQL >>> session . query ( User ) . from_statement ( stmt ) . params ( name = 'ed' ) . all ()
[ < User ( name = 'ed' , fullname = 'Ed Jones' , nickname = 'eddie' ) > ]
Novo na versão 1.1: O método TextClause.columns()
agora aceita expressões de coluna que serão correspondidas posicionalmente a um conjunto de resultados SQL em texto sem formatação, eliminando a necessidade de os nomes das colunas corresponderem ou até serem únicos na instrução SQL.
Ao selecionar a partir de uma construção text()
, a Query
ainda pode especificar quais colunas e entidades devem ser retornadas; em vez de query(User)
, também podemos solicitar as colunas individualmente, como em qualquer outro caso:
>>> stmt = text ( "SELECT name, id FROM users where name=:name" )
>>> stmt = stmt . columns ( User . name , User . id )
SQL >>> session . query ( User . id , User . name ) . \
... from_statement ( stmt ) . params ( name = 'ed' ) . all ()
[( 1 , u 'ed' )]
Veja também
Usando SQL textual - A construção text()
explicada da perspectiva de consultas somente do Core.
Seqüências de caracteres literais podem ser usadas de maneira flexível com o
Os parâmetros de ligação podem ser especificados com SQL baseado em cadeia, usando dois pontos. Para especificar os valores, use o método
Para usar uma instrução inteiramente baseada em string, uma construção
As colunas correspondentes no nome funcionam em casos simples, mas podem se tornar difíceis de lidar ao lidar com instruções complexas que contêm nomes de colunas duplicados ou ao usar construções ORM anonimizadas que não correspondem facilmente a nomes específicos. Além disso, há um comportamento de digitação presente em nossas colunas mapeadas que podemos achar necessário ao lidar com linhas de resultados. Para esses casos, a construção
Query
, especificando seu uso com a construção text()
, que é aceita pela maioria dos métodos aplicáveis. Por exemplo, filter()
e order_by()
: >>> from sqlalchemy import text
SQL >>> for user in session . query ( User ) . \
... filter ( text ( "id<224" )) . \
... order_by ( text ( "id" )) . all ():
... print ( user . name )
ed
wendy
mary
fred
params()
: SQL >>> session . query ( User ) . filter ( text ( "id<:value and name=:name" )) . \
... params ( value = 224 , name = 'fred' ) . order_by ( User . id ) . one ()
< User ( name = 'fred' , fullname = 'Fred Flintstone' , nickname = 'freddy' ) >
text()
representando uma instrução completa pode ser passada para from_statement()
. Sem especificadores adicionais, as colunas na string SQL são correspondidas às colunas do modelo com base no nome, como abaixo, onde usamos apenas um asterisco para representar o carregamento de todas as colunas: SQL >>> session . query ( User ) . from_statement (
... text ( "SELECT * FROM users where name=:name" )) . \
... params ( name = 'ed' ) . all ()
[ < User ( name = 'ed' , fullname = 'Ed Jones' , nickname = 'eddie' ) > ]
text()
permite vincular seu SQL textual às expressões de coluna mapeadas no Core ou no ORM; podemos conseguir isso passando expressões de coluna como argumentos posicionais para o método TextClause.columns()
: >>> stmt = text ( "SELECT name, id, fullname, nickname "
... "FROM users where name=:name" )
>>> stmt = stmt . columns ( User . name , User . id , User . fullname , User . nickname )
SQL >>> session . query ( User ) . from_statement ( stmt ) . params ( name = 'ed' ) . all ()
[ < User ( name = 'ed' , fullname = 'Ed Jones' , nickname = 'eddie' ) > ]
Novo na versão 1.1: O método
Ao selecionar a partir de uma construção TextClause.columns()
agora aceita expressões de coluna que serão correspondidas posicionalmente a um conjunto de resultados SQL em texto sem formatação, eliminando a necessidade de os nomes das colunas corresponderem ou até serem únicos na instrução SQL.text()
, a Query
ainda pode especificar quais colunas e entidades devem ser retornadas; em vez de query(User)
, também podemos solicitar as colunas individualmente, como em qualquer outro caso: >>> stmt = text ( "SELECT name, id FROM users where name=:name" )
>>> stmt = stmt . columns ( User . name , User . id )
SQL >>> session . query ( User . id , User . name ) . \
... from_statement ( stmt ) . params ( name = 'ed' ) . all ()
[( 1 , u 'ed' )]
Veja também
Usando SQL textual - A construção text()
explicada da perspectiva de consultas somente do Core.Contando
Query
inclui um método de conveniência para contar chamado count()
:
SQL >>> session . query ( User ) . filter ( User . name . like ( ' %e d' )) . count ()
2
O método count()
é usado para determinar quantas linhas a instrução SQL retornaria. Observando o SQL gerado acima, o SQLAlchemy sempre coloca o que estamos consultando em uma subconsulta e, em seguida, conta as linhas a partir disso. Em alguns casos, isso pode ser reduzido a uma SELECT count(*) FROM table
mais simples, no entanto, as versões modernas do SQLAlchemy não tentam adivinhar quando isso é apropriado, pois o SQL exato pode ser emitido usando meios mais explícitos.
Para situações em que a “coisa a ser contada” precisa ser indicada especificamente, podemos especificar a função “contar” diretamente usando a expressão func.count()
, disponível no construto func
. Abaixo, usamos para retornar a contagem de cada nome de usuário distinto:
>>> from sqlalchemy import func
SQL >>> session . query ( func . count ( User . name ), User . name ) . group_by ( User . name ) . all ()
[( 1 , u 'ed' ), ( 1 , u 'fred' ), ( 1 , u 'mary' ), ( 1 , u 'wendy' )]
Para obter nossa SELECT count(*) FROM table
simples, podemos aplicá-la como:
SQL >>> session . query ( func . count ( '*' )) . select_from ( User ) . scalar ()
4
O uso de select_from()
pode ser removido se expressarmos a contagem em termos da chave primária do User
diretamente:
SQL >>> session . query ( func . count ( User . id )) . scalar ()
4
Query
inclui um método de conveniência para contar chamado count()
: SQL >>> session . query ( User ) . filter ( User . name . like ( ' %e d' )) . count ()
2
count()
é usado para determinar quantas linhas a instrução SQL retornaria. Observando o SQL gerado acima, o SQLAlchemy sempre coloca o que estamos consultando em uma subconsulta e, em seguida, conta as linhas a partir disso. Em alguns casos, isso pode ser reduzido a uma SELECT count(*) FROM table
mais simples, no entanto, as versões modernas do SQLAlchemy não tentam adivinhar quando isso é apropriado, pois o SQL exato pode ser emitido usando meios mais explícitos.Para situações em que a “coisa a ser contada” precisa ser indicada especificamente, podemos especificar a função “contar” diretamente usando a expressão
func.count()
, disponível no construto func
. Abaixo, usamos para retornar a contagem de cada nome de usuário distinto: >>> from sqlalchemy import func
SQL >>> session . query ( func . count ( User . name ), User . name ) . group_by ( User . name ) . all ()
[( 1 , u 'ed' ), ( 1 , u 'fred' ), ( 1 , u 'mary' ), ( 1 , u 'wendy' )]
SELECT count(*) FROM table
simples, podemos aplicá-la como: SQL >>> session . query ( func . count ( '*' )) . select_from ( User ) . scalar ()
4
select_from()
pode ser removido se expressarmos a contagem em termos da chave primária do User
diretamente: SQL >>> session . query ( func . count ( User . id )) . scalar ()
4
Construindo um relacionamento
Vamos considerar como uma segunda tabela, relacionada ao User
, pode ser mapeada e consultada. Os usuários do nosso sistema podem armazenar qualquer número de endereços de email associados ao seu nome de usuário. Isso implica uma associação básica para muitos dos users
para uma nova tabela que armazena endereços de email, que chamaremos de addresses
. Usando declarativo, definimos esta tabela junto com sua classe mapeada, Address
:
>>> from sqlalchemy import ForeignKey
>>> from sqlalchemy.orm import relationship
>>> class Address ( Base ):
... __tablename__ = 'addresses'
... id = Column ( Integer , primary_key = True )
... email_address = Column ( String , nullable = False )
... user_id = Column ( Integer , ForeignKey ( 'users.id' ))
...
... user = relationship ( "User" , back_populates = "addresses" )
...
... def __repr__ ( self ):
... return "<Address(email_address=' %s ')>" % self . email_address
>>> User . addresses = relationship (
... "Address" , order_by = Address . id , back_populates = "user" )
A classe acima introduz a construção ForeignKey
, que é uma diretiva aplicada à Column
que indica que os valores nessa coluna devem ser restritos a valores presentes na coluna remota nomeada. Esse é um recurso central dos bancos de dados relacionais e é a "cola" que transforma uma coleção de tabelas desconectada de outro modo para ter ricos relacionamentos sobrepostos. A ForeignKey
acima expressa que os valores na coluna users.id
devem ser restritos a esses valores na coluna users.id
, ou seja, sua chave primária.
Uma segunda diretiva, conhecida como relationship()
, informa ao ORM que a própria classe Address
deve ser vinculada à classe User
, usando o atributo Address.user
. relationship()
usa os relacionamentos de chave estrangeira entre as duas tabelas para determinar a natureza desse vínculo, determinando que Address.user
será muitos para um . Uma diretiva de relationship()
adicional é colocada na classe mapeada pelo User
sob o atributo User.addresses
. Nas duas diretivas relationship()
, o parâmetro relationship.back_populates
é designado para se referir aos nomes de atributos complementares; ao fazer isso, cada relationship()
pode tomar uma decisão inteligente sobre o mesmo relacionamento, expresso em sentido inverso; por um lado, Address.user
refere-se a uma instância de User
e, por outro lado, User.addresses
refere-se a uma lista de instâncias de Address
.
Nota
O parâmetro relationship.back_populates
é uma versão mais recente de um recurso SQLAlchemy muito comum chamado relationship.backref
. O parâmetro relationship.backref
não foi a lugar nenhum e sempre permanecerá disponível! O relationship.back_populates
é a mesma coisa, exceto um pouco mais detalhado e mais fácil de manipular. Para uma visão geral de todo o tópico, consulte a seção Vinculando Relacionamentos à Backref .
O verso de um relacionamento muitos-para-um é sempre um para muitos . Um catálogo completo de configurações de relationship()
disponível está em Padrões Básicos de Relacionamento .
Os dois relacionamentos complementares Address.user
e User.addresses
são referidos como um relacionamento bidirecional e são um recurso essencial do SQLAlchemy ORM. A seção Vinculando Relacionamentos ao Backref discute o recurso "backref" em detalhes.
Os argumentos para o relationship()
que dizem respeito à classe remota podem ser especificados usando cadeias, assumindo que o sistema Declarativo esteja em uso. Após a conclusão de todos os mapeamentos, essas cadeias são avaliadas como expressões Python para produzir o argumento real, no caso acima, a classe User
. Os nomes permitidos durante esta avaliação incluem, entre outras coisas, os nomes de todas as classes criadas em termos da base declarada.
Consulte a documentação para o relationship()
para obter mais detalhes sobre o estilo do argumento.
Você sabia ?
- uma restrição FOREIGN KEY na maioria dos bancos de dados relacionais (embora não todos) só pode vincular a uma coluna de chave primária ou a uma coluna que possui uma restrição UNIQUE.
- uma restrição FOREIGN KEY que se refere a uma chave primária de várias colunas, e ela própria possui várias colunas, é conhecida como "chave estrangeira composta". Também pode fazer referência a um subconjunto dessas colunas.
- As colunas FOREIGN KEY podem se atualizar automaticamente, em resposta a uma alteração na coluna ou linha referenciada. Isso é conhecido como ação referencial CASCADE e é uma função integrada do banco de dados relacional.
- CHAVE ESTRANGEIRA pode se referir a sua própria tabela. Isso é chamado de chave estrangeira "auto-referencial".
- Leia mais sobre chaves estrangeiras em Foreign Key - Wikipedia .
Precisamos criar a tabela de addresses
no banco de dados, para que possamos emitir outro CREATE de nossos metadados, que pulará as tabelas que já foram criadas:
SQL >>> Base . metadata . create_all ( engine )
User
, pode ser mapeada e consultada. Os usuários do nosso sistema podem armazenar qualquer número de endereços de email associados ao seu nome de usuário. Isso implica uma associação básica para muitos dos users
para uma nova tabela que armazena endereços de email, que chamaremos de addresses
. Usando declarativo, definimos esta tabela junto com sua classe mapeada, Address
: >>> from sqlalchemy import ForeignKey
>>> from sqlalchemy.orm import relationship
>>> class Address ( Base ):
... __tablename__ = 'addresses'
... id = Column ( Integer , primary_key = True )
... email_address = Column ( String , nullable = False )
... user_id = Column ( Integer , ForeignKey ( 'users.id' ))
...
... user = relationship ( "User" , back_populates = "addresses" )
...
... def __repr__ ( self ):
... return "<Address(email_address=' %s ')>" % self . email_address
>>> User . addresses = relationship (
... "Address" , order_by = Address . id , back_populates = "user" )
ForeignKey
, que é uma diretiva aplicada à Column
que indica que os valores nessa coluna devem ser restritos a valores presentes na coluna remota nomeada. Esse é um recurso central dos bancos de dados relacionais e é a "cola" que transforma uma coleção de tabelas desconectada de outro modo para ter ricos relacionamentos sobrepostos. A ForeignKey
acima expressa que os valores na coluna users.id
devem ser restritos a esses valores na coluna users.id
, ou seja, sua chave primária.Uma segunda diretiva, conhecida como
relationship()
, informa ao ORM que a própria classe Address
deve ser vinculada à classe User
, usando o atributo Address.user
. relationship()
usa os relacionamentos de chave estrangeira entre as duas tabelas para determinar a natureza desse vínculo, determinando que Address.user
será muitos para um . Uma diretiva de relationship()
adicional é colocada na classe mapeada pelo User
sob o atributo User.addresses
. Nas duas diretivas relationship()
, o parâmetro relationship.back_populates
é designado para se referir aos nomes de atributos complementares; ao fazer isso, cada relationship()
pode tomar uma decisão inteligente sobre o mesmo relacionamento, expresso em sentido inverso; por um lado, Address.user
refere-se a uma instância de User
e, por outro lado, User.addresses
refere-se a uma lista de instâncias de Address
.
Nota
O parâmetro relationship.back_populates
é uma versão mais recente de um recurso SQLAlchemy muito comum chamado relationship.backref
. O parâmetro relationship.backref
não foi a lugar nenhum e sempre permanecerá disponível! O relationship.back_populates
é a mesma coisa, exceto um pouco mais detalhado e mais fácil de manipular. Para uma visão geral de todo o tópico, consulte a seção Vinculando Relacionamentos à Backref .relationship()
disponível está em Padrões Básicos de Relacionamento .Os dois relacionamentos complementares
Address.user
e User.addresses
são referidos como um relacionamento bidirecional e são um recurso essencial do SQLAlchemy ORM. A seção Vinculando Relacionamentos ao Backref discute o recurso "backref" em detalhes.Os argumentos para o
relationship()
que dizem respeito à classe remota podem ser especificados usando cadeias, assumindo que o sistema Declarativo esteja em uso. Após a conclusão de todos os mapeamentos, essas cadeias são avaliadas como expressões Python para produzir o argumento real, no caso acima, a classe User
. Os nomes permitidos durante esta avaliação incluem, entre outras coisas, os nomes de todas as classes criadas em termos da base declarada.Consulte a documentação para o
relationship()
para obter mais detalhes sobre o estilo do argumento.
Você sabia ?
- uma restrição FOREIGN KEY na maioria dos bancos de dados relacionais (embora não todos) só pode vincular a uma coluna de chave primária ou a uma coluna que possui uma restrição UNIQUE.
- uma restrição FOREIGN KEY que se refere a uma chave primária de várias colunas, e ela própria possui várias colunas, é conhecida como "chave estrangeira composta". Também pode fazer referência a um subconjunto dessas colunas.
- As colunas FOREIGN KEY podem se atualizar automaticamente, em resposta a uma alteração na coluna ou linha referenciada. Isso é conhecido como ação referencial CASCADE e é uma função integrada do banco de dados relacional.
- CHAVE ESTRANGEIRA pode se referir a sua própria tabela. Isso é chamado de chave estrangeira "auto-referencial".
- Leia mais sobre chaves estrangeiras em Foreign Key - Wikipedia .
addresses
no banco de dados, para que possamos emitir outro CREATE de nossos metadados, que pulará as tabelas que já foram criadas: SQL >>> Base . metadata . create_all ( engine )
Trabalhando com objetos relacionados
Consultando com junta
Agora que temos duas tabelas, podemos mostrar mais alguns recursos Query
, especificamente como criar consultas que lidam com as duas tabelas ao mesmo tempo. A página da Wikipedia no SQL JOIN oferece uma boa introdução para juntar técnicas, várias das quais ilustraremos aqui.
Para construir uma junção implícita simples entre User
e Address
, podemos usar Query.filter()
para igualar suas colunas relacionadas. Abaixo, carregamos as entidades User
e de Address
uma só vez usando este método:
SQL >>> for u , a in session . query ( User , Address ) . \
... filter ( User . id == Address . user_id ) . \
... filter ( Address . email_address == 'jack@google.com' ) . \
... all ():
... print ( u )
... print ( a )
< User ( name = 'jack' , fullname = 'Jack Bean' , nickname = 'gjffdd' ) >
< Address ( email_address = 'jack@google.com' ) >
A sintaxe real do SQL JOIN, por outro lado, é alcançada mais facilmente usando o Query.join()
método:
SQL >>> session . query ( User ) . join ( Address ) . \
... filter ( Address . email_address == 'jack@google.com' ) . \
... all ()
[ < User ( name = 'jack' , fullname = 'Jack Bean' , nickname = 'gjffdd' ) > ]
Query.join()
sabe como se unir User
e Address
porque existe apenas uma chave estrangeira entre eles. Se não houver chaves estrangeiras, ou várias, Query.join()
funcionará melhor quando um dos seguintes formulários for usado:
query . join ( Address , User . id == Address . user_id ) # explicit condition
query . join ( User . addresses ) # specify relationship from left to right
query . join ( Address , User . addresses ) # same, with explicit target
query . join ( 'addresses' ) # same, using a string
Como seria de esperar, a mesma idéia é usada para junções "externas", usando a outerjoin()
função:
query . outerjoin ( User . addresses ) # LEFT OUTER JOIN
A documentação de referência para join()
contém informações detalhadas e exemplos dos estilos de chamada aceitos por este método; join()
é um método importante no centro de uso de qualquer aplicativo fluente em SQL.
O Query.join()
método normalmente ingressará no item mais à esquerda na lista de entidades, quando a cláusula ON for omitida ou se a cláusula ON for uma expressão SQL simples. Para controlar a primeira entidade na lista de JOINs, use o Query.select_from()
método:
query = session . query ( User , Address ) . select_from ( Address ) . join ( User )
Query
, especificamente como criar consultas que lidam com as duas tabelas ao mesmo tempo. A página da Wikipedia no SQL JOIN oferece uma boa introdução para juntar técnicas, várias das quais ilustraremos aqui.Para construir uma junção implícita simples entre
User
e Address
, podemos usar Query.filter()
para igualar suas colunas relacionadas. Abaixo, carregamos as entidades User
e de Address
uma só vez usando este método: SQL >>> for u , a in session . query ( User , Address ) . \
... filter ( User . id == Address . user_id ) . \
... filter ( Address . email_address == 'jack@google.com' ) . \
... all ():
... print ( u )
... print ( a )
< User ( name = 'jack' , fullname = 'Jack Bean' , nickname = 'gjffdd' ) >
< Address ( email_address = 'jack@google.com' ) >
Query.join()
método: SQL >>> session . query ( User ) . join ( Address ) . \
... filter ( Address . email_address == 'jack@google.com' ) . \
... all ()
[ < User ( name = 'jack' , fullname = 'Jack Bean' , nickname = 'gjffdd' ) > ]
Query.join()
sabe como se unir User
e Address
porque existe apenas uma chave estrangeira entre eles. Se não houver chaves estrangeiras, ou várias, Query.join()
funcionará melhor quando um dos seguintes formulários for usado: query . join ( Address , User . id == Address . user_id ) # explicit condition
query . join ( User . addresses ) # specify relationship from left to right
query . join ( Address , User . addresses ) # same, with explicit target
query . join ( 'addresses' ) # same, using a string
outerjoin()
função: query . outerjoin ( User . addresses ) # LEFT OUTER JOIN
join()
contém informações detalhadas e exemplos dos estilos de chamada aceitos por este método; join()
é um método importante no centro de uso de qualquer aplicativo fluente em SQL.
O
Query.join()
método normalmente ingressará no item mais à esquerda na lista de entidades, quando a cláusula ON for omitida ou se a cláusula ON for uma expressão SQL simples. Para controlar a primeira entidade na lista de JOINs, use o Query.select_from()
método: query = session . query ( User , Address ) . select_from ( Address ) . join ( User )
Usando aliases
Ao consultar várias tabelas, se a mesma tabela precisar ser referenciada mais de uma vez, o SQL normalmente exige que a tabela seja aliasada com outro nome, para que possa ser distinguida de outras ocorrências dessa tabela. O Query
suporta isso explicitamente usando a aliased
construção. Abaixo, ingressamos na Address
entidade duas vezes, para localizar um usuário que tenha dois endereços de email distintos ao mesmo tempo:
>>> from sqlalchemy.orm import aliased
>>> adalias1 = aliased ( Address )
>>> adalias2 = aliased ( Address )
SQL >>> for username , email1 , email2 in \
... session . query ( User . name , adalias1 . email_address , adalias2 . email_address ) . \
... join ( adalias1 , User . addresses ) . \
... join ( adalias2 , User . addresses ) . \
... filter ( adalias1 . email_address == 'jack@google.com' ) . \
... filter ( adalias2 . email_address == 'j25@yahoo.com' ):
... print ( username , email1 , email2 )
jack jack @google.com j25 @yahoo.com
Ao consultar várias tabelas, se a mesma tabela precisar ser referenciada mais de uma vez, o SQL normalmente exige que a tabela seja aliasada com outro nome, para que possa ser distinguida de outras ocorrências dessa tabela. O
Query
suporta isso explicitamente usando a aliased
construção. Abaixo, ingressamos na Address
entidade duas vezes, para localizar um usuário que tenha dois endereços de email distintos ao mesmo tempo: >>> from sqlalchemy.orm import aliased
>>> adalias1 = aliased ( Address )
>>> adalias2 = aliased ( Address )
SQL >>> for username , email1 , email2 in \
... session . query ( User . name , adalias1 . email_address , adalias2 . email_address ) . \
... join ( adalias1 , User . addresses ) . \
... join ( adalias2 , User . addresses ) . \
... filter ( adalias1 . email_address == 'jack@google.com' ) . \
... filter ( adalias2 . email_address == 'j25@yahoo.com' ):
... print ( username , email1 , email2 )
jack jack @google.com j25 @yahoo.com
Usando Subqueries
O Query
é adequado para gerar instruções que podem ser usadas como subconsultas. Suponha que desejássemos carregar User
objetos juntamente com uma contagem de quantos Address
registros cada usuário possui. A melhor maneira de gerar SQL dessa maneira é obter a contagem de endereços agrupados por IDs de usuário e JOIN para o pai. Nesse caso, usamos um LEFT OUTTER JOIN para recuperar as linhas dos usuários que não possuem endereços, por exemplo:
SELECT users .* , adr_count . address_count FROM users LEFT OUTER JOIN
( SELECT user_id , count ( * ) AS address_count
FROM addresses GROUP BY user_id ) AS adr_count
ON users . id = adr_count . user_id
Usando o Query
, construímos uma declaração como essa de dentro para fora. O statement
acessador retorna uma expressão SQL que representa a instrução gerada por um determinado Query
- esta é uma instância de uma select()
construção, descrita no Tutorial da Linguagem de Expressão SQL :
>>> from sqlalchemy.sql import func
>>> stmt = session . query ( Address . user_id , func . count ( '*' ) . \
... label ( 'address_count' )) . \
... group_by ( Address . user_id ) . subquery ()
A func
palavra-chave gera funções SQL, e o subquery()
método Query
produz uma construção de expressão SQL representando uma instrução SELECT incorporada em um alias (na verdade, é uma abreviação de query.statement.alias()
).
Depois de termos nossa declaração, ela se comporta como uma Table
construção, como a que criamos users
no início deste tutorial. As colunas na instrução são acessíveis através de um atributo chamado c
:
SQL >>> for u , count in session . query ( User , stmt . c . address_count ) . \
... outerjoin ( stmt , User . id == stmt . c . user_id ) . order_by ( User . id ):
... print ( u , count )
< User ( name = 'ed' , fullname = 'Ed Jones' , nickname = 'eddie' ) > None
< User ( name = 'wendy' , fullname = 'Wendy Williams' , nickname = 'windy' ) > None
< User ( name = 'mary' , fullname = 'Mary Contrary' , nickname = 'mary' ) > None
< User ( name = 'fred' , fullname = 'Fred Flintstone' , nickname = 'freddy' ) > None
< User ( name = 'jack' , fullname = 'Jack Bean' , nickname = 'gjffdd' ) > 2
O
Usando o
A
Depois de termos nossa declaração, ela se comporta como uma
Query
é adequado para gerar instruções que podem ser usadas como subconsultas. Suponha que desejássemos carregar User
objetos juntamente com uma contagem de quantos Address
registros cada usuário possui. A melhor maneira de gerar SQL dessa maneira é obter a contagem de endereços agrupados por IDs de usuário e JOIN para o pai. Nesse caso, usamos um LEFT OUTTER JOIN para recuperar as linhas dos usuários que não possuem endereços, por exemplo: SELECT users .* , adr_count . address_count FROM users LEFT OUTER JOIN
( SELECT user_id , count ( * ) AS address_count
FROM addresses GROUP BY user_id ) AS adr_count
ON users . id = adr_count . user_id
Query
, construímos uma declaração como essa de dentro para fora. O statement
acessador retorna uma expressão SQL que representa a instrução gerada por um determinado Query
- esta é uma instância de uma select()
construção, descrita no Tutorial da Linguagem de Expressão SQL : >>> from sqlalchemy.sql import func
>>> stmt = session . query ( Address . user_id , func . count ( '*' ) . \
... label ( 'address_count' )) . \
... group_by ( Address . user_id ) . subquery ()
func
palavra-chave gera funções SQL, e o subquery()
método Query
produz uma construção de expressão SQL representando uma instrução SELECT incorporada em um alias (na verdade, é uma abreviação de query.statement.alias()
).Depois de termos nossa declaração, ela se comporta como uma
Table
construção, como a que criamos users
no início deste tutorial. As colunas na instrução são acessíveis através de um atributo chamado c
: SQL >>> for u , count in session . query ( User , stmt . c . address_count ) . \
... outerjoin ( stmt , User . id == stmt . c . user_id ) . order_by ( User . id ):
... print ( u , count )
< User ( name = 'ed' , fullname = 'Ed Jones' , nickname = 'eddie' ) > None
< User ( name = 'wendy' , fullname = 'Wendy Williams' , nickname = 'windy' ) > None
< User ( name = 'mary' , fullname = 'Mary Contrary' , nickname = 'mary' ) > None
< User ( name = 'fred' , fullname = 'Fred Flintstone' , nickname = 'freddy' ) > None
< User ( name = 'jack' , fullname = 'Jack Bean' , nickname = 'gjffdd' ) > 2
Seleção de Entidades de Subqueries
Acima, apenas selecionamos um resultado que incluía uma coluna de uma subconsulta. E se quiséssemos que nossa subconsulta fosse mapeada para uma entidade? Para isso, usamos aliased()
para associar um "alias" de uma classe mapeada a uma subconsulta:
SQL >>> stmt = session . query ( Address ) . \
... filter ( Address . email_address != 'j25@yahoo.com' ) . \
... subquery ()
>>> adalias = aliased ( Address , stmt )
>>> for user , address in session . query ( User , adalias ) . \
... join ( adalias , User . addresses ):
... print ( user )
... print ( address )
< User ( name = 'jack' , fullname = 'Jack Bean' , nickname = 'gjffdd' ) >
< Address ( email_address = 'jack@google.com' ) >
Acima, apenas selecionamos um resultado que incluía uma coluna de uma subconsulta. E se quiséssemos que nossa subconsulta fosse mapeada para uma entidade? Para isso, usamos
aliased()
para associar um "alias" de uma classe mapeada a uma subconsulta: SQL >>> stmt = session . query ( Address ) . \
... filter ( Address . email_address != 'j25@yahoo.com' ) . \
... subquery ()
>>> adalias = aliased ( Address , stmt )
>>> for user , address in session . query ( User , adalias ) . \
... join ( adalias , User . addresses ):
... print ( user )
... print ( address )
< User ( name = 'jack' , fullname = 'Jack Bean' , nickname = 'gjffdd' ) >
< Address ( email_address = 'jack@google.com' ) >
Usando EXISTS
A palavra-chave EXISTS no SQL é um operador booleano que retorna True se a expressão especificada contiver alguma linha. Pode ser usado em muitos cenários no lugar de junções e também é útil para localizar linhas que não possuem uma linha correspondente em uma tabela relacionada.
Existe uma construção EXISTS explícita, que se parece com isso:
>>> from sqlalchemy.sql import exists
>>> stmt = exists () . where ( Address . user_id == User . id )
SQL >>> for name , in session . query ( User . name ) . filter ( stmt ):
... print ( name )
jack
O Query
dispõe de vários operadores que fazem uso de EXISTE automaticamente. Acima, a declaração pode ser expressa ao longo do User.addresses
relacionamento usando any()
:
SQL >>> for name , in session . query ( User . name ) . \
... filter ( User . addresses . any ()):
... print ( name )
jack
any()
também aceita o critério, para limitar as linhas correspondentes:
SQL >>> for name , in session . query ( User . name ) . \
... filter ( User . addresses . any ( Address . email_address . like ( ' %g oogle%' ))):
... print ( name )
jack
has()
é o mesmo operador any()
das relações muitos-para-um (observe também o ~
operador aqui, que significa "NÃO"):
SQL >>> session . query ( Address ) . \
... filter ( ~ Address . user . has ( User . name == 'jack' )) . all ()
[]
A palavra-chave EXISTS no SQL é um operador booleano que retorna True se a expressão especificada contiver alguma linha. Pode ser usado em muitos cenários no lugar de junções e também é útil para localizar linhas que não possuem uma linha correspondente em uma tabela relacionada.
Existe uma construção EXISTS explícita, que se parece com isso:
O
Existe uma construção EXISTS explícita, que se parece com isso:
>>> from sqlalchemy.sql import exists
>>> stmt = exists () . where ( Address . user_id == User . id )
SQL >>> for name , in session . query ( User . name ) . filter ( stmt ):
... print ( name )
jack
Query
dispõe de vários operadores que fazem uso de EXISTE automaticamente. Acima, a declaração pode ser expressa ao longo do User.addresses
relacionamento usando any()
: SQL >>> for name , in session . query ( User . name ) . \
... filter ( User . addresses . any ()):
... print ( name )
jack
any()
também aceita o critério, para limitar as linhas correspondentes: SQL >>> for name , in session . query ( User . name ) . \
... filter ( User . addresses . any ( Address . email_address . like ( ' %g oogle%' ))):
... print ( name )
jack
has()
é o mesmo operador any()
das relações muitos-para-um (observe também o ~
operador aqui, que significa "NÃO"): SQL >>> session . query ( Address ) . \
... filter ( ~ Address . user . has ( User . name == 'jack' )) . all ()
[]
Operadores de relacionamento comuns
Aqui estão todos os operadores que constroem relacionamentos - cada um deles está vinculado à documentação da API, que inclui detalhes completos sobre o uso e o comportamento:
__eq__()
(comparação "igual a muitos" para um):
query . filter ( Address . user == someuser )
__ne__()
(comparação "muitos para um" não é igual "):
query . filter ( Address . user != someuser )
- É NULL (comparação muitos-para-um, também usa
__eq__()
):
query . filter ( Address . user == None )
contains()
(usado para coleções um para muitos):
query . filter ( User . addresses . contains ( someaddress ))
any()
(usado para coleções):
query . filter ( User . addresses . any ( Address . email_address == 'bar' ))
# also takes keyword arguments:
query . filter ( User . addresses . any ( email_address = 'bar' ))
has()
(usado para referências escalares):
query . filter ( Address . user . has ( name = 'ed' ))
Query.with_parent()
(usado para qualquer relacionamento):
session . query ( Address ) . with_parent ( someuser , 'addresses' )
Aqui estão todos os operadores que constroem relacionamentos - cada um deles está vinculado à documentação da API, que inclui detalhes completos sobre o uso e o comportamento:
__eq__()
(comparação "igual a muitos" para um):
query . filter ( Address . user == someuser )
__ne__()
(comparação "muitos para um" não é igual "):
query . filter ( Address . user != someuser )
- É NULL (comparação muitos-para-um, também usa
__eq__()
):
query . filter ( Address . user == None )
contains()
(usado para coleções um para muitos):
query . filter ( User . addresses . contains ( someaddress ))
any()
(usado para coleções):
query . filter ( User . addresses . any ( Address . email_address == 'bar' )) # also takes keyword arguments: query . filter ( User . addresses . any ( email_address = 'bar' ))
has()
(usado para referências escalares):
query . filter ( Address . user . has ( name = 'ed' ))
Query.with_parent()
(usado para qualquer relacionamento):
session . query ( Address ) . with_parent ( someuser , 'addresses' )
Carregamento ansioso
Lembre-se de que ilustramos uma operação de carregamento lento , quando acessamos a User.addresses
coleção de a User
e SQL foi emitida. Se você deseja reduzir o número de consultas (drasticamente, em muitos casos), podemos aplicar uma carga ágil à operação de consulta. O SQLAlchemy oferece três tipos de carregamento rápido, dois dos quais são automáticos e um terceiro que envolve critérios personalizados. Todos os três são geralmente chamados por meio de funções conhecidas como opções de consulta, que fornecem instruções adicionais Query
sobre como gostaríamos que vários atributos fossem carregados, através do Query.options()
método
User.addresses
coleção de a User
e SQL foi emitida. Se você deseja reduzir o número de consultas (drasticamente, em muitos casos), podemos aplicar uma carga ágil à operação de consulta. O SQLAlchemy oferece três tipos de carregamento rápido, dois dos quais são automáticos e um terceiro que envolve critérios personalizados. Todos os três são geralmente chamados por meio de funções conhecidas como opções de consulta, que fornecem instruções adicionais Query
sobre como gostaríamos que vários atributos fossem carregados, através do Query.options()
métodoSelecionar carga
Neste caso, gostaríamos de indicar que User.addresses
deve carregar ansiosamente. Uma boa opção para carregar um conjunto de objetos e suas coleções relacionadas é a orm.selectinload()
opção, que emite uma segunda instrução SELECT que carrega completamente as coleções associadas aos resultados recém-carregados. O nome "selectin" se origina do fato de a instrução SELECT usar uma cláusula IN para localizar linhas relacionadas para vários objetos de uma só vez:
>>> from sqlalchemy.orm import selectinload
SQL >>> jack = session . query ( User ) . \
... options ( selectinload ( User . addresses )) . \
... filter_by ( name = 'jack' ) . one ()
>>> jack
< User ( name = 'jack' , fullname = 'Jack Bean' , nickname = 'gjffdd' ) >
>>> jack . addresses
[ < Address ( email_address = 'jack@google.com' ) > , < Address ( email_address = 'j25@yahoo.com' ) > ]
Neste caso, gostaríamos de indicar que
User.addresses
deve carregar ansiosamente. Uma boa opção para carregar um conjunto de objetos e suas coleções relacionadas é a orm.selectinload()
opção, que emite uma segunda instrução SELECT que carrega completamente as coleções associadas aos resultados recém-carregados. O nome "selectin" se origina do fato de a instrução SELECT usar uma cláusula IN para localizar linhas relacionadas para vários objetos de uma só vez: >>> from sqlalchemy.orm import selectinload
SQL >>> jack = session . query ( User ) . \
... options ( selectinload ( User . addresses )) . \
... filter_by ( name = 'jack' ) . one ()
>>> jack
< User ( name = 'jack' , fullname = 'Jack Bean' , nickname = 'gjffdd' ) >
>>> jack . addresses
[ < Address ( email_address = 'jack@google.com' ) > , < Address ( email_address = 'j25@yahoo.com' ) > ]
Carga
A outra função de carregamento rápido automático é mais conhecida e é chamada orm.joinedload()
. Esse estilo de carregamento emite um JOIN, por padrão, um LEFT OUTER JOIN, para que o objeto principal e o objeto ou coleção relacionado sejam carregados em uma etapa. Ilustramos o carregamento da mesma addresses
coleção dessa maneira - observe que, embora a User.addresses
coleção ativada jack
esteja realmente preenchida no momento, a consulta emitirá a junção extra, independentemente:
>>> from sqlalchemy.orm import joinedload
SQL >>> jack = session . query ( User ) . \
... options ( joinedload ( User . addresses )) . \
... filter_by ( name = 'jack' ) . one ()
>>> jack
< User ( name = 'jack' , fullname = 'Jack Bean' , nickname = 'gjffdd' ) >
>>> jack . addresses
[ < Address ( email_address = 'jack@google.com' ) > , < Address ( email_address = 'j25@yahoo.com' ) > ]
Observe que, embora o OUTER JOIN tenha resultado em duas linhas, ainda temos apenas uma instância de User
volta. Isso ocorre porque Query
aplica uma estratégia de "exclusividade", baseada na identidade do objeto, às entidades retornadas. Isso é especificamente para que o carregamento antecipado combinado possa ser aplicado sem afetar os resultados da consulta.
Enquanto joinedload()
existe há muito tempo, selectinload()
é uma forma mais nova de carregamento ansioso. selectinload()
tende a ser mais apropriado para carregar coleções relacionadas, enquanto joinedload()
tende a ser mais adequado para relacionamentos muitos-para-um, devido ao fato de que apenas uma linha é carregada para o lead e o objeto relacionado. subqueryload()
Também existe outra forma de carregamento, que pode ser usada no lugar do selectinload()
uso de chaves primárias compostas em determinados back-end.
joinedload()
não é um substituto para join()
A junção criada por joinedload()
é anonimamente aliasada, de modo que não afeta os resultados da consulta . Uma chamada Query.order_by()
ou não pode fazer referência a essas tabelas com alias - as chamadas junções de "espaço do usuário" são construídas usando . A lógica para isso é que é aplicada apenas para afetar como objetos ou coleções relacionados são carregados como um detalhe de otimização - eles podem ser adicionados ou removidos sem afetar os resultados reais. Consulte a seção O Zen do carregamento ansioso para obter uma descrição detalhada de como isso é usado.Query.filter()
Query.join()
joinedload()
A outra função de carregamento rápido automático é mais conhecida e é chamada
Observe que, embora o OUTER JOIN tenha resultado em duas linhas, ainda temos apenas uma instância de
Enquanto
A junção criada por
orm.joinedload()
. Esse estilo de carregamento emite um JOIN, por padrão, um LEFT OUTER JOIN, para que o objeto principal e o objeto ou coleção relacionado sejam carregados em uma etapa. Ilustramos o carregamento da mesma addresses
coleção dessa maneira - observe que, embora a User.addresses
coleção ativada jack
esteja realmente preenchida no momento, a consulta emitirá a junção extra, independentemente: >>> from sqlalchemy.orm import joinedload
SQL >>> jack = session . query ( User ) . \
... options ( joinedload ( User . addresses )) . \
... filter_by ( name = 'jack' ) . one ()
>>> jack
< User ( name = 'jack' , fullname = 'Jack Bean' , nickname = 'gjffdd' ) >
>>> jack . addresses
[ < Address ( email_address = 'jack@google.com' ) > , < Address ( email_address = 'j25@yahoo.com' ) > ]
User
volta. Isso ocorre porque Query
aplica uma estratégia de "exclusividade", baseada na identidade do objeto, às entidades retornadas. Isso é especificamente para que o carregamento antecipado combinado possa ser aplicado sem afetar os resultados da consulta.Enquanto
joinedload()
existe há muito tempo, selectinload()
é uma forma mais nova de carregamento ansioso. selectinload()
tende a ser mais apropriado para carregar coleções relacionadas, enquanto joinedload()
tende a ser mais adequado para relacionamentos muitos-para-um, devido ao fato de que apenas uma linha é carregada para o lead e o objeto relacionado. subqueryload()
Também existe outra forma de carregamento, que pode ser usada no lugar do selectinload()
uso de chaves primárias compostas em determinados back-end.joinedload()
não é um substituto para join()
joinedload()
é anonimamente aliasada, de modo que não afeta os resultados da consulta . Uma chamada Query.order_by()
ou não pode fazer referência a essas tabelas com alias - as chamadas junções de "espaço do usuário" são construídas usando . A lógica para isso é que é aplicada apenas para afetar como objetos ou coleções relacionados são carregados como um detalhe de otimização - eles podem ser adicionados ou removidos sem afetar os resultados reais. Consulte a seção O Zen do carregamento ansioso para obter uma descrição detalhada de como isso é usado.Query.filter()
Query.join()
joinedload()
Junção explícita + Eagerload
Um terceiro estilo de carregamento rápido é quando estamos construindo um JOIN explicitamente para localizar as linhas primárias e gostaria de aplicar adicionalmente a tabela extra a um objeto ou coleção relacionada no objeto primário. Esse recurso é fornecido por meio da orm.contains_eager()
função e normalmente é útil para pré-carregar o objeto muitos para um em uma consulta que precisa filtrar no mesmo objeto. A seguir, ilustramos o carregamento de uma Address
linha e o User
objeto relacionado , filtrando o User
"jack" nomeado e usando orm.contains_eager()
para aplicar as colunas "user" ao Address.user
atributo:
>>> from sqlalchemy.orm import contains_eager
SQL >>> jacks_addresses = session . query ( Address ) . \
... join ( Address . user ) . \
... filter ( User . name == 'jack' ) . \
... options ( contains_eager ( Address . user )) . \
... all ()
>>> jacks_addresses
[ < Address ( email_address = 'jack@google.com' ) > , < Address ( email_address = 'j25@yahoo.com' ) > ]
>>> jacks_addresses [ 0 ] . user
< User ( name = 'jack' , fullname = 'Jack Bean' , nickname = 'gjffdd' ) >
Para obter mais informações sobre carregamento rápido, incluindo como configurar várias formas de carregamento por padrão, consulte a seção Técnicas de carregamento de relacionamento .
Um terceiro estilo de carregamento rápido é quando estamos construindo um JOIN explicitamente para localizar as linhas primárias e gostaria de aplicar adicionalmente a tabela extra a um objeto ou coleção relacionada no objeto primário. Esse recurso é fornecido por meio da
Para obter mais informações sobre carregamento rápido, incluindo como configurar várias formas de carregamento por padrão, consulte a seção Técnicas de carregamento de relacionamento .
orm.contains_eager()
função e normalmente é útil para pré-carregar o objeto muitos para um em uma consulta que precisa filtrar no mesmo objeto. A seguir, ilustramos o carregamento de uma Address
linha e o User
objeto relacionado , filtrando o User
"jack" nomeado e usando orm.contains_eager()
para aplicar as colunas "user" ao Address.user
atributo: >>> from sqlalchemy.orm import contains_eager
SQL >>> jacks_addresses = session . query ( Address ) . \
... join ( Address . user ) . \
... filter ( User . name == 'jack' ) . \
... options ( contains_eager ( Address . user )) . \
... all ()
>>> jacks_addresses
[ < Address ( email_address = 'jack@google.com' ) > , < Address ( email_address = 'j25@yahoo.com' ) > ]
>>> jacks_addresses [ 0 ] . user
< User ( name = 'jack' , fullname = 'Jack Bean' , nickname = 'gjffdd' ) >
Excluindo
Vamos tentar excluir jack
e ver como isso acontece. Marcaremos o objeto como excluído na sessão e emitiremos uma count
consulta para ver se não há linhas restantes:
>>> session . delete ( jack )
SQL >>> session . query ( User ) . filter_by ( name = 'jack' ) . count ()
0
Por enquanto, tudo bem.E os Address
objetos de Jack ?
SQL >>> session . query ( Address ) . filter (
... Address . email_address . in_ ([ 'jack@google.com' , 'j25@yahoo.com' ])
... ) . count ()
2
Ainda estão lá! Analisando o SQL de liberação, podemos ver que a user_id
coluna de cada endereço foi definida como NULL, mas as linhas não foram excluídas. SQLAlchemy não assume que exclui cascata, você precisa dizer para fazê-lo.
jack
e ver como isso acontece. Marcaremos o objeto como excluído na sessão e emitiremos uma count
consulta para ver se não há linhas restantes: >>> session . delete ( jack )
SQL >>> session . query ( User ) . filter_by ( name = 'jack' ) . count ()
0
Address
objetos de Jack ? SQL >>> session . query ( Address ) . filter (
... Address . email_address . in_ ([ 'jack@google.com' , 'j25@yahoo.com' ])
... ) . count ()
2
user_id
coluna de cada endereço foi definida como NULL, mas as linhas não foram excluídas. SQLAlchemy não assume que exclui cascata, você precisa dizer para fazê-lo.Configurando o Cascade delete / delete-órfão
Vamos configurar as opções em cascata no User.addresses
relacionamento para alterar o comportamento. Embora o SQLAlchemy permita adicionar novos atributos e relacionamentos aos mapeamentos a qualquer momento, nesse caso, o relacionamento existente precisa ser removido, portanto, precisamos desmontar os mapeamentos completamente e começar novamente - fecharemos o Session
:
>>> session . close ()
ROLLBACK
e use um novo declarative_base()
:
>>> Base = declarative_base ()
Em seguida, declararemos a User
classe, adicionando o addresses
relacionamento, incluindo a configuração em cascata (deixaremos o construtor de fora também):
>>> class User ( Base ):
... __tablename__ = 'users'
...
... id = Column ( Integer , primary_key = True )
... name = Column ( String )
... fullname = Column ( String )
... nickname = Column ( String )
...
... addresses = relationship ( "Address" , back_populates = 'user' ,
... cascade = "all, delete, delete-orphan" )
...
... def __repr__ ( self ):
... return "<User(name=' %s ', fullname=' %s ', nickname=' %s ')>" % (
... self . name , self . fullname , self . nickname )
Em seguida, recriamos Address
, observando que, nesse caso, já criamos o Address.user
relacionamento por meio da User
classe:
>>> class Address ( Base ):
... __tablename__ = 'addresses'
... id = Column ( Integer , primary_key = True )
... email_address = Column ( String , nullable = False )
... user_id = Column ( Integer , ForeignKey ( 'users.id' ))
... user = relationship ( "User" , back_populates = "addresses" )
...
... def __repr__ ( self ):
... return "<Address(email_address=' %s ')>" % self . email_address
Agora, quando carregamos o usuário jack
(abaixo de usar get()
, que carrega pela chave primária), a remoção de um endereço da addresses
coleção correspondente resultará na Address
exclusão:
# load Jack by primary key
SQL >>> jack = session . query ( User ) . get ( 5 )
# remove one Address (lazy load fires off)
SQL >>> del jack . addresses [ 1 ]
# only one address remains
SQL >>> session . query ( Address ) . filter (
... Address . email_address . in_ ([ 'jack@google.com' , 'j25@yahoo.com' ])
... ) . count ()
1
Excluir Jack excluirá Jack e o restante Address
associado ao usuário:
Mais sobre Cascades
Mais detalhes sobre a configuração de cascatas estão em Cascades . A funcionalidade em cascata também pode se integrar facilmente à funcionalidade do banco de dados relacional. Consulte Usando exclusões passivas para obter detalhes.ON DELETE CASCADE
Vamos configurar as opções em cascata no
e use um novo
Em seguida, declararemos a
Em seguida, recriamos
Agora, quando carregamos o usuário
Excluir Jack excluirá Jack e o restante
User.addresses
relacionamento para alterar o comportamento. Embora o SQLAlchemy permita adicionar novos atributos e relacionamentos aos mapeamentos a qualquer momento, nesse caso, o relacionamento existente precisa ser removido, portanto, precisamos desmontar os mapeamentos completamente e começar novamente - fecharemos o Session
: >>> session . close ()
ROLLBACK
declarative_base()
: >>> Base = declarative_base ()
User
classe, adicionando o addresses
relacionamento, incluindo a configuração em cascata (deixaremos o construtor de fora também): >>> class User ( Base ):
... __tablename__ = 'users'
...
... id = Column ( Integer , primary_key = True )
... name = Column ( String )
... fullname = Column ( String )
... nickname = Column ( String )
...
... addresses = relationship ( "Address" , back_populates = 'user' ,
... cascade = "all, delete, delete-orphan" )
...
... def __repr__ ( self ):
... return "<User(name=' %s ', fullname=' %s ', nickname=' %s ')>" % (
... self . name , self . fullname , self . nickname )
Address
, observando que, nesse caso, já criamos o Address.user
relacionamento por meio da User
classe: >>> class Address ( Base ):
... __tablename__ = 'addresses'
... id = Column ( Integer , primary_key = True )
... email_address = Column ( String , nullable = False )
... user_id = Column ( Integer , ForeignKey ( 'users.id' ))
... user = relationship ( "User" , back_populates = "addresses" )
...
... def __repr__ ( self ):
... return "<Address(email_address=' %s ')>" % self . email_address
jack
(abaixo de usar get()
, que carrega pela chave primária), a remoção de um endereço da addresses
coleção correspondente resultará na Address
exclusão: # load Jack by primary key
SQL >>> jack = session . query ( User ) . get ( 5 )
# remove one Address (lazy load fires off)
SQL >>> del jack . addresses [ 1 ]
# only one address remains
SQL >>> session . query ( Address ) . filter (
... Address . email_address . in_ ([ 'jack@google.com' , 'j25@yahoo.com' ])
... ) . count ()
1
Address
associado ao usuário:
Mais sobre Cascades
Mais detalhes sobre a configuração de cascatas estão em Cascades . A funcionalidade em cascata também pode se integrar facilmente à funcionalidade do banco de dados relacional. Consulte Usando exclusões passivas para obter detalhes.ON DELETE CASCADE
Construindo um relacionamento de muitos para muitos
Estamos entrando na rodada de bônus aqui, mas vamos mostrar um relacionamento de muitos para muitos. Também vamos nos infiltrar em alguns outros recursos, apenas para fazer um tour. Tornaremos nosso aplicativo um aplicativo de blog, onde os usuários podem escrever BlogPost
itens com Keyword
itens associados a eles.
Para um muitos para muitos, precisamos criar uma Table
construção não mapeada para servir como tabela de associação. Parece com o seguinte:
>>> from sqlalchemy import Table , Text
>>> # association table
>>> post_keywords = Table ( 'post_keywords' , Base . metadata ,
... Column ( 'post_id' , ForeignKey ( 'posts.id' ), primary_key = True ),
... Column ( 'keyword_id' , ForeignKey ( 'keywords.id' ), primary_key = True )
... )
Acima, podemos ver que declarar Table
diretamente é um pouco diferente de declarar uma classe mapeada. Table
é uma função construtora, portanto, cada Column
argumento individual é separado por vírgula. O Column
objeto também recebe seu nome explicitamente, em vez de ser retirado de um nome de atributo atribuído.
Em seguida, definimos BlogPost
e Keyword
, usando relationship()
construções complementares , cada um referente à post_keywords
tabela como uma tabela de associação:
>>> class BlogPost ( Base ):
... __tablename__ = 'posts'
...
... id = Column ( Integer , primary_key = True )
... user_id = Column ( Integer , ForeignKey ( 'users.id' ))
... headline = Column ( String ( 255 ), nullable = False )
... body = Column ( Text )
...
... # many to many BlogPost<->Keyword
... keywords = relationship ( 'Keyword' ,
... secondary = post_keywords ,
... back_populates = 'posts' )
...
... def __init__ ( self , headline , body , author ):
... self . author = author
... self . headline = headline
... self . body = body
...
... def __repr__ ( self ):
... return "BlogPost( %r , %r , %r )" % ( self . headline , self . body , self . author )
>>> class Keyword ( Base ):
... __tablename__ = 'keywords'
...
... id = Column ( Integer , primary_key = True )
... keyword = Column ( String ( 50 ), nullable = False , unique = True )
... posts = relationship ( 'BlogPost' ,
... secondary = post_keywords ,
... back_populates = 'keywords' )
...
... def __init__ ( self , keyword ):
... self . keyword = keyword
Nota
As declarações de classe acima ilustram __init__()
métodos explícitos . Lembre-se, ao usar Declarative, é opcional!
Acima, o relacionamento muitos-para-muitos é BlogPost.keywords
. O recurso definidor de um relacionamento muitos para muitos é o secondary
argumento de palavra - chave que faz referência a um Table
objeto que representa a tabela de associação. Esta tabela contém apenas colunas que referenciam os dois lados do relacionamento; se tiver quaisquer outras colunas, como a sua própria chave primária ou chaves estrangeiras para outras tabelas, SQLAlchemy requer um padrão de uso diferente chamado de “objeto associação”, descrito na Associação Objeto .
Também gostaríamos que a nossa BlogPost
turma tivesse um author
campo. Adicionaremos isso como outro relacionamento bidirecional, exceto que um problema que temos é que um único usuário pode ter muitas postagens no blog. Quando acessamos User.posts
, gostaríamos de poder filtrar ainda mais os resultados para não carregar a coleção inteira. Para isso, usamos uma configuração aceita pelo relationship()
chamado lazy='dynamic'
, que configura uma estratégia de carregador alternativo no atributo:
>>> BlogPost . author = relationship ( User , back_populates = "posts" )
>>> User . posts = relationship ( BlogPost , back_populates = "author" , lazy = "dynamic" )
Crie novas tabelas:
SQL >>> Base . metadata . create_all ( engine )
O uso não é muito diferente do que estamos fazendo. Vamos dar a Wendy algumas postagens no blog:
SQL >>> wendy = session . query ( User ) . \
... filter_by ( name = 'wendy' ) . \
... one ()
>>> post = BlogPost ( "Wendy's Blog Post" , "This is a test" , wendy )
>>> session . add ( post )
Estamos armazenando palavras-chave exclusivamente no banco de dados, mas sabemos que ainda não temos nenhuma, para que possamos criá-las:
>>> post . keywords . append ( Keyword ( 'wendy' ))
>>> post . keywords . append ( Keyword ( 'firstpost' ))
Agora, podemos procurar todas as postagens do blog com a palavra-chave 'firstpost'. Usaremos o any
operador para localizar "postagens de blog em que qualquer uma de suas palavras-chave tenha a cadeia de palavras-chave 'firstpost'":
SQL >>> session . query ( BlogPost ) . \
... filter ( BlogPost . keywords . any ( keyword = 'firstpost' )) . \
... all ()
[ BlogPost ( "Wendy's Blog Post" , 'This is a test' , < User ( name = 'wendy' , fullname = 'Wendy Williams' , nickname = 'windy' ) > )]
Se quisermos procurar postagens pertencentes ao usuário wendy
, podemos dizer à consulta para restringir esse User
objeto como pai:
SQL >>> session . query ( BlogPost ) . \
... filter ( BlogPost . author == wendy ) . \
... filter ( BlogPost . keywords . any ( keyword = 'firstpost' )) . \
... all ()
[ BlogPost ( "Wendy's Blog Post" , 'This is a test' , < User ( name = 'wendy' , fullname = 'Wendy Williams' , nickname = 'windy' ) > )]
Ou podemos usar o próprio posts
relacionamento de Wendy , que é um relacionamento "dinâmico", para consultar diretamente a partir daí:
SQL >>> wendy . posts . \
... filter ( BlogPost . keywords . any ( keyword = 'firstpost' )) . \
... all ()
[ BlogPost ( "Wendy's Blog Post" , 'This is a test' , < User ( name = 'wendy' , fullname = 'Wendy Williams' , nickname = 'windy' ) > )]
BlogPost
itens com Keyword
itens associados a eles.Para um muitos para muitos, precisamos criar uma
Table
construção não mapeada para servir como tabela de associação. Parece com o seguinte: >>> from sqlalchemy import Table , Text
>>> # association table
>>> post_keywords = Table ( 'post_keywords' , Base . metadata ,
... Column ( 'post_id' , ForeignKey ( 'posts.id' ), primary_key = True ),
... Column ( 'keyword_id' , ForeignKey ( 'keywords.id' ), primary_key = True )
... )
Table
diretamente é um pouco diferente de declarar uma classe mapeada. Table
é uma função construtora, portanto, cada Column
argumento individual é separado por vírgula. O Column
objeto também recebe seu nome explicitamente, em vez de ser retirado de um nome de atributo atribuído.Em seguida, definimos
BlogPost
e Keyword
, usando relationship()
construções complementares , cada um referente à post_keywords
tabela como uma tabela de associação: >>> class BlogPost ( Base ):
... __tablename__ = 'posts'
...
... id = Column ( Integer , primary_key = True )
... user_id = Column ( Integer , ForeignKey ( 'users.id' ))
... headline = Column ( String ( 255 ), nullable = False )
... body = Column ( Text )
...
... # many to many BlogPost<->Keyword
... keywords = relationship ( 'Keyword' ,
... secondary = post_keywords ,
... back_populates = 'posts' )
...
... def __init__ ( self , headline , body , author ):
... self . author = author
... self . headline = headline
... self . body = body
...
... def __repr__ ( self ):
... return "BlogPost( %r , %r , %r )" % ( self . headline , self . body , self . author )
>>> class Keyword ( Base ):
... __tablename__ = 'keywords'
...
... id = Column ( Integer , primary_key = True )
... keyword = Column ( String ( 50 ), nullable = False , unique = True )
... posts = relationship ( 'BlogPost' ,
... secondary = post_keywords ,
... back_populates = 'keywords' )
...
... def __init__ ( self , keyword ):
... self . keyword = keyword
Nota
As declarações de classe acima ilustram __init__()
métodos explícitos . Lembre-se, ao usar Declarative, é opcional!BlogPost.keywords
. O recurso definidor de um relacionamento muitos para muitos é o secondary
argumento de palavra - chave que faz referência a um Table
objeto que representa a tabela de associação. Esta tabela contém apenas colunas que referenciam os dois lados do relacionamento; se tiver quaisquer outras colunas, como a sua própria chave primária ou chaves estrangeiras para outras tabelas, SQLAlchemy requer um padrão de uso diferente chamado de “objeto associação”, descrito na Associação Objeto .Também gostaríamos que a nossa
BlogPost
turma tivesse um author
campo. Adicionaremos isso como outro relacionamento bidirecional, exceto que um problema que temos é que um único usuário pode ter muitas postagens no blog. Quando acessamos User.posts
, gostaríamos de poder filtrar ainda mais os resultados para não carregar a coleção inteira. Para isso, usamos uma configuração aceita pelo relationship()
chamado lazy='dynamic'
, que configura uma estratégia de carregador alternativo no atributo: >>> BlogPost . author = relationship ( User , back_populates = "posts" )
>>> User . posts = relationship ( BlogPost , back_populates = "author" , lazy = "dynamic" )
SQL >>> Base . metadata . create_all ( engine )
SQL >>> wendy = session . query ( User ) . \
... filter_by ( name = 'wendy' ) . \
... one ()
>>> post = BlogPost ( "Wendy's Blog Post" , "This is a test" , wendy )
>>> session . add ( post )
>>> post . keywords . append ( Keyword ( 'wendy' ))
>>> post . keywords . append ( Keyword ( 'firstpost' ))
any
operador para localizar "postagens de blog em que qualquer uma de suas palavras-chave tenha a cadeia de palavras-chave 'firstpost'": SQL >>> session . query ( BlogPost ) . \
... filter ( BlogPost . keywords . any ( keyword = 'firstpost' )) . \
... all ()
[ BlogPost ( "Wendy's Blog Post" , 'This is a test' , < User ( name = 'wendy' , fullname = 'Wendy Williams' , nickname = 'windy' ) > )]
wendy
, podemos dizer à consulta para restringir esse User
objeto como pai: SQL >>> session . query ( BlogPost ) . \
... filter ( BlogPost . author == wendy ) . \
... filter ( BlogPost . keywords . any ( keyword = 'firstpost' )) . \
... all ()
[ BlogPost ( "Wendy's Blog Post" , 'This is a test' , < User ( name = 'wendy' , fullname = 'Wendy Williams' , nickname = 'windy' ) > )]
posts
relacionamento de Wendy , que é um relacionamento "dinâmico", para consultar diretamente a partir daí: SQL >>> wendy . posts . \
... filter ( BlogPost . keywords . any ( keyword = 'firstpost' )) . \
... all ()
[ BlogPost ( "Wendy's Blog Post" , 'This is a test' , < User ( name = 'wendy' , fullname = 'Wendy Williams' , nickname = 'windy' ) > )]
Referência adicional
Referência de consulta: API de consulta
Referência do Mapper: Configuração do Mapper
Referência de relacionamento: configuração de relacionamento
Referência da sessão: usando a sessão
Referência do Mapper: Configuração do Mapper
Referência de relacionamento: configuração de relacionamento
Referência da sessão: usando a sessão
0 Comentários