Índice de columnas múltiples cuando se usa la extensión ORM declarativa de sqlalchemy

95

De acuerdo con la documentación y los comentarios de la sqlalchemy.Columnclase, deberíamos usar la clase sqlalchemy.schema.Indexpara especificar un índice que contenga múltiples columnas.

Sin embargo, el ejemplo muestra cómo hacerlo usando directamente el objeto Table de esta manera:

meta = MetaData()
mytable = Table('mytable', meta,
    # an indexed column, with index "ix_mytable_col1"
    Column('col1', Integer, index=True),

    # a uniquely indexed column with index "ix_mytable_col2"
    Column('col2', Integer, index=True, unique=True),

    Column('col3', Integer),
    Column('col4', Integer),

    Column('col5', Integer),
    Column('col6', Integer),
    )

# place an index on col3, col4
Index('idx_col34', mytable.c.col3, mytable.c.col4)

¿Cómo deberíamos hacerlo si usamos la extensión ORM declarativa?

class A(Base):
    __tablename__ = 'table_A'
    id = Column(Integer, , primary_key=True)
    a = Column(String(32))
    b = Column(String(32))

Me gustaría un índice en la columna "a" y "b".

yorjo
fuente
1
La pregunta es un poco confusa sobre si desea múltiples índices o un solo índice en múltiples columnas (y estaba más confundido antes de editarlo; originalmente pedía deliciosamente "un índice que contenga múltiples índices múltiples" ). Pero no importa, supongo, ya que la respuesta de zzzeek aborda ambos casos.
Mark Amery

Respuestas:

138

esos son solo Columnobjetos, index = True flag funciona normalmente:

class A(Base):
    __tablename__ = 'table_A'
    id = Column(Integer, primary_key=True)
    a = Column(String(32), index=True)
    b = Column(String(32), index=True)

si desea un índice compuesto, nuevamente Tableestá presente aquí como de costumbre, simplemente no tiene que declararlo, todo funciona igual (asegúrese de estar en 0.6 o 0.7 reciente para que el contenedor declarativo Aa se interprete como un Columndespués de que se complete la declaración de clase):

class A(Base):
    __tablename__ = 'table_A'
    id = Column(Integer, primary_key=True)
    a = Column(String(32))
    b = Column(String(32))

Index('my_index', A.a, A.b)

En 0.7 también Indexpuede estar en los Tableargumentos, que con declarativo es a través de __table_args__:

class A(Base):
    __tablename__ = 'table_A'
    id = Column(Integer, primary_key=True)
    a = Column(String(32))
    b = Column(String(32))
    __table_args__ = (Index('my_index', "a", "b"), )
zzzeek
fuente
1
Gracias, actualicé a 0.7 y el uso de table_args funciona bien
yorjo
6
¿Qué sucede si tienes un diccionario para table_args como el que tengo actualmente? table_args = {'mysql_engine': 'InnoDB'}
Nick Holden
7
Así que supongo que puedo hacer table_args = (Index ('my_index', "a", "b"), {'mysql_engine': 'InnoDB'})
Nick Holden
1
@RyanChou docs.sqlalchemy.org/en/latest/orm/extensions/declarative/… "Los argumentos de palabras clave se pueden especificar con el formulario anterior especificando el último argumento como un diccionario"
zzzeek
13

Para completar la respuesta de @ zzzeek .

Si desea agregar un índice compuesto con DESC y utilizar el método declarativo ORM, puede hacer lo siguiente.

Además, estaba luchando con la documentación de índices funcionales de SQSAlchemy, tratando de averiguar cómo sustituir mytable.c.somecol.

from sqlalchemy import Index

Index('someindex', mytable.c.somecol.desc())

Podemos simplemente usar la propiedad del modelo e invocarla .desc():

from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()

class GpsReport(db.Model):
    __tablename__ = 'gps_report'

    id = db.Column(db.Integer, db.Sequence('gps_report_id_seq'), nullable=False, autoincrement=True, server_default=db.text("nextval('gps_report_id_seq'::regclass)"))

    timestamp = db.Column(db.DateTime, nullable=False, primary_key=True)

    device_id = db.Column(db.Integer, db.ForeignKey('device.id'), primary_key=True, autoincrement=False)
    device = db.relationship("Device", back_populates="gps_reports")


    # Indexes

    __table_args__ = (
        db.Index('gps_report_timestamp_device_id_idx', timestamp.desc(), device_id),
    )

Si usa Alembic, estoy usando Flask-Migrate, genera algo como:

from alembic import op  
import sqlalchemy as sa
# Added manually this import
from sqlalchemy.schema import Sequence, CreateSequence


def upgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    # Manually added the Sequence creation
    op.execute(CreateSequence(Sequence('gps_report_id_seq')))

    op.create_table('gps_report',
    sa.Column('id', sa.Integer(), server_default=sa.text("nextval('gps_report_id_seq'::regclass)"), nullable=False),
    sa.Column('timestamp', sa.DateTime(), nullable=False))
    sa.Column('device_id', sa.Integer(), autoincrement=False, nullable=False),
    op.create_index('gps_report_timestamp_device_id_idx', 'gps_report', [sa.text('timestamp DESC'), 'device_id'], unique=False)


def downgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.drop_index('gps_report_timestamp_device_id_idx', table_name='gps_report')
    op.drop_table('gps_report')

    # Manually added the Sequence removal
    op.execute(sa.schema.DropSequence(sa.Sequence('gps_report_id_seq'))) 
    # ### end Alembic commands ###

Finalmente, debería tener la siguiente tabla e índices en su base de datos PostgreSQL:

psql> \d gps_report;
                                           Table "public.gps_report"
     Column      |            Type             | Collation | Nullable |                Default                 
-----------------+-----------------------------+-----------+----------+----------------------------------------
 id              | integer                     |           | not null | nextval('gps_report_id_seq'::regclass)
 timestamp       | timestamp without time zone |           | not null | 
 device_id       | integer                     |           | not null | 
Indexes:
    "gps_report_pkey" PRIMARY KEY, btree ("timestamp", device_id)
    "gps_report_timestamp_device_id_idx" btree ("timestamp" DESC, device_id)
Foreign-key constraints:
    "gps_report_device_id_fkey" FOREIGN KEY (device_id) REFERENCES device(id)
Mickael
fuente