37

I am designing a fairly complex database, and know that some of my queries will be far outside the scope of Django's ORM. Has anyone integrated SP's with Django's ORM successfully? If so, what RDBMS and how did you do it?

kkubasik
  • 3,684
  • 6
  • 25
  • 23

7 Answers7

30

We (musicpictures.com / eviscape.com) wrote that django snippet but its not the whole story (actually that code was only tested on Oracle at that time).

Stored procedures make sense when you want to reuse tried and tested SP code or where one SP call will be faster than multiple calls to the database - or where security requires moderated access to the database - or where the queries are very complicated / multistep. We're using a hybrid model/SP approach against both Oracle and Postgres databases.

The trick is to make it easy to use and keep it "django" like. We use a make_instance function which takes the result of cursor and creates instances of a model populated from the cursor. This is nice because the cursor might return additional fields. Then you can use those instances in your code / templates much like normal django model objects.

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

# Use it like this:

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

# And here are some helper functions:

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]    

cheers, Simon.

  • Why do you close the cursor in `fn_generic`? – Joe Holloway Oct 13 '11 at 16:39
  • 5
    I work on an immense system with a database that is accessed by multiple applications, some c++, some python, some perl, some php, some web are based, many are not. I love it when the business logic is in SPs because it means the logic is consistent across all the implementations, and in our case at least, makes maintenance much easier. – compound eye Nov 08 '11 at 05:52
  • 6
    i found this comment by russ magee: "We have specifically avoided adding obvious SQL-like features to Django's ORM, because at the end of the day, we're not trying to replace SQL - we're just trying to provide a convenient way to express simple queries. It is fully expected that you will fall back to just calling raw SQL for complex cases" – compound eye Nov 10 '11 at 04:30
22

You have to use the connection utility in Django:

from django.db import connection

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

If you are expecting more than one row, use cursor.fetchall() to fetch a list of them.

More info here: http://docs.djangoproject.com/en/dev/topics/db/sql/

Adam Johnson
  • 471
  • 1
  • 5
  • 11
igorgue
  • 17,884
  • 13
  • 37
  • 54
6

Don't.

Seriously.

Move the stored procedure logic into your model where it belongs.

Putting some code in Django and some code in the database is a maintenance nightmare. I've spent too many of my 30+ years in IT trying to clean up this kind of mess.

S.Lott
  • 384,516
  • 81
  • 508
  • 779
  • What he's asking is about integrating the ORM with SPs. This probably isn't possible, and using stored procedures probably requires you to just access django.db.connection directly like in the other answers, but it would be interesting if you could automatically move common queries the ORM makes into stored procedures, to save on query generation time, and do it transparently, as an optimization. It wouldn't work on every database, and the performance gain probably isn't worthwhile, but it would be fun to investigate. – Chad May 11 '11 at 19:09
  • @Chad: "What he's asking is about integrating the ORM with SPs." Understood. Hence my answer. SP's fragment your application logic between proper application code and the database. They often create more problems than they solve. I think that SP's are not helpful under any circumstances and should not be used. – S.Lott May 11 '11 at 19:52
  • 2
    @S.Lott I think you misunderstood the point I made. I'm talking about an imaginary/future Django ORM. Stored procedures will not be written by developers. This ORM will dynamically/transparently convert commonly executed ORM queries into stored procedures, so that it can save on SQL string generation time and make use of the pre-compiled nature of SP. Again, I'm not claiming to think this is even possible, or that it would be worth the speedup. Just pointing out an interesting idea his question spawned for me. This approach could leave all the logic in the code and have SP performance. – Chad May 18 '11 at 01:07
  • @Chad: I think you misunderstood the point I made. I'm talking about all SP's as being uniformly a bad idea. It's not "interesting". It's a mistake. SP's don't magically create high performance. – S.Lott May 18 '11 at 01:14
  • 1
    @S. Lott it isn't "magical". It's faster to generate "EXEC some_sp_name(with, params)" than it is to generate a big SQL statement. You might say, "well thats just strings, it's super fast". Yeah, but if you've peaked into django's ORM SQL generation I think you'd see it's a little more frightening than that. Plus, stored procedures take advantage of the SQL being precompiled, like a parameterized query. I agree that stored procedures totally suck but you have to admit that it's an interesting idea to have the ORM transparently generate them for you instead of generating the SQL every time. – Chad May 19 '11 at 21:01
  • 3
    @Chad: I'm talking about all SP's as being uniformly a bad idea. Uniformly. Overall. The code is now in two places. It never seems to work out well in the long run to fragment the code like that. – S.Lott May 19 '11 at 21:06
  • 4
    @S.Lott So after 30 years you can say that Uniformly and Overall stored procedures are a bad idea? Wow that covers lots of cases and I don't buy it for one second. There are many cases out there and I can't imagine you personally can attest to all of them. Just my 2cents. I can come up with many cases they make PERFECT sense and other cases where they don't. Just for the record in some of the cases I can imagine or have worked on I completely agree with you and on others I am on the other side of the universe from your view point. – Kuberchaun Mar 08 '12 at 00:55
  • 1
    @S.Lott you're still misunderstanding what I was trying to say. The code is still in ONE place. All of the code only exists in ORM logic in Python. The stored procedures I'm talking about are not written by a programmer. When the ORM notices a certain exact query is being sent a lot, as a micro-optimization, the ORM dynamically creates a new stored procedure and uses that instead of generate SQL each time. No fragmenting code! Developers _only_ write Python code and all of the "benefits" of SPs are gained transparently without having to write any SPs or "fragment" your business logic. – Chad Mar 20 '12 at 21:56
  • Hahahah well, even tho this post is 13 years old I'll add my 2 cents... What you mean to say is "Don't do this because Django assumes that they own your database..... NO, not YOUR database, THEIR database.. and that nothing will ever touch that database except via Django models. Anything you can't do using the standard ORM methods, you simply should not be doing.. says Django. "WONTFIX". – little_birdie Aug 30 '22 at 15:23
5

There is a good example : 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()
Hemant
  • 181
  • 3
  • 14
2

If you want to look at an actual running project that uses SP, check out minibooks. A good deal of custom SQL and uses Postgres pl/pgsql for SP. I think they're going to remove the SP eventually though (justification in trac ticket 92).

Van Gale
  • 43,536
  • 9
  • 71
  • 81
0

I guess the improved raw sql queryset support in Django 1.2 can make this easier as you wouldn't have to roll your own make_instance type code.

Anentropic
  • 32,188
  • 12
  • 99
  • 147
0

Cx_Oracle can be used. Also, It is fairly helpful when we do not have access to production deployed code and need arises to make major changes in database.

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)