在 Django 的 ORM 中访问存储过程的最佳方法是啥

Posted

技术标签:

【中文标题】在 Django 的 ORM 中访问存储过程的最佳方法是啥【英文标题】:What is the best way to access stored procedures in Django's ORM在 Django 的 ORM 中访问存储过程的最佳方法是什么 【发布时间】:2010-10-22 18:25:12 【问题描述】:

我正在设计一个相当复杂的数据库,并且知道我的一些查询将远远超出 Django 的 ORM 的范围。有没有人成功地将 SP 与 Django 的 ORM 集成?如果是这样,什么 RDBMS 以及您是如何做到的?

【问题讨论】:

Django Using Stored Procedure 会给出一些想法。 【参考方案1】:

你必须在 Django 中使用连接工具:

from django.db import connection

with connection.cursor() as cursor:
    cursor.execute("SQL STATEMENT CAN BE ANYTHING")
    data = cursor.fetchone()

如果您期望不止一行,请使用 cursor.fetchall() 获取它们的列表。

更多信息在这里:http://docs.djangoproject.com/en/dev/topics/db/sql/

【讨论】:

【参考方案2】:

可以使用Cx_Oracle。此外,当我们无法访问生产部署的代码并且需要对数据库进行重大更改时,它也很有帮助。

import cx_Oracle
try:
    db = dev_plng_con
    con = cx_Oracle.connect(db)
    cur = con.cursor()
    P_ERROR = str(error)
    cur.callproc('NAME_OF_PACKAGE.PROCEDURENAME', [P_ERROR])

except Exception as error:
    error_logger.error(message)

【讨论】:

【参考方案3】:

有一个很好的例子: https://djangosnippets.org/snippets/118/

from django.db import connection


cursor = connection.cursor()
ret = cursor.callproc("MY_UTIL.LOG_MESSAGE", (control_in, message_in))# calls PROCEDURE named LOG_MESSAGE which resides in MY_UTIL Package
cursor.close()

【讨论】:

【参考方案4】:

我们(musicpictures.com / eviscape.com)编写了 django sn-p,但它并不是故事的全部(实际上该代码当时仅在 Oracle 上测试过)。

当您想要重复使用经过验证的 SP 代码或一次 SP 调用将比多次调用数据库更快的情况 - 或安全性要求对数据库进行适度访问的情况 - 或查询非常复杂的情况时,存储过程很有意义 /多重步骤。我们正在对 Oracle 和 Postgres 数据库使用混合模型/SP 方法。

诀窍是让它易于使用并保持“django”的风格。我们使用 make_instance 函数获取游标的结果并创建从游标填充的模型的实例。这很好,因为游标可能会返回其他字段。然后你可以在你的代码/模板中使用这些实例,就像普通的 django 模型对象一样。

def make_instance(instance, values):
    '''
    Copied from eviscape.com

    generates an instance for dict data coming from an sp

    expects:
        instance - empty instance of the model to generate
        values -   dictionary from a stored procedure with keys that are named like the
                   model's attributes
    use like:
        evis = InstanceGenerator(Evis(), evis_dict_from_SP)

    >>> make_instance(Evis(), 'evi_id': '007', 'evi_subject': 'J. Bond, Architect')
    <Evis: J. Bond, Architect>

    '''
    attributes = filter(lambda x: not x.startswith('_'), instance.__dict__.keys())

    for a in attributes:
        try:
            # field names from oracle sp are UPPER CASE
            # we want to put PIC_ID in pic_id etc.
            setattr(instance, a, values[a.upper()])
            del values[a.upper()]
        except:
            pass

    #add any values that are not in the model as well
    for v in values.keys():
        setattr(instance, v, values[v])
        #print 'setting %s to %s' % (v, values[v])

    return instance

# 像这样使用它:

pictures = [make_instance(Pictures(), item) for item in picture_dict]

# 这里有一些辅助函数:

def call_an_sp(self, var):
    cursor = connection.cursor()
    cursor.callproc("fn_sp_name", (var,))
    return self.fn_generic(cursor)


def fn_generic(self, cursor):
    msg = cursor.fetchone()[0]
    cursor.execute('FETCH ALL IN "%s"' % msg)
    thing = create_dict_from_cursor(cursor)
    cursor.close()
    return thing

def create_dict_from_cursor(cursor):
    rows = cursor.fetchall()
    # DEBUG settings (used to) affect what gets returned. 
    if DEBUG:
        desc = [item[0] for item in cursor.cursor.description]
    else:
        desc = [item[0] for item in cursor.description]
    return [dict(zip(desc, item)) for item in rows]    

干杯,西蒙。

【讨论】:

fn_generic中的光标为什么要关闭? 我在一个巨大的系统上工作,该系统有一个可供多个应用程序访问的数据库,一些 c++,一些 python,一些 perl,一些 php,一些基于 web,很多不是。当业务逻辑在 SP 中时,我喜欢它,因为这意味着逻辑在所有实现中都是一致的,并且至少在我们的例子中,使维护变得更加容易。 我发现了 russ magee 的评论:“我们特别避免在 Django 的 ORM 中添加明显的类似 SQL 的功能,因为归根结底,我们并没有试图替换 SQL - 我们”只是试图提供一种方便的方式来表达简单的查询。完全可以预料到您将退回到只为复杂的情况调用原始 SQL”【参考方案5】:

我想 Django 1.2 中改进的原始 sql 查询集支持可以使这更容易,因为您不必滚动自己的 make_instance 类型代码。

【讨论】:

【参考方案6】:

不要。

说真的。

将存储过程逻辑移到它所属的模型中。

将一些代码放在 Django 中,将一些代码放在数据库中是维护的噩梦。我在 IT 行业 30 多年的时间里花了太多时间试图清理这种烂摊子。

【讨论】:

@S.Lott 我认为你误解了我的观点。我说的是一个虚构的/未来的 Django ORM。存储过程不会由开发人员编写。此 ORM 将动态/透明地将常用的 ORM 查询转换为存储过程,从而可以节省 SQL 字符串生成时间并利用 SP 的预编译特性。同样,我并没有声称这甚至是可能的,或者值得加速。只是指出一个有趣的想法,他的问题对我产生了影响。这种方法可以将所有逻辑留在代码中,并具有 SP 性能。 @S.洛特这不是“神奇的”。生成“EXEC some_sp_name(with, params)”比生成大 SQL 语句要快。你可能会说,“嗯,这只是字符串,它超级快”。是的,但是如果你已经达到了 django 的 ORM SQL 生成的顶峰,我想你会发现它比这更可怕。此外,存储过程还利用了预编译的 SQL,例如参数化查询。我同意存储过程非常糟糕,但您必须承认,让 ORM 透明地为您生成它们而不是每次都生成 SQL 是一个有趣的想法。 @Chad:我说所有 SP 都是一个坏主意。统一。全面的。代码现在在两个地方。从长远来看,这样分割代码似乎永远不会奏效。 @S.Lott 所以30年后你可以说统一和整体存储过程是一个坏主意吗?哇,这涵盖了很多案例,我一秒钟都不会买它。那里有很多案例,我无法想象您个人可以证明所有这些案例。只是我的2美分。我可以想出许多他们认为完美的案例和其他没有意义的案例。只是为了记录在一些我可以想象或已经工作过的情况下,我完全同意你的观点,而在其他情况下,从你的角度来看,我是在宇宙的另一端。 @S.Lott 你仍然误解了我想说的话。代码仍然在一个地方。所有代码只存在于 Python 中的 ORM 逻辑中。我说的存储过程不是程序员写的。当 ORM 注意到某个确切的查询被大量发送时,作为微优化,ORM 会动态创建一个新的存储过程并使用它,而不是每次都生成 SQL。没有碎片代码!开发人员编写 Python 代码,SP 的所有“好处”都可以透明地获得,而无需编写任何 SP 或“分割”您的业务逻辑。【参考方案7】:

如果您想查看使用 SP 的实际运行项目,请查看minibooks。大量自定义 SQL 并使用 Postgres pl/pgsql for SP。我认为他们最终会删除 SP(trac ticket 92 中的理由)。

【讨论】:

以上是关于在 Django 的 ORM 中访问存储过程的最佳方法是啥的主要内容,如果未能解决你的问题,请参考以下文章

Django之ORM性能优化建议

最佳实践:通过存储在 DB 中的方案在运行时创建 ORM 对象

在存储库级别在 Doctrine2 ORM 中实现分页的最佳实践

Django之ORM

使用 Android ORM Room 的最佳方式?

Django之ORM操作