Django的ORM中访问存储过程的最佳方法是什么?

37

我正在设计一个相当复杂的数据库,而且知道我的一些查询将远超出Django ORM的范围。是否有人成功地将SP与Django ORM集成?如果有,使用了什么关系型数据库管理系统(RDBMS),您是如何做到的?


Django 使用存储过程 可以提供一些思路。 - Dhanapal
7个回答

30

我们(musicpictures.com / eviscape.com)编写了那个Django片段,但这并不是全部(实际上,那段代码当时只在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 中要关闭游标? - Joe Holloway
5
我在一套庞大的系统上工作,它有一个数据库,被多个应用程序所使用,其中一些是用c++、python、perl、php编写的,一些是基于web的,很多则不是。我喜欢业务逻辑在存储过程中的情况,因为这意味着逻辑在所有实现中都是一致的,在我们这种情况下,这使得维护变得更加容易。 - compound eye
6
我发现了Russ Magee的这条评论:“我们有意避免向Django的ORM添加明显的类SQL功能,因为归根结底,我们并不试图取代SQL——我们只是试图提供一种方便的方式来表达简单的查询。针对复杂情况,完全可以使用纯粹的SQL语句。” - compound eye

22

您需要使用 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/

6

不要这样做。

说真的。

把存储过程的逻辑移动到你的模型中,那才是它应该呆的地方。

在Django中放置一些代码,在数据库中放置一些代码会导致维护成本极高。我已经在IT行业度过了30多年,尝试清理这种混乱的情况太多了。


2
@S.Lott 我觉得你误解了我的意思。我是在谈论一个虚构/未来的Django ORM。存储过程不会由开发人员编写。这个ORM会动态/透明地将常用的ORM查询转换成存储过程,以节省SQL字符串生成时间并利用SP预编译的特性。再次声明,我不认为这是可能的,或者这样做是否值得加速。只是指出他的问题对我激发了一个有趣的想法。这种方法可以将所有逻辑留在代码中,并具有SP的性能。 - Chad
1
@S. Lott,这并不是“神奇”的。生成“EXEC some_sp_name(with, params)”比生成一个大的SQL语句更快。你可能会说,“好吧,那只是字符串,速度超级快”。是的,但如果你看过Django的ORM SQL生成,我想你会发现它比那更可怕。此外,存储过程利用了SQL的预编译,就像参数化查询一样。我同意存储过程确实很糟糕,但你必须承认,让ORM透明地为你生成它们而不是每次生成SQL是一个有趣的想法。 - Chad
3
@ Chad:我指的是所有SP都是一种统一的坏想法。 统一的。 总体而言。 代码现在分为两个部分。 长期来看,像这样分割代码似乎从未取得良好的效果。 - S.Lott
4
@S.Lott,所以经过30年的经验,您可以说统一和总体存储过程是一个糟糕的想法?哇,这涵盖了很多情况,但我一秒钟都不信。世界上有很多情况,我无法想象您个人可以证实所有这些情况。这只是我的看法。我可以举出许多情况来证明它们非常合理,而其他情况则不然。仅供记录,在我能够想象或处理的某些情况下,我完全同意您的观点,而在其他情况下,我与您的观点背道而驰。 - Kuberchaun
1
@S.Lott,您仍然误解了我的意思。代码仍然在一个地方。所有的代码都存在于Python中的ORM逻辑中。我所说的存储过程并不是由程序员编写的。当ORM注意到某个确切的查询被频繁发送时,作为微小优化,ORM会动态创建一个新的存储过程,并使用它来代替每次生成SQL。没有分散的代码!开发人员只编写Python代码,并且所有SP的“好处”都可以透明地获得,而无需编写任何SP或“分裂”业务逻辑。 - Chad
显示剩余4条评论

5
这里有一个很好的例子: 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()

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

0

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


0

可以使用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)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接