使用关系型数据库时,从数据归档、性能等角度综合性考虑后经常会采取的一种措施是根据不同的维度进行分表。这里笔者想说的是将数据拆分到不同的表中去,同时数据的定义不变,一个常见的例子是日志根据时间拆分。

在使用ORM查询时,对于业务来说其实是一类数据,仅仅是数据来源不同,故而期望能使用相对一致的映射对象。在使用SQLAlchemy时,常见的用法是预先定义好ORM类,直接使用即可。对于动态的表名来说不那么简单。

不过参考SQLAlchemy的DDL用法1,也即对数据表进行描述来看,本身是可以写成过程式的代码的,我们不妨稍加利用一下,动态地组装出需要的对象。

# -*- coding: utf-8 -*-
from datetime import datetime

from sqlalchemy import (
    Table, Column, Integer, MetaData, String, create_engine,
)
from sqlalchemy.orm import mapper, sessionmaker

en = create_engine('mysql+mysqldb://root@127.0.0.1/test?charset=utf8')
# 创建表的时候需要用到meta对象,用于绑定到某个数据库引擎里
# 便于操作meta信息
meta = MetaData(bind=en)


class UserLog(object):

    mapper = {}

    @staticmethod
    def getTable(date):
        """
        根据时间获取动态的ORM对象
        """
        # 先拿到动态的表名与类名
        tableName = date.strftime('user_log_%Y%m%d')
        clsName = date.strftime('UserLog%Y%m%d')

        # 缓存,不需要重复创建
        if clsName in UserLog.mapper:
            return UserLog.mapper[clsName]

        # 定义一个新的映射对象
        table = Table(
            tableName, meta,

            # 这里 key 参数作用是作为对象的属性使用
            # 例如 userLog.rowId
            Column(
                'id', Integer, autoincrement=True, primary_key=True,
                key='rowId'),
            Column('name', String(255), default=''),
        )
        # 动态定义一个类,三个参数含义分别为
        # - 类名
        # - 从哪里继承
        # - 定义成员内容
        cls = type(clsName, (UserLog,), {})

        # sqlalchemy,将类映射到表
        mapper(cls, table)
        UserLog.mapper[clsName] = cls
        return cls


ss = sessionmaker(bind=en)
s = ss()

dt = datetime.now()
# 根据参数动态获取ORM对象
model = UserLog.getTable(dt)
# 接下来用法和正常的保持一致
s.query(model).all()

这段代码是可以执行的,由于加上了访问缓存,也不会出现重复定义的问题,使用方法基本上和常见的代码保持一致,对使用者来说只是多了一个获取ORM类的调用。

参考资料