注意:由于降价,代码示例可能显示不正确。 我们建议继续阅读我们博客上的原始文章,以确保正确显示所有示例。
使用Django框架开发产品通常很容易直接。 优质的文档,开箱即用的许多工具,大量的开源库和庞大的社区。 Django ORM完全控制SQL层,以保护您免受错误以及查询的底层细节的影响,因此您可以花更多时间在Python代码中设计和构建应用程序结构。 但是,有时这种行为可能会造成伤害-例如,当您在构建与数据分析相关的项目时。 用Django构建高级查询并不是一件容易的事。 如果没有在某处记录或打印生成的SQL查询,则很难(在Python中)阅读和理解SQL级别的情况。 此外,这样的查询效率还不够高,因此当您将更多数据加载到数据库中以供使用时,这将使您反感。 片刻之内,您会发现自己通过Django游标做了太多的原始SQL,这时应该休息一下,看看另一种有趣的工具,该工具位于ORM层和原始SQL层之间查询。
从文章标题可以看到,我们成功地将Django ORM和SQLAlchemy Core混合在一起,并且我们对结果非常满意。 我们构建了一个应用程序,该应用程序可通过将数据汇总到图表和表格中,按吞吐量/效率/员工成本进行评分并突出显示离群值,从而有助于分析EMR系统生成的数据,从而可以优化诊所的业务流程并节省资金。
将Django ORM与SQLAlchemy混合的意义何在?
我们出于此任务而退出Django ORM的原因有几个:
- 对于ORM世界,一个对象就是数据库中的一条记录,但是这里我们仅处理聚合数据。
- 有些聚合非常棘手,而Django ORM功能不足以满足需求。 老实说,有时在某些简单情况下,很难(甚至不可能)使ORM完全按照您想要的方式生成SQL查询,并且当您处理大数据时 ,它将对性能产生很大影响。
- 如果要通过Django ORM构建高级查询,则很难在Python中阅读和理解此类查询,也很难预测将生成和处理哪个SQL查询并将其处理到数据库中。
值得一提的是,我们还建立了第二个数据库,该数据库由Django ORM处理,可以满足其他Web应用程序相关的任务和业务逻辑需求,而这正是它的完美做到。 Django ORM正在从一个版本发展到另一个版本,并提供了越来越多的功能。 例如,在最新版本中,添加了许多简洁的功能,例如对子查询表达式或Window函数的支持,以及许多其他功能 ,如果您的问题比解决问题更复杂,那么在进行原始SQL或查看SQLAlchemy之类的工具之前,您绝对应该尝试一下一些查询。
这就是我们决定看一下SQLAlchemy的原因。 它由两部分组成-ORM和Core。 SQLAlchemy ORM与Django ORM相似,但同时它们有所不同。 与Django的Active Record方法相比,SQLAlchemy ORM使用了不同的概念Data Mapper。 至于要在Django上构建项目,绝对不要切换ORM(如果您没有非常特殊的理由),因为您想使用Django REST框架,Django-admin和其他精巧的工具与Django模型相关。
SQLAlchemy的第二部分称为核心。 它位于高级ORM和低级SQL之间。 核心功能强大而灵活。 它使您能够构建所需的任何SQL查询,并且当您在Python中看到此类查询时,很容易理解发生了什么。 例如,查看文档中的示例查询:
q = session.query(User).filter(User.name.like('e%')).\ limit(5).from_self().\ join(User.addresses).filter(Address.email.like('q%')).\ order_by(User.name)
这将导致
SELECT anon_1.user_id AS anon_1_user_id, anon_1.user_name AS anon_1_user_name FROM (SELECT "user".id AS user_id, "user".name AS user_name FROM "user" WHERE "user".name LIKE :name_1 LIMIT :param_1) AS anon_1 JOIN address ON anon_1.user_id = address.user_id WHERE address.email LIKE :email_1 ORDER BY anon_1.user_name
注意:通过这种技巧,我们不会陷入N+1 problem : from_select在查询周围添加了一个额外的SELECT包装器,因此我们首先减少了行数(通过LIKE和LIMIT ),然后才加入地址信息。
如何混合Django应用程序和SQLALchemy
因此,如果您有兴趣并且想尝试将SQLAlchemy与Django应用程序混合使用,那么以下提示可能会对您有所帮助。
首先,您需要使用Engine 创建一个全局变量,但是与数据库的实际连接将在首次connect或execute调用时建立。
sa_engine = create_engine(settings.DB_CONNECTION_URL, pool_recycle=settings.POOL_RECYCLE)
创建引擎接受其他连接配置。 MySQL / MariaDB / AWS Aurora(与MySQL兼容)具有默认为8h 的交互式超时设置,因此,如果没有pool_recycle额外的参数,您将得到恼人的SQLError: (OperationalError) (2006, 'MySQL server has gone away') 。 因此, POOL_RECYCLE应该小于interactive_timeout 。 例如一半: POOL_RECYCLE = 4 * 60 * 60
下一步是建立查询。 根据您的应用程序体系结构,您可以使用 Table和Column类(也可以与ORM一起使用)声明表和字段,或者如果您的应用程序已经以其他方式存储了表和列的名称,则可以通过table来就地进行( table_name )和柱( col_name )函数(如所示在这里 )。
在我们的应用程序中,我们选择了第二个选项,因为我们以自己的声明性语法存储了有关聚合,公式和格式的信息。 然后,我们建立了一个读取此类结构并根据提供的指令执行查询的层。
查询准备就绪后,只需调用sa_engine.execute(query) 。 游标将一直打开,直到您读取所有数据或显式关闭它为止。
有一件非常烦人的事情值得一提。 正如文档所述 ,SQLAlchemy执行查询字符串化的能力有限,因此要执行最终查询并非易事。 您可以自己打印查询:
print(query)
SELECT role_group_id, role_group_name, nr_patients FROM "StaffSummary" WHERE day >= :day_1 AND day <= :day_2 AND location_id = :location_id_1 AND service_id = :service_id_1
(这个看起来并不那么可怕,但是对于更复杂的查询,它可能是大约20个以上的占位符,要手动填充以便稍后在SQL控制台中播放,这非常烦人并且耗时。)
如果您只需要在查询中插入字符串和数字,这将对您有用
print(s.compile(compile_kwargs={"literal_binds": True}))
对于日期,这种技巧将不起作用。 关于如何获得所需结果的StackOverflow进行了讨论 ,但是解决方案似乎没有吸引力。
另一个选择是启用查询通过数据库配置登录到文件,但是在这种情况下,您可能会遇到另一个问题。 如果Django ORM也连接到该数据库,将很难找到要调试的查询。
你也许也喜欢:
初学者的最佳Python和Django书籍和教程 无论您打算扩展编程技能还是作为软件开发人员重新开始职业生涯,都可以学习... djangostars.com
Django性能优化技巧 当开发人员收到在Django上进行性能优化的任务时,我经常会遇到反复出现的情况 。djangostars.com
测试中
注意:Pytest multidb注释说:“目前pytest-django不专门支持Django的多数据库支持。 不过,您可以使用普通的Django TestCase实例来使用它的multi_db支持。”
那是什么意思-不支持? 默认情况下,Django将为DATABASES定义中列出的每个数据库创建和删除(在所有测试的最后)一个测试数据库。 该功能也可以与pytests完美配合。
带有multi_db=True Django TestCase和TransactionTestCase允许在测试之间擦除多个数据库中的数据。 此外,它还可以通过django-fixtures数据加载到第二个数据库中,但是最好使用模型mommy 或 factory boy ,因为不受此属性的影响。
pytest-django讨论中建议一些技巧,如何解决此问题并使multi_db继续进行pytesting。
有一条重要的建议-对于具有Django模型的表,您应该通过Django-ORM将数据保存到DB。 否则,您将在编写测试时遇到问题。 TestCase将无法回滚在Django DB连接之外发生的其他事务。 如果遇到这种情况,可以将TransactionalTestCase与multi_db=True用于触发功能的测试,这些功能会通过SQLAlchemy连接生成数据库写操作,但请记住,此类测试的速度比常规TestCase慢。
另外,另一种情况是可能的-您仅在一个数据库中拥有Django模型,并且正在通过SQLAlchemy使用第二个数据库。 在这种情况下, multi_db完全不会影响您。 在这种情况下,您需要编写一个pytest-fixture(如果使用unittests,则可以在setUp作为混合和触发逻辑来完成),它将从SQL文件创建数据库结构。 这样的文件应该在CREATE TABLE之前包含DROP TABLE IF EXISTS语句。 该夹具应应用于使用此数据库进行操作的每个测试用例。 其他夹具可以将数据加载到创建的表中。
注意:这样的测试将变慢,因为将为每个测试重新创建表。 理想情况下,表应该创建一次(声明为@pytest.fixture(scope='session', autouse=True) ),并且每个事务应回滚每个测试的数据。 由于存在不同的连接,因此实现起来并不容易:Django和SQLAlchemy或SQLAlchemy连接池的不同连接,例如,在测试中,您启动事务,用测试数据填充数据库,然后运行测试和回滚事务(未提交) 。 但是在测试期间,您的应用程序代码可能会像对connection.execute(query)一样对数据库执行查询,该查询在创建测试数据的事务之外执行。 因此,使用默认事务隔离级别,应用程序将看不到任何数据,只能看到空表。 对于SQLAlchemy连接,可以将事务隔离级别更改为READ UNCOMMITTED ,并且一切都会按预期进行,但这绝对不是解决方案。
结论
综上所述,SQLAlchemy Core是一个很棒的工具,它使您更接近SQL,并可以理解和完全控制查询。 如果您要构建需要高级聚合的应用程序(或其一部分),那么值得一试的是使用SQLAlchemy Core功能替代Django ORM工具。
请继续阅读以了解如何使为数据分析项目构建高级查询变得更加容易。 了解我们如何设法混合使用Django ORM和SQLAlchemy Core以及从中获得的收益。
关于合并Django ORM的这篇文章是由Django Stars的高级软件开发人员Gleb Pushkov撰写的
如果您发现此帖子有用,请点击下面的👏按钮:)