Python SQLAlquimia (ORM)

De

Documentação do SQLAlchemy: https://www.sqlalchemy.org/

Introdução e avançado com SQLAlchemy: https://zhuanlan.zhihu.com/p/27400862

1. Introdução ao ORM e SQLAlchemy

ORM significa Mapeamento Relacional de Objeto (Object Relational Mapping). É mapear a “ estrutura da tabela do banco de dados relacional ” para o “ objeto Python ”, de forma que o objeto Python possa ser manipulado diretamente sem escrever SQL para operar, ou seja, o objeto é considerado no nível do código, não SQL.

A implementação específica é

  • Converter tabelas de banco de dados em classes Python
  • onde as colunas de dados são  atributos da classe
  • Operações de banco de dados como métodos

vantagem:

  1. Conciso e fácil de ler: abstraia a tabela de dados em um objeto (modelo de dados), que é mais intuitivo e fácil de ler
  2. Portátil: Encapsula uma variedade de mecanismos de banco de dados, a operação é basicamente a mesma para vários bancos de dados e o código é fácil de manter
  3. Mais seguro: evita efetivamente a injeção de SQL

O framework ORM mais famoso em Python é o SQLAlchemy. Pode ser combinado com qualquer framework web de terceiros, como flask, tornado, django, fastapi, etc. Comparado com o Django ORM, o SQLALchemy está mais próximo das instruções SQL nativas, portanto, é menos difícil de aprender.

SQLALchemy consiste nas seguintes 5 partes:

  • Mecanismo: mecanismo de estrutura
  • Pool de conexões: pool de conexões de banco de dados
  • Dialeto: Dialeto, chamando diferentes APIs de banco de dados (Oracle, postgresql, Mysql) e executando instruções SQL correspondentes. Ou seja, a categoria de API do banco de dados DB.
  • Esquema / Tipos: regras de mapeamento entre "classes para tabelas"
  • Linguagem de Expressão SQL: Linguagem de Expressão SQL

O diagrama é o seguinte:

Processo em execução:

  • Primeiro, a operação inserida pelo usuário será entregue ao objeto ORM
  • Em seguida, o objeto ORM enviará a operação do usuário para SQLALchemy Core
  • Em segundo lugar, a operação será convertida em instruções SQL por Schema/Types e SQL Expression Language
  • Em seguida, o mecanismo corresponderá ao mecanismo configurado pelo usuário e retirará uma conexão do pool de conexões
  • Por fim, o link chamará o DBAPI por meio do dialeto e transferirá a instrução SQL para o DBAPI para execução

Conceitos relacionados

tipos de dados comuns

instalar sqlalchemy

Instalar: pip install sqlalchemy

string de conexão do banco de dados

O SQLAlchemy deve contar com outros módulos que manipulam o banco de dados a ser utilizado, que é o DBAPI citado acima.

Quando SQLAlchemy é usado com DBAPI, a string de conexão também é diferente, conforme a seguir:

MySQL-Python
    mysql+mysqldb://<usuário>:<senha>@<host>[:<porta>]/<dbname>

pymysql
    mysql+pymysql://<username>:<password>@<host>/<dbname>[?<options>]

MySQL-Connector
    mysql+mysqlconnector://<user>:<password>@<host>[:<port>]/<dbname>

cx_Oracle
    oracle+cx_oracle://user:pass@host:port/dbname[?key=value&key=value...]

mecanismo de conexão

O início de qualquer aplicativo SQLAlchemy é um objeto Engine, esse objeto atua como uma fonte central de conexões para um banco de dados específico, fornecendo o que é chamado de pool de conexões para essas conexões de banco de dados.

O objeto Engine geralmente é um objeto global que é criado apenas uma vez para um determinado servidor de banco de dados e é configurado com uma string de URL que descreve como se conectar ao host ou back-end do banco de dados.

>>> from sqlalchemy import create_engine
>>> engine = create_engine('sqlite:///:memory:', echo=True)

Inicialmente, crie o mecanismo, que mantém um Pool (pool de conexão) e um Dialeto (dialeto) dentro do mecanismo, e o dialeto é usado para identificar o tipo de banco de dados de conexão específico.

Quando o mecanismo é criado, o Pool e o Dialeto também foram criados, mas eles não estão realmente conectados ao banco de dados neste momento e não serão conectados ao banco de dados até que a instrução específica .connect() seja executada.

Existem muitos parâmetros de create_engine, listo alguns dos mais comumente usados:

  • echo=False  -- se verdadeiro, o mecanismo registrará todas as instruções junto com  repr() o manipulador de log padrão da lista de argumentos.
  • enable_from_linting  – O padrão é True. Um aviso será emitido se a instrução SELECT fornecida não estiver vinculada a elementos que resultariam em um produto cartesiano.
  • codificação  -- o padrão é utf-8
  • futuro  -- use o estilo 2.0
  • hide_parameters  -- Valor booleano, quando definido como True, os parâmetros da instrução SQL não serão exibidos no log de informações, nem serão formatados como objetos StatementError.
  • listeners  – uma lista de um ou mais  PoolListener objetos que receberão eventos do pool de conexões.
  • logging_name  – identificador de string, o padrão é a string hexadecimal do id do objeto.
  • max_identifier_length  – inteiro; substitui o comprimento máximo do identificador determinado pelo dialeto.
  • max_overflow=10  -- O número de conexões permitidas para "overflow" no pool de conexões, ou seja, o número de conexões que podem ser abertas acima ou além da configuração de tamanho do pool (5 por padrão).
  • pool_size=5  -- O número de conexões a serem mantidas abertas no pool de conexões. O padrão é 5, quando definido como 0 significa conexões ilimitadas
  • Pool_recycle    define o tempo para limitar quanto tempo o banco de dados não está conectado e desconectado automaticamente
  • plugins  – lista de strings de nomes de plugins para carregar.

mapeamento de declaração

Ou seja, uma classe criada em Python corresponde a uma tabela no banco de dados, e cada atributo da classe é o nome do campo da tabela, essa classe corresponde à classe da tabela no banco de dados, e é chamada de classe de mapeamento.

Queremos criar uma classe de mapeamento, que é definida com base na classe base, e cada classe de mapeamento deve herdar essa classe base  declarative_base().

de sqlalchemy.orm import declarative_base
Base = declarative_base()

from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()

declarative_base() é um método encapsulado dentro do sqlalchemy. Através dele, uma classe base é construída. Esta classe base e suas subclasses podem associar e mapear classes Python e tabelas de banco de dados.

A classe de modelo de tabela de banco de dados está associada à tabela por meio de __tablename__ e Column representa a coluna da tabela de dados.

Exemplo:

  • Crie uma nova tabela chamada users, que é a tabela de usuários.
  • Crie uma nova classe chamada User que será a classe para a qual mapeamos esta tabela. Na classe definimos os detalhes da tabela a ser mapeada, principalmente o nome da tabela e os nomes e tipos de dados das colunas:
from sqlalchemy import Column, Integer, String


class User(Base):
    __tablename__ = "users"

    id = Column(Integer, primary_key=True)
    name = Column(String)
    fullname = Column(String)
    nickname = Column(String)

    def __repr__(self):
        return "<User(name='%s', fullname='%s', nickname='%s')>" % (
            self.name,
            self.fullname,
            self.nickname,
        )

__tablename__   representa o nome da tabela

Coluna:  representa uma coluna na tabela de dados e o tipo de dados é definido internamente

primary_key: chave primária

Criar tabela para banco de dados

Ao definir a classe User, definimos informações sobre a tabela, chamadas de metadados da tabela, que são os metadados da tabela. Podemos fazer isso inspecionando o atributo __table__:

User.__table__ 
Table('users', MetaData(),
            Column('id', Integer(), table=<users>, primary_key=True, nullable=False),
            Column('name', String(), table= <usuários>),
            Column('nome completo', String(), tabela=<usuários>),
            Column('apelido', String(), tabela=<usuários>), esquema=Nenhum)

Comece a criar a tabela: ignore-a se existir, execute o código a seguir e você descobrirá que a tabela de usuários foi criada no banco de dados.

Base.metadata.create_all(motor)

Criar uma sessão (  sessão )

A sessão é usada no sqlalchemy para criar uma sessão entre o programa e o banco de dados, e todos os objetos precisam ser carregados e salvos por meio do objeto de sessão. Ou seja, todas as operações na tabela são implementadas por meio de sessões.

Crie uma fábrica por meio da chamada do sessionmaker e associe o mecanismo para garantir que cada sessão possa usar o mecanismo para conectar recursos:

de sqlalchemy.orm import sessionmaker

Session = sessionmaker(bind=engine)
# 实例化
session = Session()

Os métodos comuns de operação da sessão incluem:

  1. flush: pré-enviado, submetido ao arquivo de banco de dados, ainda não gravado no arquivo de banco de dados
  2. commit: confirmar uma transação
  3. reversão: reversão
  4. fechar: fechar

objeto "adicionar, atualizar"

>>> ed_user = User(name='ed', fullname='Ed Jones', apelido='edsnickname')
>>> session.add(ed_user)

Um novo usuário é adicionado. Neste momento, os dados não estão no banco de dados sincronizado, mas estão em estado de espera.
No código acima, o objeto de instância só é válido na memória do ambiente e não gera dados na tabela.
Somente após a execução do método commit() é que os dados serão realmente criados na tabela de dados.
Se consultarmos o banco de dados, todas as informações pendentes serão descarregadas primeiro e, em seguida, a consulta será emitida imediatamente.

>>> our_user = session.query(User).filter_by(name='ed').first() 
>>> our_user
<User(name='ed', fullname='Ed Jones', nick='edsnickname') >

O resultado obtido neste momento não é o dado final da tabela do banco de dados, mas sim um objeto da classe de mapeamento.

Adicionar, excluir, modificar, verificar

aumentar

add_user = Users("test", "[email protected]")
session.add(add_user)
session.commit()

session.add() adicionará o modelo ao espaço persistente mantido pela sessão atual (pode ser visto em session.dirty) e o enviará ao banco de dados até o commit.
Execute db.session.flush() após add, para que as propriedades do objeto possam ser obtidas na sessão.
Existem vários métodos para inserção de lotes, e seus lotes são comparados, a saber:
session.add_all() < bulk_save_object() < bulk_insert_mappings() < SQLAlchemy_core()

verificar

A consulta é a operação mais usada. Aqui está o exemplo de consulta mais simples:

users = session.query(Users).filter_by(id=1).all()
para item em users:
    print(item.name)

Normalmente, obtemos dados por meio do modo de consulta acima. Deve-se observar que, por meio de session.query(), consultamos e retornamos um objeto Query. Neste momento, ainda não consultamos no banco de dados específico. Somente quando o específico .all( ) é executado, First() e outras funções irão realmente operar o banco de dados.

Dentre eles, query possui dois métodos de filtragem, filter e filter_by, que são normalmente utilizados.

O exemplo acima também pode ser escrito como:

usuários = sessão.query(Usuários).filter_by(Usuários.id == 1).all()

mudar

Existem duas maneiras de atualizar os dados, uma delas é usar o método update na consulta:

session.query(Users).filter_by(id=1).update({'name': "Jack"})

A outra é operar o modelo de tabela correspondente:

users = session.query(Users).filter_by(name="Jack").first()
users.name = "test"
session.add(users)

Geralmente, você pode escolher o primeiro para atualizações em lote, mas precisa usar o último para cenários em que deseja atualizar os atributos do objeto após consultá-los.

excluir

Semelhante à atualização de dados, existem duas maneiras de excluir dados, a primeira:

delete_users = session.query(Users).filter(Users.name == "test").first()
if delete_users:
    session.delete(delete_users)
    session.commit()

O segundo método: (recomendado ao excluir em lotes)

session.query(Users).filter(Users.name == "test").delete()
session.commit()

reversão

Antes de  commit()  , as alterações feitas nas propriedades do objeto de instância podem ser revertidas e retornadas antes das alterações.

>>> sessão.rollback()

Em essência, ele apenas exclui um determinado dado (ou seja, uma instância da classe de mapeamento) da memória e não executa nenhuma operação no banco de dados.

Investigar

Consulta por meio  da  palavra-chave de consulta.

>>> por exemplo em session.query(User).order_by(User.id):
... print(instance.name, instance.fullname)
ed Ed Jones
wendy Wendy Williams
mary Mary Contrário
fred Fred Flintstone

  •  filtro query.filter()
  •  filtros query.filter_by() com base em palavras-chave
  • query.all()  retorna uma lista
  • query.first()  retorna o primeiro elemento
  • query.one()  retorna corretamente quando há apenas um elemento
  • query.one_or_none() , como um, mas não gera um erro se nenhum resultado for encontrado
  • query.scalar() , chama um método e retorna a primeira coluna da linha em caso de sucesso
  • query.count()  contagem
  •  classificação query.order_by()

 consulta de conexão query.join()

>>> session.query(User).join(Address).\
... filter(Address.email_address=='[email protected]').\
... all()
[<User(name='jack ', fullname='Jack Bean', apelido='gjffdd')>]

query(column.label())  pode definir um alias para o nome do campo (coluna):

>>> para linha em session.query(User.name.label('name_label')).all():
... print(row.name_label)
ed
wendy
mary
fred

aliased() define um alias para o objeto de consulta:

>>> from sqlalchemy.orm import aliased
>>> user_alias = aliased(User, name='user_alias')

SQL>>> para linha em 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', nick='windy')>
<User(name='mary', fullname='Mary Contrary', nick='mary')>
<User(name='fred', fullname='Fred Flintstone', apelido='freddy')>

Consultar operadores de filtro comuns

# Igual a
query.filter(User.name == 'ed')

# Diferente de
query.filter(User.name != 'ed')

# like e ilike
query.filter(User.name.like('%ed%'))
query.filter(User.name.ilike('%ed%')) # não diferencia maiúsculas de minúsculas

# in
query.filter(User.name.in_(['ed', 'wendy', 'jack']))
query.filter(User.name.in_(
    session.query(User.name).filter(User. name.like('%ed%')))
)
# não está em
query.filter(~User.name.in_(['ed', 'wendy', 'jack'])) 

# é
query.filter(User.name == None)
query.filter(User.name.is_(None))

# não é
query.filter(User.name != None)
query.filter(User.name.is_not(None))

# e
de sqlalchemy import and_
query.filter(and_(User.name == 'ed', User.fullname == 'Ed Jones'))
query.filter(User.name == 'ed', User.fullname == 'Ed Jones')
query.filter(User.name == 'ed').filter(User.fullname == 'Ed Jones')

# or
from sqlalchemy import or_
query.filter(or_(User.name == 'ed', User.name == 'wendy'))

# match
query.filter(User.name.match('wendy'))

Usar SQL textual

Strings literais podem ser usadas de forma flexível Query em consultas.

>>> from sqlalchemy import text
SQL >>> for user in session.query(User).\
... filter(text("id<224")).\
... order_by(text("id")) .all():
... print(nome.usuário)
ed
wendy
mary
fred 

Use dois pontos para especificar os parâmetros de ligação. Para especificar um valor, use Query.params()o método:

>>> 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', apelido='freddy')>

um para muitos

Um usuário pode ter vários endereços de e-mail, o que significa que precisamos criar uma nova tabela para mapear e consultar a tabela do usuário.

>>> da importação do sqlalchemy ForeignKey
>>> do relacionamento de importação do sqlalchemy.orm

>>> class Address(Base):
... __tablename__ = 'addresses'
... id = Column(Integer, primary_key=True)
... email_address = Column(String, nullable=False)
... user_id = Column( Integer, ForeignKey('users.id'))
...
... usuário = relacionamento("Usuário", back_populates="endereços")
...
... def __repr__(self):
... return "<Endereço (email_address='%s')>" % self.email_address

>>> User.addresses = relacionamento(
... "Address", order_by=Address.id, back_populates="user")

ForeignKeyDefina o relacionamento de dependência entre duas colunas, indicando o ID do usuário associado à tabela de usuários

O relacionamento  informa à própria classe ORM Addressque ela deve ser vinculada à Userclasse e back_populates  indica o nome do atributo complementar referenciado, que é seu próprio nome de tabela.

muitos para muitos

Além da tabela um-para-muitos, há também uma relação muitos-para-muitos.Por exemplo, em um site de blog, há muitos blogs , BlogPostcada blog tem muitos Keyword, e cada blog Keywordpode corresponder a muitos blogs.

Para um muitos-para-muitos normais, precisamos criar uma Tableconstrução não mapeada para ser usada como uma tabela de associação. Do seguinte modo:

>>> from sqlalchemy import Table, Text
>>> # tabela de associação
>>> 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)
... ) 

Em seguida, definimos BlogPoste Keyword, usando a  construção de relacionamento  complementar , cada tabela referenciada post_keywordscomo 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)
...
... # muitos para muitos BlogPost<->Palavra-chave
... palavras-chave = relacionamento('Palavra-chave ',
... secundário=post_keywords,
... back_populates='posts')
...
... def __init__(self, headline, body, author):
... self.author = author
... self.headline = título
... self.body = corpo
...
... 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 = relacionamento('BlogPost',
... secundário=post_keywords,
... back_populates='keywords')
...
... def __init__(self, palavra-chave):
... self.keyword = palavra-chave

A característica definidora de um relacionamento muitos-para-muitos é que secondaryos argumentos de palavra-chave referem-se ao objeto que representa Tablea tabela associada.

2. Use SQLAlchemy para manipular tabelas

Criar uma única tabela

SQLAlchemy não permite modificar a estrutura da tabela. Se você precisar modificar a estrutura da tabela, você deve excluir a tabela antiga e criar uma nova tabela ou executar a instrução SQL original ALERT TABLE para modificar.

Isso significa que, ao usar instruções SQL não nativas para modificar a estrutura da tabela, todos os registros existentes na tabela serão perdidos, portanto, é melhor projetar toda a estrutura da tabela de uma só vez para evitar modificações posteriores:

# models.py
import datetime
from sqlalchemy.orm import sessionmaker
from sqlalchemy.orm import scoped_session

from sqlalchemy import (
    create_engine,
    Column,
    Integer,
    String,
    Enum,
    DECIMAL,
    DateTime,
    Boolean,
    UniqueConstraint,
    Index,
)
from sqlalchemy.ext.declarative import declarative_base

# 基础类
Base = declarative_base()

# 创建引擎
engine = create_engine(
    "mysql+pymysql://tom:[email protected]:3306/db1?charset=utf8mb4",
    # "mysql+pymysql://[email protected]:3306/db1?charset=utf8mb4", # 无密码时
    # 超过链接池大小外最多创建的链接
    max_overflow=0,
    # 链接池大小
    pool_size=5,
    # 链接池中没有可用链接则最多等待的秒数,超过该秒数后报错
    pool_timeout=10,
    # 多久之后对链接池中的链接进行一次回收
    pool_recycle=1,
    # 查看原生语句(未格式化)
    echo=True,
)

# 绑定引擎
Session = sessionmaker(bind=engine)
# 创建数据库链接池,直接使用session即可为当前线程拿出一个链接对象conn
# 内部会采用threading.local进行隔离
session = scoped_session(Session)


class UserInfo(Base):
    """必须继承Base"""

    # 数据库中存储的表名
    __tablename__ = "userInfo"
    # 对于必须插入的字段,采用nullable=False进行约束,它相当于NOT NULL
    id = Column(Integer, primary_key=True, autoincrement=True, comment="主键")
    name = Column(String(32), index=True, nullable=False, comment="姓名")
    age = Column(Integer, nullable=False, comment="年龄")
    phone = Column(DECIMAL(6), nullable=False, unique=True, comment="手机号")
    address = Column(String(64), nullable=False, comment="地址")
    # 对于非必须插入的字段,不用采取nullable=False进行约束
    gender = Column(Enum("male", "female"), default="male", comment="性别")
    create_time = Column(DateTime, default=datetime.datetime.now, comment="创建时间")
    last_update_time = Column(
        DateTime, onupdate=datetime.datetime.now, comment="最后更新时间"
    )
    delete_status = Column(Boolean(), default=False, comment="是否删除")

    __table__args__ = (
        UniqueConstraint("name", "age", "phone"),  # 联合唯一约束
        Index("name", "addr", unique=True),  # 联合唯一索引
    )

    def __str__(self):
        return f"object : <id:{self.id} name:{self.name}>"


if __name__ == "__main__":
    # 删除表
    Base.metadata.drop_all(engine)
    # 创建表
    Base.metadata.create_all(engine)

operação de registro

novo recorde

Adicione um único registro:

# 获取链接池、ORM表对象
import models
​
​
user_instance = models.UserInfo(
    name="Jack",
    age=18,
    phone=330621,
    address="Beijing",
    gender="male"
)
​
models.session.add(user_instance)
​
# 提交
models.session.commit()
​
# 关闭链接,亦可使用session.remove(),它将回收该链接
models.session.close()

Adicionar em lotes

Adicionar em lotes pode reduzir o número de conexões TCP e melhorar o desempenho da inserção:

# 获取链接池、ORM表对象
import models
​
​
user_instance1 = models.UserInfo(
    name="Tom",
    age=19,
    phone=330624,
    address="Shanghai",
    gender="male"
)
​
user_instance2 = models.UserInfo(
    name="Mary",
    age=20,
    phone=330623,
    address="Chongqing",
    gender="female"
)
​
​
models.session.add_all(
    (
        user_instance1,
        user_instance2
    )
)
​
# 提交
models.session.commit()
# 关闭链接,亦可使用session.remove(),它将回收该链接
models.session.close()

registro de modificação

Modifique alguns registros:

# 获取链接池、ORM表对象
import models
​
# 修改的信息:
#  - Jack -> Jack + son
# 在SQLAlchemy中,四则运算符号只能用于数值类型
# 如果是字符串类型需要在原本的基础值上做改变,必须设置
#  - age -> age + 1
# synchronize_session=False
​
models.session.query(models.UserInfo)\
    .filter_by(name="Jack")\
    .update(
        {
            "name": models.UserInfo.name + "son",
            "age": models.UserInfo.age + 1
        },
        synchronize_session=False
)
# 本次修改具有字符串字段在原值基础上做更改的操作,所以必须添加
# synchronize_session=False
# 如果只修改年龄,则不用添加
​
# 提交
models.session.commit()
# 关闭链接,亦可使用session.remove(),它将回收该链接
models.session.close()

Apagar registro

A exclusão de registros é relativamente raramente usada, apenas entenda, geralmente adicione um campo delete_status como acima, se for 1, significa excluir:

# 获取链接池、ORM表对象
import models
​
models.session.query(models.UserInfo).filter_by(name="Mary").delete()
​
# 提交
models.session.commit()
# 关闭链接,亦可使用session.remove(),它将回收该链接
models.session.close()

consulta de tabela única

consulta básica

Para verificar todos os registros e todos os campos, o método all() retornará uma lista, que envolve o objeto de registro de cada linha:

# 获取链接池、ORM表对象
import models
​
result = models.session.query(models.UserInfo)\
    .all()
​
print(result)
# [<models.UserInfo object at 0x7f4d3d606fd0>, <models.UserInfo object at 0x7f4d3d606f70>]
​
for row in result:
    print(row)
# object : <id:1 name:Jackson>
# object : <id:2 name:Tom>
​
# 关闭链接,亦可使用session.remove(),它将回收该链接
models.session.close()

Verifique todos os registros e determinados campos (observe que a tupla retornada abaixo é na verdade uma tupla nomeada, que pode ser operada diretamente pelo operador .):

# 获取链接池、ORM表对象
import models
​
result = models.session.query(
    models.UserInfo.id,
    models.UserInfo.name,
    models.UserInfo.age
).all()
​
print(result)
# [(1, 'Jackson', 19), (2, 'Tom', 19)]
​
for row in result:
    print(row)
# (1, 'Jackson', 19)
# (2, 'Tom', 19)
​
# 关闭链接,亦可使用session.remove(),它将回收该链接
models.session.close()

Pegue apenas o primeiro registro, o método first() retornará um único objeto de registro (observe que a tupla retornada abaixo é na verdade uma tupla nomeada, que pode ser operada diretamente pelo operador .):

# 获取链接池、ORM表对象
import models

result = models.session.query(
    models.UserInfo.id,
    models.UserInfo.name,
    models.UserInfo.age
).first()

print(result)
# (1, 'Jackson', 19)

# 关闭链接,亦可使用session.remove(),它将回收该链接
models.session.close()

pseudônimo de AS

Através do método label() do campo, podemos dar a ele um alias:

# 获取链接池、ORM表对象
import models
​
result = models.session.query(
    models.UserInfo.name.label("s_name"),
    models.UserInfo.age.label("s_age")
).all()
​
for row in result:
    print(row.s_name)
    print(row.s_age)
​
​
# 关闭链接,亦可使用session.remove(),它将回收该链接
models.session.close()

consulta condicional

Filtre por uma condição:

# 获取链接池、ORM表对象
import models
​
result = models.session.query(
    models.UserInfo,
).filter(
    models.UserInfo.name == "Jackson"
).all()
​
# 上面是Python语句形式的过滤条件,由filter方法调用
# 亦可以使用ORM的形式进行过滤,通过filter_by方法调用
# 如下所示
# .filter_by(name="Jackson").all()
# 个人更推荐使用filter过滤,它看起来更直观,更简单,可以支持 == != > < >= <=等常见符号
​
# 过滤成功的结果数量
print(len(result))
# 1
​
# 过滤成功的结果
print(result)
# [<models.UserInfo object at 0x7f11391ea2b0>]
​
# 关闭链接,亦可使用session.remove(),它将回收该链接
models.session.close()

E consulta:

# 获取链接池、ORM表对象
import models
# 导入AND
from sqlalchemy import and_
​
result = models.session.query(
    models.UserInfo,
).filter(
    and_(
        models.UserInfo.name == "Jackson",
        models.UserInfo.gender == "male"
    )
).all()
​
# 过滤成功的结果数量
print(len(result))
# 1
​
# 过滤成功的结果
print(result)
# [<models.UserInfo object at 0x7f11391ea2b0>]
​
# 关闭链接,亦可使用session.remove(),它将回收该链接
models.session.close()

OU consulta:

# 获取链接池、ORM表对象
import models
# 导入OR
from sqlalchemy import or_
​
result = models.session.query(
    models.UserInfo,
).filter(
    or_(
        models.UserInfo.name == "Jackson",
        models.UserInfo.gender == "male"
    )
).all()
​
# 过滤成功的结果数量
print(len(result))
# 1
​
# 过滤成功的结果
print(result)
# [<models.UserInfo object at 0x7f11391ea2b0>]
​
# 关闭链接,亦可使用session.remove(),它将回收该链接
models.session.close()

NÃO consultar:

# 获取链接池、ORM表对象
import models
# 导入NOT
from sqlalchemy import not_
​
result = models.session.query(
    models.UserInfo,
).filter(
    not_(
        models.UserInfo.name == "Jackson",
    )
).all()
​
# 过滤成功的结果数量
print(len(result))
# 1
​
# 过滤成功的结果
print(result)
# [<models.UserInfo object at 0x7f11391ea2b0>]
​
# 关闭链接,亦可使用session.remove(),它将回收该链接
models.session.close()

consulta de intervalo

ENTRE consulta:

# 获取链接池、ORM表对象
import models

result = models.session.query(
    models.UserInfo,
).filter(
    models.UserInfo.age.between(15, 21)
).all()

# 过滤成功的结果数量
print(len(result))
# 1

# 过滤成功的结果
print(result)
# [<models.UserInfo object at 0x7f11391ea2b0>]

# 关闭链接,亦可使用session.remove(),它将回收该链接
models.session.close()

contém consulta

EM consulta:

# 获取链接池、ORM表对象
import models
​
result = models.session.query(
    models.UserInfo,
).filter(
    models.UserInfo.age.in_((18, 19, 20))
).all()
​
# 过滤成功的结果数量
print(len(result))
# 2
​
# 过滤成功的结果
print(result)
# [<models.UserInfo object at 0x7fdeeaa774f0>, <models.UserInfo object at 0x7fdeeaa77490>]
​
# 关闭链接,亦可使用session.remove(),它将回收该链接
models.session.close()

NOT IN, basta adicionar ~:

# 获取链接池、ORM表对象
import models
​
result = models.session.query(
    models.UserInfo,
).filter(
    ~models.UserInfo.age.in_((18, 19, 20))
).all()
​
# 过滤成功的结果数量
print(len(result))
# 0
​
# 过滤成功的结果
print(result)
# []
​
# 关闭链接,亦可使用session.remove(),它将回收该链接
models.session.close()

jogo difuso

Consulta LIKE:

# 获取链接池、ORM表对象
import models
​
result = models.session.query(
    models.UserInfo,
).filter(
    models.UserInfo.name.like("Jack%")
).all()
​
# 过滤成功的结果数量
print(len(result))
# 1
​
# 过滤成功的结果
print(result)
# [<models.UserInfo object at 0x7fee1614f4f0>]
​
# 关闭链接,亦可使用session.remove(),它将回收该链接
models.session.close()

consulta de paginação

Fatie a lista retornada pelo resultado all() uma vez:

# 获取链接池、ORM表对象
import models
​
result = models.session.query(
    models.UserInfo,
).all()[0:1]
​
# 过滤成功的结果数量
print(len(result))
# 1
​
# 过滤成功的结果
print(result)
# [<models.UserInfo object at 0x7fee1614f4f0>]
​
# 关闭链接,亦可使用session.remove(),它将回收该链接
models.session.close()

Consulta de classificação

Ordem ascendente ASC, ordem descendente DESC, você precisa especificar as regras de classificação:

# 获取链接池、ORM表对象
import models
​
result = models.session.query(
    models.UserInfo,
).filter(
    models.UserInfo.age > 12
).order_by(
    models.UserInfo.age.desc()
).all()
​
# 过滤成功的结果数量
print(len(result))
# 2
​
# 过滤成功的结果
print(result)
# [<models.UserInfo object at 0x7f90eccd26d0>, <models.UserInfo object at 0x7f90eccd2670>]
​
# 关闭链接,亦可使用session.remove(),它将回收该链接
models.session.close()

agrupamento de agregação

Agrupamento de agregação e filtragem:

# 获取链接池、ORM表对象
import models
# 导入聚合函数
from sqlalchemy import func
​
result = models.session.query(
    func.sum(models.UserInfo.age)
).group_by(
    models.UserInfo.gender
).having(
    func.sum(models.UserInfo.id > 1)
).all()
​
# 过滤成功的结果数量
print(len(result))
# 1
​
# 过滤成功的结果
print(result)
# [(Decimal('38'),)]
​
# 关闭链接,亦可使用session.remove(),它将回收该链接
models.session.close()

consulta de várias tabelas

Criar várias tabelas

Relação de cinco mesas:

Criar declaração de tabela:

# models.py
from sqlalchemy.orm import sessionmaker
from sqlalchemy.orm import scoped_session
from sqlalchemy.orm import relationship
​
from sqlalchemy import (
    create_engine,
    Column,
    Integer,
    Date,
    String,
    Enum,
    ForeignKey,
    UniqueConstraint,
)
from sqlalchemy.ext.declarative import declarative_base
​
# 基础类
Base = declarative_base()
​
# 创建引擎
engine = create_engine(
    "mysql+pymysql://tom:[email protected]:3306/db1?charset=utf8mb4",
    # "mysql+pymysql://[email protected]:3306/db1?charset=utf8mb4", # 无密码时
    # 超过链接池大小外最多创建的链接
    max_overflow=0,
    # 链接池大小
    pool_size=5,
    # 链接池中没有可用链接则最多等待的秒数,超过该秒数后报错
    pool_timeout=10,
    # 多久之后对链接池中的链接进行一次回收
    pool_recycle=1,
    # 查看原生语句
    # echo=True
)
​
# 绑定引擎
Session = sessionmaker(bind=engine)
# 创建数据库链接池,直接使用session即可为当前线程拿出一个链接对象
# 内部会采用threading.local进行隔离
session = scoped_session(Session)
​
​
class StudentsNumberInfo(Base):
    """学号表"""
    __tablename__ = "studentsNumberInfo"
    id = Column(Integer, primary_key=True, autoincrement=True, comment="主键")
    number = Column(Integer, nullable=False, unique=True, comment="学生编号")
    admission = Column(Date, nullable=False, comment="入学时间")
    graduation = Column(Date, nullable=False, comment="毕业时间")
​
​
class TeachersInfo(Base):
    """教师表"""
    __tablename__ = "teachersInfo"
    id = Column(Integer, primary_key=True, autoincrement=True, comment="主键")
    number = Column(Integer, nullable=False, unique=True, comment="教师编号")
    name = Column(String(64), nullable=False, comment="教师姓名")
    gender = Column(Enum("male", "female"), nullable=False, comment="教师性别")
    age = Column(Integer, nullable=False, comment="教师年龄")
​
​
class ClassesInfo(Base):
    """班级表"""
    __tablename__ = "classesInfo"
    id = Column(Integer, primary_key=True, autoincrement=True, comment="主键")
    number = Column(Integer, nullable=False, unique=True, comment="班级编号")
    name = Column(String(64), nullable=False, unique=True, comment="班级名称")
    # 一对一关系必须为连接表的连接字段创建UNIQUE的约束,这样才能是一对一,否则是一对多
    fk_teacher_id = Column(
        Integer,
        ForeignKey(
            "teachersInfo.id",
            ondelete="CASCADE",
            onupdate="CASCADE",
        ),
        nullable=False,
        unique=True,
        comment="班级负责人"
    )
    # 下面这2个均属于逻辑字段,适用于正反向查询。在使用ORM的时候,我们不必每次都进行JOIN查询,而恰好正反向的查询使用频率会更高
    # 这种逻辑字段不会在物理层面上创建,它只适用于查询,本身不占据任何数据库的空间
    # sqlalchemy的正反向概念与Django有所不同,Django是外键字段在那边,那边就作为正
    # 而sqlalchemy是relationship字段在那边,那边就作为正
    # 比如班级表拥有 relationship 字段,而老师表不曾拥有
    # 那么用班级表的这个relationship字段查老师时,就称为正向查询
    # 反之,如果用老师来查班级,就称为反向查询
    # 另外对于这个逻辑字段而言,根据不同的表关系,创建的位置也不一样:
    #  - 1 TO 1:建立在任意一方均可,查询频率高的一方最好
    #  - 1 TO M:建立在M的一方
    #  - M TO M:中间表中建立2个逻辑字段,这样任意一方都可以先反向,再正向拿到另一方
    #  - 遵循一个原则,ForeignKey建立在那个表上,那个表上就建立relationship
    #  - 有几个ForeignKey,就建立几个relationship
    # 总而言之,使用ORM与原生SQL最直观的区别就是正反向查询能带来更高的代码编写效率,也更加简单
    # 甚至我们可以不用外键约束,只创建这种逻辑字段,让表与表之间的耦合度更低,但是这样要避免脏数据的产生
​
    # 班级负责人,这里是一对一关系,一个班级只有一个负责人
    leader_teacher = relationship(
        # 正向查询时所链接的表,当使用 classesInfo.leader_teacher 时,它将自动指向fk的那一条记录
        "TeachersInfo",
        # 反向查询时所链接的表,当使用 teachersInfo.leader_class 时,它将自动指向该老师所管理的班级
        backref="leader_class",
    )
​
​
class ClassesAndTeachersRelationship(Base):
    """任教老师与班级的关系表"""
    __tablename__ = "classesAndTeachersRelationship"
    id = Column(Integer, primary_key=True, autoincrement=True, comment="主键")
    # 中间表中注意不要设置单列的UNIQUE约束,否则就会变为一对一
    fk_teacher_id = Column(
        Integer,
        ForeignKey(
            "teachersInfo.id",
            ondelete="CASCADE",
            onupdate="CASCADE",
        ),
        nullable=False,
        comment="教师记录"
    )
​
    fk_class_id = Column(
        Integer,
        ForeignKey(
            "classesInfo.id",
            ondelete="CASCADE",
            onupdate="CASCADE",
        ),
        nullable=False,
        comment="班级记录"
    )
    # 多对多关系的中间表必须使用联合唯一约束,防止出现重复数据
    __table_args__ = (
        UniqueConstraint("fk_teacher_id", "fk_class_id"),
    )
​
    # 逻辑字段
    # 给班级用的,查看所有任教老师
    mid_to_teacher = relationship(
        "TeachersInfo",
        backref="mid",
    )
​
    # 给老师用的,查看所有任教班级
    mid_to_class = relationship(
        "ClassesInfo",
        backref="mid"
    )
​
​
class StudentsInfo(Base):
    """学生信息表"""
    __tablename__ = "studentsInfo"
    id = Column(Integer, primary_key=True, autoincrement=True, comment="主键")
    name = Column(String(64), nullable=False, comment="学生姓名")
    gender = Column(Enum("male", "female"), nullable=False, comment="学生性别")
    age = Column(Integer, nullable=False, comment="学生年龄")
    # 外键约束
    # 一对一关系必须为连接表的连接字段创建UNIQUE的约束,这样才能是一对一,否则是一对多
    fk_student_id = Column(
        Integer,
        ForeignKey(
            "studentsNumberInfo.id",
            ondelete="CASCADE",
            onupdate="CASCADE"
        ),
        nullable=False,
        comment="学生编号"
    )
    # 相比于一对一,连接表的连接字段不用UNIQUE约束即为多对一关系
    fk_class_id = Column(
        Integer,
        ForeignKey(
            "classesInfo.id",
            ondelete="CASCADE",
            onupdate="CASCADE"
        ),
        comment="班级编号"
    )
    # 逻辑字段
    # 所在班级, 这里是一对多关系,一个班级中可以有多名学生
    from_class = relationship(
        "ClassesInfo",
        backref="have_student",
    )
    # 学生学号,这里是一对一关系,一个学生只能拥有一个学号
    number_info = relationship(
        "StudentsNumberInfo",
        backref="student_info",
    )
​
​
if __name__ == "__main__":
    # 删除表
    Base.metadata.drop_all(engine)
    # 创建表
    Base.metadata.create_all(engine)

Inserir dados:

# 获取链接池、ORM表对象
import models
import datetime
​
​
models.session.add_all(
    (
        # 插入学号表数据
        models.StudentsNumberInfo(
            number=160201,
            admission=datetime.datetime.date(datetime.datetime(2016, 9, 1)),
            graduation=datetime.datetime.date(datetime.datetime(2021, 6, 15))
        ),
        models.StudentsNumberInfo(
            number=160101,
            admission=datetime.datetime.date(datetime.datetime(2016, 9, 1)),
            graduation=datetime.datetime.date(datetime.datetime(2021, 6, 15))
        ),
        models.StudentsNumberInfo(
            number=160301,
            admission=datetime.datetime.date(datetime.datetime(2016, 9, 1)),
            graduation=datetime.datetime.date(datetime.datetime(2021, 6, 15))
        ),
        models.StudentsNumberInfo(
            number=160102,
            admission=datetime.datetime.date(datetime.datetime(2016, 9, 1)),
            graduation=datetime.datetime.date(datetime.datetime(2021, 6, 15))
        ),
        models.StudentsNumberInfo(
            number=160302,
            admission=datetime.datetime.date(datetime.datetime(2016, 9, 1)),
            graduation=datetime.datetime.date(datetime.datetime(2021, 6, 15))
        ),
        models.StudentsNumberInfo(
            number=160202,
            admission=datetime.datetime.date(datetime.datetime(2016, 9, 1)),
            graduation=datetime.datetime.date(datetime.datetime(2021, 6, 15))
        ),
        # 插入教师表数据
        models.TeachersInfo(
            number=3341, name="David", gender="male", age=32,
        ),
        models.TeachersInfo(
            number=3342, name="Jason", gender="male", age=30,
        ),
        models.TeachersInfo(
            number=3343, name="Lisa", gender="female", age=28,
        ),
        # 插入班级表数据
        models.ClassesInfo(
            number=1601, name="one year one class", fk_teacher_id=1
        ),
        models.ClassesInfo(
            number=1602, name="one year two class", fk_teacher_id=2
        ),
        models.ClassesInfo(
            number=1603, name="one year three class", fk_teacher_id=3
        ),
        # 插入中间表数据
        models.ClassesAndTeachersRelationship(
            fk_class_id=1, fk_teacher_id=1
        ),
        models.ClassesAndTeachersRelationship(
            fk_class_id=2, fk_teacher_id=1
        ),
        models.ClassesAndTeachersRelationship(
            fk_class_id=3, fk_teacher_id=1
        ),
        models.ClassesAndTeachersRelationship(
            fk_class_id=1, fk_teacher_id=2
        ),
        models.ClassesAndTeachersRelationship(
            fk_class_id=3, fk_teacher_id=3
        ),
        # 插入学生表数据
        models.StudentsInfo(
            name="Jack", gender="male", age=17, fk_student_id=1, fk_class_id=2
        ),
        models.StudentsInfo(
            name="Tom", gender="male", age=18, fk_student_id=2, fk_class_id=1
        ),
        models.StudentsInfo(
            name="Mary", gender="female", age=16, fk_student_id=3,
            fk_class_id=3
        ),
        models.StudentsInfo(
            name="Anna", gender="female", age=17, fk_student_id=4,
            fk_class_id=1
        ),
        models.StudentsInfo(
            name="Bobby", gender="male", age=18, fk_student_id=6, fk_class_id=2
        ),
    )
)
​
models.session.commit()
​
# 关闭链接,亦可使用session.remove(),它将回收该链接
models.session.close()

consulta JOIN

JUNÇÃO INTERNA:

# 获取链接池、ORM表对象
import models
​
result = models.session.query(
    models.StudentsInfo.name,
    models.StudentsNumberInfo.number,
    models.ClassesInfo.number
).join(
    models.StudentsNumberInfo,
    models.StudentsInfo.fk_student_id == models.StudentsNumberInfo.id
).join(
    models.ClassesInfo,
    models.StudentsInfo.fk_class_id == models.ClassesInfo.id
).all()
​
print(result)
# [('Jack', 160201, 1602), ('Tom', 160101, 1601), ('Mary', 160301, 1603), ('Anna', 160102, 1601), ('Bobby', 160202, 1602)]
​
# 关闭链接,亦可使用session.remove(),它将回收该链接
models.session.close()

LEFT JOIN só precisa especificar o parâmetro de palavra-chave isouter como True em cada JOIN:

session.query(
    左表.字段,
    右表.字段
)
.join(
    右表,
    链接条件,
    isouter=True
).all()

RIGHT JOIN precisa mudar a posição da tabela. SQLALchemy em si não fornece RIGHT JOIN, então você deve prestar atenção à ordem de condução ao usá-lo. Tabelas pequenas conduzem tabelas grandes (se você não prestar atenção à ordem, o MySQL otimizador também otimizará):

session.query(
    左表.字段,
    右表.字段
)
.join(
    左表,
    链接条件,
    isouter=True
).all()

UNIÃO&UNIÃO TODOS

Para combinar vários resultados de consulta, filter() deve ser usado sem o método all() por trás.

Porque all() retorna uma lista e filter() retorna um objeto de consulta <class 'sqlalchemy.orm.query.Query'>. Além disso, um determinado campo deve ser usado sozinho e query() não pode ser especificado diretamente sem especificar o campo:

# 获取链接池、ORM表对象
import models
​
students_name = models.session.query(models.StudentsInfo.name).filter()
students_number = models.session.query(models.StudentsNumberInfo.number)\
    .filter()
class_name = models.session.query(models.ClassesInfo.name).filter()
​
result = students_name.union_all(students_number).union_all(class_name)
​
print(result.all())
# [
#      ('Jack',), ('Tom',), ('Mary',), ('Anna',), ('Bobby',),
#      ('160101',), ('160102',), ('160201',), ('160202',), ('160301',), ('160302',),
#      ('one year one class',), ('one year three class',), ('one year two class',)
# ]
​
# 关闭链接,亦可使用session.remove(),它将回收该链接
models.session.close()

subconsulta

A subconsulta é implementada usando subquery() da seguinte forma, consultando a pessoa mais jovem de cada classe:

# 获取链接池、ORM表对象
import models
from sqlalchemy import func
​
# 子查询中所有字段的访问都需要加上c的前缀
# 如 sub_query.c.id、 sub_query.c.name等
sub_query = models.session.query(
    # 使用label()来为字段AS一个别名
    # 后续访问需要通过sub_query.c.alias进行访问
    func.min(models.StudentsInfo.age).label("min_age"),
    models.ClassesInfo.id,
    models.ClassesInfo.name
).join(
    models.ClassesInfo,
    models.StudentsInfo.fk_class_id == models.ClassesInfo.id
).group_by(
    models.ClassesInfo.id
).subquery()
​
​
result = models.session.query(
    models.StudentsInfo.name,
    sub_query.c.min_age,
    sub_query.c.name
).join(
    sub_query,
    sub_query.c.id == models.StudentsInfo.fk_class_id
).filter(
   sub_query.c.min_age == models.StudentsInfo.age
)
​
print(result.all())
# [('Jack', 17, 'one year two class'), ('Mary', 16, 'one year three class'), ('Anna', 17, 'one year one class')]
​
# 关闭链接,亦可使用session.remove(),它将回收该链接
models.session.close()

consulta positiva e negativa

Acima, todos consultamos por meio de JOIN, na verdade, também podemos consultar por meio do relacionamento de campo lógico.

Veja a seguir um exemplo de uma consulta direta. Uma consulta direta refere-se a iniciar uma consulta a partir de uma tabela com um campo lógico de relacionamento:

# 查询所有学生的所在班级,我们可以通过学生的from_class字段拿到其所在班级
# 另外,对于学生来说,班级只能有一个,所以have_student应当是一个对象
​
# 获取链接池、ORM表对象
import models
​
students_lst = models.session.query(
    models.StudentsInfo
).all()
​
for row in students_lst:
    print(f"""
            student name : {row.name}
            from : {row.from_class.name}
          """)
​
# student name : Mary
# from : one year three class
​
# student name : Anna
# from : one year one class
​
# student name : Bobby
# from : one year two class
​
​
# 关闭链接,亦可使用session.remove(),它将回收该链接
models.session.close()

Veja a seguir um exemplo de consulta reversa, que se refere à consulta de uma tabela sem um campo lógico de relacionamento:

# 查询所有班级中的所有学生,学生表中有relationship,并且它的backref为have_student,所以我们可以通过班级.have_student来获取所有学生记录
​
# 另外,对于班级来说,学生可以有多个,所以have_student应当是一个序列
​
# 获取链接池、ORM表对象
import models
​
classes_lst = models.session.query(
    models.ClassesInfo
).all()
​
for row in classes_lst:
    print("class name :", row.name)
    for student in row.have_student:
        print("student name :", student.name)
​
# class name : one year one class
#      student name : Jack
#      student name : Anna
# class name : one year two class
#      student name : Tom
# class name : one year three class
#      student name : Mary
#      student name : Bobby
​
# 关闭链接,亦可使用session.remove(),它将回收该链接
models.session.close()

Resumindo, o campo lógico da consulta direta sempre obtém um objeto e o campo lógico da consulta reversa sempre obtém uma lista.

método reverso

O uso do relacionamento de campo lógico pode adicionar, excluir, modificar e consultar diretamente alguns registros de tabela cruzada.

Como o campo lógico é uma existência semelhante a uma lista (somente para consultas inversas, as consultas diretas sempre obtêm um objeto), a maioria dos métodos de listas pode ser usada.

<class 'sqlalchemy.orm.collections.InstrumentedList'>
    - append()
    - clear()
    - copy()
    - count()
    - extend()
    - index()
    - insert()
    - pop()
    - remove()
    - reverse()
    - sort()

A demonstração da máquina real não será realizada abaixo, porque fizemos muitas restrições nas tabelas acima.

# 比如
# 给老师增加班级
result = session.query(Teachers).first()
# extend方法:
result.re_class.extend([
    Classes(name="三年级一班",),
    Classes(name="三年级二班",),
])
​
# 比如
# 减少老师所在的班级
result = session.query(Teachers).first()
​
# 待删除的班级对象,集合查找比较快
delete_class_set = {
    session.query(Classes).filter_by(id=7).first(),
    session.query(Classes).filter_by(id=8).first(),
}
​
# 循换老师所在的班级
# remove方法:
for class_obj in result.re_class:
    if class_obj in delete_class_set:
        result.re_class.remove(class_obj)
​
# 比如
# 清空老师所任教的所有班级
# 拿出一个老师
result = session.query(Teachers).first()
result.re_class.clear()

caso de consulta

1) Para ver quantos alunos há em cada turma:

Consultas JOIN:

# 获取链接池、ORM表对象
import models
​
from sqlalchemy import func
​
result = models.session.query(
    models.ClassesInfo.name,
    func.count(models.StudentsInfo.id)
).join(
    models.StudentsInfo,
    models.ClassesInfo.id == models.StudentsInfo.fk_class_id
).group_by(
    models.ClassesInfo.id
).all()
​
print(result)
# [('one year one class', 2), ('one year two class', 2), ('one year three class', 1)]
​
​
# 关闭链接,亦可使用session.remove(),它将回收该链接
models.session.close()

Consulta positiva e negativa:

# 获取链接池、ORM表对象
import models
​
result = {}
class_lst = models.session.query(
    models.ClassesInfo
).all()
​
for row in class_lst:
    for student in row.have_student:
        count = result.setdefault(row.name, 0)
        result[row.name] = count + 1
​
print(result.items())
# dict_items([('one year one class', 2), ('one year two class', 2), ('one year three class', 1)])
​
# 关闭链接,亦可使用session.remove(),它将回收该链接
models.session.close()

2) Veja a matrícula de cada aluno, o ano de formatura e o nome da turma:

Consultas JOIN:

# 获取链接池、ORM表对象
import models
​
result = models.session.query(
    models.StudentsNumberInfo.number,
    models.StudentsInfo.name,
    models.ClassesInfo.name,
    models.StudentsNumberInfo.admission,
    models.StudentsNumberInfo.graduation
).join(
    models.StudentsInfo,
    models.StudentsInfo.fk_class_id == models.ClassesInfo.id
).join(
    models.StudentsNumberInfo,
    models.StudentsNumberInfo.id == models.StudentsInfo.fk_student_id
).order_by(
    models.StudentsNumberInfo.number.asc()
).all()
​
print(result)
# [
#     (160101, 'Tom', 'one year one class', datetime.date(2016, 9, 1), datetime.date(2021, 6, 15)),
#     (160102, 'Anna', 'one year one class', datetime.date(2016, 9, 1), datetime.date(2021, 6, 15)),
#     (160201, 'Jack', 'one year two class', datetime.date(2016, 9, 1), datetime.date(2021, 6, 15)),
#     (160202, 'Bobby', 'one year two class', datetime.date(2016, 9, 1), datetime.date(2021, 6, 15)),
#     (160301, 'Mary', 'one year three class', datetime.date(2016, 9, 1), datetime.date(2021, 6, 15))
# ]
​
# 关闭链接,亦可使用session.remove(),它将回收该链接
models.session.close()

Consulta positiva e negativa:

# 获取链接池、ORM表对象
import models
​
result = []
​
student_lst = models.session.query(
    models.StudentsInfo
).all()
​
for row in student_lst:
    result.append((
        row.number_info.number,
        row.name,
        row.from_class.name,
        row.number_info.admission,
        row.number_info.graduation
    ))
​
print(result)
# [
#     (160101, 'Tom', 'one year one class', datetime.date(2016, 9, 1), datetime.date(2021, 6, 15)),
#     (160102, 'Anna', 'one year one class', datetime.date(2016, 9, 1), datetime.date(2021, 6, 15)),
#     (160201, 'Jack', 'one year two class', datetime.date(2016, 9, 1), datetime.date(2021, 6, 15)),
#     (160202, 'Bobby', 'one year two class', datetime.date(2016, 9, 1), datetime.date(2021, 6, 15)),
#     (160301, 'Mary', 'one year three class', datetime.date(2016, 9, 1), datetime.date(2021, 6, 15))
# ]
​
# 关闭链接,亦可使用session.remove(),它将回收该链接
models.session.close()

3) Veja o aluno mais novo entre os alunos ensinados por David:

Consultas JOIN:

# 获取链接池、ORM表对象
import models
​
result = models.session.query(
    models.TeachersInfo.name,
    models.StudentsInfo.name,
    models.StudentsInfo.age,
    models.ClassesInfo.name
).join(
    models.ClassesAndTeachersRelationship,
    models.ClassesAndTeachersRelationship.fk_class_id == models.ClassesInfo.id
).join(
    models.TeachersInfo,
    models.ClassesAndTeachersRelationship.fk_teacher_id == models.TeachersInfo.id
).join(
    models.StudentsInfo,
    models.StudentsInfo.fk_class_id == models.ClassesInfo.id
).filter(
    models.TeachersInfo.name == "David"
).order_by(
    models.StudentsInfo.age.asc(),
    models.StudentsInfo.id.asc()
).limit(1).all()
​
print(result)
# [('David', 'Mary', 16, 'one year three class')]
​
# 关闭链接,亦可使用session.remove(),它将回收该链接
models.session.close()

Consulta positiva e negativa:

# 获取链接池、ORM表对象
import models
​
david = models.session.query(
    models.TeachersInfo
).filter(
    models.TeachersInfo.name == "David"
).first()
​
student_lst = []
​
# 反向查询拿到任教班级,反向是一个列表,所以直接for
for row in david.mid:
    cls = row.mid_to_class
    # 通过任教班级,反向拿到其下的所有学生
    cls_students = cls.have_student
    # 遍历学生
    for student in cls_students:
        student_lst.append(
            (
                david.name,
                student.name,
                student.age,
                cls.name
            )
        )
​
# 筛选出年龄最小的
min_age_student_lst = sorted(
    student_lst, key=lambda tpl: tpl[2])[0]
​
print(min_age_student_lst)
# ('David', 'Mary', 16, 'one year three class')
​
# 关闭链接,亦可使用session.remove(),它将回收该链接
models.session.close()

4) Confira quem é o responsável por cada turma e quem são os professores:

Consultas JOIN:

# 获取链接池、ORM表对象
import models
​
from sqlalchemy import func
​
# 先查任课老师
sub_query = models.session.query(
    models.ClassesAndTeachersRelationship.fk_class_id.label("class_id"),
    func.group_concat(models.TeachersInfo.name).label("have_teachers")
).join(
    models.ClassesInfo,
    models.ClassesAndTeachersRelationship.fk_class_id == models.ClassesInfo.id
).join(
    models.TeachersInfo,
    models.ClassesAndTeachersRelationship.fk_teacher_id == models.TeachersInfo.id
).group_by(
    models.ClassesAndTeachersRelationship.fk_class_id
).subquery()
​
result = models.session.query(
    models.ClassesInfo.name.label("class_name"),
    models.TeachersInfo.name.label("leader_teacher"),
    sub_query.c.have_teachers.label("have_teachers")
).join(
    models.TeachersInfo,
    models.ClassesInfo.fk_teacher_id == models.TeachersInfo.id
).join(
    sub_query,
    sub_query.c.class_id == models.ClassesInfo.id
).all()
​
print(result)
# [('one year one class', 'David', 'Jason,David'), ('one year two class', 'Jason', 'David'), ('one year three class', 'Lisa', 'David,Lisa')]
​
# 关闭链接,亦可使用session.remove(),它将回收该链接
models.session.close()

Consulta positiva e negativa:

# 获取链接池、ORM表对象
import models
​
result = []
​
# 获取所有班级
classes_lst = models.session.query(
    models.ClassesInfo
).all()
​
for cls in classes_lst:
    cls_message = [
        cls.name,
        cls.leader_teacher.name,
        [],
    ]
    for row in cls.mid:
        cls_message[-1].append(row.mid_to_teacher.name)
    result.append(cls_message)
​
print(result)
# [['one year one class', 'David', ['David', 'Jason']], ['one year two class', 'Jason', ['David']], ['one year three class', 'Lisa', ['David', 'Lisa']]]
​
# 关闭链接,亦可使用session.remove(),它将回收该链接
models.session.close()

SQL nativo

Ver comandos de execução

Se uma instrução de consulta terminar com filter(), o método __str__ do objeto retornará a instrução de consulta formatada:

print(
    models.session.query(models.StudentsInfo).filter()
)
​
SELECT `studentsInfo`.id AS `studentsInfo_id`, `studentsInfo`.name AS `studentsInfo_name`, `studentsInfo`.gender AS `studentsInfo_gender`, `studentsInfo`.age AS `studentsInfo_age`, `studentsInfo`.fk_student_id AS `studentsInfo_fk_student_id`, `studentsInfo`.fk_class_id AS `studentsInfo_fk_class_id`
FROM `studentsInfo`

Executar comandos nativos

Para executar comandos nativos, utilize o método session.execute(), que retornará um objeto cursor, conforme imagem abaixo:

# 获取链接池、ORM表对象
import models
​
cursor = models.session.execute(
    "SELECT * FROM studentsInfo WHERE id = (:uid)", params={'uid': 1})
​
print(cursor.fetchall())
​
# 关闭链接,亦可使用session.remove(),它将回收该链接
models.session.close()  # 获取链接池、ORM表对象

3. Ditalquimia da biblioteca Python (pacote)

Documentação: https://pythonhosted.org/dictalchemy/

dictalchemy é uma biblioteca Python para converter modelos SQLAlchemy em dicionários. Ele fornece uma maneira fácil de converter objetos de modelo SQLAlchemy em formato de dicionário para fácil processamento e serialização em Python.

Instalação: pip install dictalchemy

Instalação: pip install dictalchemy3 (remove o suporte para Python2, compatível com o SQLAlchemy mais recente)

exemplo

from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, scoped_session
from dictalchemy import make_class_dictable

# 创建 SQLAlchemy 数据库引擎和会话
engine = create_engine('sqlite:///mydatabase.db')
Base = declarative_base(bind=engine)
session = scoped_session(sessionmaker(bind=engine))


# 定义 SQLAlchemy 模型
class User(Base):
    __tablename__ = 'users'
    id = Column(Integer, primary_key=True)
    username = Column(String)
    email = Column(String)


# 将模型类转换为可序列化的字典类型
make_class_dictable(User)

# 查询数据库获取模型对象
user = session.query(User).first()

# 将模型对象转换为字典
user_dict = user.to_dict()
print(user_dict)

Acho que você gosta

Origin blog.csdn.net/freeking101/article/details/132179381
Recomendado
Clasificación