How did I not know about this gem earlier?

I've been resorting to executesql() for my heavy joins-within-joins queries 
because, well, I thought I had to.  The select join syntax doesn't do 
nested joins directly and I'd never heard of nested_select.

Wow, what a great tool!  I just make my sub-join its own select, make an 
alias for it (required to use it with "on") and then put it in another 
query.  It is a thing of beauty.  (Well, maybe I have low standards of 
beauty!)

This result is the equivalent of a very complex executesql() query:

    def gettimes(self, mid, eid, stage):
        assert mid and eid and stage
        db = current.db
        dbt,dbc,dbs,dbei,dbts,dbti = 
db.times,db.club,db.swimmer,db.event_index,db.time_std,db.time_std_info 
        cols = [dbt[c] for c in 
['ord','rank','evt','heat','lane','age','stime','stage','id']]
        cols += [dbc[c] for c in ['code','name','id']]
        cols += [dbei[c] for c in 
['course','sex','distance','stroke','lower_age','upper_age']]
        cols += [dbs[c] for c in ['id','first','last','mi','preferred']]
        subq = db(dbts.id>0).nested_select(dbts.ALL,
                    
join=dbti.on((dbti.id==dbts.id_info)&(dbti.id_lsc=='51')&(dbti.code=='Default')))
        dbtt = subq.with_alias('dbtt')
        cols += [dbtt[c] for c in ['name','next','time']]
        q = (dbt.id_meet==mid)&(dbt.id_event==eid)&(dbt.stage==stage)
        rows = db(q).select(*cols,cacheable=True,processor=self.as_list,
                           join=[dbei.on(dbei.id==dbt.id_event),
                                 dbc.on(dbc.id==dbt.id_club),
                                 dbs.on(dbs.id==dbt.id_swim)],
                           left=dbtt.on
((dbtt.event_code==dbei.code2)&(dbt.stime>0)
                                        
&(dbtt.lower_age<=dbt.age)&(dbtt.upper_age>=dbt.age)
                                        
&(dbtt.next<dbt.stime)&(dbtt.time>=dbt.stime)))
        return rows

To save creation of Rows I used the "processor" keyword and made a little 
processor that basically returns a list of nested dicts, just as the normal 
"as_list" would to to a set of Rows.  But it does it without creating the 
Rows first, saving time and memory.

    @staticmethod
    def as_list(rows, fields, colnames, blob_decode=True, cacheable = 
False):
        from collections import defaultdict
        rtn = list()
        cols = [(f.tablename,f.name) for f in fields]
        for row in rows:
            d = defaultdict(dict)
            for r,v in zip(cols,row):
                d[c[0]][c[1]] = r
            rtn.append(d)
        return rtn

I'm pleased to get really good performance and still keep the power and 
generality of DAL.  This feature might deserve additional explanation in 
the book.

-- Joe

If you want to see the executesql version, hold your nose and peek below. 
 I'm sure it is ghastly and could be done much better.  There are two 
variants I was playing with, one that uses Rows and as_list and another 
which makes the list of nested dicts directly.

    def gettimes2(self, mid, eid, stage):
        db = current.db
        dbt, dbc, dbs, dbei, dbts, dbti = db.times, db.club, db.swimmer, 
db.event_index, db.time_std, db.time_std_info
        assert mid and eid and stage
        table_alias = 
{'times':'dbt','club':'dbc','swimmer':'dbs','event_index':'dbei','time_std':'dbtt'}
        colnames = 
['times.ord','times.rank','times.evt','times.heat','times.lane','times.age','times.stime','times.stage','times.id',
                    'club.code','club.name','club.id',
                    
'event_index.course','event_index.sex','event_index.distance','event_index.stroke',
                    'event_index.lower_age','event_index.upper_age',
                    
'swimmer.id','swimmer.first','swimmer.last','swimmer.mi','swimmer.preferred',
                    'time_std.name','time_std.next','time_std.time']
        split_colnames = [fld.split('.') for fld in colnames]
        rnames=['.'.join([table_alias[t],db[t][f]._rname]) for t,f in 
split_colnames]
        sqlvars = dict(eid=eid, mid=mid, stage=stage, 
fields=','.join(rnames))
        stmt = """
        SELECT %(fields)s FROM sstimes.times dbt
        JOIN ssdata.event_index dbei ON dbei.id=dbt.id_event
        JOIN ssdata.club dbc ON dbc.id=dbt.id_club
        JOIN sstimes.swimmer dbs ON dbs.id=dbt.id_swim
        LEFT JOIN (
            SELECT dbts.*,dbti.id_lsc FROM ssdata.time_std dbts 
            JOIN ssdata.time_std_info dbti 
            ON dbti.id=dbts.id_info AND dbti.id_lsc=51 AND 
dbti.code='Default'
        ) dbtt ON (
            dbtt.event_code=dbei.code2 AND dbt.stime>0
            AND dbtt.lower_age<=dbt.age AND dbtt.upper_age>=dbt.age
            AND dbtt.next<dbt.stime AND dbtt.time>=dbt.stime
        ) WHERE dbt.id_meet=%(mid)s AND dbt.id_event=%(eid)s AND 
dbt.stage='%(stage)s'
        """
        if False:
            fields = [db[t][f] for t,f in split_colnames]
            rtn = db.executesql(stmt%sqlvars, fields=fields).as_list()
        else:
            rows = db.executesql(stmt%sqlvars, colnames=colnames, 
as_dict=True)
            # vvv turn [name1.name2] into [name1][name2], not needed 
fields=columns
            from collections import defaultdict
            rtn = []
            for r in rows:
                d = defaultdict(dict)
                for k,v in r.items():
                    t,f = k.split('.')
                    d[t][f] = v
                rtn.append(d)
            # ^^^ end of conversion 
        return rtn



-- 
Resources:
- http://web2py.com
- http://web2py.com/book (Documentation)
- http://github.com/web2py/web2py (Source code)
- https://code.google.com/p/web2py/issues/list (Report Issues)
--- 
You received this message because you are subscribed to the Google Groups 
"web2py-users" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to web2py+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to